@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.
Files changed (2) hide show
  1. package/dist/index.mjs +30 -46
  2. 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 ClaudeCodeStopInputSchema = z.object({
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
- stop_hook_active: z.boolean().optional()
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 = ClaudeCodeStopInputSchema.safeParse(parsedJson);
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 MEMORY_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.", "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."].join("\n");
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
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${MEMORY_GUIDANCE}\n</memory-guidance>`);
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 userPromptSubmitInner = (Array.isArray(hooks.UserPromptSubmit) ? hooks.UserPromptSubmit : []).flatMap(getHooksArray);
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
- event: "SessionStart",
1151
- command: "npx -y @membank/cli inject --harness claude-code",
1152
- existingCommand: extractInjectCommand(sessionStartInner) || null
1153
- },
1154
- {
1155
- event: "UserPromptSubmit",
1156
- command: "npx -y @membank/cli inject --harness claude-code --event user-prompt-submit",
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("UserPromptSubmit")) newHooks.UserPromptSubmit = [...filterOutMembank(Array.isArray(hooks.UserPromptSubmit) ? hooks.UserPromptSubmit : []), {
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 Stop hook payload from stdin").option("--harness <name>", "harness whose stop-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) => {
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.14.1",
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.12.1",
25
- "@membank/mcp": "0.14.3"
24
+ "@membank/core": "0.13.0",
25
+ "@membank/mcp": "0.15.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/node": "^25.6.0",