Skip to content

feat: liberate reads AI-* trailers from commit history (GIT-69)#40

Merged
TonyCasey merged 2 commits intomainfrom
git-69
Feb 13, 2026
Merged

feat: liberate reads AI-* trailers from commit history (GIT-69)#40
TonyCasey merged 2 commits intomainfrom
git-69

Conversation

@TonyCasey
Copy link
Copy Markdown
Owner

@TonyCasey TonyCasey commented Feb 13, 2026

Summary

  • LiberateService now reads AI-* trailers during commit scanning and imports them as high-confidence memories with source: 'commit-trailer'
  • Heuristic extraction is skipped for memory types already covered by trailers (prevents duplicates)
  • LLM enrichment still runs independently alongside trailer import
  • Existing behavior preserved when no trailerService is injected

Closes GIT-69

Test plan

  • 4 new tests: trailer import, dedup with heuristics, mixed extraction, backward compat
  • All 298 unit tests pass
  • Type-check and lint clean

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

New Features

  • Memory enrichment pipeline now extracts and incorporates facts from commit trailers alongside existing enrichment methods
  • Enhanced deduplication across all enrichment sources prevents duplicate facts when the same information is identified through multiple pathways

When scanning commits, LiberateService now reads existing AI-* trailers
and imports them as high-confidence memories with source 'commit-trailer'.
Heuristic extraction is skipped for memory types already covered by
trailers, preventing duplicates. LLM enrichment still runs independently.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 13, 2026 09:54
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 13, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Integrates trailer-based fact extraction into ExtractService by adding a new extractTrailerFacts method, wiring an optional TrailerService dependency, and extending fact merging to incorporate commit trailer-derived facts alongside heuristic and LLM enrichment results.

Changes

Cohort / File(s) Summary
Trailer-based Extraction Implementation
src/application/services/ExtractService.ts
Adds trailer extraction capability with new IUnifiedFact interface (supporting 'commit-trailer' source), TRAILER_KEY_TO_MEMORY_TYPE mapping, and extractTrailerFacts method. Updates constructor to accept optional trailerService and modifies control flow to merge trailer-derived facts while skipping conflicting heuristic matches.
Test Coverage for Trailer Integration
tests/unit/application/services/ExtractService.test.ts
Wires TrailerService into ExtractService and introduces "extract with trailers" test suite validating: extraction of trailer-derived memories, proper source attribution, de-duplication between trailer and heuristic signals, continued heuristic extraction for uncovered types, and backward compatibility without trailerService.

Sequence Diagram

sequenceDiagram
    participant Client
    participant ExtractService
    participant TrailerService
    participant MemoryRepository

    Client->>ExtractService: extract(commit)
    activate ExtractService
    
    ExtractService->>ExtractService: heuristic extraction
    
    opt trailerService provided
        ExtractService->>TrailerService: read trailers from commit
        activate TrailerService
        TrailerService-->>ExtractService: trailers data
        deactivate TrailerService
        
        ExtractService->>ExtractService: convert trailers to IUnifiedFact[]
        ExtractService->>ExtractService: skip heuristic matches<br/>covered by trailers
    end
    
    opt LLM enrichment enabled
        ExtractService->>ExtractService: llm enrichment
    end
    
    ExtractService->>ExtractService: merge heuristic + trailer + llm facts
    
    ExtractService->>MemoryRepository: persist unified facts
    activate MemoryRepository
    MemoryRepository-->>ExtractService: persisted
    deactivate MemoryRepository
    
    ExtractService-->>Client: result
    deactivate ExtractService
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (37 files):

