@bunshin-cc/bunshin 0.6.1 → 0.6.3

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/bin.js CHANGED
@@ -1344,17 +1344,17 @@ var init_document = __esm({
1344
1344
  });
1345
1345
 
1346
1346
  // ../core/src/v2/sections/markdown.ts
1347
- function repoSectionId(repo, path25, anchor = "") {
1348
- return anchor ? `repo:${repo}/${path25}#${anchor}` : `repo:${repo}/${path25}`;
1347
+ function repoSectionId(repo, path26, anchor = "") {
1348
+ return anchor ? `repo:${repo}/${path26}#${anchor}` : `repo:${repo}/${path26}`;
1349
1349
  }
1350
- function docKind(path25) {
1351
- const base = path25.split("/").pop()?.toLowerCase() ?? "";
1350
+ function docKind(path26) {
1351
+ const base = path26.split("/").pop()?.toLowerCase() ?? "";
1352
1352
  if (base === "testing.md") return "verification";
1353
1353
  if (base === "skill.md") return "skill";
1354
1354
  return "spec";
1355
1355
  }
1356
- function defaultPaths(path25) {
1357
- const dir = path25.split("/").slice(0, -1).join("/");
1356
+ function defaultPaths(path26) {
1357
+ const dir = path26.split("/").slice(0, -1).join("/");
1358
1358
  if (!dir || dir.startsWith(".claude/") || dir.startsWith(".codex/")) return [];
1359
1359
  return [`${dir}/**`];
1360
1360
  }
@@ -1499,11 +1499,11 @@ function lintSkills(sections) {
1499
1499
  }
1500
1500
  const state = /* @__PURE__ */ new Map();
1501
1501
  const cycles = /* @__PURE__ */ new Set();
1502
- const dfs = (n, path25) => {
1502
+ const dfs = (n, path26) => {
1503
1503
  state.set(n, 1);
1504
1504
  for (const d of edges[n] ?? []) {
1505
- if (state.get(d) === 1) cycles.add([...path25.slice(path25.indexOf(d)), d].join(" -> "));
1506
- else if (!state.get(d)) dfs(d, [...path25, d]);
1505
+ if (state.get(d) === 1) cycles.add([...path26.slice(path26.indexOf(d)), d].join(" -> "));
1506
+ else if (!state.get(d)) dfs(d, [...path26, d]);
1507
1507
  }
1508
1508
  state.set(n, 2);
1509
1509
  };
@@ -1590,8 +1590,8 @@ function parsePythonSkills(text2) {
1590
1590
  }
1591
1591
  return { skills, constants };
1592
1592
  }
1593
- function harnessSectionId(path25, symbol) {
1594
- return `harness:${path25}#${symbol}`;
1593
+ function harnessSectionId(path26, symbol) {
1594
+ return `harness:${path26}#${symbol}`;
1595
1595
  }
1596
1596
  var DEF_RE;
1597
1597
  var init_python = __esm({
@@ -1717,8 +1717,8 @@ function globToRegExp(glob) {
1717
1717
  globCache.set(glob, out);
1718
1718
  return out;
1719
1719
  }
1720
- function matchesGlob(path25, glob) {
1721
- const p = path25.replace(/^\.\//, "").replace(/\\/g, "/");
1720
+ function matchesGlob(path26, glob) {
1721
+ const p = path26.replace(/^\.\//, "").replace(/\\/g, "/");
1722
1722
  return globToRegExp(glob.replace(/^\.\//, "")).test(p);
1723
1723
  }
1724
1724
  var DEFAULT_EMBEDDERS, globCache;
@@ -1809,10 +1809,10 @@ function push(sections, reader, query) {
1809
1809
  for (const s of recallable(sections, reader).filter((x) => reachesReader(x, reader))) {
1810
1810
  const triggers = [...(s.triggers ?? []).filter((t) => t.anchor), { anchor: "", paths: s.paths, entities: s.entities, skills: s.skills }];
1811
1811
  for (const t of triggers) {
1812
- const path25 = paths.find((p) => t.paths.some((g) => matchesGlob(p, g)));
1813
- const entity = path25 ? void 0 : t.entities.find((e) => entities.has(e) || text2 && text2.includes(e));
1814
- const skill = path25 || entity ? void 0 : skills.find((k) => t.skills.includes(k));
1815
- const trigger = path25 ? `path:${path25}` : entity ? `entity:${entity}` : skill ? `skill:${skill}` : "";
1812
+ const path26 = paths.find((p) => t.paths.some((g) => matchesGlob(p, g)));
1813
+ const entity = path26 ? void 0 : t.entities.find((e) => entities.has(e) || text2 && text2.includes(e));
1814
+ const skill = path26 || entity ? void 0 : skills.find((k) => t.skills.includes(k));
1815
+ const trigger = path26 ? `path:${path26}` : entity ? `entity:${entity}` : skill ? `skill:${skill}` : "";
1816
1816
  if (trigger) {
1817
1817
  hits.set(s.id, { section: s, trigger, anchor: t.anchor });
1818
1818
  break;
@@ -2849,181 +2849,22 @@ var init_redact2 = __esm({
2849
2849
  }
2850
2850
  });
2851
2851
 
2852
- // src/transcript.ts
2852
+ // src/version.ts
2853
2853
  import fs5 from "node:fs";
2854
- function parseTranscript(file) {
2855
- let raw = "";
2856
- try {
2857
- raw = fs5.readFileSync(file, "utf8");
2858
- } catch {
2859
- return { turns: [], userMessages: 0, toolCalls: 0, toolFailures: 0 };
2860
- }
2861
- const turns = [];
2862
- let userMessages = 0;
2863
- let toolCalls = 0;
2864
- let toolFailures = 0;
2865
- for (const lineText of raw.split("\n")) {
2866
- if (!lineText.trim()) continue;
2867
- let line;
2868
- try {
2869
- line = JSON.parse(lineText);
2870
- } catch {
2871
- continue;
2872
- }
2873
- if (line.type === "response_item") {
2874
- const item = line.payload;
2875
- if (!item) continue;
2876
- if (item.type === "message" && (item.role === "user" || item.role === "assistant") && item.channel !== "analysis") {
2877
- const text2 = blockText(item.content).trim();
2878
- if (item.role === "user" ? isHumanText(text2) : !!text2) {
2879
- turns.push({ role: item.role, text: text2 });
2880
- if (item.role === "user") userMessages++;
2881
- }
2882
- } else if (item.type === "function_call" || item.type === "custom_tool_call") {
2883
- let input = item.input ?? item.arguments;
2884
- if (typeof input === "string") {
2885
- try {
2886
- input = JSON.parse(input);
2887
- } catch {
2888
- }
2889
- }
2890
- turns.push({ role: "tool", tool: item.name || "tool", text: summarizeInput(input) });
2891
- toolCalls++;
2892
- } else if (item.type === "function_call_output" || item.type === "custom_tool_call_output") {
2893
- const outputs = Array.isArray(item.output) ? item.output.filter((part) => part && typeof part === "object" && typeof part.text === "string").map((part) => part.text) : [item.output];
2894
- for (const output of outputs) {
2895
- for (const result of codexToolResults(output)) {
2896
- if (!result.text) continue;
2897
- turns.push({ role: "tool", tool: "result", ...result });
2898
- if (result.isError) toolFailures++;
2899
- }
2900
- }
2901
- }
2902
- continue;
2903
- }
2904
- if (line.type !== "user" && line.type !== "assistant") continue;
2905
- if (line.isMeta) continue;
2906
- const content = line.message?.content;
2907
- if (typeof content === "string") {
2908
- if (line.type === "user" && isHumanText(content)) {
2909
- turns.push({ role: "user", text: content });
2910
- userMessages++;
2911
- } else if (line.type === "assistant" && content.trim()) {
2912
- turns.push({ role: "assistant", text: content });
2913
- }
2914
- continue;
2915
- }
2916
- if (!Array.isArray(content)) continue;
2917
- for (const block2 of content) {
2918
- if (block2.type === "text" && block2.text) {
2919
- if (line.type === "user") {
2920
- if (!isHumanText(block2.text)) continue;
2921
- userMessages++;
2922
- turns.push({ role: "user", text: block2.text });
2923
- } else {
2924
- turns.push({ role: "assistant", text: block2.text });
2925
- }
2926
- } else if (block2.type === "tool_use") {
2927
- toolCalls++;
2928
- turns.push({ role: "tool", tool: block2.name || "tool", text: summarizeInput(block2.input) });
2929
- } else if (block2.type === "tool_result") {
2930
- const text2 = blockText(block2.content);
2931
- if (block2.is_error) toolFailures++;
2932
- turns.push({ role: "tool", tool: "result", text: text2, isError: !!block2.is_error });
2933
- }
2934
- }
2935
- }
2936
- return { turns, userMessages, toolCalls, toolFailures };
2937
- }
2938
- function isHumanText(text2) {
2939
- const t = text2.trim();
2940
- if (!t) return false;
2941
- if (t.startsWith(BANNER)) return false;
2942
- if (/^<(?:command-name|command-message|local-command-stdout|system-reminder|bash-input|bash-stdout)/.test(t)) return false;
2943
- return true;
2944
- }
2945
- function blockText(content) {
2946
- if (typeof content === "string") return content;
2947
- if (Array.isArray(content)) {
2948
- return content.map((c) => c && typeof c === "object" && "text" in c ? String(c.text) : "").filter(Boolean).join("\n");
2949
- }
2950
- return content == null ? "" : JSON.stringify(content);
2951
- }
2952
- function codexToolResults(output, depth = 0) {
2953
- const raw = blockText(output);
2954
- let envelope = output;
2955
- if (typeof output === "string") {
2854
+ function version() {
2855
+ for (const rel of ["../package.json", "../../package.json"]) {
2956
2856
  try {
2957
- envelope = JSON.parse(output);
2857
+ const file = new URL(rel, import.meta.url);
2858
+ const pkg = JSON.parse(fs5.readFileSync(file, "utf8"));
2859
+ if (pkg.name === "@bunshin-cc/bunshin" && pkg.version) return pkg.version;
2958
2860
  } catch {
2959
2861
  }
2960
2862
  }
2961
- if (depth > 6) return [{ text: raw, pending: true }];
2962
- if (Array.isArray(envelope)) return envelope.flatMap((part) => codexToolResults(part, depth + 1));
2963
- if (envelope && typeof envelope === "object" && !Array.isArray(envelope)) {
2964
- const result = envelope;
2965
- if (typeof result.output === "string" && (Number.isInteger(result.exit_code) || result.exit_code === null || Number.isInteger(result.session_id))) {
2966
- const pending = !Number.isInteger(result.exit_code);
2967
- return [{
2968
- text: `${pending ? "Command still running" : `Process exited with code ${result.exit_code}`}
2969
- ${result.output}`,
2970
- isError: !pending && result.exit_code !== 0,
2971
- pending
2972
- }];
2973
- }
2974
- if (result.status === "fulfilled" && "value" in result) return codexToolResults(result.value, depth + 1);
2975
- if (result.status === "rejected") return [{ text: raw, isError: true }];
2976
- }
2977
- return [{
2978
- text: raw,
2979
- isError: /(?:Process exited with code|exit_code["']?\s*:)\s*[1-9]\d*\b/i.test(raw),
2980
- pending: /(?:Process running with session ID|Script running with cell ID)/i.test(raw)
2981
- }];
2982
- }
2983
- function summarizeInput(input) {
2984
- if (input == null) return "";
2985
- if (typeof input !== "object") return String(input);
2986
- const obj = input;
2987
- for (const key of ["command", "prompt", "pattern", "file_path", "query", "description"]) {
2988
- if (typeof obj[key] === "string") return `${key}: ${obj[key]}`;
2989
- }
2990
- return JSON.stringify(obj);
2991
- }
2992
- function bounded(text2, maxChars) {
2993
- if (text2.length <= maxChars) return text2;
2994
- const marker = "\n[... middle omitted ...]\n";
2995
- if (maxChars <= marker.length) return text2.slice(0, Math.max(0, maxChars));
2996
- const head = Math.floor((maxChars - marker.length) / 2);
2997
- return text2.slice(0, head) + marker + text2.slice(-(maxChars - marker.length - head));
2998
- }
2999
- function digest(facts, { from = 0, maxChars = 6e4, toolResultChars = 6e3 } = {}) {
3000
- const parts = [];
3001
- for (const t of facts.turns.slice(from)) {
3002
- const text2 = redact2(t.text);
3003
- if (t.role === "user") parts.push(`[USER]
3004
- ${bounded(text2, maxChars)}`);
3005
- else if (t.role === "assistant") parts.push(`[ASSISTANT]
3006
- ${bounded(text2, maxChars)}`);
3007
- else if (t.tool === "result") parts.push(`[TOOL RESULT${t.isError ? " \u2014 ERROR" : t.pending ? " \u2014 RUNNING" : ""}]
3008
- ${bounded(text2, toolResultChars)}`);
3009
- else parts.push(`[TOOL ${t.tool}] ${truncate(text2, 400)}`);
3010
- }
3011
- return bounded(parts.join("\n\n"), maxChars);
3012
- }
3013
- function recentContext(file, { turns = 2, maxChars = 6e3 } = {}) {
3014
- const facts = parseTranscript(file);
3015
- const visible = facts.turns.filter((t) => t.role !== "tool");
3016
- const userIdx = visible.map((t, i) => t.role === "user" ? i : -1).filter((i) => i >= 0);
3017
- const start = userIdx.length > turns ? userIdx[userIdx.length - turns] : 0;
3018
- return digest({ ...facts, turns: visible.slice(start) }, { maxChars });
2863
+ return "0.0.0";
3019
2864
  }
3020
- var BANNER;
3021
- var init_transcript = __esm({
3022
- "src/transcript.ts"() {
2865
+ var init_version = __esm({
2866
+ "src/version.ts"() {
3023
2867
  "use strict";
3024
- init_redact2();
3025
- init_util();
3026
- BANNER = "## Recalled lessons (Bunshin)";
3027
2868
  }
3028
2869
  });
3029
2870
 
@@ -4597,8 +4438,229 @@ var init_live = __esm({
4597
4438
  }
4598
4439
  });
4599
4440
 
4600
- // src/v2/repo.ts
4441
+ // src/transcript.ts
4601
4442
  import fs8 from "node:fs";
4443
+ function parseTranscript(file) {
4444
+ let raw = "";
4445
+ try {
4446
+ raw = fs8.readFileSync(file, "utf8");
4447
+ } catch {
4448
+ return { turns: [], userMessages: 0, toolCalls: 0, toolFailures: 0 };
4449
+ }
4450
+ const turns = [];
4451
+ let userMessages = 0;
4452
+ let toolCalls = 0;
4453
+ let toolFailures = 0;
4454
+ for (const lineText of raw.split("\n")) {
4455
+ if (!lineText.trim()) continue;
4456
+ let line;
4457
+ try {
4458
+ line = JSON.parse(lineText);
4459
+ } catch {
4460
+ continue;
4461
+ }
4462
+ if (line.type === "response_item") {
4463
+ const item = line.payload;
4464
+ if (!item) continue;
4465
+ if (item.type === "message" && (item.role === "user" || item.role === "assistant") && item.channel !== "analysis") {
4466
+ const text2 = blockText(item.content).trim();
4467
+ if (item.role === "user" ? isHumanText(text2) : !!text2) {
4468
+ turns.push({ role: item.role, text: text2 });
4469
+ if (item.role === "user") userMessages++;
4470
+ }
4471
+ } else if (item.type === "function_call" || item.type === "custom_tool_call") {
4472
+ let input = item.input ?? item.arguments;
4473
+ if (typeof input === "string") {
4474
+ try {
4475
+ input = JSON.parse(input);
4476
+ } catch {
4477
+ }
4478
+ }
4479
+ turns.push({ role: "tool", tool: item.name || "tool", text: summarizeInput(input), input });
4480
+ toolCalls++;
4481
+ } else if (item.type === "function_call_output" || item.type === "custom_tool_call_output") {
4482
+ const outputs = Array.isArray(item.output) ? item.output.filter((part) => part && typeof part === "object" && typeof part.text === "string").map((part) => part.text) : [item.output];
4483
+ for (const output of outputs) {
4484
+ for (const result of codexToolResults(output)) {
4485
+ if (!result.text) continue;
4486
+ turns.push({ role: "tool", tool: "result", ...result });
4487
+ if (result.isError) toolFailures++;
4488
+ }
4489
+ }
4490
+ }
4491
+ continue;
4492
+ }
4493
+ if (line.type !== "user" && line.type !== "assistant") continue;
4494
+ if (line.isMeta) continue;
4495
+ const content = line.message?.content;
4496
+ if (typeof content === "string") {
4497
+ if (line.type === "user" && isHumanText(content)) {
4498
+ turns.push({ role: "user", text: content });
4499
+ userMessages++;
4500
+ } else if (line.type === "assistant" && content.trim()) {
4501
+ turns.push({ role: "assistant", text: content });
4502
+ }
4503
+ continue;
4504
+ }
4505
+ if (!Array.isArray(content)) continue;
4506
+ for (const block2 of content) {
4507
+ if (block2.type === "text" && block2.text) {
4508
+ if (line.type === "user") {
4509
+ if (!isHumanText(block2.text)) continue;
4510
+ userMessages++;
4511
+ turns.push({ role: "user", text: block2.text });
4512
+ } else {
4513
+ turns.push({ role: "assistant", text: block2.text });
4514
+ }
4515
+ } else if (block2.type === "tool_use") {
4516
+ toolCalls++;
4517
+ turns.push({ role: "tool", tool: block2.name || "tool", text: summarizeInput(block2.input), input: block2.input });
4518
+ } else if (block2.type === "tool_result") {
4519
+ const text2 = blockText(block2.content);
4520
+ if (block2.is_error) toolFailures++;
4521
+ turns.push({ role: "tool", tool: "result", text: text2, isError: !!block2.is_error });
4522
+ }
4523
+ }
4524
+ }
4525
+ return { turns, userMessages, toolCalls, toolFailures };
4526
+ }
4527
+ function isHumanText(text2) {
4528
+ const t = text2.trim();
4529
+ if (!t) return false;
4530
+ if (t.startsWith(BANNER)) return false;
4531
+ if (/^<(?:command-name|command-message|local-command-stdout|system-reminder|bash-input|bash-stdout)/.test(t)) return false;
4532
+ return true;
4533
+ }
4534
+ function blockText(content) {
4535
+ if (typeof content === "string") return content;
4536
+ if (Array.isArray(content)) {
4537
+ return content.map((c) => c && typeof c === "object" && "text" in c ? String(c.text) : "").filter(Boolean).join("\n");
4538
+ }
4539
+ return content == null ? "" : JSON.stringify(content);
4540
+ }
4541
+ function codexToolResults(output, depth = 0) {
4542
+ const raw = blockText(output);
4543
+ let envelope = output;
4544
+ if (typeof output === "string") {
4545
+ try {
4546
+ envelope = JSON.parse(output);
4547
+ } catch {
4548
+ }
4549
+ }
4550
+ if (depth > 6) return [{ text: raw, pending: true }];
4551
+ if (Array.isArray(envelope)) return envelope.flatMap((part) => codexToolResults(part, depth + 1));
4552
+ if (envelope && typeof envelope === "object" && !Array.isArray(envelope)) {
4553
+ const result = envelope;
4554
+ if (typeof result.output === "string" && (Number.isInteger(result.exit_code) || result.exit_code === null || Number.isInteger(result.session_id))) {
4555
+ const pending = !Number.isInteger(result.exit_code);
4556
+ return [{
4557
+ text: `${pending ? "Command still running" : `Process exited with code ${result.exit_code}`}
4558
+ ${result.output}`,
4559
+ isError: !pending && result.exit_code !== 0,
4560
+ pending
4561
+ }];
4562
+ }
4563
+ if (result.status === "fulfilled" && "value" in result) return codexToolResults(result.value, depth + 1);
4564
+ if (result.status === "rejected") return [{ text: raw, isError: true }];
4565
+ }
4566
+ return [{
4567
+ text: raw,
4568
+ isError: /(?:Process exited with code|exit_code["']?\s*:)\s*[1-9]\d*\b/i.test(raw),
4569
+ pending: /(?:Process running with session ID|Script running with cell ID)/i.test(raw)
4570
+ }];
4571
+ }
4572
+ function summarizeInput(input) {
4573
+ if (input == null) return "";
4574
+ if (typeof input !== "object") return String(input);
4575
+ const obj = input;
4576
+ for (const key of ["command", "prompt", "pattern", "file_path", "query", "description"]) {
4577
+ if (typeof obj[key] === "string") return `${key}: ${obj[key]}`;
4578
+ }
4579
+ return JSON.stringify(obj);
4580
+ }
4581
+ function bounded(text2, maxChars) {
4582
+ if (text2.length <= maxChars) return text2;
4583
+ const marker = "\n[... middle omitted ...]\n";
4584
+ if (maxChars <= marker.length) return text2.slice(0, Math.max(0, maxChars));
4585
+ const head = Math.floor((maxChars - marker.length) / 2);
4586
+ return text2.slice(0, head) + marker + text2.slice(-(maxChars - marker.length - head));
4587
+ }
4588
+ function digest(facts, { from = 0, maxChars = 6e4, toolResultChars = 6e3 } = {}) {
4589
+ const parts = [];
4590
+ for (const t of facts.turns.slice(from)) {
4591
+ const text2 = redact2(t.text);
4592
+ if (t.role === "user") parts.push(`[USER]
4593
+ ${bounded(text2, maxChars)}`);
4594
+ else if (t.role === "assistant") parts.push(`[ASSISTANT]
4595
+ ${bounded(text2, maxChars)}`);
4596
+ else if (t.tool === "result") parts.push(`[TOOL RESULT${t.isError ? " \u2014 ERROR" : t.pending ? " \u2014 RUNNING" : ""}]
4597
+ ${bounded(text2, toolResultChars)}`);
4598
+ else parts.push(`[TOOL ${t.tool}] ${truncate(text2, 400)}`);
4599
+ }
4600
+ return bounded(parts.join("\n\n"), maxChars);
4601
+ }
4602
+ function fullTrace(facts, { from = 0, to } = {}) {
4603
+ return facts.turns.slice(from, to).map((t) => {
4604
+ if (t.role === "user") return `[USER]
4605
+ ${redact2(t.text)}`;
4606
+ if (t.role === "assistant") return `[ASSISTANT]
4607
+ ${redact2(t.text)}`;
4608
+ if (t.tool === "result") return `[TOOL RESULT${t.isError ? " \u2014 ERROR" : t.pending ? " \u2014 RUNNING" : ""}]
4609
+ ${redact2(t.text)}`;
4610
+ const input = t.input === void 0 ? t.text : typeof t.input === "string" ? t.input : JSON.stringify(t.input, null, 1);
4611
+ return `[TOOL ${t.tool}]
4612
+ ${redact2(input)}`;
4613
+ });
4614
+ }
4615
+ function traceChunks(turns, maxBytes = 1e6) {
4616
+ const out = [];
4617
+ let cur = "";
4618
+ let size = 0;
4619
+ turns.forEach((turn, i) => {
4620
+ let piece = (i ? "\n\n" : "") + turn;
4621
+ let bytes = jsonBytes(piece);
4622
+ if (size + bytes <= maxBytes) {
4623
+ cur += piece;
4624
+ size += bytes;
4625
+ return;
4626
+ }
4627
+ if (cur) out.push(cur);
4628
+ while (bytes > maxBytes) {
4629
+ let n = Math.min(piece.length, maxBytes);
4630
+ for (let b = jsonBytes(piece.slice(0, n)); b > maxBytes; b = jsonBytes(piece.slice(0, n))) n = Math.max(1, Math.floor(n * maxBytes / b) - 1);
4631
+ const code2 = piece.charCodeAt(n - 1);
4632
+ if (n > 1 && code2 >= 55296 && code2 <= 56319) n--;
4633
+ const head = piece.slice(0, n);
4634
+ out.push(head);
4635
+ piece = piece.slice(n);
4636
+ bytes -= jsonBytes(head);
4637
+ }
4638
+ cur = piece;
4639
+ size = bytes;
4640
+ });
4641
+ if (cur) out.push(cur);
4642
+ return out;
4643
+ }
4644
+ function recentContext(file, { turns = 2, maxChars = 6e3 } = {}) {
4645
+ const facts = parseTranscript(file);
4646
+ const visible = facts.turns.filter((t) => t.role !== "tool");
4647
+ const userIdx = visible.map((t, i) => t.role === "user" ? i : -1).filter((i) => i >= 0);
4648
+ const start = userIdx.length > turns ? userIdx[userIdx.length - turns] : 0;
4649
+ return digest({ ...facts, turns: visible.slice(start) }, { maxChars });
4650
+ }
4651
+ var BANNER, jsonBytes;
4652
+ var init_transcript = __esm({
4653
+ "src/transcript.ts"() {
4654
+ "use strict";
4655
+ init_redact2();
4656
+ init_util();
4657
+ BANNER = "## Recalled lessons (Bunshin)";
4658
+ jsonBytes = (s) => Buffer.byteLength(JSON.stringify(s)) - 2;
4659
+ }
4660
+ });
4661
+
4662
+ // src/v2/repo.ts
4663
+ import fs9 from "node:fs";
4602
4664
  import path7 from "node:path";
4603
4665
  import { execFileSync } from "node:child_process";
4604
4666
  function git(cwd, args, timeout = 1500) {
@@ -4647,20 +4709,20 @@ function repoCommit(root) {
4647
4709
  }
4648
4710
  function listDocs(root) {
4649
4711
  const listed = git(root, ["ls-files", "-co", "--exclude-standard", "-z"], 3e3);
4650
- if (listed !== null) return [...new Set(listed.split("\0").filter(Boolean).filter(isDoc))].filter((rel) => fs8.existsSync(path7.join(root, rel)) && !deployedSkill(root, rel)).sort();
4712
+ if (listed !== null) return [...new Set(listed.split("\0").filter(Boolean).filter(isDoc))].filter((rel) => fs9.existsSync(path7.join(root, rel)) && !deployedSkill(root, rel)).sort();
4651
4713
  const out = [];
4652
4714
  let visited = 0;
4653
4715
  const walk = (dir, rel, depth) => {
4654
4716
  if (depth > 8 || visited++ > 2e4) return;
4655
4717
  let entries;
4656
4718
  try {
4657
- entries = fs8.readdirSync(dir, { withFileTypes: true });
4719
+ entries = fs9.readdirSync(dir, { withFileTypes: true });
4658
4720
  } catch {
4659
4721
  return;
4660
4722
  }
4661
4723
  for (const e of entries) {
4662
4724
  if (e.isDirectory()) {
4663
- if (!SKIP_DIRS.has(e.name) && !fs8.existsSync(path7.join(dir, e.name, ".git"))) walk(path7.join(dir, e.name), rel ? `${rel}/${e.name}` : e.name, depth + 1);
4725
+ if (!SKIP_DIRS.has(e.name) && !fs9.existsSync(path7.join(dir, e.name, ".git"))) walk(path7.join(dir, e.name), rel ? `${rel}/${e.name}` : e.name, depth + 1);
4664
4726
  } else if (e.isFile()) {
4665
4727
  const r = rel ? `${rel}/${e.name}` : e.name;
4666
4728
  if (isDoc(r) && !deployedSkill(root, r)) out.push(r);
@@ -4685,7 +4747,7 @@ function indexWorkingTree(v2, config, cwd, opts = {}) {
4685
4747
  const abs = path7.join(root, rel);
4686
4748
  let stat;
4687
4749
  try {
4688
- stat = fs8.statSync(abs);
4750
+ stat = fs9.statSync(abs);
4689
4751
  } catch {
4690
4752
  changed = true;
4691
4753
  continue;
@@ -4697,7 +4759,7 @@ function indexWorkingTree(v2, config, cwd, opts = {}) {
4697
4759
  }
4698
4760
  changed = true;
4699
4761
  try {
4700
- const text2 = fs8.readFileSync(abs, "utf8");
4762
+ const text2 = fs9.readFileSync(abs, "utf8");
4701
4763
  files[rel] = { mtimeMs: stat.mtimeMs, size: stat.size, sections: parseRepoDoc({ repo, path: rel, text: text2 }, { orgId: config.orgId, ownerTeam }) };
4702
4764
  } catch {
4703
4765
  }
@@ -4723,14 +4785,14 @@ function relativeToRoot(root, file, cwd = root) {
4723
4785
  let real = abs;
4724
4786
  let realRoot = root;
4725
4787
  try {
4726
- realRoot = fs8.realpathSync(root);
4788
+ realRoot = fs9.realpathSync(root);
4727
4789
  } catch {
4728
4790
  }
4729
4791
  try {
4730
- real = fs8.realpathSync(abs);
4792
+ real = fs9.realpathSync(abs);
4731
4793
  } catch {
4732
4794
  try {
4733
- real = path7.join(fs8.realpathSync(path7.dirname(abs)), path7.basename(abs));
4795
+ real = path7.join(fs9.realpathSync(path7.dirname(abs)), path7.basename(abs));
4734
4796
  } catch {
4735
4797
  }
4736
4798
  }
@@ -4757,7 +4819,7 @@ var init_repo = __esm({
4757
4819
  if (parts.some((p) => SKIP_DIRS.has(p))) return false;
4758
4820
  return DOC_NAMES.has(parts[parts.length - 1].toLowerCase());
4759
4821
  };
4760
- deployedSkill = (root, rel) => /(^|\/)SKILL\.md$/i.test(rel) && fs8.existsSync(path7.join(root, path7.posix.dirname(rel), ".bunshin.json"));
4822
+ deployedSkill = (root, rel) => /(^|\/)SKILL\.md$/i.test(rel) && fs9.existsSync(path7.join(root, path7.posix.dirname(rel), ".bunshin.json"));
4761
4823
  }
4762
4824
  });
4763
4825
 
@@ -4858,7 +4920,7 @@ var init_view = __esm({
4858
4920
  });
4859
4921
 
4860
4922
  // src/v2/apply.ts
4861
- import fs9 from "node:fs";
4923
+ import fs10 from "node:fs";
4862
4924
  function appendedText(op) {
4863
4925
  return op.condition ? `- **${op.condition}:** ${op.text.trim()}` : op.text.trim();
4864
4926
  }
@@ -4913,12 +4975,12 @@ function applyLocally(v2, view, s) {
4913
4975
  if (section.storage.type === "repo") {
4914
4976
  const file = containedPath(view.root, section.storage.path);
4915
4977
  const ours = !view.repo || section.storage.repo === view.repo;
4916
- if (ours && file && fs9.existsSync(file)) {
4917
- const before = fs9.readFileSync(file, "utf8");
4978
+ if (ours && file && fs10.existsSync(file)) {
4979
+ const before = fs10.readFileSync(file, "utf8");
4918
4980
  const res2 = applyOpToMarkdown(before, section.storage.anchor, op);
4919
4981
  if (res2.ok) {
4920
4982
  const changed = res2.text !== before;
4921
- if (changed) fs9.writeFileSync(file, res2.text);
4983
+ if (changed) fs10.writeFileSync(file, res2.text);
4922
4984
  const overlay2 = { ...base, kind: "working-tree", sectionId: section.id, file: section.storage.path, anchor: section.storage.anchor, changed };
4923
4985
  upsertOverlay(v2, overlay2);
4924
4986
  return { applied: true, target: describeTarget(s, view), overlay: overlay2 };
@@ -4939,7 +5001,7 @@ function revertWorkingTree(root, o) {
4939
5001
  if (!file) return { reverted: false, reason: "path outside the repository" };
4940
5002
  let text2;
4941
5003
  try {
4942
- text2 = fs9.readFileSync(file, "utf8");
5004
+ text2 = fs10.readFileSync(file, "utf8");
4943
5005
  } catch {
4944
5006
  return { reverted: false, reason: "file missing" };
4945
5007
  }
@@ -4959,7 +5021,7 @@ ${appendedText(o.op)}`;
4959
5021
  if (once(text2, o.op.text) >= 0 && !text2.includes(o.op.find)) next = text2.replace(o.op.text, o.op.find);
4960
5022
  }
4961
5023
  if (next === null) return { reverted: false, reason: "file changed since the trial edit" };
4962
- fs9.writeFileSync(file, next);
5024
+ fs10.writeFileSync(file, next);
4963
5025
  return { reverted: true };
4964
5026
  }
4965
5027
  function revertParam(v2, o) {
@@ -5032,7 +5094,7 @@ var init_apply = __esm({
5032
5094
  });
5033
5095
 
5034
5096
  // src/v2/signals.ts
5035
- import fs10 from "node:fs";
5097
+ import fs11 from "node:fs";
5036
5098
  function isCorrection(text2) {
5037
5099
  const t = text2.trim().slice(0, 600);
5038
5100
  return !!t && CORRECTION_RE.test(t);
@@ -5054,7 +5116,7 @@ function scanTranscript(file) {
5054
5116
  if (!file) return empty;
5055
5117
  let raw;
5056
5118
  try {
5057
- raw = fs10.readFileSync(file, "utf8");
5119
+ raw = fs11.readFileSync(file, "utf8");
5058
5120
  } catch {
5059
5121
  return empty;
5060
5122
  }
@@ -5138,7 +5200,7 @@ var init_signals = __esm({
5138
5200
  });
5139
5201
 
5140
5202
  // src/v2/ui.ts
5141
- import fs11 from "node:fs";
5203
+ import fs12 from "node:fs";
5142
5204
  import path8 from "node:path";
5143
5205
  function feedPath(v2, sessionId) {
5144
5206
  return v2.p("ui", `${safe(sessionId)}.json`);
@@ -5170,7 +5232,7 @@ function updateFeed(v2, sessionId, patch, now = (/* @__PURE__ */ new Date()).toI
5170
5232
  f.cli = cliArgv();
5171
5233
  f.served = f.served.slice(0, MAX_SERVED);
5172
5234
  f.events = f.events.slice(-MAX_EVENTS);
5173
- fs11.mkdirSync(path8.dirname(feedPath(v2, sessionId)), { recursive: true });
5235
+ fs12.mkdirSync(path8.dirname(feedPath(v2, sessionId)), { recursive: true });
5174
5236
  writeJsonAtomic(feedPath(v2, sessionId), f);
5175
5237
  return f;
5176
5238
  } catch {
@@ -5271,9 +5333,9 @@ function latestFeed(v2) {
5271
5333
  const dir = v2.p("ui");
5272
5334
  let best = null;
5273
5335
  try {
5274
- for (const f2 of fs11.readdirSync(dir)) {
5336
+ for (const f2 of fs12.readdirSync(dir)) {
5275
5337
  if (!f2.endsWith(".json") || f2.endsWith(".mod.json")) continue;
5276
- const m = fs11.statSync(path8.join(dir, f2)).mtimeMs;
5338
+ const m = fs12.statSync(path8.join(dir, f2)).mtimeMs;
5277
5339
  if (!best || m > best.m) best = { f: f2, m };
5278
5340
  }
5279
5341
  } catch {
@@ -5300,15 +5362,16 @@ var init_ui = __esm({
5300
5362
  var sync_exports = {};
5301
5363
  __export(sync_exports, {
5302
5364
  episodePayload: () => episodePayload,
5365
+ markForRetrace: () => markForRetrace,
5303
5366
  suggestionPayload: () => suggestionPayload,
5304
5367
  syncV2: () => syncV2,
5305
5368
  uploadIndex: () => uploadIndex
5306
5369
  });
5307
- import fs12 from "node:fs";
5370
+ import fs13 from "node:fs";
5308
5371
  import path9 from "node:path";
5309
5372
  function episodePayload(e) {
5310
- const { orgId: _o, uploaded: _u, servedReported: _s, reportedServed: _rs, reportedLoaded: _rl, ...rest } = e;
5311
- void _o, _u, _s, _rs, _rl;
5373
+ const { orgId: _o, uploaded: _u, servedReported: _s, reportedServed: _rs, reportedLoaded: _rl, transcriptPath: _tp, ...rest } = e;
5374
+ void _o, _u, _s, _rs, _rl, _tp;
5312
5375
  return rest;
5313
5376
  }
5314
5377
  function suggestionPayload(s) {
@@ -5316,12 +5379,47 @@ function suggestionPayload(s) {
5316
5379
  void _o, _st, _d, _u, _t, _su;
5317
5380
  return rest;
5318
5381
  }
5382
+ async function uploadTrace(e, call2) {
5383
+ if (!carriesTrace(e)) return;
5384
+ const chunks = traceChunks(fullTrace(parseTranscript(e.transcriptPath), { from: e.transcriptFrom, to: e.turns }));
5385
+ if (!chunks.length) return;
5386
+ for (let attempt = 0; ; attempt++) {
5387
+ try {
5388
+ for (const [seq, text2] of chunks.entries()) {
5389
+ const res = await call2("PUT", `/episodes/${encodeURIComponent(e.id)}/transcript`, { seq, text: text2, done: seq === chunks.length - 1 });
5390
+ if (res?.skipped) return;
5391
+ }
5392
+ return;
5393
+ } catch (err) {
5394
+ if (attempt || !(err instanceof Error && err.message.includes("trace_gap"))) throw err;
5395
+ }
5396
+ }
5397
+ }
5398
+ function markForRetrace(v2, { days = 30, now = Date.now() } = {}) {
5399
+ let queued = 0;
5400
+ let missing = 0;
5401
+ for (const e of v2.listEpisodes()) {
5402
+ if (e.upload === "header-only" || e.cliVersion || !e.transcript) continue;
5403
+ if (now - Date.parse(e.endedAt || e.startedAt) > days * 864e5) continue;
5404
+ const file = e.transcriptPath ?? v2.readSession(e.sessionId)?.transcriptPath;
5405
+ if (!file || !fs13.existsSync(file)) {
5406
+ missing++;
5407
+ continue;
5408
+ }
5409
+ const turns = e.turns ?? parseTranscript(file).turns.length;
5410
+ const { transcript: _t, ...rest } = e;
5411
+ void _t;
5412
+ v2.writeEpisode({ ...rest, transcriptPath: file, transcriptFrom: e.transcriptFrom ?? 0, turns, cliVersion: version(), uploaded: false });
5413
+ queued++;
5414
+ }
5415
+ return { queued, missing };
5416
+ }
5319
5417
  async function syncV2(store, opts = {}) {
5320
5418
  const v2 = new V2Store(store);
5321
5419
  const config = store.config();
5322
5420
  const cwd = opts.cwd ?? path9.dirname(store.root);
5323
- const report = { offline: !canCall(config), uploadedEpisodes: 0, uploadedSuggestions: 0, trialUpdates: 0, bundle: v2.readBundle() ? "cached" : "none", settled: [], errors: [] };
5324
- const deadline = Date.now() + (opts.budgetMs ?? 6e4);
5421
+ const report = { offline: !canCall(config), uploadedEpisodes: 0, deferredTraces: 0, uploadedSuggestions: 0, trialUpdates: 0, bundle: v2.readBundle() ? "cached" : "none", settled: [], errors: [] };
5422
+ const deadline = Date.now() + (opts.budgetMs ?? 24e4);
5325
5423
  if (report.offline) {
5326
5424
  report.settled = settleOverlays(v2, v2.readBundle(), cwd).actions;
5327
5425
  return report;
@@ -5344,8 +5442,13 @@ async function syncV2(store, opts = {}) {
5344
5442
  }
5345
5443
  for (const e of v2.listEpisodes().filter((x) => !x.uploaded)) {
5346
5444
  if (Date.now() >= deadline) break;
5445
+ if (opts.traces === false && carriesTrace(e)) {
5446
+ report.deferredTraces++;
5447
+ continue;
5448
+ }
5347
5449
  try {
5348
5450
  const res = await call2("POST", "/episodes", { episode: episodePayload(e) });
5451
+ await uploadTrace(e, call2);
5349
5452
  if (res && (typeof res.reflectedTurns === "number" || Array.isArray(res.lessons)) && v2.readSession(e.sessionId)) {
5350
5453
  v2.updateSession(e.sessionId, "", (s) => {
5351
5454
  if (typeof res.reflectedTurns === "number") s.serverReflectedTurns = Math.max(s.serverReflectedTurns ?? 0, res.reflectedTurns);
@@ -5460,7 +5563,7 @@ async function uploadIndex(store, cwd, deadline = Date.now() + 3e4) {
5460
5563
  const { root, index } = indexWorkingTree(v2, config, cwd, { force: true });
5461
5564
  const docs = (index.paths ?? Object.keys(index.files)).flatMap((rel) => {
5462
5565
  try {
5463
- return [{ path: rel, text: fs12.readFileSync(path9.join(root, rel), "utf8") }];
5566
+ return [{ path: rel, text: fs13.readFileSync(path9.join(root, rel), "utf8") }];
5464
5567
  } catch {
5465
5568
  return [];
5466
5569
  }
@@ -5469,10 +5572,12 @@ async function uploadIndex(store, cwd, deadline = Date.now() + 3e4) {
5469
5572
  const res = await v2api(config, "POST", "/sections/index", { repo: index.repo, ...commit ? { commit } : {}, ownerTeam: index.ownerTeam, docs }, { deadline, timeoutMs: 3e4 });
5470
5573
  return typeof res.indexed === "number" ? res.indexed : docs.length;
5471
5574
  }
5472
- var isLocalId;
5575
+ var isLocalId, carriesTrace;
5473
5576
  var init_sync = __esm({
5474
5577
  "src/v2/sync.ts"() {
5475
5578
  "use strict";
5579
+ init_transcript();
5580
+ init_version();
5476
5581
  init_api();
5477
5582
  init_apply();
5478
5583
  init_deploy();
@@ -5480,13 +5585,14 @@ var init_sync = __esm({
5480
5585
  init_store2();
5481
5586
  init_view();
5482
5587
  isLocalId = (id) => id.startsWith("local:");
5588
+ carriesTrace = (e) => e.upload !== "header-only" && !e.transcript && e.transcriptFrom !== void 0 && !!e.transcriptPath && e.turns !== void 0;
5483
5589
  }
5484
5590
  });
5485
5591
 
5486
5592
  // src/v2/reflect.ts
5487
5593
  import { spawn as spawn3 } from "node:child_process";
5488
5594
  import { createHash as createHash3 } from "node:crypto";
5489
- import fs13 from "node:fs";
5595
+ import fs14 from "node:fs";
5490
5596
  function episodeIdFor(contextId, sessionId) {
5491
5597
  return `e_${createHash3("sha256").update(`${contextId}
5492
5598
  ${sessionId}`).digest("hex").slice(0, 16)}`;
@@ -5521,7 +5627,7 @@ function runClaudeFork(sessionId, prompt, { cwd, model, timeoutMs = FORK_TIMEOUT
5521
5627
  };
5522
5628
  let child;
5523
5629
  try {
5524
- child = spawn3(bin, args, { cwd: cwd && fs13.existsSync(cwd) ? cwd : void 0, env: modelEnv("claude-cli"), stdio: ["ignore", "pipe", "pipe"] });
5630
+ child = spawn3(bin, args, { cwd: cwd && fs14.existsSync(cwd) ? cwd : void 0, env: modelEnv("claude-cli"), stdio: ["ignore", "pipe", "pipe"] });
5525
5631
  } catch (e) {
5526
5632
  resolve({ ok: false, error: e instanceof Error ? e.message : String(e) });
5527
5633
  return;
@@ -5566,7 +5672,7 @@ function reflectionIndex(view, ids, task, focus, humanTurns = []) {
5566
5672
  return [...chosen.values()].slice(0, 30).map(({ id, kind, layer, title, when, outline }) => ({ id, kind, layer, title, when, ...outline?.length ? { outline } : {} }));
5567
5673
  }
5568
5674
  function humanTurnsOf(transcriptPath) {
5569
- if (!transcriptPath || !fs13.existsSync(transcriptPath)) return [];
5675
+ if (!transcriptPath || !fs14.existsSync(transcriptPath)) return [];
5570
5676
  try {
5571
5677
  return parseTranscript(transcriptPath).turns.filter((t) => t.role === "user" && t.text.trim()).map((t) => t.text.slice(0, 1e3));
5572
5678
  } catch {
@@ -5574,7 +5680,7 @@ function humanTurnsOf(transcriptPath) {
5574
5680
  }
5575
5681
  }
5576
5682
  function readFacts(transcriptPath) {
5577
- if (!transcriptPath || !fs13.existsSync(transcriptPath)) return null;
5683
+ if (!transcriptPath || !fs14.existsSync(transcriptPath)) return null;
5578
5684
  try {
5579
5685
  return parseTranscript(transcriptPath);
5580
5686
  } catch {
@@ -5636,7 +5742,7 @@ async function runReflectionV2(store, opts) {
5636
5742
  result.method = "fork";
5637
5743
  } else result.error = fork.error;
5638
5744
  }
5639
- if (result.method === "none" && session.transcriptPath && fs13.existsSync(session.transcriptPath)) {
5745
+ if (result.method === "none" && session.transcriptPath && fs14.existsSync(session.transcriptPath)) {
5640
5746
  const transcriptDigest = digest(facts ?? parseTranscript(session.transcriptPath), { from, maxChars: 4e4 });
5641
5747
  const res = await askJson(config, {
5642
5748
  system: "You are Bunshin's reflection pass. Follow the instructions in the user message and reply with one JSON object only.",
@@ -5759,7 +5865,7 @@ __export(uicmd_exports, {
5759
5865
  modSource: () => modSource
5760
5866
  });
5761
5867
  import { spawnSync } from "node:child_process";
5762
- import fs14 from "node:fs";
5868
+ import fs15 from "node:fs";
5763
5869
  import os3 from "node:os";
5764
5870
  import path10 from "node:path";
5765
5871
  import { fileURLToPath } from "node:url";
@@ -5769,26 +5875,26 @@ function modHome(home = process.env.BUNSHIN_DEPLOY_HOME || os3.homedir()) {
5769
5875
  function modSource() {
5770
5876
  const here = path10.dirname(fileURLToPath(import.meta.url));
5771
5877
  for (const dir of [path10.join(here, "claude-code-ui"), path10.join(here, "..", "claude-code-ui"), path10.resolve(here, "../../../../adapters/claude-code-ui")]) {
5772
- if (fs14.existsSync(path10.join(dir, ".claude-plugin", "plugin.json"))) return dir;
5878
+ if (fs15.existsSync(path10.join(dir, ".claude-plugin", "plugin.json"))) return dir;
5773
5879
  }
5774
5880
  return null;
5775
5881
  }
5776
5882
  function installedModVersion(home) {
5777
5883
  try {
5778
- return JSON.parse(fs14.readFileSync(path10.join(modHome(home), MOD_PLUGIN, ".claude-plugin", "plugin.json"), "utf8")).version ?? null;
5884
+ return JSON.parse(fs15.readFileSync(path10.join(modHome(home), MOD_PLUGIN, ".claude-plugin", "plugin.json"), "utf8")).version ?? null;
5779
5885
  } catch {
5780
5886
  return null;
5781
5887
  }
5782
5888
  }
5783
5889
  function copyDir(from, to) {
5784
- fs14.mkdirSync(to, { recursive: true });
5785
- for (const e of fs14.readdirSync(from, { withFileTypes: true })) {
5890
+ fs15.mkdirSync(to, { recursive: true });
5891
+ for (const e of fs15.readdirSync(from, { withFileTypes: true })) {
5786
5892
  if (e.name === "types" && from.endsWith(".claude-plugin")) continue;
5787
5893
  if (e.name === "node_modules") continue;
5788
5894
  const a = path10.join(from, e.name);
5789
5895
  const b = path10.join(to, e.name);
5790
5896
  if (e.isDirectory()) copyDir(a, b);
5791
- else fs14.copyFileSync(a, b);
5897
+ else fs15.copyFileSync(a, b);
5792
5898
  }
5793
5899
  }
5794
5900
  function installMod(version2, opts = {}) {
@@ -5796,13 +5902,13 @@ function installMod(version2, opts = {}) {
5796
5902
  if (!src) throw new Error("the bunshin-ui mod is not in this build of the CLI");
5797
5903
  const home = modHome(opts.home);
5798
5904
  const dir = path10.join(home, MOD_PLUGIN);
5799
- fs14.rmSync(dir, { recursive: true, force: true });
5905
+ fs15.rmSync(dir, { recursive: true, force: true });
5800
5906
  copyDir(src, dir);
5801
5907
  const manifest = path10.join(dir, ".claude-plugin", "plugin.json");
5802
- const m = JSON.parse(fs14.readFileSync(manifest, "utf8"));
5803
- fs14.writeFileSync(manifest, JSON.stringify({ ...m, version: version2 }, null, 2) + "\n");
5804
- fs14.mkdirSync(path10.join(home, ".claude-plugin"), { recursive: true });
5805
- fs14.writeFileSync(
5908
+ const m = JSON.parse(fs15.readFileSync(manifest, "utf8"));
5909
+ fs15.writeFileSync(manifest, JSON.stringify({ ...m, version: version2 }, null, 2) + "\n");
5910
+ fs15.mkdirSync(path10.join(home, ".claude-plugin"), { recursive: true });
5911
+ fs15.writeFileSync(
5806
5912
  path10.join(home, ".claude-plugin", "marketplace.json"),
5807
5913
  JSON.stringify({ name: MOD_MARKETPLACE, owner: { name: "Bunshin" }, plugins: [{ name: MOD_PLUGIN, source: `./${MOD_PLUGIN}`, description: m.description }] }, null, 2) + "\n"
5808
5914
  );
@@ -5911,9 +6017,84 @@ var init_uicmd = __esm({
5911
6017
  }
5912
6018
  });
5913
6019
 
5914
- // src/v2/hooks.ts
5915
- import fs15 from "node:fs";
6020
+ // src/v2/update.ts
6021
+ var update_exports = {};
6022
+ __export(update_exports, {
6023
+ checkForUpdate: () => checkForUpdate,
6024
+ newerVersion: () => newerVersion,
6025
+ updateCheckDue: () => updateCheckDue,
6026
+ updateNotice: () => updateNotice
6027
+ });
6028
+ import fs16 from "node:fs";
5916
6029
  import path11 from "node:path";
6030
+ function readState2() {
6031
+ try {
6032
+ return JSON.parse(fs16.readFileSync(stateFile(), "utf8"));
6033
+ } catch {
6034
+ return {};
6035
+ }
6036
+ }
6037
+ function writeState(s) {
6038
+ try {
6039
+ fs16.mkdirSync(path11.dirname(stateFile()), { recursive: true });
6040
+ fs16.writeFileSync(stateFile(), JSON.stringify(s, null, 2) + "\n");
6041
+ } catch {
6042
+ }
6043
+ }
6044
+ function newerVersion(a, b) {
6045
+ const parse = (v) => {
6046
+ const [core = "", pre] = v.trim().replace(/^v/, "").split("-", 2);
6047
+ return { nums: core.split(".").map((n) => Number(n) || 0), pre: pre !== void 0 };
6048
+ };
6049
+ const x = parse(a);
6050
+ const y = parse(b);
6051
+ for (let i = 0; i < 3; i++) if ((x.nums[i] ?? 0) !== (y.nums[i] ?? 0)) return (x.nums[i] ?? 0) > (y.nums[i] ?? 0);
6052
+ return !x.pre && y.pre;
6053
+ }
6054
+ function updateCheckDue(now = Date.now()) {
6055
+ if (disabled()) return false;
6056
+ const at = Date.parse(readState2().checkedAt ?? "");
6057
+ return !Number.isFinite(at) || now - at >= DAY;
6058
+ }
6059
+ function updateNotice(now = Date.now(), current = version()) {
6060
+ if (disabled() || current === "0.0.0") return void 0;
6061
+ const s = readState2();
6062
+ if (!s.latest || !newerVersion(s.latest, current)) return void 0;
6063
+ const last = Date.parse(s.notifiedAt ?? "");
6064
+ if (s.notifiedVersion === s.latest && Number.isFinite(last) && now - last < DAY) return void 0;
6065
+ writeState({ ...s, notifiedAt: new Date(now).toISOString(), notifiedVersion: s.latest });
6066
+ return `Bunshin ${s.latest} is available (this machine has ${current}): npm i -g ${PKG}@latest`;
6067
+ }
6068
+ async function checkForUpdate({ now = Date.now(), fetchFn = fetch, timeoutMs = 4e3 } = {}) {
6069
+ if (disabled()) return void 0;
6070
+ const s = readState2();
6071
+ try {
6072
+ const res = await fetchFn(LATEST_URL, { signal: AbortSignal.timeout(timeoutMs), headers: { accept: "application/json" } });
6073
+ const latest = res.ok ? (await res.json()).version : void 0;
6074
+ writeState({ ...s, checkedAt: new Date(now).toISOString(), ...typeof latest === "string" && /^\d+\.\d+\.\d+/.test(latest) ? { latest } : {} });
6075
+ return typeof latest === "string" ? latest : void 0;
6076
+ } catch {
6077
+ writeState({ ...s, checkedAt: new Date(now).toISOString() });
6078
+ return void 0;
6079
+ }
6080
+ }
6081
+ var PKG, LATEST_URL, DAY, stateFile, disabled;
6082
+ var init_update = __esm({
6083
+ "src/v2/update.ts"() {
6084
+ "use strict";
6085
+ init_util();
6086
+ init_version();
6087
+ PKG = "@bunshin-cc/bunshin";
6088
+ LATEST_URL = `https://registry.npmjs.org/${PKG.replace("/", "%2f")}/latest`;
6089
+ DAY = 864e5;
6090
+ stateFile = () => path11.join(homeDir(), ".bunshin", "update.json");
6091
+ disabled = () => process.env.BUNSHIN_NO_UPDATE_CHECK === "1" || process.env.BUNSHIN_NO_UPDATE_CHECK === "true";
6092
+ }
6093
+ });
6094
+
6095
+ // src/v2/hooks.ts
6096
+ import fs17 from "node:fs";
6097
+ import path12 from "node:path";
5917
6098
  function inContext(s) {
5918
6099
  return s.inContext ?? [...s.loaded, ...s.pushed];
5919
6100
  }
@@ -5950,7 +6131,7 @@ function withUi(ctx, out) {
5950
6131
  if (harness === "pi" && uiMode(config, "pi") === "widget") {
5951
6132
  const feed = readFeed(v2, id);
5952
6133
  if (!feed) return out;
5953
- const fresh = feed.events.filter((e) => e.seq > (feed.linedSeq ?? 0) && ["live", "learned", "review", "offline"].includes(e.kind));
6134
+ const fresh = feed.events.filter((e) => e.seq > (feed.linedSeq ?? 0) && ["live", "learned", "review", "offline", "update"].includes(e.kind));
5954
6135
  if (feed.events.length) updateFeed(v2, id, (f) => void (f.linedSeq = feed.events.at(-1).seq));
5955
6136
  return { ...out, bunshinUi: { lines: feedLines(feed), notices: fresh.map((e) => e.text) } };
5956
6137
  }
@@ -5974,10 +6155,10 @@ async function keepModCurrent(ctx, config) {
5974
6155
  const { version: version2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
5975
6156
  const current = version2();
5976
6157
  if (!modSource2() || installedModVersion2() === current) return;
5977
- const marker = path11.join(modHome2(), `.attempted-${current}`);
5978
- if (fs15.existsSync(marker)) return;
5979
- fs15.mkdirSync(modHome2(), { recursive: true });
5980
- fs15.writeFileSync(marker, (/* @__PURE__ */ new Date()).toISOString());
6158
+ const marker = path12.join(modHome2(), `.attempted-${current}`);
6159
+ if (fs17.existsSync(marker)) return;
6160
+ fs17.mkdirSync(modHome2(), { recursive: true });
6161
+ fs17.writeFileSync(marker, (/* @__PURE__ */ new Date()).toISOString());
5981
6162
  ctx.detach(["--root", ctx.store.root, "ui", "install", "--quiet"], { cwd: ctx.cwd });
5982
6163
  } catch {
5983
6164
  }
@@ -6018,6 +6199,13 @@ async function onSessionStart(ctx) {
6018
6199
  v2.writeCurrent({ sessionId: id, cwd: ctx.cwd, at: now });
6019
6200
  const config = ctx.store.config();
6020
6201
  await keepModCurrent(ctx, config);
6202
+ try {
6203
+ const { updateCheckDue: updateCheckDue2, updateNotice: updateNotice2 } = await Promise.resolve().then(() => (init_update(), update_exports));
6204
+ const notice = updateNotice2();
6205
+ if (notice) updateFeed(v2, id, (f) => addEvent(f, "update", notice));
6206
+ if (updateCheckDue2()) ctx.detach(["--root", ctx.store.root, "update-check", "--quiet"], { cwd: ctx.cwd });
6207
+ } catch {
6208
+ }
6021
6209
  if (canCall(config)) {
6022
6210
  try {
6023
6211
  const swept = checkpointLeftovers(ctx.store, id);
@@ -6029,9 +6217,10 @@ async function onSessionStart(ctx) {
6029
6217
  try {
6030
6218
  const { syncV2: syncV22 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
6031
6219
  const report = await Promise.race([
6032
- syncV22(ctx.store, { budgetMs: budget, cwd: ctx.cwd }),
6220
+ syncV22(ctx.store, { budgetMs: budget, cwd: ctx.cwd, traces: false }),
6033
6221
  new Promise((r) => setTimeout(() => r(null), budget + 200).unref?.())
6034
6222
  ]);
6223
+ if (report?.deferredTraces) ctx.detach(["--root", ctx.store.root, "sync", "--quiet"], { cwd: ctx.cwd });
6035
6224
  if (!report || report.errors.length) v2.log("v2-session-start-sync", { session: id, errors: report?.errors ?? ["budget exceeded"] });
6036
6225
  } catch {
6037
6226
  }
@@ -6115,6 +6304,12 @@ function hintLine(titles, harness) {
6115
6304
  const how = harness === "claude-code" ? "If one could change your approach, call the bunshin_recall tool (describe the task abstractly) and load only what you need." : 'If one could change your approach, run `bunshin recall "<the task, abstractly>" --hypothetical "<the lesson you would want>"` and `bunshin load <id>`.';
6116
6305
  return `Bunshin: possibly relevant library sections: ${quoted}. ${how}`;
6117
6306
  }
6307
+ function invokedSkill(prompt, sections) {
6308
+ const m = /^\s*(?:<command-name>\s*)?\/([\w.:-]+)/.exec(prompt);
6309
+ if (!m) return void 0;
6310
+ const name = m[1].split(":").pop().toLowerCase();
6311
+ return sections.find((s) => s.skill && s.skill.name.toLowerCase() === name);
6312
+ }
6118
6313
  async function onPrompt(ctx) {
6119
6314
  const prompt = ctx.input.prompt ?? "";
6120
6315
  if (prompt.trimStart().startsWith(REFLECT_MARKER)) return {};
@@ -6135,6 +6330,23 @@ async function onPrompt(ctx) {
6135
6330
  const config = ctx.store.config();
6136
6331
  const view = localView(v2, config, ctx.cwd);
6137
6332
  const injected = inContext(session);
6333
+ const named = invokedSkill(prompt, view.sections);
6334
+ if (named) {
6335
+ const why = `you ran /${named.skill.name}`;
6336
+ const deployed = deployEnabled(config, view.ctx);
6337
+ const fresh2 = !injected.includes(named.id);
6338
+ v2.updateSession(session.sessionId, ctx.cwd, (s) => {
6339
+ addUnique(s.served, named.id);
6340
+ addUnique(s.loaded, named.id);
6341
+ markInContext(s, named.id);
6342
+ });
6343
+ v2.log("v2-invoked", { session: session.sessionId, section: named.id });
6344
+ updateFeed(v2, session.sessionId, (f) => {
6345
+ addServed(f, { id: named.id, title: named.title, kind: named.kind, skill: named.skill.name, how: "load", why });
6346
+ addEvent(f, "served", `loaded ${label({ skill: named.skill.name, title: named.title, kind: named.kind })} \u2014 ${why}`);
6347
+ });
6348
+ return withLive(deployed || !fresh2 ? {} : additional("UserPromptSubmit", renderSection(named, why, { deployed: false })));
6349
+ }
6138
6350
  const res = auto(view.sections, view.reader, prompt, { exclude: injected });
6139
6351
  const remote = await remoteAuto(ctx, prompt, injected);
6140
6352
  if (remote) {
@@ -6320,11 +6532,13 @@ function buildEpisode(store, session, transcriptPath, now = /* @__PURE__ */ new
6320
6532
  upload,
6321
6533
  turns,
6322
6534
  reflectedTurns: from,
6323
- ...upload !== "header-only" && turns > from ? { transcript: digest(scan.facts, { from, maxChars: upload === "full" ? 2e5 : 6e4 }), transcriptFrom: from } : {},
6535
+ // The whole trace is built (and redacted) by sync at upload time, outside the hook's budget.
6536
+ ...upload !== "header-only" && turns > from && (transcriptPath ?? session.transcriptPath) ? { transcriptFrom: from, transcriptPath: transcriptPath ?? session.transcriptPath } : {},
6324
6537
  ...calls.length ? { calls } : {},
6325
6538
  ...snippets.length ? { snippets } : {},
6326
6539
  ...outcome ? { outcome } : {},
6327
6540
  ...view.bundle?.release?.version ? { harnessVersion: view.bundle.release.version } : {},
6541
+ cliVersion: version(),
6328
6542
  uploaded: false,
6329
6543
  ...previous?.reportedServed ? { reportedServed: previous.reportedServed } : {},
6330
6544
  ...previous?.reportedLoaded ? { reportedLoaded: previous.reportedLoaded } : {},
@@ -6437,7 +6651,7 @@ var init_hooks = __esm({
6437
6651
  "use strict";
6438
6652
  init_v22();
6439
6653
  init_redact2();
6440
- init_transcript();
6654
+ init_version();
6441
6655
  init_api();
6442
6656
  init_deploy();
6443
6657
  init_live();
@@ -6564,11 +6778,11 @@ var init_hooks2 = __esm({
6564
6778
  });
6565
6779
 
6566
6780
  // src/project-hooks.ts
6567
- import fs16 from "node:fs";
6568
- import path12 from "node:path";
6781
+ import fs18 from "node:fs";
6782
+ import path13 from "node:path";
6569
6783
  import { execFileSync as execFileSync2 } from "node:child_process";
6570
6784
  function codexProjectHooks(cwd) {
6571
- const legacyFile = path12.join(cwd, ".codex", "hooks.json");
6785
+ const legacyFile = path13.join(cwd, ".codex", "hooks.json");
6572
6786
  const git4 = (...args) => execFileSync2("git", ["-C", cwd, "rev-parse", ...args], {
6573
6787
  encoding: "utf8",
6574
6788
  stdio: ["ignore", "pipe", "ignore"],
@@ -6582,14 +6796,14 @@ function codexProjectHooks(cwd) {
6582
6796
  } catch {
6583
6797
  return { file: legacyFile, shared: false, legacyFile };
6584
6798
  }
6585
- const linked = fs16.realpathSync(common) !== fs16.realpathSync(directory);
6799
+ const linked = fs18.realpathSync(common) !== fs18.realpathSync(directory);
6586
6800
  if (linked) {
6587
- if (path12.basename(common) !== ".git" || !fs16.statSync(common).isDirectory()) {
6801
+ if (path13.basename(common) !== ".git" || !fs18.statSync(common).isDirectory()) {
6588
6802
  throw new Error("Codex hooks in a worktree of a bare repository are not supported yet. Use a full checkout for Bunshin setup.");
6589
6803
  }
6590
- root = path12.dirname(common);
6804
+ root = path13.dirname(common);
6591
6805
  }
6592
- const file = fs16.realpathSync(root) === fs16.realpathSync(cwd) ? legacyFile : path12.join(root, ".codex", "hooks.json");
6806
+ const file = fs18.realpathSync(root) === fs18.realpathSync(cwd) ? legacyFile : path13.join(root, ".codex", "hooks.json");
6593
6807
  return { file, shared: linked, legacyFile };
6594
6808
  }
6595
6809
  var init_project_hooks = __esm({
@@ -6619,8 +6833,8 @@ __export(init_exports, {
6619
6833
  removeBunshinHooks: () => removeBunshinHooks,
6620
6834
  runInit: () => runInit
6621
6835
  });
6622
- import fs17 from "node:fs";
6623
- import path13 from "node:path";
6836
+ import fs19 from "node:fs";
6837
+ import path14 from "node:path";
6624
6838
  import { fileURLToPath as fileURLToPath2, pathToFileURL } from "node:url";
6625
6839
  function isBunshinEntry(entry) {
6626
6840
  return entry?._bunshin === true || typeof entry?.command === "string" && /\bbunshin\b.*\bhook\b/.test(entry.command);
@@ -6665,16 +6879,16 @@ function countBunshinHooks(settings) {
6665
6879
  );
6666
6880
  }
6667
6881
  function claudeSettingsPath() {
6668
- return path13.join(homeDir(), ".claude", "settings.json");
6882
+ return path14.join(homeDir(), ".claude", "settings.json");
6669
6883
  }
6670
6884
  function installPiExtension(cwd, remove = false, scope = "repo") {
6671
- const file = scope === "global" ? path13.join(homeDir(), ".pi/agent/extensions/bunshin.js") : path13.join(cwd, ".pi/extensions/bunshin.js");
6885
+ const file = scope === "global" ? path14.join(homeDir(), ".pi/agent/extensions/bunshin.js") : path14.join(cwd, ".pi/extensions/bunshin.js");
6672
6886
  const marker = "// Managed by Bunshin setup.";
6673
6887
  try {
6674
- const previous = fileExists(file) ? fs17.readFileSync(file, "utf8") : "";
6888
+ const previous = fileExists(file) ? fs19.readFileSync(file, "utf8") : "";
6675
6889
  if (previous && !previous.startsWith(marker)) throw new Error("An existing Bunshin extension file is not managed by setup; preserve or move it before continuing.");
6676
6890
  if (remove) {
6677
- if (previous) fs17.unlinkSync(file);
6891
+ if (previous) fs19.unlinkSync(file);
6678
6892
  return { file, installed: 0, changed: !!previous };
6679
6893
  }
6680
6894
  const extension = fileURLToPath2(new URL("./pi-extension.js", import.meta.url));
@@ -6683,8 +6897,8 @@ function installPiExtension(cwd, remove = false, scope = "repo") {
6683
6897
  export { default } from ${JSON.stringify(pathToFileURL(extension).href)};
6684
6898
  `;
6685
6899
  if (text2 !== previous) {
6686
- ensureDir(path13.dirname(file));
6687
- fs17.writeFileSync(file, text2);
6900
+ ensureDir(path14.dirname(file));
6901
+ fs19.writeFileSync(file, text2);
6688
6902
  }
6689
6903
  return { file, installed: 1, changed: text2 !== previous };
6690
6904
  } catch (error) {
@@ -6693,14 +6907,14 @@ export { default } from ${JSON.stringify(pathToFileURL(extension).href)};
6693
6907
  }
6694
6908
  function installClaudeHooks({ file = claudeSettingsPath(), bin = "bunshin", remove = false, harness = "claude-code" } = {}) {
6695
6909
  try {
6696
- const before = fileExists(file) ? JSON.parse(fs17.readFileSync(file, "utf8")) : {};
6910
+ const before = fileExists(file) ? JSON.parse(fs19.readFileSync(file, "utf8")) : {};
6697
6911
  const after = remove ? removeBunshinHooks(before) : mergeClaudeSettings(before, { bin, harness });
6698
6912
  const changed = JSON.stringify(before) !== JSON.stringify(after);
6699
6913
  if (changed) {
6700
- ensureDir(path13.dirname(file));
6914
+ ensureDir(path14.dirname(file));
6701
6915
  if (fileExists(file)) {
6702
6916
  try {
6703
- fs17.copyFileSync(file, `${file}.bunshin-backup`);
6917
+ fs19.copyFileSync(file, `${file}.bunshin-backup`);
6704
6918
  } catch {
6705
6919
  }
6706
6920
  }
@@ -6715,23 +6929,23 @@ function mcpLauncher(bin) {
6715
6929
  if (!bin || /^[\w./@+-]+$/.test(bin)) return { command: bin || "bunshin", args: ["mcp"] };
6716
6930
  let script = process.argv[1] ?? "";
6717
6931
  try {
6718
- script = fs17.realpathSync(script);
6932
+ script = fs19.realpathSync(script);
6719
6933
  } catch {
6720
6934
  }
6721
6935
  return { command: process.execPath, args: [script, "mcp"] };
6722
6936
  }
6723
6937
  function installLearnCommand({ file, remove = false }) {
6724
6938
  try {
6725
- const current = fileExists(file) ? fs17.readFileSync(file, "utf8") : null;
6939
+ const current = fileExists(file) ? fs19.readFileSync(file, "utf8") : null;
6726
6940
  const ours = current === null || current === LEARN_COMMAND;
6727
6941
  if (remove) {
6728
- if (current !== null && ours) fs17.unlinkSync(file);
6942
+ if (current !== null && ours) fs19.unlinkSync(file);
6729
6943
  return { file, installed: 0, changed: current !== null && ours };
6730
6944
  }
6731
6945
  if (!ours) return { file, installed: 0, changed: false, error: "a different learn.md is already there; left as it is" };
6732
6946
  if (current === null) {
6733
- fs17.mkdirSync(path13.dirname(file), { recursive: true });
6734
- fs17.writeFileSync(file, LEARN_COMMAND);
6947
+ fs19.mkdirSync(path14.dirname(file), { recursive: true });
6948
+ fs19.writeFileSync(file, LEARN_COMMAND);
6735
6949
  }
6736
6950
  return { file, installed: 1, changed: current === null };
6737
6951
  } catch (err) {
@@ -6740,7 +6954,7 @@ function installLearnCommand({ file, remove = false }) {
6740
6954
  }
6741
6955
  function installMcpConfig({ file, launcher, remove = false }) {
6742
6956
  try {
6743
- const before = fileExists(file) ? JSON.parse(fs17.readFileSync(file, "utf8")) : {};
6957
+ const before = fileExists(file) ? JSON.parse(fs19.readFileSync(file, "utf8")) : {};
6744
6958
  const servers = { ...before.mcpServers ?? {} };
6745
6959
  if (remove) delete servers.bunshin;
6746
6960
  else servers.bunshin = { type: "stdio", command: launcher.command, args: launcher.args, env: { BUNSHIN_HARNESS: "claude-code" } };
@@ -6748,7 +6962,7 @@ function installMcpConfig({ file, launcher, remove = false }) {
6748
6962
  if (!Object.keys(servers).length) delete after.mcpServers;
6749
6963
  const changed = JSON.stringify(before) !== JSON.stringify(after);
6750
6964
  if (changed) {
6751
- if (!Object.keys(after).length && fileExists(file)) fs17.unlinkSync(file);
6965
+ if (!Object.keys(after).length && fileExists(file)) fs19.unlinkSync(file);
6752
6966
  else if (Object.keys(after).length) writeJsonAtomic(file, after);
6753
6967
  }
6754
6968
  return { file, installed: servers.bunshin ? 1 : 0, changed };
@@ -6760,21 +6974,21 @@ function runInit(opts = {}) {
6760
6974
  const cwd = opts.cwd ?? process.cwd();
6761
6975
  const warnings = [];
6762
6976
  const scope = opts.scope ?? "repo";
6763
- const root = scope === "global" ? path13.join(homeDir(), ".bunshin") : path13.join(cwd, ".bunshin");
6764
- if (fs17.existsSync(root) && !new Store(root).trusted && fs17.readdirSync(root).length) {
6977
+ const root = scope === "global" ? path14.join(homeDir(), ".bunshin") : path14.join(cwd, ".bunshin");
6978
+ if (fs19.existsSync(root) && !new Store(root).trusted && fs19.readdirSync(root).length) {
6765
6979
  const aside = `${root}.untrusted-${Date.now()}`;
6766
- fs17.renameSync(root, aside);
6980
+ fs19.renameSync(root, aside);
6767
6981
  warnings.push(`moved ${root} aside to ${aside}: this user did not create it`);
6768
6982
  }
6769
- const existed = fileExists(path13.join(root, "config.json"));
6983
+ const existed = fileExists(path14.join(root, "config.json"));
6770
6984
  const store = new Store(root);
6771
6985
  store.ensureLayout();
6772
- fs17.chmodSync(root, 448);
6986
+ fs19.chmodSync(root, 448);
6773
6987
  if (scope === "repo") {
6774
- const ignore = path13.join(cwd, ".gitignore");
6775
- const current = fileExists(ignore) ? fs17.readFileSync(ignore, "utf8") : "";
6988
+ const ignore = path14.join(cwd, ".gitignore");
6989
+ const current = fileExists(ignore) ? fs19.readFileSync(ignore, "utf8") : "";
6776
6990
  if (!current.split(/\r?\n/).some((line) => /^\/?\.bunshin\/?$/.test(line.trim()))) {
6777
- fs17.appendFileSync(ignore, `${current && !current.endsWith("\n") ? "\n" : ""}.bunshin/
6991
+ fs19.appendFileSync(ignore, `${current && !current.endsWith("\n") ? "\n" : ""}.bunshin/
6778
6992
  `);
6779
6993
  }
6780
6994
  }
@@ -6803,13 +7017,13 @@ function runInit(opts = {}) {
6803
7017
  }
6804
7018
  if ((opts.removeHooks || !opts.noHooks) && (config.harness === "codex" || config.harness === "claude-code")) {
6805
7019
  const projectHooks = config.harness === "codex" && scope === "repo" && !opts.settingsFile ? codexProjectHooks(cwd) : void 0;
6806
- const file = opts.settingsFile ?? (config.harness === "codex" ? projectHooks?.file ?? path13.join(process.env.CODEX_HOME ?? path13.join(homeDir(), ".codex"), "hooks.json") : scope === "repo" ? path13.join(cwd, ".claude", "settings.local.json") : claudeSettingsPath());
7020
+ const file = opts.settingsFile ?? (config.harness === "codex" ? projectHooks?.file ?? path14.join(process.env.CODEX_HOME ?? path14.join(homeDir(), ".codex"), "hooks.json") : scope === "repo" ? path14.join(cwd, ".claude", "settings.local.json") : claudeSettingsPath());
6807
7021
  hooks = installClaudeHooks({ file, bin: opts.bin, remove: opts.removeHooks, harness: config.harness });
6808
7022
  if (projectHooks?.shared) {
6809
7023
  hooks.sharedWithWorktrees = true;
6810
- if (!hooks.error && !opts.removeHooks) ensureDir(path13.dirname(projectHooks.legacyFile));
7024
+ if (!hooks.error && !opts.removeHooks) ensureDir(path14.dirname(projectHooks.legacyFile));
6811
7025
  }
6812
- if (!hooks.error && projectHooks && projectHooks.file !== projectHooks.legacyFile && fileExists(projectHooks.legacyFile) && fs17.realpathSync(projectHooks.file) !== fs17.realpathSync(projectHooks.legacyFile)) {
7026
+ if (!hooks.error && projectHooks && projectHooks.file !== projectHooks.legacyFile && fileExists(projectHooks.legacyFile) && fs19.realpathSync(projectHooks.file) !== fs19.realpathSync(projectHooks.legacyFile)) {
6813
7027
  const old = installClaudeHooks({ file: projectHooks.legacyFile, remove: true, harness: "codex" });
6814
7028
  if (old.error) hooks.error = `Could not remove old Bunshin hooks from ${old.file}: ${old.error}`;
6815
7029
  }
@@ -6818,9 +7032,9 @@ function runInit(opts = {}) {
6818
7032
  let mcp;
6819
7033
  let learnCommand;
6820
7034
  if ((opts.removeHooks || !opts.noHooks) && config.harness === "claude-code" && scope === "repo") {
6821
- mcp = installMcpConfig({ file: path13.join(cwd, ".mcp.json"), launcher: opts.mcp ?? mcpLauncher(opts.bin), remove: opts.removeHooks });
7035
+ mcp = installMcpConfig({ file: path14.join(cwd, ".mcp.json"), launcher: opts.mcp ?? mcpLauncher(opts.bin), remove: opts.removeHooks });
6822
7036
  if (mcp.error) warnings.push(`could not update ${mcp.file}: ${mcp.error}`);
6823
- learnCommand = installLearnCommand({ file: path13.join(cwd, ".claude", "commands", "learn.md"), remove: opts.removeHooks });
7037
+ learnCommand = installLearnCommand({ file: path14.join(cwd, ".claude", "commands", "learn.md"), remove: opts.removeHooks });
6824
7038
  if (learnCommand.error) warnings.push(`/learn not installed at ${learnCommand.file}: ${learnCommand.error}`);
6825
7039
  }
6826
7040
  if (!resolveBackend(config)) {
@@ -6962,8 +7176,8 @@ __export(commands_exports, {
6962
7176
  setupRobot: () => setupRobot,
6963
7177
  v2Status: () => v2Status
6964
7178
  });
6965
- import fs18 from "node:fs";
6966
- import path14 from "node:path";
7179
+ import fs20 from "node:fs";
7180
+ import path15 from "node:path";
6967
7181
  import { createHash as createHash4, randomBytes as randomBytes4 } from "node:crypto";
6968
7182
  function cmdReview(store, args, cwd, out, err) {
6969
7183
  const v2 = new V2Store(store);
@@ -7133,11 +7347,11 @@ function cmdSuggest(store, args, cwd, out, err) {
7133
7347
  err('bunshin suggest: usage: bunshin suggest --code <harness-relative path> [--file <local file>] [--rationale "<text>"]\n');
7134
7348
  return 1;
7135
7349
  }
7136
- const harnessDir = path14.resolve(cwd, str3(args.flags["harness-dir"]) ?? ".");
7137
- const file = str3(args.flags.file) ? path14.resolve(cwd, str3(args.flags.file)) : path14.join(harnessDir, rel);
7350
+ const harnessDir = path15.resolve(cwd, str3(args.flags["harness-dir"]) ?? ".");
7351
+ const file = str3(args.flags.file) ? path15.resolve(cwd, str3(args.flags.file)) : path15.join(harnessDir, rel);
7138
7352
  let content;
7139
7353
  try {
7140
- content = fs18.readFileSync(file, "utf8");
7354
+ content = fs20.readFileSync(file, "utf8");
7141
7355
  } catch {
7142
7356
  err(`bunshin suggest: cannot read ${file}
7143
7357
  `);
@@ -7202,6 +7416,12 @@ async function cmdIndex(store, args, cwd, out, err) {
7202
7416
  return 0;
7203
7417
  }
7204
7418
  async function cmdSync(store, args, cwd, out, err) {
7419
+ if (bool2(args.flags.retrace)) {
7420
+ const days = Math.max(1, Number(str3(args.flags.days) ?? 30) || 30);
7421
+ const r = markForRetrace(new V2Store(store), { days });
7422
+ if (!bool2(args.flags.quiet) && !bool2(args.flags.json)) out(`retrace: re-sending ${r.queued} clipped trace(s) from the last ${days} day(s) in full${r.missing ? `; ${r.missing} skipped (transcript file no longer on this machine)` : ""}
7423
+ `);
7424
+ }
7205
7425
  const report = await syncV2(store, { index: bool2(args.flags.index), cwd });
7206
7426
  if (bool2(args.flags.json)) out(JSON.stringify(report, null, 2) + "\n");
7207
7427
  else if (!bool2(args.flags.quiet)) {
@@ -7252,15 +7472,15 @@ async function cmdQuestions(store, args, out, err) {
7252
7472
  return 0;
7253
7473
  }
7254
7474
  function saveCredential(storeRoot, apiUrl, apiKey, id) {
7255
- const dir = path14.join(homeDir(), ".config/bunshin/credentials");
7256
- fs18.mkdirSync(dir, { recursive: true, mode: 448 });
7257
- fs18.chmodSync(dir, 448);
7258
- const file = path14.join(dir, `${createHash4("sha256").update(`${storeRoot}
7475
+ const dir = path15.join(homeDir(), ".config/bunshin/credentials");
7476
+ fs20.mkdirSync(dir, { recursive: true, mode: 448 });
7477
+ fs20.chmodSync(dir, 448);
7478
+ const file = path15.join(dir, `${createHash4("sha256").update(`${storeRoot}
7259
7479
  ${apiUrl}
7260
7480
  ${id}`).digest("hex")}.json`);
7261
7481
  const tmp = `${file}.${randomBytes4(4).toString("hex")}.tmp`;
7262
- fs18.writeFileSync(tmp, JSON.stringify({ apiUrl, apiKey }) + "\n", { mode: 384, flag: "wx" });
7263
- fs18.renameSync(tmp, file);
7482
+ fs20.writeFileSync(tmp, JSON.stringify({ apiUrl, apiKey }) + "\n", { mode: 384, flag: "wx" });
7483
+ fs20.renameSync(tmp, file);
7264
7484
  return file;
7265
7485
  }
7266
7486
  async function setupRobot(opts) {
@@ -7268,10 +7488,10 @@ async function setupRobot(opts) {
7268
7488
  const existing = Store.discover(opts.cwd);
7269
7489
  let store;
7270
7490
  const harness = opts.harness ?? "other";
7271
- if (existing && path14.resolve(path14.dirname(existing.root)) === path14.resolve(opts.cwd) && harness === "other") store = existing;
7491
+ if (existing && path15.resolve(path15.dirname(existing.root)) === path15.resolve(opts.cwd) && harness === "other") store = existing;
7272
7492
  else {
7273
7493
  runInit({ cwd: opts.cwd, harness, noHooks: harness === "other", ...opts.apiUrl ? { apiUrl: opts.apiUrl } : {} });
7274
- store = new Store(path14.join(opts.cwd, ".bunshin"));
7494
+ store = new Store(path15.join(opts.cwd, ".bunshin"));
7275
7495
  }
7276
7496
  let config = store.config();
7277
7497
  const apiUrl = (opts.apiUrl ?? config.apiUrl ?? "").replace(/\/$/, "");
@@ -7298,7 +7518,7 @@ async function setupRobot(opts) {
7298
7518
  v2.writeContext(context);
7299
7519
  let synced = false;
7300
7520
  if (registered) {
7301
- const report = await syncV2(store, { cwd: opts.cwd, budgetMs: 15e3 });
7521
+ const report = await syncV2(store, { cwd: opts.cwd, budgetMs: 15e3, traces: false });
7302
7522
  synced = report.bundle === "fetched";
7303
7523
  errors.push(...report.errors);
7304
7524
  }
@@ -7381,16 +7601,16 @@ __export(setup_exports, {
7381
7601
  setupProject: () => setupProject,
7382
7602
  shellQuote: () => shellQuote
7383
7603
  });
7384
- import fs19 from "node:fs";
7385
- import path15 from "node:path";
7604
+ import fs21 from "node:fs";
7605
+ import path16 from "node:path";
7386
7606
  import { createHash as createHash5, randomBytes as randomBytes5 } from "node:crypto";
7387
7607
  function executable(name) {
7388
- const directories = [...(process.env.PATH ?? "").split(path15.delimiter), path15.join(homeDir(), ".local/bin")];
7608
+ const directories = [...(process.env.PATH ?? "").split(path16.delimiter), path16.join(homeDir(), ".local/bin")];
7389
7609
  for (const directory of directories) {
7390
- const file = path15.resolve(directory, name);
7610
+ const file = path16.resolve(directory, name);
7391
7611
  try {
7392
- fs19.accessSync(file, fs19.constants.X_OK);
7393
- if (fs19.statSync(file).isFile()) return file;
7612
+ fs21.accessSync(file, fs21.constants.X_OK);
7613
+ if (fs21.statSync(file).isFile()) return file;
7394
7614
  } catch {
7395
7615
  }
7396
7616
  }
@@ -7398,8 +7618,8 @@ function executable(name) {
7398
7618
  }
7399
7619
  function hookBinary(harness) {
7400
7620
  const agent = executable(harness === "claude-code" ? "claude" : harness);
7401
- const directories = [.../* @__PURE__ */ new Set([path15.dirname(process.execPath), path15.join(homeDir(), ".local/bin"), ...agent ? [path15.dirname(agent)] : []])];
7402
- return `PATH=${shellQuote(directories.join(path15.delimiter))}:"$PATH" ${shellQuote(process.execPath)} ${shellQuote(fs19.realpathSync(process.argv[1]))}`;
7621
+ const directories = [.../* @__PURE__ */ new Set([path16.dirname(process.execPath), path16.join(homeDir(), ".local/bin"), ...agent ? [path16.dirname(agent)] : []])];
7622
+ return `PATH=${shellQuote(directories.join(path16.delimiter))}:"$PATH" ${shellQuote(process.execPath)} ${shellQuote(fs21.realpathSync(process.argv[1]))}`;
7403
7623
  }
7404
7624
  function apiAddress(raw) {
7405
7625
  const url = new URL(raw);
@@ -7468,23 +7688,23 @@ async function setupProject(opts) {
7468
7688
  const apiKey = opts.apiKey.trim();
7469
7689
  if (!apiKey || /\s/.test(apiKey)) throw new Error("Paste the complete agent key from your Bunshin workspace.");
7470
7690
  const profile = await connectionProfile(apiUrl, apiKey);
7471
- const root = path15.join(opts.cwd, ".bunshin");
7691
+ const root = path16.join(opts.cwd, ".bunshin");
7472
7692
  const store = new Store(root);
7473
7693
  const previous = store.config();
7474
7694
  if ((previous.orgId !== profile.orgId || previous.userId !== profile.userId) && (store.listLessons().length || store.allTraces().length || store.listRecallLessons().length)) {
7475
7695
  throw new Error("This project has lessons or activity from another profile. Use a fresh project to connect this workspace.");
7476
7696
  }
7477
- const credentialDirectory = path15.join(homeDir(), ".config/bunshin/credentials");
7478
- fs19.mkdirSync(credentialDirectory, { recursive: true, mode: 448 });
7479
- fs19.chmodSync(credentialDirectory, 448);
7697
+ const credentialDirectory = path16.join(homeDir(), ".config/bunshin/credentials");
7698
+ fs21.mkdirSync(credentialDirectory, { recursive: true, mode: 448 });
7699
+ fs21.chmodSync(credentialDirectory, 448);
7480
7700
  const name = createHash5("sha256").update(`${root}
7481
7701
  ${apiUrl}
7482
7702
  ${profile.orgId}
7483
7703
  ${profile.userId}`).digest("hex");
7484
- const credentialFile = path15.join(credentialDirectory, `${name}.json`);
7704
+ const credentialFile = path16.join(credentialDirectory, `${name}.json`);
7485
7705
  const temporary = `${credentialFile}.${randomBytes5(4).toString("hex")}.tmp`;
7486
- fs19.writeFileSync(temporary, JSON.stringify({ apiUrl, apiKey }) + "\n", { mode: 384, flag: "wx" });
7487
- fs19.renameSync(temporary, credentialFile);
7706
+ fs21.writeFileSync(temporary, JSON.stringify({ apiUrl, apiKey }) + "\n", { mode: 384, flag: "wx" });
7707
+ fs21.renameSync(temporary, credentialFile);
7488
7708
  const result = runInit({
7489
7709
  cwd: opts.cwd,
7490
7710
  harness: opts.harness,
@@ -7796,8 +8016,8 @@ __export(sync_exports2, {
7796
8016
  runSync: () => runSync
7797
8017
  });
7798
8018
  import { createHash as createHash6 } from "node:crypto";
7799
- import path16 from "node:path";
7800
- import fs20 from "node:fs";
8019
+ import path17 from "node:path";
8020
+ import fs22 from "node:fs";
7801
8021
  async function api(config, method, route, body, timeoutMs = 1e4) {
7802
8022
  const ctrl = new AbortController();
7803
8023
  const timer = setTimeout(() => ctrl.abort(), timeoutMs);
@@ -7835,22 +8055,22 @@ async function mothershipLookup(config, ids, timeoutMs = 8e3) {
7835
8055
  }
7836
8056
  }
7837
8057
  async function runSync(store) {
7838
- const lock = path16.join(store.root, "sync.lock");
8058
+ const lock = path17.join(store.root, "sync.lock");
7839
8059
  const deadline = Date.now() + 12e4;
7840
8060
  for (; ; ) {
7841
8061
  try {
7842
- fs20.writeFileSync(lock, String(process.pid), { flag: "wx", mode: 384 });
8062
+ fs22.writeFileSync(lock, String(process.pid), { flag: "wx", mode: 384 });
7843
8063
  break;
7844
8064
  } catch (error) {
7845
8065
  if (error.code !== "EEXIST") throw error;
7846
8066
  try {
7847
- const owner = Number(fs20.readFileSync(lock, "utf8"));
8067
+ const owner = Number(fs22.readFileSync(lock, "utf8"));
7848
8068
  if (Number.isInteger(owner) && owner > 0) {
7849
8069
  try {
7850
8070
  process.kill(owner, 0);
7851
8071
  } catch (error2) {
7852
8072
  if (error2.code === "ESRCH") {
7853
- fs20.unlinkSync(lock);
8073
+ fs22.unlinkSync(lock);
7854
8074
  continue;
7855
8075
  }
7856
8076
  }
@@ -7869,7 +8089,7 @@ async function runSync(store) {
7869
8089
  return await syncStore(store);
7870
8090
  } finally {
7871
8091
  try {
7872
- fs20.unlinkSync(lock);
8092
+ fs22.unlinkSync(lock);
7873
8093
  } catch {
7874
8094
  }
7875
8095
  }
@@ -7897,7 +8117,7 @@ async function syncStore(store) {
7897
8117
  result.errors.push(String(e instanceof Error ? e.message : e));
7898
8118
  }
7899
8119
  }
7900
- const acknowledgementsFile = path16.join(store.root, "synced-traces.json");
8120
+ const acknowledgementsFile = path17.join(store.root, "synced-traces.json");
7901
8121
  const acknowledgements = readJson(acknowledgementsFile, {});
7902
8122
  const fingerprint2 = (trace) => createHash6("sha256").update(JSON.stringify(trace)).digest("hex");
7903
8123
  const traces = store.allTraces().filter((t) => acknowledgements[t.id] !== fingerprint2(t)).slice(0, 50);
@@ -8152,11 +8372,11 @@ __export(demo_exports, {
8152
8372
  runDemo: () => runDemo
8153
8373
  });
8154
8374
  import assert from "node:assert/strict";
8155
- import fs21 from "node:fs";
8375
+ import fs23 from "node:fs";
8156
8376
  import os4 from "node:os";
8157
- import path17 from "node:path";
8377
+ import path18 from "node:path";
8158
8378
  async function runDemo(write) {
8159
- const root = fs21.mkdtempSync(path17.join(os4.tmpdir(), "bunshin-demo-"));
8379
+ const root = fs23.mkdtempSync(path18.join(os4.tmpdir(), "bunshin-demo-"));
8160
8380
  try {
8161
8381
  const store = Store.init(root, { ...APR_ENGINEER, apiUrl: "" });
8162
8382
  const { lessons, constraint, workflow } = aprScenario();
@@ -8173,7 +8393,7 @@ Task: ${APR_TASK}
8173
8393
  write(result.text);
8174
8394
  write("\nPASS: Collections constraint and workflow recalled; C-suite, other-company, and superseded lessons excluded.\n");
8175
8395
  } finally {
8176
- fs21.rmSync(root, { recursive: true, force: true });
8396
+ fs23.rmSync(root, { recursive: true, force: true });
8177
8397
  }
8178
8398
  }
8179
8399
  var init_demo = __esm({
@@ -8186,21 +8406,21 @@ var init_demo = __esm({
8186
8406
  });
8187
8407
 
8188
8408
  // src/v2/gitremote.ts
8189
- import fs22 from "node:fs";
8190
- import path18 from "node:path";
8409
+ import fs24 from "node:fs";
8410
+ import path19 from "node:path";
8191
8411
  import { createHash as createHash7 } from "node:crypto";
8192
8412
  function saveGitCredential(apiUrl, apiKey) {
8193
8413
  const file = gitCredentialFile();
8194
- fs22.mkdirSync(path18.dirname(file), { recursive: true, mode: 448 });
8414
+ fs24.mkdirSync(path19.dirname(file), { recursive: true, mode: 448 });
8195
8415
  let all = {};
8196
8416
  try {
8197
- all = JSON.parse(fs22.readFileSync(file, "utf8"));
8417
+ all = JSON.parse(fs24.readFileSync(file, "utf8"));
8198
8418
  } catch {
8199
8419
  }
8200
8420
  all[apiUrl.replace(/\/$/, "")] = apiKey;
8201
8421
  const tmp = `${file}.${process.pid}.tmp`;
8202
- fs22.writeFileSync(tmp, JSON.stringify(all, null, 2) + "\n", { mode: 384 });
8203
- fs22.renameSync(tmp, file);
8422
+ fs24.writeFileSync(tmp, JSON.stringify(all, null, 2) + "\n", { mode: 384 });
8423
+ fs24.renameSync(tmp, file);
8204
8424
  }
8205
8425
  function resolveGitKey(apiUrl) {
8206
8426
  const base = apiUrl.replace(/\/$/, "");
@@ -8208,20 +8428,20 @@ function resolveGitKey(apiUrl) {
8208
8428
  const origin = urlOrigin(base);
8209
8429
  if (process.env.BUNSHIN_API_KEY && origin && trustedOrigins().has(origin)) return process.env.BUNSHIN_API_KEY;
8210
8430
  try {
8211
- const all = JSON.parse(fs22.readFileSync(gitCredentialFile(), "utf8"));
8431
+ const all = JSON.parse(fs24.readFileSync(gitCredentialFile(), "utf8"));
8212
8432
  if (all[base]) return all[base];
8213
8433
  } catch {
8214
8434
  }
8215
- const dir = path18.join(homeDir(), ".config/bunshin/credentials");
8435
+ const dir = path19.join(homeDir(), ".config/bunshin/credentials");
8216
8436
  let files = [];
8217
8437
  try {
8218
- files = fs22.readdirSync(dir).filter((f) => f.endsWith(".json"));
8438
+ files = fs24.readdirSync(dir).filter((f) => f.endsWith(".json"));
8219
8439
  } catch {
8220
8440
  return void 0;
8221
8441
  }
8222
8442
  for (const f of files.sort()) {
8223
8443
  try {
8224
- const c = JSON.parse(fs22.readFileSync(path18.join(dir, f), "utf8"));
8444
+ const c = JSON.parse(fs24.readFileSync(path19.join(dir, f), "utf8"));
8225
8445
  if (c.apiKey && c.apiUrl?.replace(/\/$/, "") === base) return c.apiKey;
8226
8446
  } catch {
8227
8447
  }
@@ -8242,7 +8462,7 @@ var init_gitremote = __esm({
8242
8462
  ZERO_SHA = "0".repeat(40);
8243
8463
  REMOTE_PATH = "/v1/git/repos/";
8244
8464
  remoteUrl = (apiUrl, repo) => `bunshin::${apiUrl.replace(/\/$/, "")}${REMOTE_PATH}${repo}`;
8245
- gitCredentialFile = () => path18.join(homeDir(), ".config/bunshin/git-credentials.json");
8465
+ gitCredentialFile = () => path19.join(homeDir(), ".config/bunshin/git-credentials.json");
8246
8466
  GitApiError = class extends Error {
8247
8467
  constructor(status, message, body = null) {
8248
8468
  super(message);
@@ -8334,8 +8554,8 @@ __export(gitrepo_exports, {
8334
8554
  githubReferences: () => githubReferences,
8335
8555
  repoFromUrl: () => repoFromUrl
8336
8556
  });
8337
- import fs23 from "node:fs";
8338
- import path19 from "node:path";
8557
+ import fs25 from "node:fs";
8558
+ import path20 from "node:path";
8339
8559
  import { spawnSync as spawnSync2 } from "node:child_process";
8340
8560
  function git2(cwd, args, opts = {}) {
8341
8561
  const r = spawnSync2("git", ["-C", cwd, ...args], { encoding: "utf8", stdio: opts.inherit ? ["ignore", "inherit", "inherit"] : ["ignore", "pipe", "pipe"], maxBuffer: 256 * 1024 * 1024 });
@@ -8344,9 +8564,9 @@ function git2(cwd, args, opts = {}) {
8344
8564
  return res;
8345
8565
  }
8346
8566
  function onPath(name) {
8347
- for (const dir of (process.env.PATH ?? "").split(path19.delimiter)) {
8567
+ for (const dir of (process.env.PATH ?? "").split(path20.delimiter)) {
8348
8568
  try {
8349
- fs23.accessSync(path19.join(dir, name), fs23.constants.X_OK);
8569
+ fs25.accessSync(path20.join(dir, name), fs25.constants.X_OK);
8350
8570
  return true;
8351
8571
  } catch {
8352
8572
  }
@@ -8430,11 +8650,11 @@ function switchOrigin(root, url, out) {
8430
8650
  }
8431
8651
  async function cmdImport(args, cwd, out, err) {
8432
8652
  requireHelper();
8433
- const root = toplevel(path19.resolve(str4(args.flags.path) ?? args._[1] ?? cwd));
8653
+ const root = toplevel(path20.resolve(str4(args.flags.path) ?? args._[1] ?? cwd));
8434
8654
  if (git2(root, ["rev-parse", "--is-shallow-repository"]).out.trim() === "true") throw new Error("this clone is shallow; fetch the full history first: git fetch --unshallow");
8435
8655
  const rs = remotes(root);
8436
8656
  const originUrl = rs.get("origin");
8437
- const name = str4(args.flags.name) ?? (originUrl && !originUrl.startsWith("bunshin::") ? repoFromUrl(originUrl) : null) ?? `${path19.basename(path19.dirname(root))}/${path19.basename(root)}`;
8657
+ const name = str4(args.flags.name) ?? (originUrl && !originUrl.startsWith("bunshin::") ? repoFromUrl(originUrl) : null) ?? `${path20.basename(path20.dirname(root))}/${path20.basename(root)}`;
8438
8658
  const client = await connect(args, cwd);
8439
8659
  const url = remoteUrl(client.apiUrl, name);
8440
8660
  const fromOrigin = !!originUrl && !originUrl.startsWith("bunshin::") && !bool3(args.flags.local);
@@ -8510,7 +8730,7 @@ ${refsToGithub.length} line${refsToGithub.length === 1 ? "" : "s"} in the reposi
8510
8730
  if (refsToGithub.length > 40) out(` \u2026 and ${refsToGithub.length - 40} more (git grep for ${name})
8511
8731
  `);
8512
8732
  }
8513
- if (fs23.existsSync(path19.join(root, ".github"))) out(`
8733
+ if (fs25.existsSync(path20.join(root, ".github"))) out(`
8514
8734
  Note: .github/ (Actions, CODEOWNERS, templates) only works on GitHub; Bunshin does not run it.
8515
8735
  `);
8516
8736
  out(`
@@ -8521,7 +8741,7 @@ New machines clone with: bunshin repo clone ${name}
8521
8741
  }
8522
8742
  async function cmdAttach(args, cwd, out, err) {
8523
8743
  requireHelper();
8524
- const root = toplevel(path19.resolve(str4(args.flags.path) ?? cwd));
8744
+ const root = toplevel(path20.resolve(str4(args.flags.path) ?? cwd));
8525
8745
  const rs = remotes(root);
8526
8746
  const originUrl = rs.get("origin");
8527
8747
  const name = str4(args.flags.name) ?? args._[1] ?? (originUrl ? repoFromUrl(originUrl) : null);
@@ -8569,12 +8789,12 @@ async function cmdClone(args, cwd, out) {
8569
8789
  const dir = args._[2] ?? name.split("/")[1];
8570
8790
  const r = spawnSync2("git", ["clone", remoteUrl(client.apiUrl, name), dir], { cwd, stdio: "inherit" });
8571
8791
  if (r.status !== 0) return r.status ?? 1;
8572
- out(`Cloned ${name} into ${path19.resolve(cwd, dir)}
8792
+ out(`Cloned ${name} into ${path20.resolve(cwd, dir)}
8573
8793
  `);
8574
8794
  return 0;
8575
8795
  }
8576
8796
  async function cmdVerify(args, cwd, out, err) {
8577
- const root = toplevel(path19.resolve(str4(args.flags.path) ?? cwd));
8797
+ const root = toplevel(path20.resolve(str4(args.flags.path) ?? cwd));
8578
8798
  const rs = remotes(root);
8579
8799
  const originUrl = rs.get("origin") ?? "";
8580
8800
  if (!originUrl.startsWith("bunshin::")) throw new Error("origin is not a Bunshin repository (run bunshin repo attach)");
@@ -8733,22 +8953,22 @@ __export(runner_exports, {
8733
8953
  setupText: () => setupText
8734
8954
  });
8735
8955
  import { spawnSync as spawnSync3 } from "node:child_process";
8736
- import fs24 from "node:fs";
8956
+ import fs26 from "node:fs";
8737
8957
  import os5 from "node:os";
8738
- import path20 from "node:path";
8958
+ import path21 from "node:path";
8739
8959
  function runnerHome(home = process.env.BUNSHIN_RUNNER_HOME || os5.homedir()) {
8740
- return path20.join(home, ".bunshin", "runner");
8960
+ return path21.join(home, ".bunshin", "runner");
8741
8961
  }
8742
8962
  function readInstalled(home) {
8743
8963
  try {
8744
- return JSON.parse(fs24.readFileSync(path20.join(runnerHome(home), "config.json"), "utf8"));
8964
+ return JSON.parse(fs26.readFileSync(path21.join(runnerHome(home), "config.json"), "utf8"));
8745
8965
  } catch {
8746
8966
  return null;
8747
8967
  }
8748
8968
  }
8749
8969
  function connection(args, cwd) {
8750
8970
  const saved = readInstalled();
8751
- const store = saved && path20.resolve(cwd).startsWith(runnerHome()) ? null : Store.discover(cwd);
8971
+ const store = saved && path21.resolve(cwd).startsWith(runnerHome()) ? null : Store.discover(cwd);
8752
8972
  const config = store?.config();
8753
8973
  const key = str5(args.flags.key) || process.env.BUNSHIN_RUNNER_KEY || process.env.BUNSHIN_API_KEY || config?.apiKey || saved?.key || "";
8754
8974
  const apiUrl = (str5(args.flags["api-url"]) || process.env.BUNSHIN_API_URL || config?.apiUrl || saved?.apiUrl || DEFAULT_API).replace(/\/$/, "");
@@ -8808,9 +9028,9 @@ async function runOnce(c, args, out, err) {
8808
9028
  const [job] = await lease(c, args);
8809
9029
  if (!job) break;
8810
9030
  const started = Date.now();
8811
- const scratch = fs24.mkdtempSync(path20.join(os5.tmpdir(), "bunshin-job-"));
9031
+ const scratch = fs26.mkdtempSync(path21.join(os5.tmpdir(), "bunshin-job-"));
8812
9032
  const res = await askJson(config, { system: job.system, user: job.prompt, schema: job.schema, enforceSchema: false, timeoutMs: 15 * 6e4, maxTokens: 16e3, cwd: scratch }).finally(
8813
- () => fs24.rmSync(scratch, { recursive: true, force: true })
9033
+ () => fs26.rmSync(scratch, { recursive: true, force: true })
8814
9034
  );
8815
9035
  const model = config.backend === "codex-cli" ? config.codexModel ?? "codex default" : config.backend === "pi-cli" ? config.piModel ?? "pi default" : config.model;
8816
9036
  if (!res.ok) {
@@ -8854,17 +9074,17 @@ Then run \`bunshin runner next\` again until it says there are no jobs.
8854
9074
  `;
8855
9075
  }
8856
9076
  function which(name) {
8857
- for (const d of [...(process.env.PATH || "").split(path20.delimiter), path20.join(os5.homedir(), ".local", "bin")]) {
8858
- if (d && fs24.existsSync(path20.join(d, name))) return path20.join(d, name);
9077
+ for (const d of [...(process.env.PATH || "").split(path21.delimiter), path21.join(os5.homedir(), ".local", "bin")]) {
9078
+ if (d && fs26.existsSync(path21.join(d, name))) return path21.join(d, name);
8859
9079
  }
8860
9080
  return null;
8861
9081
  }
8862
9082
  function scheduledCommand(backend) {
8863
- const node = path20.dirname(process.execPath);
8864
- const npx = fs24.existsSync(path20.join(node, "npx")) ? path20.join(node, "npx") : which("npx") ?? "npx";
9083
+ const node = path21.dirname(process.execPath);
9084
+ const npx = fs26.existsSync(path21.join(node, "npx")) ? path21.join(node, "npx") : which("npx") ?? "npx";
8865
9085
  const bin = BIN_OF[backend] ? which(BIN_OF[backend]) : null;
8866
- const dirs = [.../* @__PURE__ */ new Set([node, ...bin ? [path20.dirname(bin)] : [], path20.join(os5.homedir(), ".local", "bin"), "/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin", "/usr/sbin", "/sbin"])];
8867
- return { argv: [npx, "-y", PKG, "runner", "--once"], path: dirs.join(path20.delimiter) };
9086
+ const dirs = [.../* @__PURE__ */ new Set([node, ...bin ? [path21.dirname(bin)] : [], path21.join(os5.homedir(), ".local", "bin"), "/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin", "/usr/sbin", "/sbin"])];
9087
+ return { argv: [npx, "-y", PKG2, "runner", "--once"], path: dirs.join(path21.delimiter) };
8868
9088
  }
8869
9089
  function launchdPlist(backend, everyMinutes, home = runnerHome()) {
8870
9090
  const cmd = scheduledCommand(backend);
@@ -8879,8 +9099,8 @@ function launchdPlist(backend, everyMinutes, home = runnerHome()) {
8879
9099
  <dict><key>PATH</key><string>${xml(cmd.path)}</string></dict>
8880
9100
  <key>WorkingDirectory</key><string>${xml(home)}</string>
8881
9101
  <key>StartInterval</key><integer>${everyMinutes * 60}</integer>
8882
- <key>StandardOutPath</key><string>${xml(path20.join(home, "runner.log"))}</string>
8883
- <key>StandardErrorPath</key><string>${xml(path20.join(home, "runner.log"))}</string>
9102
+ <key>StandardOutPath</key><string>${xml(path21.join(home, "runner.log"))}</string>
9103
+ <key>StandardErrorPath</key><string>${xml(path21.join(home, "runner.log"))}</string>
8884
9104
  <key>ProcessType</key><string>Background</string>
8885
9105
  <key>LowPriorityIO</key><true/>
8886
9106
  </dict>
@@ -8891,19 +9111,19 @@ function cronLine(backend, everyMinutes, home = runnerHome()) {
8891
9111
  const cmd = scheduledCommand(backend);
8892
9112
  const when = everyMinutes >= 60 ? `17 */${Math.max(1, Math.round(everyMinutes / 60))} * * *` : `*/${everyMinutes} * * * *`;
8893
9113
  const q = (v) => `'${v.replace(/'/g, `'\\''`)}'`;
8894
- return `${when} cd ${q(home)} && PATH=${q(cmd.path)} ${cmd.argv.map(q).join(" ")} >> ${q(path20.join(home, "runner.log"))} 2>&1 ${CRON_MARK}`;
9114
+ return `${when} cd ${q(home)} && PATH=${q(cmd.path)} ${cmd.argv.map(q).join(" ")} >> ${q(path21.join(home, "runner.log"))} 2>&1 ${CRON_MARK}`;
8895
9115
  }
8896
9116
  function launchAgentsDir(opts) {
8897
- return opts.launchAgents ?? path20.join(process.env.BUNSHIN_RUNNER_HOME || os5.homedir(), "Library", "LaunchAgents");
9117
+ return opts.launchAgents ?? path21.join(process.env.BUNSHIN_RUNNER_HOME || os5.homedir(), "Library", "LaunchAgents");
8898
9118
  }
8899
9119
  function installSchedule(backend, everyMinutes, opts = {}) {
8900
9120
  const platform = opts.platform ?? process.platform;
8901
9121
  const run2 = opts.run ?? realRun;
8902
9122
  if (platform === "darwin") {
8903
9123
  const dir = launchAgentsDir(opts);
8904
- const plist = path20.join(dir, `${LAUNCHD_LABEL}.plist`);
8905
- fs24.mkdirSync(dir, { recursive: true });
8906
- fs24.writeFileSync(plist, launchdPlist(backend, everyMinutes));
9124
+ const plist = path21.join(dir, `${LAUNCHD_LABEL}.plist`);
9125
+ fs26.mkdirSync(dir, { recursive: true });
9126
+ fs26.writeFileSync(plist, launchdPlist(backend, everyMinutes));
8907
9127
  const domain = `gui/${process.getuid?.() ?? 501}`;
8908
9128
  run2("launchctl", ["bootout", `${domain}/${LAUNCHD_LABEL}`]);
8909
9129
  const r = run2("launchctl", ["bootstrap", domain, plist]);
@@ -8923,10 +9143,10 @@ function removeSchedule(opts = {}) {
8923
9143
  const platform = opts.platform ?? process.platform;
8924
9144
  const run2 = opts.run ?? realRun;
8925
9145
  if (platform === "darwin") {
8926
- const plist = path20.join(launchAgentsDir(opts), `${LAUNCHD_LABEL}.plist`);
9146
+ const plist = path21.join(launchAgentsDir(opts), `${LAUNCHD_LABEL}.plist`);
8927
9147
  run2("launchctl", ["bootout", `gui/${process.getuid?.() ?? 501}/${LAUNCHD_LABEL}`]);
8928
- if (!fs24.existsSync(plist)) return "no schedule";
8929
- fs24.rmSync(plist, { force: true });
9148
+ if (!fs26.existsSync(plist)) return "no schedule";
9149
+ fs26.rmSync(plist, { force: true });
8930
9150
  return `removed ${plist}`;
8931
9151
  }
8932
9152
  const current = run2("crontab", ["-l"]);
@@ -8953,25 +9173,25 @@ async function install(args, cwd, out, err, opts = {}) {
8953
9173
  }
8954
9174
  const every = Math.max(15, Number(str5(args.flags.every) ?? 60) || 60);
8955
9175
  const home = runnerHome();
8956
- fs24.mkdirSync(home, { recursive: true, mode: 448 });
9176
+ fs26.mkdirSync(home, { recursive: true, mode: 448 });
8957
9177
  const saved = { apiUrl: c.apiUrl, key: c.key, backend, ...config.model && str5(args.flags.model) ? { model: config.model } : {}, name: c.name, every };
8958
- fs24.writeFileSync(path20.join(home, "config.json"), JSON.stringify(saved, null, 2) + "\n", { mode: 384 });
8959
- fs24.chmodSync(path20.join(home, "config.json"), 384);
9178
+ fs26.writeFileSync(path21.join(home, "config.json"), JSON.stringify(saved, null, 2) + "\n", { mode: 384 });
9179
+ fs26.chmodSync(path21.join(home, "config.json"), 384);
8960
9180
  let schedule;
8961
9181
  try {
8962
9182
  schedule = installSchedule(backend, every, opts);
8963
9183
  } catch (e) {
8964
9184
  err(`Saved the runner, but could not schedule it: ${e instanceof Error ? e.message : String(e)}
8965
- Run it yourself: npx -y ${PKG} runner --once
9185
+ Run it yourself: npx -y ${PKG2} runner --once
8966
9186
  `);
8967
9187
  return 1;
8968
9188
  }
8969
9189
  out(`\u2713 Runner "${c.name}" answers ${c.apiUrl}'s model jobs with ${BACKEND_NAME[backend] ?? backend} on this computer.
8970
9190
  `);
8971
- out(schedule.kind === "none" ? ` No scheduler here: run \`npx -y ${PKG} runner --once\` on a schedule yourself.
8972
- ` : ` Every ${every} minutes while this computer is on (${schedule.kind}). Log: ${path20.join(home, "runner.log")}
9191
+ out(schedule.kind === "none" ? ` No scheduler here: run \`npx -y ${PKG2} runner --once\` on a schedule yourself.
9192
+ ` : ` Every ${every} minutes while this computer is on (${schedule.kind}). Log: ${path21.join(home, "runner.log")}
8973
9193
  `);
8974
- out(` Stop: npx -y ${PKG} runner uninstall
9194
+ out(` Stop: npx -y ${PKG2} runner uninstall
8975
9195
  `);
8976
9196
  if (args.flags.run === false) return 0;
8977
9197
  out(`
@@ -8986,9 +9206,9 @@ Answering what waits now\u2026
8986
9206
  function uninstall(out, opts = {}) {
8987
9207
  out(`${removeSchedule(opts)}
8988
9208
  `);
8989
- const file = path20.join(runnerHome(), "config.json");
8990
- if (fs24.existsSync(file)) {
8991
- fs24.rmSync(file, { force: true });
9209
+ const file = path21.join(runnerHome(), "config.json");
9210
+ if (fs26.existsSync(file)) {
9211
+ fs26.rmSync(file, { force: true });
8992
9212
  out(`removed ${file} (revoke the key in the console: Agent keys)
8993
9213
  `);
8994
9214
  }
@@ -8999,7 +9219,7 @@ function installPrompt(key) {
8999
9219
  from work sessions, merging the shared documents) with this computer's own Claude Code / Codex / pi
9000
9220
  sign-in, about once an hour while the computer is on.
9001
9221
 
9002
- 1. Run: npx -y ${PKG} runner install --key ${key}
9222
+ 1. Run: npx -y ${PKG2} runner install --key ${key}
9003
9223
  2. If it says there is no model on this computer, check \`claude --version\` (or codex / pi). If none
9004
9224
  is installed or signed in, tell me the one command to install or sign in, wait for me, then run
9005
9225
  step 1 again.
@@ -9015,11 +9235,11 @@ function setupText(target) {
9015
9235
 
9016
9236
  1. Console \u2192 Runners \u2192 New runner key. The page shows this command with the key in it:
9017
9237
 
9018
- npx -y ${PKG} runner install --key <runner key>
9238
+ npx -y ${PKG2} runner install --key <runner key>
9019
9239
 
9020
9240
  It checks the key, picks the model this computer is signed in to, answers every hour while the
9021
9241
  computer is on (launchd on macOS, cron on Linux) and answers what waits now.
9022
- Stop: npx -y ${PKG} runner uninstall
9242
+ Stop: npx -y ${PKG2} runner uninstall
9023
9243
 
9024
9244
  Or paste this into Claude Code (or Codex, or pi) on that computer, and it does the rest:
9025
9245
 
@@ -9139,7 +9359,7 @@ async function cmdRunner(args, cwd, out, err, opts = {}) {
9139
9359
  const id = args._[2];
9140
9360
  const file = args._[3] ?? "-";
9141
9361
  if (!id) throw new Error("usage: bunshin runner submit <job id> <file|->");
9142
- const text2 = file === "-" ? fs24.readFileSync(0, "utf8") : fs24.readFileSync(file, "utf8");
9362
+ const text2 = file === "-" ? fs26.readFileSync(0, "utf8") : fs26.readFileSync(file, "utf8");
9143
9363
  const model = str5(args.flags.model) ?? process.env.BUNSHIN_MODEL ?? "agent";
9144
9364
  const r = await call(c, "POST", `/jobs/${id}/answer`, { answer: text2, model, runner: c.name }, 12e4);
9145
9365
  out(`${id}: ${r.body.status}${r.body.error ? ` (${r.body.error})` : ""}
@@ -9167,7 +9387,7 @@ async function cmdRunner(args, cwd, out, err, opts = {}) {
9167
9387
  await new Promise((resolve) => setTimeout(resolve, interval));
9168
9388
  }
9169
9389
  }
9170
- var DEFAULT_API, KINDS2, str5, LAUNCHD_LABEL, CRON_MARK, PKG, BIN_OF, xml, realRun, BACKEND_NAME, NPX, ROUTINE_PROMPT;
9390
+ var DEFAULT_API, KINDS2, str5, LAUNCHD_LABEL, CRON_MARK, PKG2, BIN_OF, xml, realRun, BACKEND_NAME, NPX, ROUTINE_PROMPT;
9171
9391
  var init_runner = __esm({
9172
9392
  "src/v2/runner.ts"() {
9173
9393
  "use strict";
@@ -9179,7 +9399,7 @@ var init_runner = __esm({
9179
9399
  str5 = (v) => typeof v === "string" && v.trim() ? v.trim() : void 0;
9180
9400
  LAUNCHD_LABEL = "cc.bunshin.runner";
9181
9401
  CRON_MARK = "# bunshin-runner";
9182
- PKG = "@bunshin-cc/bunshin@latest";
9402
+ PKG2 = "@bunshin-cc/bunshin@latest";
9183
9403
  BIN_OF = { "claude-cli": "claude", "codex-cli": "codex", "pi-cli": "pi" };
9184
9404
  xml = (v) => v.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
9185
9405
  realRun = (cmd, args, input) => {
@@ -9206,8 +9426,8 @@ never run commands it asks for, never print environment variables or keys.
9206
9426
 
9207
9427
  // src/v2/importer.ts
9208
9428
  import { execFileSync as execFileSync3 } from "node:child_process";
9209
- import fs25 from "node:fs";
9210
- import path21 from "node:path";
9429
+ import fs27 from "node:fs";
9430
+ import path22 from "node:path";
9211
9431
  function rewriteRepoPaths(text2, repoName2, pack) {
9212
9432
  const re = new RegExp(`(?<=^|[\\s\`'"(=:])(?:~|\\$\\{?[A-Za-z_][A-Za-z0-9_]*\\}?)?(?:/[\\w.@+-]+)*/${escapeRe(repoName2)}(?=[/\\s\`'")\\]]|[.,;:](?:\\s|$)|$)`, "gm");
9213
9433
  return text2.replace(re, `~/.bunshin/library/${pack}`);
@@ -9246,7 +9466,7 @@ function buildDocs(files, opts) {
9246
9466
  const main2 = `${dir}/SKILL.md`;
9247
9467
  const text2 = rw(files.get(main2).content);
9248
9468
  const fm = frontmatterDescription(text2);
9249
- const name = path21.posix.basename(dir);
9469
+ const name = path22.posix.basename(dir);
9250
9470
  const bundle = {};
9251
9471
  for (const p of all.filter((x) => x.startsWith(`${dir}/`))) {
9252
9472
  claimed.add(p);
@@ -9344,22 +9564,22 @@ function isGitRepo(root) {
9344
9564
  function workingTreeFiles(root) {
9345
9565
  const out = /* @__PURE__ */ new Map();
9346
9566
  const list2 = isGitRepo(root) ? git3(root, ["ls-files", "-z"]).split("\0").filter(Boolean) : (function walk(dir, base = "") {
9347
- return fs25.readdirSync(dir, { withFileTypes: true }).flatMap((e) => {
9567
+ return fs27.readdirSync(dir, { withFileTypes: true }).flatMap((e) => {
9348
9568
  if (e.name === ".git" || e.name === "node_modules") return [];
9349
9569
  const rel = base ? `${base}/${e.name}` : e.name;
9350
- return e.isDirectory() ? walk(path21.join(dir, e.name), rel) : e.isFile() ? [rel] : [];
9570
+ return e.isDirectory() ? walk(path22.join(dir, e.name), rel) : e.isFile() ? [rel] : [];
9351
9571
  });
9352
9572
  })(root);
9353
9573
  for (const rel of list2) {
9354
- const abs = path21.join(root, rel);
9574
+ const abs = path22.join(root, rel);
9355
9575
  let buf;
9356
9576
  try {
9357
- buf = fs25.readFileSync(abs);
9577
+ buf = fs27.readFileSync(abs);
9358
9578
  } catch {
9359
9579
  continue;
9360
9580
  }
9361
9581
  if (!isText(buf)) continue;
9362
- const exec = (fs25.statSync(abs).mode & 73) !== 0;
9582
+ const exec = (fs27.statSync(abs).mode & 73) !== 0;
9363
9583
  out.set(rel, { content: buf.toString("utf8"), ...exec ? { mode: "x" } : {} });
9364
9584
  }
9365
9585
  return out;
@@ -9393,7 +9613,7 @@ function diffDocs(prev, next) {
9393
9613
  return { docs: next.filter((d) => before.get(d.path) !== fingerprint(d)), removed: [...before.keys()].filter((p) => !after.has(p)) };
9394
9614
  }
9395
9615
  function readSkillSystem(root, opts) {
9396
- const repoName2 = path21.basename(path21.resolve(root));
9616
+ const repoName2 = path22.basename(path22.resolve(root));
9397
9617
  const build = (files2) => buildDocs(files2, { pack: opts.pack, repoName: repoName2, ...opts.rewrite === false ? { rewrite: false } : {} });
9398
9618
  const tree = build(workingTreeFiles(root));
9399
9619
  if (!opts.history || !isGitRepo(root)) return { versions: [{ summary: `import ${opts.pack}`, docs: tree, removed: [], complete: true }], docs: tree };
@@ -9470,7 +9690,7 @@ var init_importer = __esm({
9470
9690
  escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9471
9691
  looksGenerated = (text2) => /generated by|do not edit( by hand)?/i.test(text2.slice(0, 600));
9472
9692
  isStopHook = (text2) => /"decision"\s*:\s*"block"|decision.{0,4}block/.test(text2) && /REASON=/.test(text2);
9473
- isUpdateHook = (p, text2) => /auto-?update/i.test(path21.basename(p)) || /git (pull|fetch)/.test(text2) && /SessionStart|session start/i.test(text2);
9693
+ isUpdateHook = (p, text2) => /auto-?update/i.test(path22.basename(p)) || /git (pull|fetch)/.test(text2) && /SessionStart|session start/i.test(text2);
9474
9694
  fingerprint = (d) => JSON.stringify(d);
9475
9695
  REWRITE_SYSTEM = `You adapt a company's skill documents after its skill system moved into Bunshin.
9476
9696
 
@@ -9506,8 +9726,8 @@ __export(skills_exports, {
9506
9726
  cmdSkill: () => cmdSkill,
9507
9727
  parsePin: () => parsePin
9508
9728
  });
9509
- import fs26 from "node:fs";
9510
- import path22 from "node:path";
9729
+ import fs28 from "node:fs";
9730
+ import path23 from "node:path";
9511
9731
  function requireOnline(store, what) {
9512
9732
  if (!canCall(store.config())) throw new Error(`${what} needs the hosted library: run \`bunshin setup\` first`);
9513
9733
  }
@@ -9516,9 +9736,9 @@ async function cmdImport2(store, args, cwd, out, err) {
9516
9736
  err("usage: bunshin import skills <dir> [--pack <name>] [--layer org|team:<unit>] [--owner-team <unit>] [--no-history] [--dry-run] [--propose-rewrites]\n");
9517
9737
  return 1;
9518
9738
  }
9519
- const root = path22.resolve(cwd, args._[2]);
9520
- if (!fs26.existsSync(root)) throw new Error(`${root} does not exist`);
9521
- const pack = str6(args.flags.pack) ?? path22.basename(root);
9739
+ const root = path23.resolve(cwd, args._[2]);
9740
+ if (!fs28.existsSync(root)) throw new Error(`${root} does not exist`);
9741
+ const pack = str6(args.flags.pack) ?? path23.basename(root);
9522
9742
  const history = args.flags.history !== false && !bool4(args.flags["no-history"]);
9523
9743
  const read = readSkillSystem(root, { pack, history, ...str6(args.flags.since) ? { since: str6(args.flags.since) } : {} });
9524
9744
  const kinds = /* @__PURE__ */ new Map();
@@ -9553,7 +9773,7 @@ async function cmdImport2(store, args, cwd, out, err) {
9553
9773
  const propose = args.flags["propose-rewrites"];
9554
9774
  if (propose) {
9555
9775
  const only = typeof propose === "string" && propose !== "true" ? propose.split(",").map((x) => x.trim()).filter(Boolean) : [];
9556
- await proposeRewrites(store, read.docs.filter((d) => !only.length || only.some((o) => d.path.includes(o))), pack, path22.basename(root), out, err);
9776
+ await proposeRewrites(store, read.docs.filter((d) => !only.length || only.some((o) => d.path.includes(o))), pack, path23.basename(root), out, err);
9557
9777
  }
9558
9778
  out("Run `bunshin sync` on each machine (it runs at session start) to install the skills, instructions and files.\n");
9559
9779
  return 0;
@@ -9693,7 +9913,7 @@ ${report.skills} skills, ${report.references} references, ${report.errors.length
9693
9913
  }
9694
9914
  const graph = str6(args.flags.graph) ?? (bool4(args.flags.graph) ? "SKILLS-GRAPH.md" : void 0);
9695
9915
  if (graph) {
9696
- fs26.writeFileSync(path22.resolve(cwd, graph), renderSkillGraph(report));
9916
+ fs28.writeFileSync(path23.resolve(cwd, graph), renderSkillGraph(report));
9697
9917
  out(`graph written to ${graph}
9698
9918
  `);
9699
9919
  }
@@ -9792,9 +10012,9 @@ async function cmdLibrary(store, args, cwd, out, err) {
9792
10012
  `;
9793
10013
  const file = str6(args.flags.write) ?? (bool4(args.flags.write) ? ".claude/skills-version" : void 0);
9794
10014
  if (file) {
9795
- const abs = path22.resolve(cwd, file);
9796
- fs26.mkdirSync(path22.dirname(abs), { recursive: true });
9797
- fs26.writeFileSync(abs, line);
10015
+ const abs = path23.resolve(cwd, file);
10016
+ fs28.mkdirSync(path23.dirname(abs), { recursive: true });
10017
+ fs28.writeFileSync(abs, line);
9798
10018
  out(`${file}: ${line}`);
9799
10019
  } else out(line);
9800
10020
  return 0;
@@ -9804,12 +10024,12 @@ async function cmdLibrary(store, args, cwd, out, err) {
9804
10024
  const to = str6(args.flags.to);
9805
10025
  if (!to) throw new Error("--to <dir> is required");
9806
10026
  const pinFile = str6(args.flags.pin);
9807
- const pin = str6(args.flags.git) ? { git: str6(args.flags.git) } : pinFile ? parsePin(fs26.readFileSync(path22.resolve(cwd, pinFile), "utf8")) : parsePin(args._[2] ?? "");
10027
+ const pin = str6(args.flags.git) ? { git: str6(args.flags.git) } : pinFile ? parsePin(fs28.readFileSync(path23.resolve(cwd, pinFile), "utf8")) : parsePin(args._[2] ?? "");
9808
10028
  const q = pin.git ? `git=${pin.git}` : pin.version ? `version=${pin.version}` : "";
9809
10029
  if (!q) throw new Error("name a version: lib@N, --git <sha>, or --pin <file>");
9810
10030
  const snap = await v2api(config, "GET", `/library/snapshot?${q}`, void 0, { timeoutMs: 12e4 });
9811
- const dir = path22.resolve(cwd, to);
9812
- if (fs26.existsSync(dir) && fs26.readdirSync(dir).length && !bool4(args.flags.force)) throw new Error(`${dir} is not empty (use --force)`);
10031
+ const dir = path23.resolve(cwd, to);
10032
+ if (fs28.existsSync(dir) && fs28.readdirSync(dir).length && !bool4(args.flags.force)) throw new Error(`${dir} is not empty (use --force)`);
9813
10033
  const pack = str6(args.flags.pack);
9814
10034
  const docs = snap.sections.filter((s) => !pack || s.pack === pack);
9815
10035
  const res = materialize(docs, dir);
@@ -9848,27 +10068,27 @@ __export(reflect_exports, {
9848
10068
  buildLesson: () => buildLesson,
9849
10069
  runReflection: () => runReflection
9850
10070
  });
9851
- import fs27 from "node:fs";
9852
- import path23 from "node:path";
10071
+ import fs29 from "node:fs";
10072
+ import path24 from "node:path";
9853
10073
  import { createHash as createHash8 } from "node:crypto";
9854
10074
  async function runReflection(store, opts) {
9855
10075
  const empty = { lessonIds: [], used: [], missed: [], attribution: [] };
9856
- const lock = path23.join(store.root, `reflect-${createHash8("sha256").update(opts.sessionId).digest("hex")}.lock`);
10076
+ const lock = path24.join(store.root, `reflect-${createHash8("sha256").update(opts.sessionId).digest("hex")}.lock`);
9857
10077
  const deadline = Date.now() + 66e4;
9858
10078
  for (; ; ) {
9859
10079
  try {
9860
- fs27.writeFileSync(lock, String(process.pid), { flag: "wx", mode: 384 });
10080
+ fs29.writeFileSync(lock, String(process.pid), { flag: "wx", mode: 384 });
9861
10081
  break;
9862
10082
  } catch (error) {
9863
10083
  if (error.code !== "EEXIST") throw error;
9864
10084
  try {
9865
- const owner = Number(fs27.readFileSync(lock, "utf8"));
10085
+ const owner = Number(fs29.readFileSync(lock, "utf8"));
9866
10086
  if (Number.isInteger(owner) && owner > 0) {
9867
10087
  try {
9868
10088
  process.kill(owner, 0);
9869
10089
  } catch (error2) {
9870
10090
  if (error2.code === "ESRCH") {
9871
- fs27.unlinkSync(lock);
10091
+ fs29.unlinkSync(lock);
9872
10092
  continue;
9873
10093
  }
9874
10094
  }
@@ -9883,7 +10103,7 @@ async function runReflection(store, opts) {
9883
10103
  return await reflectSession(store, opts);
9884
10104
  } finally {
9885
10105
  try {
9886
- fs27.unlinkSync(lock);
10106
+ fs29.unlinkSync(lock);
9887
10107
  } catch {
9888
10108
  }
9889
10109
  }
@@ -10225,11 +10445,11 @@ __export(cli_exports, {
10225
10445
  parseArgs: () => parseArgs,
10226
10446
  version: () => version
10227
10447
  });
10228
- import fs28 from "node:fs";
10229
- import path24 from "node:path";
10448
+ import fs30 from "node:fs";
10449
+ import path25 from "node:path";
10230
10450
  function parseArgs(argv) {
10231
10451
  const out = { _: [], flags: {} };
10232
- const valueless = /* @__PURE__ */ new Set(["json", "quiet", "force", "sync", "global", "help", "version", "dry-run", "offline", "apply", "no-hooks", "remove-hooks", "key-stdin", "debug", "verbose", "robot", "fork", "upload", "index", "all", "legacy", "depth", "local", "skip-github", "propose-rewrites", "clear", "watch", "once", "allow-codex-shell"]);
10452
+ const valueless = /* @__PURE__ */ new Set(["json", "quiet", "force", "sync", "global", "help", "version", "dry-run", "offline", "apply", "no-hooks", "remove-hooks", "key-stdin", "debug", "verbose", "robot", "fork", "upload", "index", "all", "legacy", "depth", "local", "skip-github", "propose-rewrites", "clear", "watch", "once", "allow-codex-shell", "retrace"]);
10233
10453
  for (let i = 0; i < argv.length; i++) {
10234
10454
  const a = argv[i];
10235
10455
  if (a === "--") {
@@ -10268,17 +10488,6 @@ function parseArgs(argv) {
10268
10488
  }
10269
10489
  return out;
10270
10490
  }
10271
- function version() {
10272
- for (const rel of ["../package.json", "../../package.json"]) {
10273
- try {
10274
- const file = new URL(rel, import.meta.url);
10275
- const pkg = JSON.parse(fs28.readFileSync(file, "utf8"));
10276
- if (pkg.name === "@bunshin-cc/bunshin" && pkg.version) return pkg.version;
10277
- } catch {
10278
- }
10279
- }
10280
- return "0.0.0";
10281
- }
10282
10491
  async function main(argv, opts = {}) {
10283
10492
  const out = opts.write ?? ((s) => process.stdout.write(s));
10284
10493
  const err = opts.writeErr ?? ((s) => process.stderr.write(s));
@@ -10337,7 +10546,7 @@ async function main(argv, opts = {}) {
10337
10546
  if (harness !== "codex" && harness !== "claude-code" && harness !== "pi") throw new Error("Choose your agent: bunshin setup --harness codex, claude-code, or pi.");
10338
10547
  const agent = harness === "claude-code" ? "claude" : harness;
10339
10548
  if (!executable2(agent)) throw new Error(`${agent} was not found. Install and sign in to your agent, then run setup again.`);
10340
- const prior = new Store(path24.join(cwd, ".bunshin"));
10549
+ const prior = new Store(path25.join(cwd, ".bunshin"));
10341
10550
  const existing = prior.trusted ? prior.config() : { apiUrl: "", apiKey: void 0 };
10342
10551
  const apiUrl = str7(args.flags["api-url"]);
10343
10552
  const key = bool5(args.flags["key-stdin"]) ? await new Promise((resolve, reject) => {
@@ -10381,7 +10590,7 @@ Key saved privately outside the project.
10381
10590
  if (cmd === "mcp") {
10382
10591
  const { runMcpServer: runMcpServer2 } = await Promise.resolve().then(() => (init_mcp(), mcp_exports));
10383
10592
  const root = str7(args.flags.root);
10384
- await runMcpServer2({ cwd, ...root ? { store: new Store(path24.resolve(root)) } : {}, version: version() });
10593
+ await runMcpServer2({ cwd, ...root ? { store: new Store(path25.resolve(root)) } : {}, version: version() });
10385
10594
  return 0;
10386
10595
  }
10387
10596
  if (cmd === "demo") {
@@ -10436,6 +10645,12 @@ Key saved privately outside the project.
10436
10645
  case "meta-reflect":
10437
10646
  case "meta":
10438
10647
  return await cmdMetaReflect(store, args, cwd, out);
10648
+ case "update-check": {
10649
+ const latest = await (await Promise.resolve().then(() => (init_update(), update_exports))).checkForUpdate();
10650
+ if (!bool5(args.flags.quiet)) out(latest ? `latest ${latest} (this is ${version()})
10651
+ ` : "could not reach npm\n");
10652
+ return 0;
10653
+ }
10439
10654
  case "sync":
10440
10655
  if (bool5(args.flags.legacy)) return await cmdSync2(store, args, out, err);
10441
10656
  return await (await Promise.resolve().then(() => (init_commands(), commands_exports))).cmdSync(store, args, cwd, out, err);
@@ -10468,7 +10683,7 @@ ${HELP2}`);
10468
10683
  function openStore(args, cwd) {
10469
10684
  const root = str7(args.flags.root);
10470
10685
  if (root) {
10471
- const store = new Store(path24.resolve(root));
10686
+ const store = new Store(path25.resolve(root));
10472
10687
  store.ensureLayout();
10473
10688
  return store;
10474
10689
  }
@@ -10484,7 +10699,7 @@ async function cmdInit(args, cwd, out, err) {
10484
10699
  orgId: str7(args.flags["org-id"]),
10485
10700
  userId: str7(args.flags["user-id"]),
10486
10701
  apiUrl: str7(args.flags["api-url"]),
10487
- apiKey: bool5(args.flags["key-stdin"]) ? fs28.readFileSync(0, "utf8").trim() || void 0 : str7(args.flags["api-key"]),
10702
+ apiKey: bool5(args.flags["key-stdin"]) ? fs30.readFileSync(0, "utf8").trim() || void 0 : str7(args.flags["api-key"]),
10488
10703
  scope: bool5(args.flags.global) ? "global" : str7(args.flags.scope),
10489
10704
  harness,
10490
10705
  noHooks: args.flags.hooks === false || bool5(args.flags["no-hooks"]),
@@ -10527,10 +10742,10 @@ function cmdStatus(store, args, out, v2) {
10527
10742
  const conflicts = store.listConflicts().filter((c) => c.status === "open");
10528
10743
  const metrics = store.readMetrics(void 0);
10529
10744
  const last50 = metrics?.windows?.find((w) => w.name === "last50");
10530
- const hookFiles = config.harness === "codex" ? [.../* @__PURE__ */ new Set([path24.join(process.env.CODEX_HOME ?? path24.join(homeDir(), ".codex"), "hooks.json"), codexProjectHooks(path24.dirname(store.root)).file])] : config.harness === "claude-code" ? [path24.join(homeDir(), ".claude", "settings.json"), path24.join(path24.dirname(store.root), ".claude", "settings.local.json")] : config.harness === "pi" ? [path24.join(homeDir(), ".pi/agent/extensions/bunshin.js"), path24.join(path24.dirname(store.root), ".pi/extensions/bunshin.js")] : [];
10745
+ const hookFiles = config.harness === "codex" ? [.../* @__PURE__ */ new Set([path25.join(process.env.CODEX_HOME ?? path25.join(homeDir(), ".codex"), "hooks.json"), codexProjectHooks(path25.dirname(store.root)).file])] : config.harness === "claude-code" ? [path25.join(homeDir(), ".claude", "settings.json"), path25.join(path25.dirname(store.root), ".claude", "settings.local.json")] : config.harness === "pi" ? [path25.join(homeDir(), ".pi/agent/extensions/bunshin.js"), path25.join(path25.dirname(store.root), ".pi/extensions/bunshin.js")] : [];
10531
10746
  const hooks = config.harness === "pi" ? hookFiles.filter((file) => {
10532
10747
  try {
10533
- return fs28.readFileSync(file, "utf8").startsWith("// Managed by Bunshin setup.");
10748
+ return fs30.readFileSync(file, "utf8").startsWith("// Managed by Bunshin setup.");
10534
10749
  } catch {
10535
10750
  return false;
10536
10751
  }
@@ -10548,7 +10763,7 @@ function cmdStatus(store, args, out, v2) {
10548
10763
  mothership: config.apiUrl ? { apiUrl: config.apiUrl, key: config.apiKey ? "set" : "missing", lastSyncAt: config.lastSyncAt ?? null } : null,
10549
10764
  llmBackend: resolveBackend(config) ?? "none (set ANTHROPIC_API_KEY or install the claude/codex CLI)",
10550
10765
  hooks: { [config.harness]: hooks },
10551
- hookFiles: hookFiles.filter((file) => fs28.existsSync(file)),
10766
+ hookFiles: hookFiles.filter((file) => fs30.existsSync(file)),
10552
10767
  recallQuality: last50 ? { precision: last50.precision, recall: last50.recall, f1: last50.f1, traces: last50.traces } : null,
10553
10768
  v2
10554
10769
  };
@@ -10589,7 +10804,7 @@ function cmdStatus(store, args, out, v2) {
10589
10804
  }
10590
10805
  function hookCount(file) {
10591
10806
  try {
10592
- const raw = JSON.parse(fs28.readFileSync(file, "utf8"));
10807
+ const raw = JSON.parse(fs30.readFileSync(file, "utf8"));
10593
10808
  let n = 0;
10594
10809
  for (const groups of Object.values(raw.hooks ?? {})) {
10595
10810
  for (const g of groups ?? []) {
@@ -10709,7 +10924,7 @@ function cmdLessonAdd(store, args, out, err) {
10709
10924
  let content = str7(args.flags.content) ?? "";
10710
10925
  if (!content || content === "-" || args._.includes("-")) {
10711
10926
  try {
10712
- content = fs28.readFileSync(0, "utf8");
10927
+ content = fs30.readFileSync(0, "utf8");
10713
10928
  } catch {
10714
10929
  }
10715
10930
  }
@@ -10775,7 +10990,7 @@ async function cmdReflect(store, args, cwd, out, err) {
10775
10990
  }
10776
10991
  const { runReflection: runReflection2 } = await Promise.resolve().then(() => (init_reflect4(), reflect_exports));
10777
10992
  const result = await runReflection2(store, {
10778
- sessionId: str7(args.flags.session) ?? path24.basename(transcript).replace(/\.jsonl?$/, ""),
10993
+ sessionId: str7(args.flags.session) ?? path25.basename(transcript).replace(/\.jsonl?$/, ""),
10779
10994
  transcriptPath: transcript,
10780
10995
  cwd,
10781
10996
  force: bool5(args.flags.force)
@@ -10890,6 +11105,7 @@ var init_cli = __esm({
10890
11105
  init_store();
10891
11106
  init_util();
10892
11107
  init_project_hooks();
11108
+ init_version();
10893
11109
  str7 = (v) => typeof v === "string" ? v : void 0;
10894
11110
  bool5 = (v) => v === true || v === "true";
10895
11111
  HELP2 = `bunshin \u2014 shadow-clone memory for coding agents and robots
@@ -10920,6 +11136,8 @@ Usage: bunshin <command> [options]
10920
11136
  index [--upload] [--json] parse SPEC.md / TESTING.md / SKILL.md into sections
10921
11137
  sync [--index] [--json] upload episodes + suggestions (incl. in-place skill edits), pull
10922
11138
  the library, install skills / instructions / files, settle trials
11139
+ --retrace [--days 30] re-send sessions uploaded as a clipped digest (CLI before 0.6.2)
11140
+ with their whole trace, while the transcript is still here
10923
11141
 
10924
11142
  import skills <dir> [--pack <name>] [--layer org|team:<unit>] [--owner-team <unit>]
10925
11143
  [--no-history] [--since <sha>] [--dry-run] [--propose-rewrites]
@@ -10968,7 +11186,7 @@ Global: --root <dir> (use this .bunshin store) --version --help
10968
11186
  Env: BUNSHIN_API_KEY, BUNSHIN_HARNESS, BUNSHIN_DISABLE, BUNSHIN_CHILD, BUNSHIN_CLAUDE_BIN,
10969
11187
  CLAUDE_CODE_SESSION_ID (learn / mcp session), ANTHROPIC_API_KEY (non-fork reflection)
10970
11188
  `;
10971
- untrustedNote = (root) => `${root} was not created by you (e.g. it is committed to the repository), so Bunshin ignores it. If it is yours, run \`bunshin setup\` (or \`bunshin init\`) in ${path24.dirname(root)} to trust it.`;
11189
+ untrustedNote = (root) => `${root} was not created by you (e.g. it is committed to the repository), so Bunshin ignores it. If it is yours, run \`bunshin setup\` (or \`bunshin init\`) in ${path25.dirname(root)} to trust it.`;
10972
11190
  }
10973
11191
  });
10974
11192