Skip to content

Commit b4f285d

Browse files
Claudeclaude
andcommitted
fix: free tier recall→confirm_scars flow tracking session state
Recall on free tier returned scars to the agent but never tracked them in session state via addSurfacedScars(), causing confirm_scars to respond with "No recall-surfaced scars to confirm" even when valid confirmations were submitted. Reported across 3 clean room sessions. Added E2E regression test covering the full free tier flow: create scar → recall → confirm_scars. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 262593c commit b4f285d

4 files changed

Lines changed: 99 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [1.3.5] - 2026-02-22
11+
12+
### Fixed
13+
- **Free tier recall→confirm_scars flow broken**: Recall on free tier returned scars to the agent but never tracked them in session state, causing confirm_scars to respond with "No recall-surfaced scars to confirm" even when valid confirmations were submitted. Reported across 3 clean room sessions.
14+
15+
### Added
16+
- **E2E regression test for recall→confirm_scars**: Verifies the full free tier flow — create scar, recall it, confirm it — catches the session state tracking gap.
17+
1018
## [1.3.4] - 2026-02-22
1119

1220
### Added

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "gitmem-mcp",
3-
"version": "1.3.4",
3+
"version": "1.3.5",
44
"mcpName": "io.github.gitmem-dev/gitmem",
55
"description": "Persistent learning memory for AI coding agents. Memory that compounds.",
66
"type": "module",

src/tools/recall.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,17 @@ export async function recall(params: RecallParams): Promise<RecallResult> {
333333
search_mode: "local",
334334
});
335335

336+
// Track surfaced scars for confirm_scars (same as pro tier path)
337+
const recallSurfacedAt = new Date().toISOString();
338+
const recallSurfacedScars: SurfacedScar[] = scars.map((scar) => ({
339+
scar_id: scar.id,
340+
scar_title: scar.title,
341+
scar_severity: scar.severity || "medium",
342+
surfaced_at: recallSurfacedAt,
343+
source: "recall" as const,
344+
}));
345+
addSurfacedScars(recallSurfacedScars);
346+
336347
const freeFormatted = formatResponse(scars, plan);
337348
return {
338349
activated: scars.length > 0,

tests/e2e/free-tier.test.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,85 @@ describe("Free Tier E2E", () => {
345345
});
346346
});
347347

348+
describe("Free Tier - Recall → Confirm Scars Flow", () => {
349+
let mcpClient: McpTestClient;
350+
351+
beforeAll(async () => {
352+
const env = {
353+
...createTierEnv("free"),
354+
GITMEM_DIR: TEST_GITMEM_DIR,
355+
HOME: TEST_GITMEM_DIR,
356+
};
357+
mcpClient = await createMcpClient(env);
358+
}, 30_000);
359+
360+
afterAll(async () => {
361+
if (mcpClient) {
362+
await mcpClient.cleanup();
363+
}
364+
});
365+
366+
it("confirm_scars accepts scars surfaced by free tier recall (regression: #t-24aefd13)", async () => {
367+
// Start a session
368+
await callTool(mcpClient.client, "session_start", {
369+
agent_identity: "CLI",
370+
force: true,
371+
});
372+
373+
// Create a scar that recall will find
374+
await callTool(mcpClient.client, "create_learning", {
375+
learning_type: "scar",
376+
title: "Always verify deployment works end-to-end",
377+
description: "Deploy to production and verify the feature works. Merge alone is not enough.",
378+
severity: "high",
379+
counter_arguments: [
380+
"CI handles deployment automatically — but automated does not mean verified",
381+
"Monitoring will catch issues — but alert fatigue means problems slip through",
382+
],
383+
keywords: ["deploy", "production", "verification"],
384+
});
385+
386+
// Recall — should surface the scar we just created
387+
const recallResult = await callTool(mcpClient.client, "recall", {
388+
plan: "deploy to production and verify it works",
389+
});
390+
const recallText = getToolResultText(recallResult);
391+
392+
// Extract scar IDs from the recall display (8-char prefixes shown as id:XXXXXXXX)
393+
const scarIdMatches = recallText.match(/id:([0-9a-f]{8})/gi);
394+
if (!scarIdMatches || scarIdMatches.length === 0) {
395+
// No scars surfaced (possible on empty install) — confirm_scars should say "no scars"
396+
const confirmResult = await callTool(mcpClient.client, "confirm_scars", {
397+
confirmations: [],
398+
});
399+
const confirmText = getToolResultText(confirmResult);
400+
expect(confirmText.toLowerCase()).toContain("no recall-surfaced scars");
401+
return;
402+
}
403+
404+
// Build confirmations for all surfaced scars
405+
const scarIds = scarIdMatches.map(m => m.replace("id:", ""));
406+
const confirmations = scarIds.map(id => ({
407+
scar_id: id,
408+
decision: "N_A",
409+
evidence: `This scar does not apply to the current E2E test scenario because we are testing the confirm_scars flow, not actual deployment.`,
410+
}));
411+
412+
// Confirm — this is the bug: was returning "No recall-surfaced scars to confirm"
413+
const confirmResult = await callTool(mcpClient.client, "confirm_scars", {
414+
confirmations,
415+
});
416+
const confirmText = getToolResultText(confirmResult);
417+
418+
// Should NOT say "No recall-surfaced scars to confirm"
419+
expect(confirmText.toLowerCase()).not.toContain("no recall-surfaced scars");
420+
// Should show acceptance
421+
expect(
422+
confirmText.includes("ACCEPTED") || confirmText.includes("addressed")
423+
).toBe(true);
424+
});
425+
});
426+
348427
describe("Free Tier - Parameter Validation", () => {
349428
let mcpClient: McpTestClient;
350429

0 commit comments

Comments
 (0)