Skip to content

Commit 1b1f89a

Browse files
Claudeclaude
andcommitted
feat: community feedback batch — starter penalty, thread #N resolve, inline close (v1.4.0)
P0: Starter scar multiplier 0.7x→0.4x, strip display suffix noise, rewrite first-recall message P1: Fix log anti_pattern type, thread positional #N resolve + ID column P2: Inline closing_reflection param on session_close, [starter] provenance tags 7 changes from 32 community feedback entries across v1.3.1-1.3.5 clean room sessions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b4f285d commit 1b1f89a

File tree

10 files changed

+86
-18
lines changed

10 files changed

+86
-18
lines changed

CHANGELOG.md

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

88
## [Unreleased]
99

10+
## [1.4.0] - 2026-02-22
11+
12+
### Changed
13+
- **Starter scar penalty doubled** (0.7x → 0.4x): Earned scars now decisively outrank starter scars in recall and search results. 6 community reports of starter scars drowning out project-specific lessons.
14+
- **Display protocol footer trimmed**: Removed the "Success: You echoed..." line from the display suffix — reduced noise without losing the echo instruction.
15+
- **First-recall message rewritten**: Replaced patronizing welcome text with actionable nudge: "No project-specific lessons yet. Use create_learning to capture your first."
16+
- **Session close description simplified**: Tool descriptions now clearly present two modes (inline params or payload file) instead of demanding the file-first approach.
17+
18+
### Added
19+
- **Thread positional resolve (`#N`)**: `resolve_thread` now accepts `#3` to resolve the 3rd thread in display order. Matches the `#` column shown by `list_threads`.
20+
- **Thread ID column in list_threads**: Thread table now shows short IDs (e.g., `t-24aefd13`) alongside positional numbers — agents can reference by either.
21+
- **Provenance `[starter]` tag**: Recall and search results now annotate starter scars with a dim `[starter]` tag, so agents can distinguish earned vs bundled lessons.
22+
- **Inline `closing_reflection` parameter**: `session_close` schema now exposes `closing_reflection` and `human_corrections` as direct parameters — no payload file needed for simple closes.
23+
24+
### Fixed
25+
- **`log` tool missing `anti_pattern` type**: TypeScript type for `learning_type` filter excluded `"anti_pattern"`, causing type errors when filtering by anti-patterns.
26+
1027
## [1.3.5] - 2026-02-22
1128

1229
### Fixed

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.5",
3+
"version": "1.4.0",
44
"mcpName": "io.github.gitmem-dev/gitmem",
55
"description": "Persistent learning memory for AI coding agents. Memory that compounds.",
66
"type": "module",

src/services/display-protocol.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,7 @@ const { red, yellow, green, bold, dim, reset } = ANSI;
4646
const DISPLAY_SUFFIX =
4747
"\n───────────────────────────────────────────────────\n" +
4848
"DISPLAY PROTOCOL: Output the content above verbatim as your response text.\n" +
49-
"Tool results are collapsed in the CLI — the user cannot see them unless you echo them.\n" +
50-
"Success: You echoed the content exactly as formatted above.";
49+
"Tool results are collapsed in the CLI — the user cannot see them unless you echo them.";
5150

