backthread 0.5.0 → 0.5.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.
@@ -2,7 +2,7 @@
2
2
  "name": "backthread",
3
3
  "displayName": "Backthread",
4
4
  "description": "Backthread helps you understand your codebase while AI ships features. It captures the why behind every Claude Code session so you can ask \"how does X work?\" without digging through PRs.",
5
- "version": "0.5.0",
5
+ "version": "0.5.1",
6
6
  "author": {
7
7
  "name": "Backthread"
8
8
  },
@@ -8333,6 +8333,9 @@ var execFileP = promisify(execFile);
8333
8333
  var MCP_COMMAND = "npx";
8334
8334
  var MCP_ARGS = ["-y", "backthread", "mcp"];
8335
8335
  function hookCommand(agent) {
8336
+ return `npx -y backthread@latest capture --from-hook --agent ${agent} --detach`;
8337
+ }
8338
+ function legacyHookCommand(agent) {
8336
8339
  return `npx -y backthread capture --from-hook --agent ${agent} --detach`;
8337
8340
  }
8338
8341
  var MIN_VERSION = {
@@ -8382,16 +8385,36 @@ function withMcpServer(settings) {
8382
8385
  mcpServers.backthread = desired;
8383
8386
  return { next: { ...settings, mcpServers }, changed: true };
8384
8387
  }
8385
- function withNestedHook(settings, event, command, extra = {}) {
8388
+ function groupRunsCommand(group, command) {
8389
+ const inner = group?.hooks;
8390
+ return Array.isArray(inner) && inner.some((h) => h?.command === command);
8391
+ }
8392
+ function rewriteLegacyInGroup(group, legacyCommands, command) {
8393
+ const inner = group?.hooks;
8394
+ if (!Array.isArray(inner)) return group;
8395
+ let changed = false;
8396
+ const nextInner = inner.map((h) => {
8397
+ const cmd = h?.command;
8398
+ if (typeof cmd === "string" && cmd !== command && legacyCommands.includes(cmd)) {
8399
+ changed = true;
8400
+ return { ...h, command };
8401
+ }
8402
+ return h;
8403
+ });
8404
+ return changed ? { ...group, hooks: nextInner } : group;
8405
+ }
8406
+ function withNestedHook(settings, event, command, extra = {}, legacyCommands = []) {
8386
8407
  const hooks = asObject(settings.hooks);
8387
8408
  const list = Array.isArray(hooks[event]) ? [...hooks[event]] : [];
8388
- const present = list.some((g) => {
8389
- const inner = g?.hooks;
8390
- return Array.isArray(inner) && inner.some((h) => h?.command === command);
8409
+ if (list.some((g) => groupRunsCommand(g, command))) return { next: settings, changed: false };
8410
+ let migrated = false;
8411
+ const nextList = list.map((g) => {
8412
+ const rewritten = rewriteLegacyInGroup(g, legacyCommands, command);
8413
+ if (rewritten !== g) migrated = true;
8414
+ return rewritten;
8391
8415
  });
8392
- if (present) return { next: settings, changed: false };
8393
- list.push({ hooks: [{ type: "command", command, ...extra }] });
8394
- hooks[event] = list;
8416
+ if (!migrated) nextList.push({ hooks: [{ type: "command", command, ...extra }] });
8417
+ hooks[event] = nextList;
8395
8418
  return { next: { ...settings, hooks }, changed: true };
8396
8419
  }
8397
8420
  async function writeJson(deps, path, obj) {
@@ -8405,7 +8428,9 @@ async function installGemini(home, deps) {
8405
8428
  const path = join8(home, ".gemini", "settings.json");
8406
8429
  const current = await loadJsonObject(doRead, path);
8407
8430
  const a = withMcpServer(current);
8408
- const b = withNestedHook(a.next, "SessionEnd", hookCommand("gemini-cli"), { name: "backthread-capture" });
8431
+ const b = withNestedHook(a.next, "SessionEnd", hookCommand("gemini-cli"), { name: "backthread-capture" }, [
8432
+ legacyHookCommand("gemini-cli")
8433
+ ]);
8409
8434
  if (a.changed || b.changed) await writeJson(deps, path, b.next);
8410
8435
  return [{ path, wrote: a.changed || b.changed }];
8411
8436
  }
@@ -8435,7 +8460,7 @@ args = [${MCP_ARGS.map((a) => `"${a}"`).join(", ")}]
8435
8460
  }
8436
8461
  const hooksPath = join8(home, ".codex", "hooks.json");
8437
8462
  const current = await loadJsonObject(doRead, hooksPath);
8438
- const h = withNestedHook(current, "Stop", hookCommand("codex"), { timeout: 60 });
8463
+ const h = withNestedHook(current, "Stop", hookCommand("codex"), { timeout: 60 }, [legacyHookCommand("codex")]);
8439
8464
  if (h.changed) await writeJson(deps, hooksPath, h.next);
8440
8465
  writes.push({ path: hooksPath, wrote: h.changed });
8441
8466
  return writes;
@@ -8451,7 +8476,8 @@ async function installCursor(home, deps) {
8451
8476
  await writeCursorScript(
8452
8477
  deps,
8453
8478
  captureScriptPath,
8454
- cursorWrapperScript(nodeBinDir, "capture --from-hook --agent cursor --detach")
8479
+ // capture hook self-updating (@latest), like the other agents' hooks (ARP-739).
8480
+ cursorWrapperScript(nodeBinDir, "capture --from-hook --agent cursor --detach", true)
8455
8481
  )
8456
8482
  );
8457
8483
  writes.push(await writeCursorScript(deps, mcpScriptPath, cursorWrapperScript(nodeBinDir, "mcp")));
@@ -8470,7 +8496,8 @@ async function installCursor(home, deps) {
8470
8496
  function shSingleQuote(s) {
8471
8497
  return `'${s.replace(/'/g, `'\\''`)}'`;
8472
8498
  }
8473
- function cursorWrapperScript(nodeBinDir, backthreadArgs) {
8499
+ function cursorWrapperScript(nodeBinDir, backthreadArgs, latest = false) {
8500
+ const pkg = latest ? "backthread@latest" : "backthread";
8474
8501
  return [
8475
8502
  "#!/bin/sh",
8476
8503
  "# Backthread wrapper for Cursor \u2014 generated by `backthread install --agent cursor` (ARP-692).",
@@ -8489,7 +8516,7 @@ function cursorWrapperScript(nodeBinDir, backthreadArgs) {
8489
8516
  ' PATH="$NODE_BIN_DIR:$PATH"',
8490
8517
  " export PATH",
8491
8518
  "fi",
8492
- `exec npx -y backthread ${backthreadArgs}`
8519
+ `exec npx -y ${pkg} ${backthreadArgs}`
8493
8520
  ].join("\n") + "\n";
8494
8521
  }
8495
8522
  async function writeCursorScript(deps, path, content) {
@@ -8523,7 +8550,7 @@ function withCursorMcpServer(settings, mcpScriptPath) {
8523
8550
  return { next: { ...settings, mcpServers }, changed: true };
8524
8551
  }
8525
8552
  function withCursorStopHook(settings, captureScriptPath) {
8526
- const legacyInline = hookCommand("cursor");
8553
+ const legacyInline = legacyHookCommand("cursor");
8527
8554
  const hooks = asObject(settings.hooks);
8528
8555
  const stop = Array.isArray(hooks.stop) ? [...hooks.stop] : [];
8529
8556
  const hadDesired = stop.some((h) => h?.command === captureScriptPath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backthread",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Backthread helps you understand your codebase while AI ships features. The CLI captures the why behind every AI session and lets you ask how your codebase works, right from the terminal.",
5
5
  "license": "MIT",
6
6
  "author": "Backthread",