⚔️ .env.example (content)
⚔️ CLAUDE.md (content)
⚔️ docs/getting-started.md (content)
⚔️ docs/mcp-setup.md (content)
⚔️ src/application/services/MemoryService.ts (content)
⚔️ src/application/services/SessionCaptureService.ts (content)
⚔️ src/cli.ts (content)
⚔️ src/commands/hook.ts (content)
⚔️ src/commands/init-hooks.ts (content)
⚔️ src/commands/init-mcp.ts (content)
⚔️ src/commands/init.ts (content)
⚔️ src/commands/progress.ts (content)
⚔️ src/commands/remember.ts (content)
⚔️ src/domain/entities/IMemoryEntity.ts (content)
⚔️ src/domain/errors/GitMemError.ts (content)
⚔️ src/domain/interfaces/IHookConfig.ts (content)
⚔️ src/domain/interfaces/ILLMClient.ts (content)
⚔️ src/domain/interfaces/ITrailerService.ts (content)
⚔️ src/domain/types/IMemoryQuality.ts (content)
⚔️ src/hooks/utils/config.ts (content)
⚔️ src/index.ts (content)
⚔️ src/infrastructure/di/container.ts (content)
⚔️ src/infrastructure/di/types.ts (content)
⚔️ src/infrastructure/services/TrailerService.ts (content)
⚔️ src/mcp/README.md (content)
⚔️ src/mcp/server.ts (content)
⚔️ src/mcp/tools/remember.ts (content)
⚔️ tests/integration/hooks/helpers.ts (content)
⚔️ tests/integration/hooks/hook-session-stop.test.ts (content)
⚔️ tests/integration/mcp-e2e.test.ts (content)
⚔️ tests/unit/application/services/MemoryService.test.ts (content)
⚔️ tests/unit/application/services/SessionCaptureService.test.ts (content)
⚔️ tests/unit/commands/hook.test.ts (content)
⚔️ tests/unit/commands/init-hooks.test.ts (content)
⚔️ tests/unit/hooks/utils/config.test.ts (content)
⚔️ tests/unit/infrastructure/di/container.test.ts (content)
⚔️ tests/unit/infrastructure/services/TrailerService.test.ts (content)

These conflicts must be resolved before merging into main.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: LiberateService now reads AI-* trailers from commit history, which is the primary feature described across multiple files and test cases.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch git-69

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements bidirectional trailer integration for git-mem, enabling the system to both write and read AI-* commit trailers. The implementation adds dual-write functionality to MemoryService.remember() (writing both git notes and commit trailers) and unified recall that merges memories from both sources. The LiberateService now reads existing AI-* trailers during commit scanning and treats them as high-confidence, authoritative facts, skipping heuristic extraction for types already covered by trailers to prevent duplicates.

Changes:

  • Added addTrailers() and buildCommitMessage() methods to TrailerService for writing AI-* trailers to commit messages
  • Implemented dual-write in MemoryService.remember() to persist memories in both git notes and commit trailers (opt-out via trailers: false)
  • Extended MemoryService.recall() to merge memories from both notes and trailers, with deduplication by AI-Memory-Id
  • Modified LiberateService.liberate() to extract trailer facts and skip heuristic extraction for types already covered by trailers
  • Added --no-trailers CLI flag and trailers MCP parameter to allow opting out of trailer writes
  • Registered trailerService in DI container as singleton

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/infrastructure/services/TrailerService.ts Adds addTrailers() to amend HEAD with new trailers and buildCommitMessage() to merge trailers into commit messages
src/domain/interfaces/ITrailerService.ts Extends interface with addTrailers() and buildCommitMessage() method signatures
src/infrastructure/di/container.ts Registers trailerService as singleton in DI container
src/infrastructure/di/types.ts Adds trailerService to ICradle interface
src/application/services/MemoryService.ts Implements dual-write (notes + trailers) and unified recall merging both sources
src/application/services/LiberateService.ts Extracts trailer facts during commit scanning and skips heuristic extraction for trailer-covered types
src/domain/entities/IMemoryEntity.ts Adds trailers?: boolean option to ICreateMemoryOptions
src/commands/remember.ts Adds noTrailers option and passes trailers: !options.noTrailers to service
src/cli.ts Adds --no-trailers CLI flag to remember command
src/mcp/tools/remember.ts Adds trailers boolean parameter to MCP tool schema
tests/unit/infrastructure/services/TrailerService.test.ts Tests for buildCommitMessage() and addTrailers() methods
tests/unit/infrastructure/di/container.test.ts Adds verification that trailerService is registered and resolves correctly
tests/unit/application/services/MemoryService.test.ts Tests dual-write behavior, unified recall, and deduplication scenarios
tests/unit/application/services/LiberateService.test.ts Tests trailer extraction, deduplication with heuristics, and backward compatibility

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

