gitmem-mcp 1.1.3 โ 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -0
- package/CLAUDE.md.template +29 -20
- package/README.md +4 -4
- package/bin/gitmem.js +33 -0
- package/copilot-instructions.template +33 -14
- package/cursorrules.template +29 -20
- package/dist/commands/telemetry.d.ts +11 -0
- package/dist/commands/telemetry.js +207 -0
- package/dist/hooks/format-utils.d.ts +1 -0
- package/dist/hooks/format-utils.js +7 -6
- package/dist/lib/telemetry.d.ts +101 -0
- package/dist/lib/telemetry.js +272 -0
- package/dist/schemas/session-close.d.ts +14 -14
- package/dist/server.js +3 -2
- package/dist/services/display-protocol.d.ts +45 -4
- package/dist/services/display-protocol.js +114 -15
- package/dist/services/nudge-variants.d.ts +29 -0
- package/dist/services/nudge-variants.js +71 -0
- package/dist/tools/cleanup-threads.js +3 -3
- package/dist/tools/confirm-scars.js +28 -11
- package/dist/tools/definitions.js +2 -2
- package/dist/tools/list-threads.d.ts +1 -1
- package/dist/tools/list-threads.js +6 -7
- package/dist/tools/log.js +3 -3
- package/dist/tools/prepare-context.js +7 -10
- package/dist/tools/recall.js +7 -17
- package/dist/tools/search.js +3 -3
- package/dist/tools/session-close.js +105 -81
- package/dist/tools/session-start.js +32 -17
- package/package.json +2 -1
- package/windsurfrules.template +33 -14
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nudge Variant Support
|
|
3
|
+
*
|
|
4
|
+
* Reads GITMEM_NUDGE_VARIANT env var to select alternative header
|
|
5
|
+
* framings for recall/prepare-context output. Used by nudge-bench
|
|
6
|
+
* to A/B test how different wording affects agent compliance.
|
|
7
|
+
*
|
|
8
|
+
* When no env var is set, returns the production default.
|
|
9
|
+
*
|
|
10
|
+
* Variant IDs match nudge-bench/variants/n001-header.ts
|
|
11
|
+
*/
|
|
12
|
+
const VARIANTS = {
|
|
13
|
+
// Production default
|
|
14
|
+
"n001-a-institutional": {
|
|
15
|
+
icon: "๐ง ",
|
|
16
|
+
text: (n) => `INSTITUTIONAL MEMORY ACTIVATED\n\nFound ${n} relevant scar${n === 1 ? "" : "s"} for your plan:`,
|
|
17
|
+
},
|
|
18
|
+
// Informational/passive
|
|
19
|
+
"n001-b-recalled": {
|
|
20
|
+
icon: "\x1b[38;5;37mโฌข\x1b[0m",
|
|
21
|
+
text: (n) => `gitmem โโ ${n} learnings recalled`,
|
|
22
|
+
},
|
|
23
|
+
// Obligation
|
|
24
|
+
"n001-c-review": {
|
|
25
|
+
icon: "\x1b[38;5;37mโฌข\x1b[0m",
|
|
26
|
+
text: (n) => `gitmem โโ ${n} scar${n === 1 ? "" : "s"} to review`,
|
|
27
|
+
},
|
|
28
|
+
// Directive
|
|
29
|
+
"n001-d-directive": {
|
|
30
|
+
icon: "\x1b[38;5;37mโฌข\x1b[0m",
|
|
31
|
+
text: (n) => `gitmem โโ review ${n} learning${n === 1 ? "" : "s"} before proceeding`,
|
|
32
|
+
},
|
|
33
|
+
// Procedural โ ties to confirm_scars
|
|
34
|
+
"n001-e-confirm": {
|
|
35
|
+
icon: "\x1b[38;5;37mโฌข\x1b[0m",
|
|
36
|
+
text: (n) => `gitmem โโ ${n} scar${n === 1 ? "" : "s"} found โ confirm before acting`,
|
|
37
|
+
},
|
|
38
|
+
// Reasoning โ explains WHY (Karpathy: reasoning > command)
|
|
39
|
+
"n001-f-reasoning": {
|
|
40
|
+
icon: "\x1b[38;5;37mโฌข\x1b[0m",
|
|
41
|
+
text: (n) => `gitmem โโ ${n} past mistake${n === 1 ? "" : "s"} detected that may repeat here โ review to avoid the same outcome`,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
const DEFAULT_VARIANT = "n001-c-review";
|
|
45
|
+
/**
|
|
46
|
+
* Get the active nudge header based on GITMEM_NUDGE_VARIANT env var.
|
|
47
|
+
* Falls back to production default if unset or invalid.
|
|
48
|
+
*/
|
|
49
|
+
export function getNudgeHeader() {
|
|
50
|
+
const variantId = process.env.GITMEM_NUDGE_VARIANT;
|
|
51
|
+
if (!variantId)
|
|
52
|
+
return VARIANTS[DEFAULT_VARIANT];
|
|
53
|
+
return VARIANTS[variantId] || VARIANTS[DEFAULT_VARIANT];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Format the recall header line using the active nudge variant.
|
|
57
|
+
*/
|
|
58
|
+
export function formatNudgeHeader(scarCount) {
|
|
59
|
+
const header = getNudgeHeader();
|
|
60
|
+
return `${header.icon} ${header.text(scarCount)}`;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get the active variant ID (for logging/metrics).
|
|
64
|
+
*/
|
|
65
|
+
export function getActiveVariantId() {
|
|
66
|
+
const variantId = process.env.GITMEM_NUDGE_VARIANT;
|
|
67
|
+
if (variantId && VARIANTS[variantId])
|
|
68
|
+
return variantId;
|
|
69
|
+
return DEFAULT_VARIANT;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=nudge-variants.js.map
|
|
@@ -14,7 +14,7 @@ import { computeLifecycleStatus } from "../services/thread-vitality.js";
|
|
|
14
14
|
import { archiveDormantThreads } from "../services/thread-supabase.js";
|
|
15
15
|
import { loadThreadsFile } from "../services/thread-manager.js";
|
|
16
16
|
import { Timer, recordMetrics, buildPerformanceData, } from "../services/metrics.js";
|
|
17
|
-
import { wrapDisplay, truncate } from "../services/display-protocol.js";
|
|
17
|
+
import { wrapDisplay, truncate, productLine, boldText } from "../services/display-protocol.js";
|
|
18
18
|
// --- Display Formatting ---
|
|
19
19
|
function formatDaysAgo(days) {
|
|
20
20
|
if (days < 1)
|
|
@@ -28,7 +28,7 @@ function formatDaysAgo(days) {
|
|
|
28
28
|
}
|
|
29
29
|
function buildCleanupDisplay(summary, groups, archivedCount) {
|
|
30
30
|
const lines = [];
|
|
31
|
-
lines.push(
|
|
31
|
+
lines.push(productLine("cleanup", `${summary.total_open} open ยท ${summary.active} active ยท ${summary.cooling} cooling ยท ${summary.dormant} dormant`));
|
|
32
32
|
lines.push("");
|
|
33
33
|
const totalItems = summary.total_open;
|
|
34
34
|
if (totalItems === 0 && archivedCount === 0) {
|
|
@@ -44,7 +44,7 @@ function buildCleanupDisplay(summary, groups, archivedCount) {
|
|
|
44
44
|
for (const [label, items] of sections) {
|
|
45
45
|
if (items.length === 0)
|
|
46
46
|
continue;
|
|
47
|
-
lines.push(
|
|
47
|
+
lines.push(`${boldText(label)} (${items.length}):`);
|
|
48
48
|
lines.push("");
|
|
49
49
|
lines.push("| ID | Thread | Last Touch |");
|
|
50
50
|
lines.push("|----|--------|------------|");
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import { getCurrentSession, getSurfacedScars, addConfirmations, getConfirmations, } from "../services/session-state.js";
|
|
19
19
|
import { Timer, buildPerformanceData } from "../services/metrics.js";
|
|
20
20
|
import { getSessionPath } from "../services/gitmem-dir.js";
|
|
21
|
-
import { wrapDisplay } from "../services/display-protocol.js";
|
|
21
|
+
import { wrapDisplay, STATUS, ANSI } from "../services/display-protocol.js";
|
|
22
22
|
import * as fs from "fs";
|
|
23
23
|
// Minimum evidence length per decision type
|
|
24
24
|
const MIN_EVIDENCE_LENGTH = 50;
|
|
@@ -65,17 +65,17 @@ function validateConfirmation(confirmation, scar) {
|
|
|
65
65
|
function formatResponse(valid, confirmations, errors, missingScars) {
|
|
66
66
|
const lines = [];
|
|
67
67
|
if (valid) {
|
|
68
|
-
lines.push(
|
|
68
|
+
lines.push(`${STATUS.ok} SCAR CONFIRMATIONS ACCEPTED`);
|
|
69
69
|
lines.push("");
|
|
70
70
|
for (const conf of confirmations) {
|
|
71
|
-
const
|
|
72
|
-
lines.push(`${
|
|
71
|
+
const indicator = conf.decision === "APPLYING" ? STATUS.pass : conf.decision === "N_A" ? `${ANSI.dim}-${ANSI.reset}` : `${ANSI.yellow}!${ANSI.reset}`;
|
|
72
|
+
lines.push(`${indicator} **${conf.scar_title}** โ ${conf.decision}`);
|
|
73
73
|
}
|
|
74
74
|
lines.push("");
|
|
75
75
|
lines.push("All recalled scars addressed. Consequential actions are now unblocked.");
|
|
76
76
|
}
|
|
77
77
|
else {
|
|
78
|
-
lines.push(
|
|
78
|
+
lines.push(`${STATUS.rejected} SCAR CONFIRMATIONS REJECTED`);
|
|
79
79
|
lines.push("");
|
|
80
80
|
if (errors.length > 0) {
|
|
81
81
|
lines.push("**Validation errors:**");
|
|
@@ -124,7 +124,7 @@ export async function confirmScars(params) {
|
|
|
124
124
|
const session = getCurrentSession();
|
|
125
125
|
if (!session) {
|
|
126
126
|
const performance = buildPerformanceData("confirm_scars", timer.elapsed(), 0);
|
|
127
|
-
const noSessionMsg =
|
|
127
|
+
const noSessionMsg = `${STATUS.rejected} No active session. Call session_start before confirm_scars.`;
|
|
128
128
|
return {
|
|
129
129
|
valid: false,
|
|
130
130
|
errors: ["No active session. Call session_start first."],
|
|
@@ -140,7 +140,7 @@ export async function confirmScars(params) {
|
|
|
140
140
|
const recallScars = allSurfacedScars.filter(s => s.source === "recall");
|
|
141
141
|
if (recallScars.length === 0) {
|
|
142
142
|
const performance = buildPerformanceData("confirm_scars", timer.elapsed(), 0);
|
|
143
|
-
const noScarsMsg =
|
|
143
|
+
const noScarsMsg = `${STATUS.ok} No recall-surfaced scars to confirm. Proceed freely.`;
|
|
144
144
|
return {
|
|
145
145
|
valid: true,
|
|
146
146
|
errors: [],
|
|
@@ -165,8 +165,25 @@ export async function confirmScars(params) {
|
|
|
165
165
|
}
|
|
166
166
|
else {
|
|
167
167
|
for (const conf of params.confirmations) {
|
|
168
|
-
// Check scar exists in recalled set
|
|
169
|
-
|
|
168
|
+
// Check scar exists in recalled set (try exact match first)
|
|
169
|
+
let scar = scarById.get(conf.scar_id);
|
|
170
|
+
// If not found and looks like 8-char prefix, try prefix match
|
|
171
|
+
// This allows agents to copy IDs from recall display (which shows truncated IDs)
|
|
172
|
+
if (!scar && /^[0-9a-f]{8}$/i.test(conf.scar_id)) {
|
|
173
|
+
let matchedId = null;
|
|
174
|
+
for (const [fullId, scarData] of scarById.entries()) {
|
|
175
|
+
if (fullId.startsWith(conf.scar_id)) {
|
|
176
|
+
if (matchedId) {
|
|
177
|
+
// Ambiguous prefix - multiple matches
|
|
178
|
+
errors.push(`Ambiguous scar_id prefix "${conf.scar_id}" matches multiple scars. Use full UUID.`);
|
|
179
|
+
scar = undefined;
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
matchedId = fullId;
|
|
183
|
+
scar = scarData;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
170
187
|
if (!scar) {
|
|
171
188
|
errors.push(`Unknown scar_id "${conf.scar_id}". Only confirm scars returned by recall().`);
|
|
172
189
|
continue;
|
|
@@ -181,14 +198,14 @@ export async function confirmScars(params) {
|
|
|
181
198
|
const relevance = conf.relevance ??
|
|
182
199
|
(conf.decision === "APPLYING" ? "high" : conf.decision === "N_A" ? "low" : "low");
|
|
183
200
|
validConfirmations.push({
|
|
184
|
-
scar_id:
|
|
201
|
+
scar_id: scar.scar_id, // Use full UUID from matched scar, not potentially truncated input
|
|
185
202
|
scar_title: scar.scar_title,
|
|
186
203
|
decision: conf.decision,
|
|
187
204
|
evidence: conf.evidence.trim(),
|
|
188
205
|
confirmed_at: new Date().toISOString(),
|
|
189
206
|
relevance,
|
|
190
207
|
});
|
|
191
|
-
confirmedIds.add(
|
|
208
|
+
confirmedIds.add(scar.scar_id); // Track by full UUID
|
|
192
209
|
}
|
|
193
210
|
}
|
|
194
211
|
}
|
|
@@ -131,7 +131,7 @@ export const TOOLS = [
|
|
|
131
131
|
},
|
|
132
132
|
{
|
|
133
133
|
name: "session_close",
|
|
134
|
-
description: "Persist session with compliance validation. IMPORTANT: Before calling this tool, write all heavy payload data (closing_reflection,
|
|
134
|
+
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.",
|
|
135
135
|
inputSchema: {
|
|
136
136
|
type: "object",
|
|
137
137
|
properties: {
|
|
@@ -595,7 +595,7 @@ export const TOOLS = [
|
|
|
595
595
|
// --- Thread Lifecycle Tools () ---
|
|
596
596
|
{
|
|
597
597
|
name: "list_threads",
|
|
598
|
-
description: "List open threads across recent sessions. Shows unresolved work items that carry over between sessions
|
|
598
|
+
description: "List open threads across recent sessions. Shows unresolved work items that carry over between sessions. Use resolve_thread to mark threads as done.",
|
|
599
599
|
inputSchema: {
|
|
600
600
|
type: "object",
|
|
601
601
|
properties: {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* list_threads Tool
|
|
3
3
|
*
|
|
4
4
|
* List open threads across recent sessions. Shows unresolved work items
|
|
5
|
-
* that carry over between sessions
|
|
5
|
+
* that carry over between sessions.
|
|
6
6
|
*
|
|
7
7
|
* Primary read from Supabase (source of truth).
|
|
8
8
|
* Falls back to session-based aggregation (same as session_start),
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* list_threads Tool
|
|
3
3
|
*
|
|
4
4
|
* List open threads across recent sessions. Shows unresolved work items
|
|
5
|
-
* that carry over between sessions
|
|
5
|
+
* that carry over between sessions.
|
|
6
6
|
*
|
|
7
7
|
* Primary read from Supabase (source of truth).
|
|
8
8
|
* Falls back to session-based aggregation (same as session_start),
|
|
@@ -19,7 +19,7 @@ import { listThreadsFromSupabase } from "../services/thread-supabase.js";
|
|
|
19
19
|
import * as supabase from "../services/supabase-client.js";
|
|
20
20
|
import { Timer, recordMetrics, buildPerformanceData, } from "../services/metrics.js";
|
|
21
21
|
import { formatThreadForDisplay } from "../services/timezone.js";
|
|
22
|
-
import { wrapDisplay, truncate } from "../services/display-protocol.js";
|
|
22
|
+
import { wrapDisplay, truncate, productLine } from "../services/display-protocol.js";
|
|
23
23
|
// --- Display Formatting ---
|
|
24
24
|
/** Format date as short absolute string: "Feb 13" or "Jan 5" */
|
|
25
25
|
function shortDate(date) {
|
|
@@ -31,7 +31,7 @@ function shortDate(date) {
|
|
|
31
31
|
}
|
|
32
32
|
function buildThreadsDisplay(threads, totalOpen, totalResolved) {
|
|
33
33
|
const lines = [];
|
|
34
|
-
lines.push(
|
|
34
|
+
lines.push(productLine("threads", `${totalOpen} open ยท ${totalResolved} resolved`));
|
|
35
35
|
lines.push("");
|
|
36
36
|
if (threads.length === 0) {
|
|
37
37
|
lines.push("No threads found.");
|
|
@@ -40,14 +40,13 @@ function buildThreadsDisplay(threads, totalOpen, totalResolved) {
|
|
|
40
40
|
// Deterministic sort: oldest first by created_at
|
|
41
41
|
const sorted = [...threads].sort((a, b) => a.created_at.localeCompare(b.created_at));
|
|
42
42
|
// Markdown table โ renders cleanly in all MCP clients
|
|
43
|
-
lines.push("| # |
|
|
44
|
-
lines.push("
|
|
43
|
+
lines.push("| # | Thread | Active |");
|
|
44
|
+
lines.push("|---|--------|--------|");
|
|
45
45
|
for (let i = 0; i < sorted.length; i++) {
|
|
46
46
|
const t = sorted[i];
|
|
47
|
-
const shortId = t.id;
|
|
48
47
|
const text = truncate(t.text, 60);
|
|
49
48
|
const date = shortDate(t.last_touched_at || t.created_at);
|
|
50
|
-
lines.push(`| ${i + 1} | ${
|
|
49
|
+
lines.push(`| ${i + 1} | ${text} | ${date} |`);
|
|
51
50
|
}
|
|
52
51
|
return wrapDisplay(lines.join("\n"));
|
|
53
52
|
}
|
package/dist/tools/log.js
CHANGED
|
@@ -16,11 +16,11 @@ import { getStorage } from "../services/storage.js";
|
|
|
16
16
|
import { Timer, recordMetrics, buildPerformanceData, buildComponentPerformance, } from "../services/metrics.js";
|
|
17
17
|
import { v4 as uuidv4 } from "uuid";
|
|
18
18
|
import { formatTimestamp } from "../services/timezone.js";
|
|
19
|
-
import { wrapDisplay, relativeTime, truncate, SEV, TYPE } from "../services/display-protocol.js";
|
|
19
|
+
import { wrapDisplay, relativeTime, truncate, SEV, TYPE, productLine } from "../services/display-protocol.js";
|
|
20
20
|
// --- Display Formatting ---
|
|
21
21
|
function buildLogDisplay(entries, total, filters) {
|
|
22
22
|
const lines = [];
|
|
23
|
-
lines.push(
|
|
23
|
+
lines.push(productLine("log", `${total} most recent ยท ${filters.project}`));
|
|
24
24
|
const fp = [];
|
|
25
25
|
if (filters.learning_type)
|
|
26
26
|
fp.push(`type=${filters.learning_type}`);
|
|
@@ -37,7 +37,7 @@ function buildLogDisplay(entries, total, filters) {
|
|
|
37
37
|
}
|
|
38
38
|
for (const e of entries) {
|
|
39
39
|
const te = TYPE[e.learning_type] || "ยท";
|
|
40
|
-
const se = e.learning_type === "decision" ? "
|
|
40
|
+
const se = e.learning_type === "decision" ? "[d]" : (SEV[e.severity] || "[?]");
|
|
41
41
|
const t = truncate(e.title, 50);
|
|
42
42
|
const time = relativeTime(e.created_at);
|
|
43
43
|
const issue = e.source_linear_issue ? ` ${e.source_linear_issue}` : "";
|
|
@@ -22,7 +22,8 @@ import { hasSupabase, getTableName } from "../services/tier.js";
|
|
|
22
22
|
import { getStorage } from "../services/storage.js";
|
|
23
23
|
import { Timer, recordMetrics, buildPerformanceData, buildComponentPerformance, } from "../services/metrics.js";
|
|
24
24
|
import { v4 as uuidv4 } from "uuid";
|
|
25
|
-
import { wrapDisplay } from "../services/display-protocol.js";
|
|
25
|
+
import { wrapDisplay, productLine } from "../services/display-protocol.js";
|
|
26
|
+
import { formatNudgeHeader } from "../services/nudge-variants.js";
|
|
26
27
|
import { estimateTokens, formatCompact, formatGate, SEVERITY_EMOJI, } from "../hooks/format-utils.js";
|
|
27
28
|
/**
|
|
28
29
|
* Format scars in full mode.
|
|
@@ -36,17 +37,13 @@ function formatFull(scars, plan) {
|
|
|
36
37
|
Proceed with caution โ this may be new territory without documented lessons.`;
|
|
37
38
|
}
|
|
38
39
|
const lines = [
|
|
39
|
-
|
|
40
|
-
"",
|
|
41
|
-
`Found ${scars.length} relevant scar${scars.length === 1 ? "" : "s"} for your plan:`,
|
|
40
|
+
formatNudgeHeader(scars.length),
|
|
42
41
|
"",
|
|
43
42
|
];
|
|
44
43
|
// Blocking verification requirements first
|
|
45
44
|
const blockingScars = scars.filter((s) => s.required_verification?.blocking);
|
|
46
45
|
if (blockingScars.length > 0) {
|
|
47
|
-
lines.push("
|
|
48
|
-
lines.push("๐จ **VERIFICATION REQUIRED BEFORE PROCEEDING**");
|
|
49
|
-
lines.push("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
|
|
46
|
+
lines.push("[!!] VERIFICATION REQUIRED BEFORE PROCEEDING");
|
|
50
47
|
lines.push("");
|
|
51
48
|
for (const scar of blockingScars) {
|
|
52
49
|
const rv = scar.required_verification;
|
|
@@ -63,11 +60,11 @@ Proceed with caution โ this may be new territory without documented lessons.`;
|
|
|
63
60
|
lines.push(`**MUST SHOW:** ${rv.must_show}`);
|
|
64
61
|
lines.push("");
|
|
65
62
|
}
|
|
66
|
-
lines.push("
|
|
63
|
+
lines.push("---");
|
|
67
64
|
lines.push("");
|
|
68
65
|
}
|
|
69
66
|
for (const scar of scars) {
|
|
70
|
-
const emoji = SEVERITY_EMOJI[scar.severity] || "
|
|
67
|
+
const emoji = SEVERITY_EMOJI[scar.severity] || "[?]";
|
|
71
68
|
lines.push(`${emoji} **${scar.title}** (${scar.severity}, score: ${(scar.similarity || 0).toFixed(2)})`);
|
|
72
69
|
lines.push(scar.description);
|
|
73
70
|
if (scar.counter_arguments && scar.counter_arguments.length > 0) {
|
|
@@ -167,7 +164,7 @@ function buildResult(scars, plan, format, maxTokens, timer, metricsId, project,
|
|
|
167
164
|
blocking_scars,
|
|
168
165
|
},
|
|
169
166
|
}).catch(() => { });
|
|
170
|
-
const display = wrapDisplay(
|
|
167
|
+
const display = wrapDisplay(`${productLine("prepare_context", `${format} ยท ${scars_included} scars (${blocking_scars} blocking) ยท ~${token_estimate} tokens`)}\n\n${memory_payload}`);
|
|
171
168
|
return {
|
|
172
169
|
memory_payload,
|
|
173
170
|
display,
|
package/dist/tools/recall.js
CHANGED
|
@@ -24,7 +24,8 @@ import { getAgentIdentity } from "../services/agent-detection.js";
|
|
|
24
24
|
import { v4 as uuidv4 } from "uuid";
|
|
25
25
|
import * as fs from "fs";
|
|
26
26
|
import { getSessionPath } from "../services/gitmem-dir.js";
|
|
27
|
-
import { wrapDisplay } from "../services/display-protocol.js";
|
|
27
|
+
import { wrapDisplay, SEV, dimText, ANSI } from "../services/display-protocol.js";
|
|
28
|
+
import { formatNudgeHeader } from "../services/nudge-variants.js";
|
|
28
29
|
import { fetchDismissalCounts } from "../services/behavioral-decay.js";
|
|
29
30
|
/**
|
|
30
31
|
* Format scars into a readable response for Claude
|
|
@@ -38,16 +39,12 @@ No past lessons match this plan closely enough. Scars accumulate as you work โ
|
|
|
38
39
|
// Check if any scars have required_verification (blocking gates)
|
|
39
40
|
const scarsWithVerification = scars.filter((s) => s.required_verification?.blocking);
|
|
40
41
|
const lines = [
|
|
41
|
-
|
|
42
|
-
"",
|
|
43
|
-
`Found ${scars.length} relevant scar${scars.length === 1 ? "" : "s"} for your plan:`,
|
|
42
|
+
formatNudgeHeader(scars.length),
|
|
44
43
|
"",
|
|
45
44
|
];
|
|
46
45
|
// Display blocking verification requirements FIRST and prominently
|
|
47
46
|
if (scarsWithVerification.length > 0) {
|
|
48
|
-
lines.push(
|
|
49
|
-
lines.push("๐จ **VERIFICATION REQUIRED BEFORE PROCEEDING**");
|
|
50
|
-
lines.push("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
|
|
47
|
+
lines.push(`${ANSI.yellow}VERIFICATION REQUIRED${ANSI.reset}`);
|
|
51
48
|
lines.push("");
|
|
52
49
|
for (const scar of scarsWithVerification) {
|
|
53
50
|
const rv = scar.required_verification;
|
|
@@ -63,20 +60,13 @@ No past lessons match this plan closely enough. Scars accumulate as you work โ
|
|
|
63
60
|
lines.push("");
|
|
64
61
|
lines.push(`**MUST SHOW:** ${rv.must_show}`);
|
|
65
62
|
lines.push("");
|
|
66
|
-
lines.push(
|
|
63
|
+
lines.push(`${ANSI.red}Do not proceed until verification output is shown.${ANSI.reset}`);
|
|
67
64
|
lines.push("");
|
|
68
65
|
}
|
|
69
|
-
lines.push("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
|
|
70
|
-
lines.push("");
|
|
71
66
|
}
|
|
72
67
|
for (const scar of scars) {
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
high: "๐ ",
|
|
76
|
-
medium: "๐ก",
|
|
77
|
-
low: "๐ข",
|
|
78
|
-
}[scar.severity] || "โช";
|
|
79
|
-
lines.push(`${severityEmoji} **${scar.title}** (${scar.severity}, score: ${scar.similarity.toFixed(2)}) ยท id: ${scar.id}`);
|
|
68
|
+
const sev = SEV[scar.severity] || "[?]";
|
|
69
|
+
lines.push(`${sev} **${scar.title}** (${scar.severity}, ${scar.similarity.toFixed(2)}) ${dimText(`id:${scar.id.slice(0, 8)}`)}`);
|
|
80
70
|
// Inline archival hint: scars with high dismiss rates get annotated
|
|
81
71
|
if (dismissals) {
|
|
82
72
|
const counts = dismissals.get(scar.id);
|
package/dist/tools/search.js
CHANGED
|
@@ -17,11 +17,11 @@ import { getProject } from "../services/session-state.js";
|
|
|
17
17
|
import { getStorage } from "../services/storage.js";
|
|
18
18
|
import { Timer, recordMetrics, buildPerformanceData, buildComponentPerformance, } from "../services/metrics.js";
|
|
19
19
|
import { v4 as uuidv4 } from "uuid";
|
|
20
|
-
import { wrapDisplay, truncate, SEV, TYPE } from "../services/display-protocol.js";
|
|
20
|
+
import { wrapDisplay, truncate, SEV, TYPE, productLine } from "../services/display-protocol.js";
|
|
21
21
|
// --- Display Formatting ---
|
|
22
22
|
function buildSearchDisplay(results, total_found, query, filters) {
|
|
23
23
|
const lines = [];
|
|
24
|
-
lines.push(
|
|
24
|
+
lines.push(productLine("search", `${total_found} results ยท "${truncate(query, 60)}"`));
|
|
25
25
|
const fp = [];
|
|
26
26
|
if (filters.severity)
|
|
27
27
|
fp.push(`severity=${filters.severity}`);
|
|
@@ -36,7 +36,7 @@ function buildSearchDisplay(results, total_found, query, filters) {
|
|
|
36
36
|
}
|
|
37
37
|
for (const r of results) {
|
|
38
38
|
const te = TYPE[r.learning_type] || "ยท";
|
|
39
|
-
const se = SEV[r.severity] || "
|
|
39
|
+
const se = SEV[r.severity] || "[?]";
|
|
40
40
|
const t = truncate(r.title, 50);
|
|
41
41
|
const sim = `(${r.similarity.toFixed(2)})`;
|
|
42
42
|
const issue = r.source_linear_issue ? ` ${r.source_linear_issue}` : "";
|