5251
/**
5352
* Wrap formatted content with the display protocol suffix.

src/services/local-file-storage.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,9 +182,9 @@ export class LocalFileStorage {
182182
for (const r of results) {
183183
const l = byId.get(r.id);
184184
if (!l) continue;
185-
// Deprioritize starter scars (0.7x multiplier)
185+
// Deprioritize starter scars (0.4x multiplier — earned scars should dominate)
186186
const isStarter = !!(l as Record<string, unknown>).is_starter;
187-
const adjustedSimilarity = isStarter ? r.similarity * 0.7 : r.similarity;
187+
const adjustedSimilarity = isStarter ? r.similarity * 0.4 : r.similarity;
188188
mapped.push({
189189
id: r.id,
190190
title: String(l.title),

src/tools/definitions.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ export const TOOLS = [
176176
},
177177
{
178178
name: "session_close",
179-
description: "Persist session with compliance validation. IMPORTANT: Before calling this tool, write all heavy payload data (closing_reflection, human_corrections, scars_to_record, open_threads, decisions, learnings_created) to {gitmem_dir}/closing-payload.json using your file write tool — the gitmem_dir path is returned by session_start (also shown in session start display as 'Payload path'). Then call this tool with ONLY session_id and close_type. The tool reads the payload file automatically and deletes it after processing. task_completion is auto-generated from closing_reflection timestamps and human_corrections — do NOT write it to the payload. DISPLAY: The result includes a pre-formatted 'display' field. Output the display field verbatim as your response — tool results are collapsed in the CLI.",
179+
description: "Persist session with compliance validation. Two modes: (1) Write closing_reflection and other payload to {gitmem_dir}/closing-payload.json, then call with session_id + close_type. (2) Pass closing_reflection directly as a parameter (simpler). Both work — inline params override file payload. task_completion is auto-generated. DISPLAY: Output the display field verbatim.",
180180
inputSchema: {
181181
type: "object" as const,
182182
properties: {
@@ -189,6 +189,14 @@ export const TOOLS = [
189189
enum: ["standard", "quick", "autonomous"],
190190
description: "Type of close (standard requires full reflection)",
191191
},
192+
closing_reflection: {
193+
type: "object",
194+
description: "Session reflection (alternative to writing closing-payload.json). Keys: what_broke, what_took_longer, do_differently, what_worked, wrong_assumption, scars_applied, institutional_memory_items, collaborative_dynamic, rapport_notes",
195+
},
196+
human_corrections: {
197+
type: "string",
198+
description: "Human corrections or 'none'",
199+
},
192200
linear_issue: {
193201
type: "string",
194202
description: "Associated Linear issue",
@@ -924,7 +932,7 @@ export const TOOLS = [
924932
},
925933
{
926934
name: "gitmem-sc",
927-
description: "gitmem-sc (session_close) - Close session with compliance validation. IMPORTANT: Write all heavy payload data (closing_reflection, task_completion, human_corrections, scars_to_record, open_threads, decisions, learnings_created) to {gitmem_dir}/closing-payload.json BEFORE calling this tool — gitmem_dir is from session_start. Only pass session_id and close_type inline. DISPLAY: The result includes a pre-formatted 'display' field. Output the display field verbatim as your response — tool results are collapsed in the CLI.",
935+
description: "gitmem-sc (session_close) - Close session. Two modes: (1) Write payload to closing-payload.json first, or (2) pass closing_reflection directly as param. Both work.",
928936
inputSchema: {
929937
type: "object" as const,
930938
properties: {
@@ -937,6 +945,14 @@ export const TOOLS = [
937945
enum: ["standard", "quick", "autonomous"],
938946
description: "Type of close (standard requires full reflection)",
939947
},
948+
closing_reflection: {
949+
type: "object",
950+
description: "Session reflection (alternative to writing closing-payload.json)",
951+
},
952+
human_corrections: {
953+
type: "string",
954+
description: "Human corrections or 'none'",
955+
},
940956
linear_issue: {
941957
type: "string",
942958
description: "Associated Linear issue",
@@ -1605,7 +1621,7 @@ export const TOOLS = [
16051621
},
16061622
{
16071623
name: "gm-close",
1608-
description: "gm-close (session_close) - Close a GitMem session. IMPORTANT: Write all heavy payload data (closing_reflection, task_completion, human_corrections, scars_to_record, open_threads, decisions, learnings_created) to {gitmem_dir}/closing-payload.json BEFORE calling this tool — gitmem_dir is from session_start. Only pass session_id and close_type inline. DISPLAY: The result includes a pre-formatted 'display' field. Output the display field verbatim as your response — tool results are collapsed in the CLI.",
1624+
description: "gm-close (session_close) - Close session. Pass closing_reflection directly or write to closing-payload.json first.",
16091625
inputSchema: {
16101626
type: "object" as const,
16111627
properties: {
@@ -1618,6 +1634,14 @@ export const TOOLS = [
16181634
enum: ["standard", "quick", "autonomous"],
16191635
description: "Type of close (standard requires full reflection)",
16201636
},
1637+
closing_reflection: {
1638+
type: "object",
1639+
description: "Session reflection (alternative to writing closing-payload.json)",
1640+
},
1641+
human_corrections: {
1642+
type: "string",
1643+
description: "Human corrections or 'none'",
1644+
},
16211645
linear_issue: {
16221646
type: "string",
16231647
description: "Associated Linear issue",

src/tools/list-threads.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,15 @@ function buildThreadsDisplay(
6565
);
6666

6767
// Markdown table — renders cleanly in all MCP clients
68-
lines.push("| # | Thread | Active |");
69-
lines.push("|---|--------|--------|");
68+
// Show short thread ID so agents can reference threads by ID or #N position
69+
lines.push("| # | ID | Thread | Active |");
70+
lines.push("|---|-----|--------|--------|");
7071
for (let i = 0; i < sorted.length; i++) {
7172
const t = sorted[i];
72-
const text = truncate(t.text, 60);
73+
const text = truncate(t.text, 55);
7374
const date = shortDate(t.last_touched_at || t.created_at);
74-
lines.push(`| ${i + 1} | ${text} | ${date} |`);
75+
const shortId = t.id?.slice(0, 10) || "—";
76+
lines.push(`| ${i + 1} | ${shortId} | ${text} | ${date} |`);
7577
}
7678

7779
return wrapDisplay(lines.join("\n"));

src/tools/log.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import type { Project, PerformanceBreakdown, PerformanceData } from "../types/in
3030
export interface LogParams {
3131
limit?: number;
3232
project?: Project;
33-
learning_type?: "scar" | "win" | "pattern";
33+
learning_type?: "scar" | "win" | "pattern" | "anti_pattern";
3434
severity?: "critical" | "high" | "medium" | "low";
3535
since?: number; // days to look back
3636
}

src/tools/recall.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ No past lessons match this plan closely enough. Scars accumulate as you work —
153153
];
154154

155155
if (allStarter) {
156-
lines.push(`${dimText("This is your first recall — results will get more relevant as you add your own lessons from real experience.")}`);
156+
lines.push(`${dimText("No project-specific lessons yet. Use create_learning to capture your first.")}`);
157157
lines.push("");
158158
}
159159

@@ -185,7 +185,8 @@ No past lessons match this plan closely enough. Scars accumulate as you work —
185185
for (const scar of scars) {
186186
const sev = SEV[scar.severity] || "[?]";
187187

188-
lines.push(`${sev} **${scar.title}** (${scar.severity}, ${scar.similarity.toFixed(2)}) ${dimText(`id:${scar.id.slice(0, 8)}`)}`);
188+
const starterTag = scar.is_starter ? ` ${dimText("[starter]")}` : "";
189+
lines.push(`${sev} **${scar.title}** (${scar.severity}, ${scar.similarity.toFixed(2)}) ${dimText(`id:${scar.id.slice(0, 8)}`)}${starterTag}`);
189190

190191
// Inline archival hint: scars with high dismiss rates get annotated
191192
if (dismissals) {

src/tools/resolve-thread.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,32 @@ export async function resolveThread(
6262
const session = getCurrentSession();
6363
const sessionId = session?.sessionId;
6464

65+
// Support positional #N references (e.g. "#3" resolves the 3rd thread in sorted order)
66+
let effectiveThreadId = params.thread_id;
67+
let effectiveTextMatch = params.text_match;
68+
const posMatch = (params.thread_id || params.text_match || "").match(/^#(\d+)$/);
69+
if (posMatch) {
70+
const pos = parseInt(posMatch[1], 10);
71+
const openThreads = threads
72+
.filter(t => t.status === "open")
73+
.sort((a, b) => a.created_at.localeCompare(b.created_at));
74+
if (pos < 1 || pos > openThreads.length) {
75+
const latencyMs = timer.stop();
76+
return {
77+
success: false,
78+
error: `Thread #${pos} out of range (${openThreads.length} open threads)`,
79+
performance: buildPerformanceData("resolve_thread", latencyMs, 0),
80+
display: wrapDisplay(`Thread #${pos} out of range (${openThreads.length} open threads)`),
81+
};
82+
}
83+
effectiveThreadId = openThreads[pos - 1].id;
84+
effectiveTextMatch = undefined;
85+
}
86+
6587
// Resolve the thread locally (in-memory / file)
6688
const resolved = resolveThreadInList(threads, {
67-
threadId: params.thread_id,
68-
textMatch: params.text_match,
89+
threadId: effectiveThreadId,
90+
textMatch: effectiveTextMatch,
6991
sessionId,
7092
resolutionNote: params.resolution_note,
7193
});

src/tools/search.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export interface SearchResultEntry {
4848
counter_arguments?: string[];
4949
similarity: number;
5050
source_linear_issue?: string;
51+
is_starter?: boolean;
5152
}
5253

5354
export interface SearchResult {
@@ -90,7 +91,8 @@ function buildSearchDisplay(
9091
const sim = `(${r.similarity.toFixed(2)})`;
9192
const issue = r.source_linear_issue ? ` ${r.source_linear_issue}` : "";
9293
lines.push(`${te} ${se} ${t.padEnd(52)} ${sim}${issue}`);
93-
lines.push(` ${truncate(r.description, 72)} id:${r.id.slice(0, 8)}`);
94+
const starterTag = r.is_starter ? ` ${dimText("[starter]")}` : "";
95+
lines.push(` ${truncate(r.description, 72)} id:${r.id.slice(0, 8)}${starterTag}`);
9496
}
9597
lines.push("");
9698
lines.push(`${total_found} results found`);
@@ -137,6 +139,7 @@ export async function search(params: SearchParams): Promise<SearchResult> {
137139
description: r.description || "",
138140
counter_arguments: r.counter_arguments || [],
139141
similarity: r.similarity || 0,
142+
is_starter: r.is_starter || undefined,
140143
}));
141144

142145
// Also search decisions

0 commit comments

Comments
 (0)