.map(t => t.value);

// Shared metadata from the commit's trailers
const confidence = (commit.trailers.find(t => t.key === AI_TRAILER_KEYS.CONFIDENCE)?.value || 'high') as ConfidenceLevel;
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The confidence value from trailers is cast to ConfidenceLevel without validation. If a manually-added trailer has an invalid confidence value (e.g., "AI-Confidence: very-high"), this will silently accept it and potentially cause issues downstream. Consider using the isValidConfidence helper function from domain/types/IMemoryQuality to validate the value before casting, falling back to 'high' if invalid.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — now uses isValidConfidence() from domain types to validate the trailer confidence value, falling back to 'high' for invalid values. See 97190a7.

if (trailers.length === 0) return [];

const facts: IUnifiedFact[] = [];
const confidence = (trailers.find(t => t.key === AI_TRAILER_KEYS.CONFIDENCE)?.value || 'high') as ConfidenceLevel;
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The confidence value from trailers is cast to ConfidenceLevel without validation. If a manually-added trailer has an invalid confidence value (e.g., "AI-Confidence: very-high"), this will silently accept it and potentially cause issues downstream. Consider using the isValidConfidence helper function from domain/types/IMemoryQuality to validate the value before casting, falling back to 'high' if invalid.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — extractTrailerFacts() now validates the confidence value using isValidConfidence() with a fallback to 'high'. See 97190a7.

Comment on lines +25 to +30
const TRAILER_KEY_TO_MEMORY_TYPE: Record<string, MemoryType> = {
[AI_TRAILER_KEYS.DECISION]: 'decision',
[AI_TRAILER_KEYS.GOTCHA]: 'gotcha',
[AI_TRAILER_KEYS.CONVENTION]: 'convention',
[AI_TRAILER_KEYS.FACT]: 'fact',
};
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TRAILER_KEY_TO_MEMORY_TYPE mapping is duplicated in both MemoryService and LiberateService. This violates the DRY principle and creates a maintenance burden - any changes to the mapping would need to be made in both places. Consider moving this constant to a shared location like domain/entities/ITrailer.ts or creating a dedicated utility module for trailer-to-memory-type mappings.

Suggested change
const TRAILER_KEY_TO_MEMORY_TYPE: Record<string, MemoryType> = {
[AI_TRAILER_KEYS.DECISION]: 'decision',
[AI_TRAILER_KEYS.GOTCHA]: 'gotcha',
[AI_TRAILER_KEYS.CONVENTION]: 'convention',
[AI_TRAILER_KEYS.FACT]: 'fact',
};
const TRAILER_KEY_TO_MEMORY_TYPE: Record<string, MemoryType> = Object
.entries(MEMORY_TYPE_TO_TRAILER_KEY)
.reduce((acc, [memoryType, trailerKey]) => {
acc[trailerKey] = memoryType as MemoryType;
return acc;
}, {} as Record<string, MemoryType>);

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — moved MEMORY_TYPE_TO_TRAILER_KEY and TRAILER_KEY_TO_MEMORY_TYPE to src/domain/entities/ITrailer.ts as the single source of truth. The reverse mapping is derived automatically from the forward mapping. Both MemoryService and LiberateService now import from there. See 97190a7.

