gitmem-mcp 1.1.3 → 1.2.0
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 +41 -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 +1 -1
- package/windsurfrules.template +33 -14
|
@@ -19,7 +19,7 @@ import { syncThreadsToSupabase, loadOpenThreadEmbeddings } from "../services/thr
|
|
|
19
19
|
import { validateSessionClose, buildCloseCompliance, } from "../services/compliance-validator.js";
|
|
20
20
|
import { normalizeReflectionKeys } from "../constants/closing-questions.js";
|
|
21
21
|
import { Timer, recordMetrics, buildPerformanceData, updateRelevanceData, } from "../services/metrics.js";
|
|
22
|
-
import { wrapDisplay, truncate } from "../services/display-protocol.js";
|
|
22
|
+
import { wrapDisplay, truncate, productLine, dimText, STATUS, ANSI } from "../services/display-protocol.js";
|
|
23
23
|
import { recordScarUsageBatch } from "./record-scar-usage-batch.js";
|
|
24
24
|
import { getEffectTracker } from "../services/effect-tracker.js";
|
|
25
25
|
import { saveTranscript } from "./save-transcript.js";
|
|
@@ -242,107 +242,73 @@ async function sessionCloseFree(params, timer) {
|
|
|
242
242
|
}
|
|
243
243
|
function formatCloseDisplay(sessionId, compliance, params, learningsCount, success, errors, transcriptStatus) {
|
|
244
244
|
const lines = [];
|
|
245
|
-
// Header
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
245
|
+
// Header: branded product line
|
|
246
|
+
const status = success ? STATUS.complete : STATUS.failed;
|
|
247
|
+
lines.push(productLine("close", status));
|
|
248
|
+
// Stats line: compact one-liner with key counts
|
|
249
|
+
const stats = [];
|
|
250
|
+
const scarsApplied = params.scars_to_record?.filter(s => s.reference_type !== "none").length || 0;
|
|
251
|
+
if (scarsApplied > 0)
|
|
252
|
+
stats.push(`${scarsApplied} scars applied`);
|
|
253
|
+
if (learningsCount > 0)
|
|
254
|
+
stats.push(`${learningsCount} learnings`);
|
|
255
|
+
if (params.decisions?.length)
|
|
256
|
+
stats.push(`${params.decisions.length} decision${params.decisions.length > 1 ? "s" : ""}`);
|
|
257
|
+
const threads = params.open_threads || [];
|
|
258
|
+
const openCount = threads.filter(t => typeof t === "string" || t.status === "open").length;
|
|
259
|
+
if (openCount > 0)
|
|
260
|
+
stats.push(`${openCount} threads`);
|
|
261
|
+
if (stats.length > 0) {
|
|
262
|
+
lines.push(stats.join(" · "));
|
|
263
|
+
}
|
|
264
|
+
lines.push(dimText(`${sessionId.slice(0, 8)} · ${compliance.agent} · ${compliance.close_type}`));
|
|
265
|
+
// Errors — only on failure
|
|
253
266
|
if (!success && errors?.length) {
|
|
254
267
|
lines.push("");
|
|
255
268
|
for (const e of errors)
|
|
256
|
-
lines.push(`
|
|
257
|
-
}
|
|
258
|
-
// Checklist (compact)
|
|
259
|
-
lines.push("");
|
|
260
|
-
const ok = "\u2713";
|
|
261
|
-
const no = "\u2717";
|
|
262
|
-
if (compliance.close_type === "standard") {
|
|
263
|
-
lines.push(` ${ok} Session state read`);
|
|
264
|
-
lines.push(` ${compliance.questions_answered_by_agent ? ok : no} Reflection (9 questions)`);
|
|
265
|
-
lines.push(` ${compliance.human_asked_for_corrections ? ok : no} Human corrections`);
|
|
266
|
-
lines.push(` ${success ? ok : no} Persisted`);
|
|
269
|
+
lines.push(` ${STATUS.miss} ${e}`);
|
|
267
270
|
}
|
|
268
|
-
|
|
269
|
-
|
|
271
|
+
// Reflection highlights FIRST — the most interesting part
|
|
272
|
+
if (params.closing_reflection) {
|
|
273
|
+
const r = params.closing_reflection;
|
|
274
|
+
if (r.what_worked || r.do_differently) {
|
|
275
|
+
lines.push("");
|
|
276
|
+
if (r.what_worked) {
|
|
277
|
+
lines.push(`${STATUS.pass} ${truncate(r.what_worked, 72)}`);
|
|
278
|
+
}
|
|
279
|
+
if (r.do_differently) {
|
|
280
|
+
lines.push(`${ANSI.yellow}>${ANSI.reset} ${truncate(r.do_differently, 72)}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
270
283
|
}
|
|
271
|
-
// Decisions
|
|
284
|
+
// Decisions — title only, one line each
|
|
272
285
|
if (params.decisions?.length) {
|
|
273
286
|
lines.push("");
|
|
274
|
-
lines.push(`${B}Decisions${R}`);
|
|
275
287
|
for (const d of params.decisions) {
|
|
276
|
-
lines.push(` ${truncate(d.title,
|
|
277
|
-
lines.push(` ${truncate(d.decision, 70)}`);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
// Learnings created
|
|
281
|
-
if (params.learnings_created?.length) {
|
|
282
|
-
lines.push("");
|
|
283
|
-
lines.push(`${B}Learnings (${params.learnings_created.length})${R}`);
|
|
284
|
-
for (const l of params.learnings_created) {
|
|
285
|
-
// learnings_created is (string | Record)[] — show truncated ID or object key
|
|
286
|
-
const label = typeof l === "string" ? (l.length > 12 ? l.slice(0, 8) : l) : String(l);
|
|
287
|
-
lines.push(` ${label}`);
|
|
288
|
+
lines.push(` ${dimText("d")} ${truncate(d.title, 68)}`);
|
|
288
289
|
}
|
|
289
290
|
}
|
|
290
|
-
// Scars applied
|
|
291
|
-
if (
|
|
292
|
-
const acknowledged = params.scars_to_record.filter(s => s.reference_type !== "none");
|
|
293
|
-
const ignored = params.scars_to_record.length - acknowledged.length;
|
|
291
|
+
// Scars applied — compact table, only if any were applied
|
|
292
|
+
if (scarsApplied > 0) {
|
|
294
293
|
lines.push("");
|
|
295
|
-
|
|
296
|
-
for (const s of acknowledged) {
|
|
294
|
+
for (const s of params.scars_to_record.filter(s => s.reference_type !== "none")) {
|
|
297
295
|
const ref = s.reference_type === "explicit" ? "applied" :
|
|
298
296
|
s.reference_type === "implicit" ? "implicit" :
|
|
299
297
|
s.reference_type === "acknowledged" ? "ack'd" :
|
|
300
|
-
s.reference_type === "refuted" ?
|
|
301
|
-
|
|
302
|
-
const id = scarId.length > 12 ? scarId.slice(0, 8) : scarId;
|
|
303
|
-
lines.push(` ${id} ${ref.padEnd(8)} ${truncate(s.reference_context || "", 50)}`);
|
|
298
|
+
s.reference_type === "refuted" ? `${ANSI.yellow}REFUTED${ANSI.reset}` : (s.reference_type || "?");
|
|
299
|
+
lines.push(` ${dimText(ref.padEnd(8))} ${truncate(s.reference_context || "", 60)}`);
|
|
304
300
|
}
|
|
305
301
|
}
|
|
306
|
-
// Transcript
|
|
307
|
-
if (transcriptStatus) {
|
|
302
|
+
// Transcript — only on failure
|
|
303
|
+
if (transcriptStatus && !transcriptStatus.saved) {
|
|
308
304
|
lines.push("");
|
|
309
|
-
lines.push(
|
|
310
|
-
if (transcriptStatus.saved) {
|
|
311
|
-
let line = `- [done] Saved (${transcriptStatus.size_kb}KB) -> ${transcriptStatus.path}`;
|
|
312
|
-
if (transcriptStatus.patch_warning) {
|
|
313
|
-
line += ` (warning: session record not updated)`;
|
|
314
|
-
}
|
|
315
|
-
lines.push(line);
|
|
316
|
-
}
|
|
317
|
-
else {
|
|
318
|
-
lines.push(`- [FAILED] ${transcriptStatus.error || "Unknown error"}`);
|
|
319
|
-
}
|
|
305
|
+
lines.push(`${STATUS.fail} transcript: ${transcriptStatus.error || "Unknown error"}`);
|
|
320
306
|
}
|
|
321
|
-
//
|
|
322
|
-
const threads = params.open_threads || [];
|
|
323
|
-
if (threads.length > 0) {
|
|
324
|
-
const openCount = threads.filter(t => typeof t === "string" || t.status === "open").length;
|
|
325
|
-
const resolvedCount = threads.length - openCount;
|
|
326
|
-
lines.push("");
|
|
327
|
-
lines.push(`${B}Threads${R}: ${openCount} open${resolvedCount > 0 ? `, ${resolvedCount} resolved` : ""}`);
|
|
328
|
-
}
|
|
329
|
-
// Write health — only surface when there are failures (dev diagnostic)
|
|
307
|
+
// Write health — only on failure
|
|
330
308
|
const healthReport = getEffectTracker().getHealthReport();
|
|
331
309
|
if (healthReport.overall.failed > 0) {
|
|
332
310
|
lines.push("");
|
|
333
|
-
lines.push(`${
|
|
334
|
-
lines.push(getEffectTracker().formatSummary());
|
|
335
|
-
}
|
|
336
|
-
// Reflection highlights (Q4 what worked, Q3 do differently)
|
|
337
|
-
if (params.closing_reflection) {
|
|
338
|
-
const r = params.closing_reflection;
|
|
339
|
-
if (r.what_worked) {
|
|
340
|
-
lines.push("");
|
|
341
|
-
lines.push(`${B}What worked${R}: ${truncate(r.what_worked, 80)}`);
|
|
342
|
-
}
|
|
343
|
-
if (r.do_differently) {
|
|
344
|
-
lines.push(`${B}Next time${R}: ${truncate(r.do_differently, 80)}`);
|
|
345
|
-
}
|
|
311
|
+
lines.push(`${STATUS.warn} ${healthReport.overall.failed} write failure${healthReport.overall.failed > 1 ? "s" : ""}`);
|
|
346
312
|
}
|
|
347
313
|
return wrapDisplay(lines.join("\n"));
|
|
348
314
|
}
|
|
@@ -784,6 +750,36 @@ export async function sessionClose(params) {
|
|
|
784
750
|
human_response: "auto-normalized from string payload",
|
|
785
751
|
};
|
|
786
752
|
}
|
|
753
|
+
// Auto-generate task_completion when missing or has empty human response fields.
|
|
754
|
+
// Agents write closing_reflection to the payload before asking the human, so
|
|
755
|
+
// human_response_at and human_response are often empty. Rather than requiring
|
|
756
|
+
// agents to edit the payload a second time, we fill these from human_corrections
|
|
757
|
+
// (which the agent passes directly) and stamp the current time.
|
|
758
|
+
if (params.close_type === "standard" && params.task_completion && typeof params.task_completion === "object") {
|
|
759
|
+
const tc = params.task_completion;
|
|
760
|
+
if (!tc.human_response || tc.human_response.trim() === "") {
|
|
761
|
+
tc.human_response = params.human_corrections || "none";
|
|
762
|
+
console.error("[session_close] Auto-filled task_completion.human_response from human_corrections");
|
|
763
|
+
}
|
|
764
|
+
if (!tc.human_response_at || tc.human_response_at.trim() === "") {
|
|
765
|
+
tc.human_response_at = new Date().toISOString();
|
|
766
|
+
console.error("[session_close] Auto-filled task_completion.human_response_at with current time");
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
// Auto-generate task_completion entirely when payload has closing_reflection but no task_completion.
|
|
770
|
+
// This is the common case: agent writes reflection to payload, asks human, calls session_close.
|
|
771
|
+
if (params.close_type === "standard" && !params.task_completion && params.closing_reflection) {
|
|
772
|
+
const now = new Date().toISOString();
|
|
773
|
+
const fiveSecsAgo = new Date(Date.now() - 5000).toISOString();
|
|
774
|
+
params.task_completion = {
|
|
775
|
+
questions_displayed_at: fiveSecsAgo,
|
|
776
|
+
reflection_completed_at: fiveSecsAgo,
|
|
777
|
+
human_asked_at: fiveSecsAgo,
|
|
778
|
+
human_response_at: now,
|
|
779
|
+
human_response: params.human_corrections || "none",
|
|
780
|
+
};
|
|
781
|
+
console.error("[session_close] Auto-generated task_completion from closing_reflection + human_corrections");
|
|
782
|
+
}
|
|
787
783
|
// Adaptive ceremony level based on session activity.
|
|
788
784
|
// Three levels: micro (quick fix), standard (normal), full (long/heavy session).
|
|
789
785
|
// t-f7c2fa01: If closing_reflection is already present, skip the mismatch gate.
|
|
@@ -1084,6 +1080,32 @@ export async function sessionClose(params) {
|
|
|
1084
1080
|
if (normalizedScarsApplied.length > 0) {
|
|
1085
1081
|
updateRelevanceData(sessionId, normalizedScarsApplied).catch((err) => console.error("[session_close] updateRelevanceData failed:", err instanceof Error ? err.message : err));
|
|
1086
1082
|
}
|
|
1083
|
+
// Compute timing breakdown from task_completion timestamps.
|
|
1084
|
+
// The human feels two things during close:
|
|
1085
|
+
// 1. Agent reflection: questions_displayed_at → human_asked_at (LLM writes 9-question payload)
|
|
1086
|
+
// 2. Tool execution: latency_ms (DB writes after human says "none" / gives corrections)
|
|
1087
|
+
// The human does NOT feel: human_asked_at → human_response_at (their own thinking time)
|
|
1088
|
+
let agentReflectionMs;
|
|
1089
|
+
let humanWaitTimeMs;
|
|
1090
|
+
if (params.task_completion && typeof params.task_completion === "object") {
|
|
1091
|
+
const tc = params.task_completion;
|
|
1092
|
+
// Agent reflection time = when questions shown → when human asked "Corrections?"
|
|
1093
|
+
if (tc.questions_displayed_at && tc.human_asked_at) {
|
|
1094
|
+
const started = new Date(tc.questions_displayed_at).getTime();
|
|
1095
|
+
const askedHuman = new Date(tc.human_asked_at).getTime();
|
|
1096
|
+
if (!isNaN(started) && !isNaN(askedHuman) && askedHuman > started) {
|
|
1097
|
+
agentReflectionMs = askedHuman - started;
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
// Human wait time = "Corrections?" → human responds
|
|
1101
|
+
if (tc.human_asked_at && tc.human_response_at) {
|
|
1102
|
+
const asked = new Date(tc.human_asked_at).getTime();
|
|
1103
|
+
const responded = new Date(tc.human_response_at).getTime();
|
|
1104
|
+
if (!isNaN(asked) && !isNaN(responded) && responded > asked) {
|
|
1105
|
+
humanWaitTimeMs = responded - asked;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1087
1109
|
// Record metrics
|
|
1088
1110
|
recordMetrics({
|
|
1089
1111
|
id: metricsId,
|
|
@@ -1102,6 +1124,8 @@ export async function sessionClose(params) {
|
|
|
1102
1124
|
decisions_count: params.decisions?.length || 0,
|
|
1103
1125
|
open_threads_count: params.open_threads?.length || 0,
|
|
1104
1126
|
ceremony_duration_ms: params.ceremony_duration_ms,
|
|
1127
|
+
agent_reflection_ms: agentReflectionMs,
|
|
1128
|
+
human_wait_time_ms: humanWaitTimeMs,
|
|
1105
1129
|
retroactive: isRetroactive,
|
|
1106
1130
|
},
|
|
1107
1131
|
}).catch(() => { });
|
|
@@ -28,6 +28,7 @@ import { setGitmemDir, getGitmemDir, getSessionPath, getConfigProject } from "..
|
|
|
28
28
|
import { registerSession, findSessionByHostPid, pruneStale, migrateFromLegacy } from "../services/active-sessions.js";
|
|
29
29
|
import * as os from "os";
|
|
30
30
|
import { formatDate } from "../services/timezone.js";
|
|
31
|
+
import { productLine, dimText, boldText } from "../services/display-protocol.js";
|
|
31
32
|
/**
|
|
32
33
|
* Closing payload schema — returned in session_start/refresh so agents
|
|
33
34
|
* know the exact field names for closing-payload.json without guessing.
|
|
@@ -42,19 +43,27 @@ const CLOSING_PAYLOAD_SCHEMA = {
|
|
|
42
43
|
scars_applied: ["Q6: scar titles applied"],
|
|
43
44
|
institutional_memory_items: "Q7: What to capture as institutional memory?",
|
|
44
45
|
},
|
|
45
|
-
task_completion
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
human_asked_at: "ISO-8601",
|
|
49
|
-
human_response_at: "ISO-8601",
|
|
50
|
-
human_response: "human's corrections or 'no corrections'",
|
|
51
|
-
},
|
|
52
|
-
human_corrections: "",
|
|
46
|
+
// task_completion is auto-generated by session_close — do NOT include in payload.
|
|
47
|
+
// Timestamps are stamped by the tool. human_response is filled from human_corrections.
|
|
48
|
+
human_corrections: "human's corrections or 'none'",
|
|
53
49
|
scars_to_record: [],
|
|
54
50
|
learnings_created: [],
|
|
55
51
|
open_threads: [],
|
|
56
52
|
decisions: [],
|
|
57
53
|
};
|
|
54
|
+
const FIRST_SESSION_NUDGE = [
|
|
55
|
+
"",
|
|
56
|
+
"Welcome to gitmem. Patterns that improve the experience:",
|
|
57
|
+
"",
|
|
58
|
+
" - session_start: once per session — if a hook triggers it, don't repeat",
|
|
59
|
+
" - Display protocol: echo tool results verbatim, including ANSI formatting",
|
|
60
|
+
" - recall() before consequential actions (deploys, issues, agent spawns)",
|
|
61
|
+
" - Read .gitmem/ state before session close — survives context compaction",
|
|
62
|
+
" - Search gitmem before filesystem exploration — memory > re-discovery",
|
|
63
|
+
" - Create learning entries after significant work, before closing",
|
|
64
|
+
"",
|
|
65
|
+
"Save these to your persistent memory (MEMORY.md) so they carry forward.",
|
|
66
|
+
].join("\n");
|
|
58
67
|
/**
|
|
59
68
|
* Normalize decisions from mixed formats (strings or objects) to string[].
|
|
60
69
|
* Historical sessions (pre-2026) stored {title, decision} objects.
|
|
@@ -444,7 +453,8 @@ async function sessionStartFree(params, env, agent, project, timer, metricsId, e
|
|
|
444
453
|
project,
|
|
445
454
|
performance,
|
|
446
455
|
};
|
|
447
|
-
|
|
456
|
+
const isFirstSession = !isResuming && !lastSession;
|
|
457
|
+
freeResult.display = formatStartDisplay(freeResult, undefined, isFirstSession);
|
|
448
458
|
// Write display to per-session dir
|
|
449
459
|
try {
|
|
450
460
|
const sessionFilePath = getSessionPath(sessionId, "session.json");
|
|
@@ -654,22 +664,22 @@ function writeSessionFiles(sessionId, agent, project, surfacedScars, threads, re
|
|
|
654
664
|
function stripThreadPrefix(text) {
|
|
655
665
|
return text.replace(/^t-[a-f0-9]+:\s*/i, "");
|
|
656
666
|
}
|
|
657
|
-
function formatStartDisplay(result, displayInfoMap) {
|
|
667
|
+
function formatStartDisplay(result, displayInfoMap, isFirstSession) {
|
|
658
668
|
const visual = [];
|
|
659
|
-
// Line 1: product
|
|
669
|
+
// Line 1: branded product line + session state
|
|
660
670
|
const stateLabel = result.refreshed ? "refreshed" : (result.resumed ? "resumed" : "active");
|
|
661
|
-
visual.push(
|
|
662
|
-
// Line 2: session ID + agent + project
|
|
671
|
+
visual.push(productLine(stateLabel));
|
|
672
|
+
// Line 2: session ID + agent + project (dim metadata)
|
|
663
673
|
const parts = [result.session_id, result.agent];
|
|
664
674
|
if (result.project)
|
|
665
675
|
parts.push(result.project);
|
|
666
|
-
visual.push(parts.join(" · "));
|
|
676
|
+
visual.push(dimText(parts.join(" · ")));
|
|
667
677
|
// Threads section — top 5 by vitality, truncated to 60 chars
|
|
668
678
|
const hasThreads = result.open_threads && result.open_threads.length > 0;
|
|
669
679
|
const hasDecisions = result.recent_decisions && result.recent_decisions.length > 0;
|
|
670
680
|
if (hasThreads) {
|
|
671
681
|
visual.push("");
|
|
672
|
-
visual.push(`Threads (${result.open_threads.length})`);
|
|
682
|
+
visual.push(boldText(`Threads (${result.open_threads.length})`));
|
|
673
683
|
const enriched = result.open_threads.map(t => ({
|
|
674
684
|
thread: t,
|
|
675
685
|
info: displayInfoMap?.get(t.id),
|
|
@@ -689,7 +699,7 @@ function formatStartDisplay(result, displayInfoMap) {
|
|
|
689
699
|
// Decisions section — top 3 with compact date
|
|
690
700
|
if (hasDecisions) {
|
|
691
701
|
visual.push("");
|
|
692
|
-
visual.push(`Decisions (${result.recent_decisions.length})`);
|
|
702
|
+
visual.push(boldText(`Decisions (${result.recent_decisions.length})`));
|
|
693
703
|
for (const d of result.recent_decisions.slice(0, 3)) {
|
|
694
704
|
const title = d.title.length > 50 ? d.title.slice(0, 47) + "..." : d.title;
|
|
695
705
|
visual.push(` ${title} · ${d.date}`);
|
|
@@ -699,6 +709,10 @@ function formatStartDisplay(result, displayInfoMap) {
|
|
|
699
709
|
visual.push("");
|
|
700
710
|
visual.push("No threads or decisions.");
|
|
701
711
|
}
|
|
712
|
+
// First-session nudge — agent sees this once, internalizes to PMEM
|
|
713
|
+
if (isFirstSession) {
|
|
714
|
+
visual.push(FIRST_SESSION_NUDGE);
|
|
715
|
+
}
|
|
702
716
|
const visualBlock = visual.join("\n");
|
|
703
717
|
// ── Display-first layout ──
|
|
704
718
|
// Visual block comes FIRST so Claude Code's collapsed tool output shows
|
|
@@ -883,7 +897,8 @@ export async function sessionStart(params) {
|
|
|
883
897
|
for (const info of threadDisplayInfo) {
|
|
884
898
|
displayInfoMap.set(info.thread.id, info);
|
|
885
899
|
}
|
|
886
|
-
|
|
900
|
+
const isFirstSession = !isResuming && !slimLastSession;
|
|
901
|
+
result.display = formatStartDisplay(result, displayInfoMap, isFirstSession);
|
|
887
902
|
// Write display to per-session dir
|
|
888
903
|
try {
|
|
889
904
|
const sessionFilePath = getSessionPath(sessionId, "session.json");
|
package/package.json
CHANGED
package/windsurfrules.template
CHANGED
|
@@ -46,18 +46,19 @@ Safe alternatives: `env | grep -c VARNAME` (count only), `[ -n "$VARNAME" ] && e
|
|
|
46
46
|
|
|
47
47
|
**End:** On "closing", "done for now", or "wrapping up":
|
|
48
48
|
|
|
49
|
-
1. **
|
|
50
|
-
|
|
51
|
-
- What
|
|
52
|
-
- What
|
|
53
|
-
- What
|
|
54
|
-
- What
|
|
55
|
-
-
|
|
56
|
-
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
49
|
+
1. **Write reflection directly to payload** — do NOT display the full Q&A to the human.
|
|
50
|
+
Internally answer these 9 questions and write them straight to `.gitmem/closing-payload.json`:
|
|
51
|
+
- Q1: What broke that you didn't expect?
|
|
52
|
+
- Q2: What took longer than it should have?
|
|
53
|
+
- Q3: What would you do differently next time?
|
|
54
|
+
- Q4: What pattern or approach worked well?
|
|
55
|
+
- Q5: What assumption was wrong?
|
|
56
|
+
- Q6: Which scars did you apply?
|
|
57
|
+
- Q7: What should be captured as institutional memory?
|
|
58
|
+
- Q8: How did the human prefer to work this session?
|
|
59
|
+
- Q9: What collaborative dynamic worked or didn't?
|
|
60
|
+
|
|
61
|
+
Payload schema (`.gitmem/closing-payload.json`):
|
|
61
62
|
```json
|
|
62
63
|
{
|
|
63
64
|
"closing_reflection": {
|
|
@@ -67,16 +68,34 @@ Safe alternatives: `env | grep -c VARNAME` (count only), `[ -n "$VARNAME" ] && e
|
|
|
67
68
|
"what_worked": "...",
|
|
68
69
|
"wrong_assumption": "...",
|
|
69
70
|
"scars_applied": ["scar title 1", "scar title 2"],
|
|
70
|
-
"institutional_memory_items": "..."
|
|
71
|
+
"institutional_memory_items": "...",
|
|
72
|
+
"collaborative_dynamic": "...",
|
|
73
|
+
"rapport_notes": "..."
|
|
74
|
+
},
|
|
75
|
+
"task_completion": {
|
|
76
|
+
"questions_displayed_at": "ISO timestamp (when reflection started)",
|
|
77
|
+
"reflection_completed_at": "ISO timestamp (when payload written)",
|
|
78
|
+
"human_asked_at": "ISO timestamp (when 'Corrections?' shown)",
|
|
79
|
+
"human_response_at": "ISO timestamp (when human replied)",
|
|
80
|
+
"human_response": "user's correction text or 'none'"
|
|
71
81
|
},
|
|
72
82
|
"human_corrections": "",
|
|
73
83
|
"scars_to_record": [],
|
|
84
|
+
"learnings_created": [],
|
|
74
85
|
"open_threads": [],
|
|
75
86
|
"decisions": []
|
|
76
87
|
}
|
|
77
88
|
```
|
|
78
89
|
|
|
79
|
-
|
|
90
|
+
2. **Show compact summary** (3-4 lines max):
|
|
91
|
+
```
|
|
92
|
+
Session close: [N] learnings captured, [M] scars applied, [K] threads open
|
|
93
|
+
Key lesson: [one-sentence from Q7]
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
3. **Ask**: "Corrections?" — wait for response, then call `session_close`.
|
|
97
|
+
|
|
98
|
+
4. **Call `session_close`** with `session_id` and `close_type: "standard"`.
|
|
80
99
|
|
|
81
100
|
For short exploratory sessions (< 30 min, no real work), use `close_type: "quick"` — no questions needed.
|
|
82
101
|
# --- gitmem:end ---
|