@membank/cli 0.14.1 → 0.15.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/dist/index.mjs +30 -46
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -253,12 +253,12 @@ function exportCommand(db, formatter, opts) {
|
|
|
253
253
|
//#endregion
|
|
254
254
|
//#region src/commands/extract.ts
|
|
255
255
|
const ExtractionHarnessSchema = z.enum(["claude-code"]);
|
|
256
|
-
const
|
|
256
|
+
const ClaudeCodeSessionEndInputSchema = z.object({
|
|
257
257
|
session_id: z.string(),
|
|
258
258
|
transcript_path: z.string(),
|
|
259
259
|
cwd: z.string().optional(),
|
|
260
260
|
hook_event_name: z.string().optional(),
|
|
261
|
-
|
|
261
|
+
reason: z.string().optional()
|
|
262
262
|
});
|
|
263
263
|
function parseHookPayload(harness, raw) {
|
|
264
264
|
let parsedJson;
|
|
@@ -271,7 +271,7 @@ function parseHookPayload(harness, raw) {
|
|
|
271
271
|
};
|
|
272
272
|
}
|
|
273
273
|
if (harness === "claude-code") {
|
|
274
|
-
const parsed =
|
|
274
|
+
const parsed = ClaudeCodeSessionEndInputSchema.safeParse(parsedJson);
|
|
275
275
|
if (!parsed.success) return {
|
|
276
276
|
ok: false,
|
|
277
277
|
reason: `invalid hook payload: ${parsed.error.message}`
|
|
@@ -280,8 +280,7 @@ function parseHookPayload(harness, raw) {
|
|
|
280
280
|
ok: true,
|
|
281
281
|
value: {
|
|
282
282
|
sessionId: parsed.data.session_id,
|
|
283
|
-
transcriptPath: parsed.data.transcript_path
|
|
284
|
-
stopHookActive: parsed.data.stop_hook_active === true
|
|
283
|
+
transcriptPath: parsed.data.transcript_path
|
|
285
284
|
}
|
|
286
285
|
};
|
|
287
286
|
}
|
|
@@ -305,7 +304,6 @@ async function extractCommand(opts) {
|
|
|
305
304
|
const harness = harnessResult.data;
|
|
306
305
|
let sessionId = opts.sessionId;
|
|
307
306
|
let transcriptPath = opts.transcript;
|
|
308
|
-
let stopHookActive = false;
|
|
309
307
|
if (sessionId === void 0 || transcriptPath === void 0) {
|
|
310
308
|
const raw = await readStdin();
|
|
311
309
|
if (raw.trim().length > 0) {
|
|
@@ -316,17 +314,12 @@ async function extractCommand(opts) {
|
|
|
316
314
|
}
|
|
317
315
|
sessionId = sessionId ?? parsed.value.sessionId;
|
|
318
316
|
transcriptPath = transcriptPath ?? parsed.value.transcriptPath;
|
|
319
|
-
stopHookActive = parsed.value.stopHookActive;
|
|
320
317
|
}
|
|
321
318
|
}
|
|
322
319
|
if (sessionId === void 0 || transcriptPath === void 0) {
|
|
323
320
|
process.stderr.write("membank extract: missing session_id or transcript_path (provide via stdin or --session/--transcript).\n");
|
|
324
321
|
return;
|
|
325
322
|
}
|
|
326
|
-
if (stopHookActive) {
|
|
327
|
-
process.stderr.write("membank extract: stop_hook_active=true; skipping to avoid recursion.\n");
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
323
|
try {
|
|
331
324
|
const result = await runExtraction({
|
|
332
325
|
sessionId,
|
|
@@ -387,11 +380,16 @@ async function importCommand(filePath, db, formatter, prompt) {
|
|
|
387
380
|
}
|
|
388
381
|
//#endregion
|
|
389
382
|
//#region src/commands/inject.ts
|
|
390
|
-
const
|
|
383
|
+
const SAVE_GUIDANCE = "Save (call save_memory) when: (1) user states a preference or makes a decision; (2) user corrects you; (3) you discover a working fix after a tool error; (4) you learn a non-obvious project fact. Type ∈ correction|preference|decision|learning|fact. When unsure, save.";
|
|
384
|
+
const QUERY_GUIDANCE = "Query (call query_memory) before: answering anything that touches prior decisions, and before exploration tasks (file reads, searches, web lookups) where past corrections or preferences may apply. Skip when clearly irrelevant (e.g. trivial arithmetic). Soft guideline, not a hard rule.";
|
|
385
|
+
const MEMORY_GUIDANCE = [SAVE_GUIDANCE, QUERY_GUIDANCE].join("\n");
|
|
386
|
+
function buildGuidance(harness) {
|
|
387
|
+
return harness === "claude-code" ? QUERY_GUIDANCE : MEMORY_GUIDANCE;
|
|
388
|
+
}
|
|
391
389
|
function xmlEscape(s) {
|
|
392
390
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
393
391
|
}
|
|
394
|
-
function formatContext(ctx) {
|
|
392
|
+
function formatContext(ctx, guidance) {
|
|
395
393
|
const parts = [];
|
|
396
394
|
const statParts = Object.entries(ctx.stats).filter(([, count]) => count > 0).map(([type, count]) => `${count} ${type}${count !== 1 ? "s" : ""}`);
|
|
397
395
|
if (statParts.length > 0) parts.push(`<memory-stats>\n${statParts.join(", ")}\n</memory-stats>`);
|
|
@@ -403,7 +401,7 @@ function formatContext(ctx) {
|
|
|
403
401
|
parts.push(`<pinned-memories>\n${memLines.join("\n")}\n</pinned-memories>`);
|
|
404
402
|
}
|
|
405
403
|
}
|
|
406
|
-
parts.push(`<memory-guidance>\n${
|
|
404
|
+
parts.push(`<memory-guidance>\n${guidance}\n</memory-guidance>`);
|
|
407
405
|
return parts.join("\n");
|
|
408
406
|
}
|
|
409
407
|
function outputAdditionalContext(text, harness, eventName) {
|
|
@@ -423,7 +421,7 @@ function outputAdditionalContext(text, harness, eventName) {
|
|
|
423
421
|
function pickBestSynthesis(globalSynthesis, projectSynthesis) {
|
|
424
422
|
return projectSynthesis ?? globalSynthesis;
|
|
425
423
|
}
|
|
426
|
-
async function buildText() {
|
|
424
|
+
async function buildText(harness) {
|
|
427
425
|
const resolved = await resolveProject();
|
|
428
426
|
const db = DatabaseManager.open();
|
|
429
427
|
try {
|
|
@@ -432,13 +430,13 @@ async function buildText() {
|
|
|
432
430
|
const globalRow = synthRepo.getSynthesis(GLOBAL_SCOPE_HASH);
|
|
433
431
|
const projectRow = synthRepo.getSynthesis(resolved.hash);
|
|
434
432
|
const synthesis = pickBestSynthesis(globalRow?.inFlightSince === null ? globalRow.content : void 0, projectRow?.inFlightSince === null ? projectRow.content : void 0);
|
|
435
|
-
return formatContext(builder.getSessionContext(resolved.hash, synthesis));
|
|
433
|
+
return formatContext(builder.getSessionContext(resolved.hash, synthesis), buildGuidance(harness));
|
|
436
434
|
} finally {
|
|
437
435
|
db.close();
|
|
438
436
|
}
|
|
439
437
|
}
|
|
440
438
|
async function handleEvent(harness, eventName) {
|
|
441
|
-
const text = await buildText().catch((err) => {
|
|
439
|
+
const text = await buildText(harness).catch((err) => {
|
|
442
440
|
const msg = err instanceof Error ? err.message : String(err);
|
|
443
441
|
process.stderr.write(`membank inject: ${msg}\n`);
|
|
444
442
|
return null;
|
|
@@ -454,6 +452,7 @@ async function injectCommand(opts) {
|
|
|
454
452
|
return;
|
|
455
453
|
}
|
|
456
454
|
if (opts.event === "user-prompt-submit") {
|
|
455
|
+
if (harness === "claude-code") process.exit(0);
|
|
457
456
|
await handleEvent(harness, "UserPromptSubmit");
|
|
458
457
|
return;
|
|
459
458
|
}
|
|
@@ -1140,28 +1139,19 @@ const writers = {
|
|
|
1140
1139
|
const cfg = readJson(cfgPath);
|
|
1141
1140
|
const hooks = MaybeJsonObjectSchema.parse(cfg.hooks) ?? {};
|
|
1142
1141
|
const sessionStartInner = (Array.isArray(hooks.SessionStart) ? hooks.SessionStart : []).flatMap(getHooksArray);
|
|
1143
|
-
const
|
|
1144
|
-
const stopInner = (Array.isArray(hooks.Stop) ? hooks.Stop : []).flatMap(getHooksArray);
|
|
1142
|
+
const sessionEndInner = (Array.isArray(hooks.SessionEnd) ? hooks.SessionEnd : []).flatMap(getHooksArray);
|
|
1145
1143
|
return {
|
|
1146
1144
|
status: "ready",
|
|
1147
1145
|
configPath: cfgPath,
|
|
1148
|
-
hooks: [
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
existingCommand: extractInjectCommand(userPromptSubmitInner) || null
|
|
1158
|
-
},
|
|
1159
|
-
{
|
|
1160
|
-
event: "Stop",
|
|
1161
|
-
command: "npx -y @membank/cli extract --harness claude-code",
|
|
1162
|
-
existingCommand: extractInjectCommand(stopInner) || null
|
|
1163
|
-
}
|
|
1164
|
-
]
|
|
1146
|
+
hooks: [{
|
|
1147
|
+
event: "SessionStart",
|
|
1148
|
+
command: "npx -y @membank/cli inject --harness claude-code",
|
|
1149
|
+
existingCommand: extractInjectCommand(sessionStartInner) || null
|
|
1150
|
+
}, {
|
|
1151
|
+
event: "SessionEnd",
|
|
1152
|
+
command: "npx -y @membank/cli extract --harness claude-code",
|
|
1153
|
+
existingCommand: extractInjectCommand(sessionEndInner) || null
|
|
1154
|
+
}]
|
|
1165
1155
|
};
|
|
1166
1156
|
},
|
|
1167
1157
|
write(resolver, events) {
|
|
@@ -1171,6 +1161,7 @@ const writers = {
|
|
|
1171
1161
|
const newHooks = { ...hooks };
|
|
1172
1162
|
pruneNestedEvent(newHooks, "PostToolUseFailure");
|
|
1173
1163
|
pruneNestedEvent(newHooks, "Stop");
|
|
1164
|
+
pruneNestedEvent(newHooks, "UserPromptSubmit");
|
|
1174
1165
|
if (events.includes("SessionStart")) newHooks.SessionStart = [...filterOutMembank(Array.isArray(hooks.SessionStart) ? hooks.SessionStart : []), {
|
|
1175
1166
|
matcher: "",
|
|
1176
1167
|
hooks: [{
|
|
@@ -1178,15 +1169,8 @@ const writers = {
|
|
|
1178
1169
|
command: "npx -y @membank/cli inject --harness claude-code"
|
|
1179
1170
|
}]
|
|
1180
1171
|
}];
|
|
1181
|
-
if (events.includes("
|
|
1182
|
-
matcher: "",
|
|
1183
|
-
hooks: [{
|
|
1184
|
-
type: "command",
|
|
1185
|
-
command: "npx -y @membank/cli inject --harness claude-code --event user-prompt-submit"
|
|
1186
|
-
}]
|
|
1187
|
-
}];
|
|
1188
|
-
if (events.includes("Stop")) newHooks.Stop = [...filterOutMembank(Array.isArray(hooks.Stop) ? hooks.Stop : []), {
|
|
1189
|
-
matcher: "",
|
|
1172
|
+
if (events.includes("SessionEnd")) newHooks.SessionEnd = [...filterOutMembank(Array.isArray(hooks.SessionEnd) ? hooks.SessionEnd : []), {
|
|
1173
|
+
matcher: "clear|resume|logout|prompt_input_exit|other",
|
|
1190
1174
|
hooks: [{
|
|
1191
1175
|
type: "command",
|
|
1192
1176
|
command: "npx -y @membank/cli extract --harness claude-code",
|
|
@@ -1754,7 +1738,7 @@ program.command("inject").description("output session context for harness inject
|
|
|
1754
1738
|
process.exit(2);
|
|
1755
1739
|
}
|
|
1756
1740
|
});
|
|
1757
|
-
program.command("extract").description("(internal) run session-end memory extraction; reads the harness's
|
|
1741
|
+
program.command("extract").description("(internal) run session-end memory extraction; reads the harness's SessionEnd hook payload from stdin").option("--harness <name>", "harness whose session-end hook payload is on stdin (only claude-code is supported today)", "claude-code").option("--session <id>", "session id (otherwise read from stdin)").option("--transcript <path>", "transcript JSONL path (otherwise read from stdin)").action(async (cmdOptions) => {
|
|
1758
1742
|
try {
|
|
1759
1743
|
await extractCommand({
|
|
1760
1744
|
harness: cmdOptions.harness,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@membank/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"commander": "^14.0.3",
|
|
22
22
|
"ora": "^9.4.0",
|
|
23
23
|
"zod": "^4.4.3",
|
|
24
|
-
"@membank/core": "0.
|
|
25
|
-
"@membank/mcp": "0.
|
|
24
|
+
"@membank/core": "0.13.0",
|
|
25
|
+
"@membank/mcp": "0.15.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^25.6.0",
|