Comment on lines +191 to +192
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The createdAt and updatedAt timestamps are set to the current time (new Date()) instead of the commit's timestamp. This makes trailer-sourced memories appear to have been created "now" rather than when the commit was actually made. For historical accuracy, these timestamps should reflect the commit's author date. The ICommitTrailers interface could be extended to include the commit date, or the SHA could be used to fetch the timestamp separately.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — added a documentation comment noting this as a known limitation. Using the commit author date would require an extra git log call per trailer commit. The commit SHA can be used to look up the actual date if needed.

Comment on lines +203 to +205
const trailers = this.trailerService.readTrailers(sha, cwd);
if (trailers.length === 0) return [];

Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extractTrailerFacts method spawns a git process for each commit via readTrailers(sha). For large liberations, this could be inefficient. Consider using queryTrailers (which can batch-query multiple commits at once) to fetch all trailer data upfront, then look up by SHA during processing. This is a performance optimization opportunity, not a blocking issue, but could significantly speed up large-scale liberations.

Suggested change
const trailers = this.trailerService.readTrailers(sha, cwd);
if (trailers.length === 0) return [];
let trailers: readonly { key: string; value: string }[] = [];
// Prefer batch-capable queryTrailers when available, fall back to per-commit readTrailers.
const trailerServiceAny = this.trailerService as any;
const queried = typeof trailerServiceAny.queryTrailers === 'function'
? trailerServiceAny.queryTrailers([sha], cwd)
: undefined;
if (Array.isArray(queried)) {
// Some implementations may return the trailers array directly for a single SHA.
trailers = queried;
} else if (queried && typeof queried === 'object') {
// Others may return a map/dictionary keyed by SHA.
const bySha = (queried as Record<string, readonly { key: string; value: string }[] | undefined>)[sha];
if (Array.isArray(bySha)) {
trailers = bySha;
}
}
if (trailers.length === 0) {
trailers = this.trailerService.readTrailers(sha, cwd);
}
if (!trailers || trailers.length === 0) return [];

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good callout. For now, readTrailers() per commit is acceptable since liberate processes high-interest commits only (typically a small subset). If this becomes a bottleneck, we can switch to queryTrailers() for batch retrieval. Deferring as a future optimization.

const found = result.memories.find(m => m.content === 'Use Redis');
assert.ok(found);
assert.deepEqual(found.tags, ['cache', 'performance']);
});
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test coverage for invalid confidence values in trailers. Consider adding a test case that verifies the system handles manually-added trailers with invalid AI-Confidence values (e.g., "AI-Confidence: very-high" or "AI-Confidence: invalid"). This would help catch the validation issue at lines 170 and 207 in MemoryService.ts and LiberateService.ts respectively.

Suggested change
});
});
it('should handle invalid AI-Confidence trailer values without failing', () => {
writeFileSync(join(repoDir, 'invalid-confidence.txt'), 'invalid-confidence');
git(['add', '.'], repoDir);
git(
[
'commit',
'-m',
'feat: invalid confidence\n\n' +
'AI-Fact: Confidence parsing should be robust\n' +
'AI-Confidence: very-high',
],
repoDir,
);
const result = serviceWithTrailers.recall('Confidence parsing should be robust', { cwd: repoDir });
const found = result.memories.find(
m => m.content === 'Confidence parsing should be robust',
);
assert.ok(found);
});

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — added a test should handle invalid AI-Confidence trailer values gracefully that verifies invalid confidence values (e.g., 'very-high') fall back to 'high'. See 97190a7.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/commands/remember.ts (1)

11-32: ⚠️ Potential issue | 🟠 Major

CLI --no-trailers flag is ignored.

Commander.js stores --no-trailers as options.trailers (defaulting to true), not options.noTrailers. Since the code references options.noTrailers (which remains undefined), !options.noTrailers always evaluates to true, and trailers are always enabled regardless of the flag.

🔧 Suggested fix
 interface IRememberOptions {
   commit?: string;
   type?: string;
   confidence?: string;
   lifecycle?: string;
   tags?: string;
-  noTrailers?: boolean;
+  trailers?: boolean;
 }

   const memory = memoryService.remember(text, {
     sha: options.commit,
     type: (options.type || 'fact') as MemoryType,
     confidence: (options.confidence || 'high') as ConfidenceLevel,
     lifecycle: (options.lifecycle || 'project') as MemoryLifecycle,
     tags: options.tags,
-    trailers: !options.noTrailers,
+    trailers: options.trailers ?? true,
   });
🤖 Fix all issues with AI agents
In `@src/application/services/LiberateService.ts`:
- Around line 199-225: The extractTrailerFacts method currently reads global
confidence/tags once and applies them to all facts; instead iterate trailers in
order and apply metadata sequentially: walk the trailers array, update
currentConfidence when encountering AI_TRAILER_KEYS.CONFIDENCE and currentTags
when encountering AI_TRAILER_KEYS.TAGS (split/trim into string[]), and when you
hit a trailer whose key maps via TRAILER_KEY_TO_MEMORY_TYPE to a memory type,
create a fact using the currentConfidence/currentTags for that specific entry;
update or reset these current metadata values as needed so each memory-type
trailer uses the most recently-seen metadata rather than the first found values.

In `@src/application/services/MemoryService.ts`:
- Around line 75-107: The returned total currently undercounts when notes are
paged because it uses allMemories.length (which reflects only the sliced/merged
page) instead of the full-match counts; in recall(), compute a combinedTotal =
notesResult.total + trailerMemories.length (trailerMemories are already
trailer-only because recallFromTrailers was passed notesResult.memories), use
combinedTotal in the returned object and in the logger, and keep applying
pagination/limit to the merged array (allMemories.slice(0, limit)) so the
memories array is paged but total reflects the full available matches.
- Around line 157-194: In trailerCommitToEntities, synthetic IDs use
`trailer:${commit.sha}:${type}` which collides when a commit has multiple
trailers of the same type; change the synthetic `id` generation (where `id` is
set using `memoryIds[i] || ...`) to include a uniqueness factor such as the
trailer loop index (`i`) or a short hash of `typeTrailer.value` (or both) so
each generated `id` is unique per trailer; update any tests/code that assume the
old format if needed and ensure the new `id` still respects existing dedupe
logic.

In `@tests/unit/application/services/MemoryService.test.ts`:
- Around line 61-153: The tests in MemoryService.test.ts use real git and
filesystem calls (writeFileSync and git([...], repoDir)) via the
serviceWithTrailers.remember and service.remember flows and
trailerService.readTrailers, which breaks unit-test isolation; replace those
real-repo interactions with manual mocks: stub out trailerService.readTrailers
and any NotesService/git helper used by MemoryService so the tests call
serviceWithTrailers.remember/service.remember but rely on injected mock
implementations (or a mocked git helper) that return deterministic trailers/SHAs
instead of invoking git, and remove actual writeFileSync/git([...]) steps;
alternatively move these specs to tests/integration if you want real repo
behavior. Ensure you reference and mock the exact symbols:
serviceWithTrailers.remember, service.remember, trailerService.readTrailers, and
any helper that invokes git.

Comment thread src/application/services/ExtractService.ts
Comment thread src/application/services/MemoryService.ts
Comment thread src/application/services/MemoryService.ts
Comment thread tests/unit/application/services/MemoryService.test.ts
TonyCasey added a commit that referenced this pull request Feb 13, 2026
- Move TRAILER_KEY_TO_MEMORY_TYPE to ITrailer.ts (DRY, shared mapping)
- Validate confidence with isValidConfidence(), fallback to 'high'
- Fix synthetic ID collision by appending index suffix
- Fix total undercount: use notesResult.total + trailerMemories.length
- Normalize trailer values to single-line (strip newlines)
- Skip trailer writes for non-HEAD commits
- Guard against staged changes before amending in addTrailers
- Fix tag filter consistency (case-sensitive, matches notes)
- Add documentation for shared metadata and timestamp limitations
- Add tests for invalid confidence and synthetic ID uniqueness

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts:
#	tests/unit/application/services/ExtractService.test.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants