@bunshin-cc/bunshin 0.6.2 → 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,6 +2849,25 @@ var init_redact2 = __esm({
2849
2849
  }
2850
2850
  });
2851
2851
 
2852
+ // src/version.ts
2853
+ import fs5 from "node:fs";
2854
+ function version() {
2855
+ for (const rel of ["../package.json", "../../package.json"]) {
2856
+ try {
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;
2860
+ } catch {
2861
+ }
2862
+ }
2863
+ return "0.0.0";
2864
+ }
2865
+ var init_version = __esm({
2866
+ "src/version.ts"() {
2867
+ "use strict";
2868
+ }
2869
+ });
2870
+
2852
2871
  // src/v2/api.ts
2853
2872
  function canCall(config) {
2854
2873
  return !!config.apiUrl && !!config.apiKey;
@@ -3426,7 +3445,7 @@ var init_src = __esm({
3426
3445
  });
3427
3446
 
3428
3447
  // src/v2/store.ts
3429
- import fs5 from "node:fs";
3448
+ import fs6 from "node:fs";
3430
3449
  import path5 from "node:path";
3431
3450
  import { randomBytes as randomBytes3 } from "node:crypto";
3432
3451
  function hexId(prefix) {
@@ -3547,7 +3566,7 @@ var init_store2 = __esm({
3547
3566
  const dir = this.p("sessions");
3548
3567
  if (!fileExists(dir)) return [];
3549
3568
  const out = [];
3550
- for (const f of fs5.readdirSync(dir)) {
3569
+ for (const f of fs6.readdirSync(dir)) {
3551
3570
  if (!f.endsWith(".json") || f === "current.json") continue;
3552
3571
  const raw = readJson(path5.join(dir, f), null);
3553
3572
  if (raw?.sessionId) out.push(normalizeSession(raw.sessionId, raw));
@@ -3577,7 +3596,7 @@ var init_store2 = __esm({
3577
3596
  let at = raw.at;
3578
3597
  if (!at) {
3579
3598
  try {
3580
- at = fs5.statSync(this.p("outcome", `${fileSafe(sessionId)}.json`)).mtime.toISOString();
3599
+ at = fs6.statSync(this.p("outcome", `${fileSafe(sessionId)}.json`)).mtime.toISOString();
3581
3600
  } catch {
3582
3601
  at = (/* @__PURE__ */ new Date()).toISOString();
3583
3602
  }
@@ -3606,10 +3625,10 @@ var init_store2 = __esm({
3606
3625
  ensureDir(path5.dirname(file));
3607
3626
  for (let attempt = 0; attempt < 2; attempt++) {
3608
3627
  try {
3609
- fs5.writeFileSync(file, String(process.pid), { flag: "wx" });
3628
+ fs6.writeFileSync(file, String(process.pid), { flag: "wx" });
3610
3629
  return () => {
3611
3630
  try {
3612
- fs5.unlinkSync(file);
3631
+ fs6.unlinkSync(file);
3613
3632
  } catch {
3614
3633
  }
3615
3634
  };
@@ -3617,8 +3636,8 @@ var init_store2 = __esm({
3617
3636
  if (err.code !== "EEXIST") return null;
3618
3637
  let stale = false;
3619
3638
  try {
3620
- const owner = Number(fs5.readFileSync(file, "utf8"));
3621
- const age = Date.now() - fs5.statSync(file).mtimeMs;
3639
+ const owner = Number(fs6.readFileSync(file, "utf8"));
3640
+ const age = Date.now() - fs6.statSync(file).mtimeMs;
3622
3641
  if (age > staleMs) stale = true;
3623
3642
  else if (Number.isInteger(owner) && owner > 0) {
3624
3643
  try {
@@ -3632,7 +3651,7 @@ var init_store2 = __esm({
3632
3651
  }
3633
3652
  if (!stale) return null;
3634
3653
  try {
3635
- fs5.unlinkSync(file);
3654
+ fs6.unlinkSync(file);
3636
3655
  } catch {
3637
3656
  }
3638
3657
  }
@@ -3649,7 +3668,7 @@ var init_store2 = __esm({
3649
3668
  const dir = this.p(name);
3650
3669
  if (!fileExists(dir)) return [];
3651
3670
  const out = [];
3652
- for (const f of fs5.readdirSync(dir)) {
3671
+ for (const f of fs6.readdirSync(dir)) {
3653
3672
  if (!f.endsWith(".json")) continue;
3654
3673
  const v = readJson(path5.join(dir, f), null);
3655
3674
  if (v) out.push(v);
@@ -3665,13 +3684,13 @@ var init_store2 = __esm({
3665
3684
 
3666
3685
  // src/v2/deploy.ts
3667
3686
  import { createHash as createHash2 } from "node:crypto";
3668
- import fs6 from "node:fs";
3687
+ import fs7 from "node:fs";
3669
3688
  import os2 from "node:os";
3670
3689
  import path6 from "node:path";
3671
3690
  function deployWorkspace(env = process.env) {
3672
3691
  if (env.BUNSHIN_DEPLOY_WORKSPACE) return path6.resolve(env.BUNSHIN_DEPLOY_WORKSPACE);
3673
3692
  try {
3674
- const ws = JSON.parse(fs6.readFileSync(settingsFile(env.BUNSHIN_DEPLOY_HOME || os2.homedir()), "utf8")).workspace;
3693
+ const ws = JSON.parse(fs7.readFileSync(settingsFile(env.BUNSHIN_DEPLOY_HOME || os2.homedir()), "utf8")).workspace;
3675
3694
  return typeof ws === "string" && ws ? ws : null;
3676
3695
  } catch {
3677
3696
  return null;
@@ -3694,12 +3713,12 @@ function deployPaths(env = process.env) {
3694
3713
  function setDeployWorkspace(dir, env = process.env) {
3695
3714
  const home = env.BUNSHIN_DEPLOY_HOME || os2.homedir();
3696
3715
  const ws = dir ? path6.resolve(dir.replace(/^~(?=$|\/)/, home)) : null;
3697
- if (ws && !fs6.statSync(ws, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`${ws} is not a folder`);
3716
+ if (ws && !fs7.statSync(ws, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`${ws} is not a folder`);
3698
3717
  const stateDir = path6.join(home, ".bunshin", "deploy");
3699
- const deployed = fs6.existsSync(stateDir) && fs6.readdirSync(stateDir).some((f) => {
3718
+ const deployed = fs7.existsSync(stateDir) && fs7.readdirSync(stateDir).some((f) => {
3700
3719
  if (f === "settings.json" || !f.endsWith(".json")) return false;
3701
3720
  try {
3702
- return Object.keys(JSON.parse(fs6.readFileSync(path6.join(stateDir, f), "utf8")).docs ?? {}).length > 0;
3721
+ return Object.keys(JSON.parse(fs7.readFileSync(path6.join(stateDir, f), "utf8")).docs ?? {}).length > 0;
3703
3722
  } catch {
3704
3723
  return false;
3705
3724
  }
@@ -3707,8 +3726,8 @@ function setDeployWorkspace(dir, env = process.env) {
3707
3726
  if (deployed && ws !== deployWorkspace({ ...env, BUNSHIN_DEPLOY_WORKSPACE: "" })) {
3708
3727
  throw new Error("The library is already deployed to the current location. Set the workspace before the first sync on this machine.");
3709
3728
  }
3710
- fs6.mkdirSync(path6.dirname(settingsFile(home)), { recursive: true });
3711
- fs6.writeFileSync(settingsFile(home), JSON.stringify(ws ? { workspace: ws } : {}, null, 2) + "\n");
3729
+ fs7.mkdirSync(path6.dirname(settingsFile(home)), { recursive: true });
3730
+ fs7.writeFileSync(settingsFile(home), JSON.stringify(ws ? { workspace: ws } : {}, null, 2) + "\n");
3712
3731
  return ws;
3713
3732
  }
3714
3733
  function statePath(p, orgId) {
@@ -3720,7 +3739,7 @@ function readState(p, orgId) {
3720
3739
  }
3721
3740
  function readText(file) {
3722
3741
  try {
3723
- return fs6.readFileSync(file, "utf8");
3742
+ return fs7.readFileSync(file, "utf8");
3724
3743
  } catch {
3725
3744
  return null;
3726
3745
  }
@@ -3728,13 +3747,13 @@ function readText(file) {
3728
3747
  function writeFile(file, content, mode) {
3729
3748
  writeTextAtomic(file, content);
3730
3749
  try {
3731
- fs6.chmodSync(file, mode === "x" ? 493 : 420);
3750
+ fs7.chmodSync(file, mode === "x" ? 493 : 420);
3732
3751
  } catch {
3733
3752
  }
3734
3753
  }
3735
3754
  function removeFile(file) {
3736
3755
  try {
3737
- fs6.unlinkSync(file);
3756
+ fs7.unlinkSync(file);
3738
3757
  } catch {
3739
3758
  }
3740
3759
  }
@@ -3742,8 +3761,8 @@ function pruneDirs(dir, stop) {
3742
3761
  let d = dir;
3743
3762
  while (d.startsWith(stop) && d !== stop) {
3744
3763
  try {
3745
- if (fs6.readdirSync(d).length) return;
3746
- fs6.rmdirSync(d);
3764
+ if (fs7.readdirSync(d).length) return;
3765
+ fs7.rmdirSync(d);
3747
3766
  } catch {
3748
3767
  return;
3749
3768
  }
@@ -3753,7 +3772,7 @@ function pruneDirs(dir, stop) {
3753
3772
  function listFiles(dir, base = dir) {
3754
3773
  let entries;
3755
3774
  try {
3756
- entries = fs6.readdirSync(dir, { withFileTypes: true });
3775
+ entries = fs7.readdirSync(dir, { withFileTypes: true });
3757
3776
  } catch {
3758
3777
  return [];
3759
3778
  }
@@ -3875,7 +3894,7 @@ function removeBlock(file) {
3875
3894
  if (start < 0 || end < start) return false;
3876
3895
  const rest = (text2.slice(0, start).replace(/\s*$/, "") + "\n" + text2.slice(end + BLOCK_END.length).replace(/^\s*/, "")).trim();
3877
3896
  if (rest) writeTextAtomic(file, rest + "\n");
3878
- else fs6.unlinkSync(file);
3897
+ else fs7.unlinkSync(file);
3879
3898
  return true;
3880
3899
  }
3881
3900
  function writeBlock(file, block2) {
@@ -3984,7 +4003,7 @@ function captureNewSkills(c, state, roots, owned, incoming, out, layer, only) {
3984
4003
  for (const root of roots) {
3985
4004
  let names = [];
3986
4005
  try {
3987
- names = fs6.readdirSync(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
4006
+ names = fs7.readdirSync(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
3988
4007
  } catch {
3989
4008
  continue;
3990
4009
  }
@@ -3995,7 +4014,7 @@ function captureNewSkills(c, state, roots, owned, incoming, out, layer, only) {
3995
4014
  if (skillMd === null) continue;
3996
4015
  let born = 0;
3997
4016
  try {
3998
- const st = fs6.statSync(path6.join(dir, "SKILL.md"));
4017
+ const st = fs7.statSync(path6.join(dir, "SKILL.md"));
3999
4018
  born = Math.min(st.birthtimeMs || st.mtimeMs, st.mtimeMs);
4000
4019
  } catch {
4001
4020
  continue;
@@ -4056,11 +4075,11 @@ function lockDeploy(p) {
4056
4075
  const file = path6.join(p.state, ".lock");
4057
4076
  for (let i = 0; i < 2; i++) {
4058
4077
  try {
4059
- fs6.writeFileSync(file, String(process.pid), { flag: "wx" });
4078
+ fs7.writeFileSync(file, String(process.pid), { flag: "wx" });
4060
4079
  return () => removeFile(file);
4061
4080
  } catch {
4062
4081
  try {
4063
- const age = Date.now() - fs6.statSync(file).mtimeMs;
4082
+ const age = Date.now() - fs7.statSync(file).mtimeMs;
4064
4083
  const pid = Number(readText(file));
4065
4084
  let alive = age < 12e4;
4066
4085
  if (alive && pid > 0) {
@@ -4147,7 +4166,7 @@ function deployLocked(v2, config, ctx, bundle, opts, p, harnesses, now) {
4147
4166
  ensureDir(path6.dirname(t.abs));
4148
4167
  writeFile(t.abs, content, t.mode);
4149
4168
  report.written++;
4150
- } else if (t.mode === "x" && !isExecutable(t.abs)) fs6.chmodSync(t.abs, 493);
4169
+ } else if (t.mode === "x" && !isExecutable(t.abs)) fs7.chmodSync(t.abs, 493);
4151
4170
  } catch (err) {
4152
4171
  report.warnings.push(`${t.abs} not written: ${err instanceof Error ? err.message : String(err)}`);
4153
4172
  continue;
@@ -4167,7 +4186,7 @@ function deployLocked(v2, config, ctx, bundle, opts, p, harnesses, now) {
4167
4186
  if (d.kind === "skill") {
4168
4187
  for (const dir of d.dirs ?? []) {
4169
4188
  const manifest = safeJoin(dir, MANIFEST);
4170
- if (foreign.has(dir) || !manifest || !fs6.existsSync(path6.join(dir, "SKILL.md"))) continue;
4189
+ if (foreign.has(dir) || !manifest || !fs7.existsSync(path6.join(dir, "SKILL.md"))) continue;
4171
4190
  writeJsonAtomic(manifest, { sectionId: id, name: d.name, version: d.section.version, libraryVersion: bundle.libraryVersion ?? null, managedBy: "bunshin sync" });
4172
4191
  }
4173
4192
  report.skills++;
@@ -4194,7 +4213,7 @@ function deployLocked(v2, config, ctx, bundle, opts, p, harnesses, now) {
4194
4213
  if (!owned(dir)) continue;
4195
4214
  removeFile(path6.join(dir, MANIFEST));
4196
4215
  pruneDirs(dir, path6.dirname(dir));
4197
- if (fs6.existsSync(dir)) {
4216
+ if (fs7.existsSync(dir)) {
4198
4217
  const left = listFiles(dir);
4199
4218
  if (left.length) report.warnings.push(`${dir} kept: it has files Bunshin did not write`);
4200
4219
  else pruneDirs(dir, path6.dirname(dir));
@@ -4216,14 +4235,14 @@ function deployLocked(v2, config, ctx, bundle, opts, p, harnesses, now) {
4216
4235
  const link = path6.join(p.bin, path6.basename(t.abs));
4217
4236
  ensureDir(p.bin);
4218
4237
  try {
4219
- const cur = fs6.readlinkSync(link);
4238
+ const cur = fs7.readlinkSync(link);
4220
4239
  if (cur !== t.abs) {
4221
- fs6.unlinkSync(link);
4222
- fs6.symlinkSync(t.abs, link);
4240
+ fs7.unlinkSync(link);
4241
+ fs7.symlinkSync(t.abs, link);
4223
4242
  }
4224
4243
  } catch {
4225
4244
  try {
4226
- if (!fs6.existsSync(link)) fs6.symlinkSync(t.abs, link);
4245
+ if (!fs7.existsSync(link)) fs7.symlinkSync(t.abs, link);
4227
4246
  } catch {
4228
4247
  }
4229
4248
  }
@@ -4237,12 +4256,12 @@ function deployLocked(v2, config, ctx, bundle, opts, p, harnesses, now) {
4237
4256
  if (!dir || !insideRoot(dir, path6.join(dir, ".claude", "x"))) continue;
4238
4257
  const link = path6.join(dir, ".claude", "skills");
4239
4258
  try {
4240
- fs6.lstatSync(link);
4259
+ fs7.lstatSync(link);
4241
4260
  } catch {
4242
4261
  ensureDir(path6.dirname(link));
4243
4262
  ensureDir(p.claudeSkills);
4244
4263
  try {
4245
- fs6.symlinkSync(p.claudeSkills, link);
4264
+ fs7.symlinkSync(p.claudeSkills, link);
4246
4265
  } catch {
4247
4266
  }
4248
4267
  }
@@ -4251,8 +4270,8 @@ function deployLocked(v2, config, ctx, bundle, opts, p, harnesses, now) {
4251
4270
  for (const old of state.links) {
4252
4271
  if (links.has(old)) continue;
4253
4272
  try {
4254
- const target = fs6.readlinkSync(old);
4255
- if (!fs6.existsSync(target)) fs6.unlinkSync(old);
4273
+ const target = fs7.readlinkSync(old);
4274
+ if (!fs7.existsSync(target)) fs7.unlinkSync(old);
4256
4275
  } catch {
4257
4276
  }
4258
4277
  }
@@ -4301,7 +4320,7 @@ var init_deploy = __esm({
4301
4320
  MAX_FILE = 2 * 1024 * 1024;
4302
4321
  isExecutable = (file) => {
4303
4322
  try {
4304
- return (fs6.statSync(file).mode & 73) !== 0;
4323
+ return (fs7.statSync(file).mode & 73) !== 0;
4305
4324
  } catch {
4306
4325
  return false;
4307
4326
  }
@@ -4420,11 +4439,11 @@ var init_live = __esm({
4420
4439
  });
4421
4440
 
4422
4441
  // src/transcript.ts
4423
- import fs7 from "node:fs";
4442
+ import fs8 from "node:fs";
4424
4443
  function parseTranscript(file) {
4425
4444
  let raw = "";
4426
4445
  try {
4427
- raw = fs7.readFileSync(file, "utf8");
4446
+ raw = fs8.readFileSync(file, "utf8");
4428
4447
  } catch {
4429
4448
  return { turns: [], userMessages: 0, toolCalls: 0, toolFailures: 0 };
4430
4449
  }
@@ -4641,7 +4660,7 @@ var init_transcript = __esm({
4641
4660
  });
4642
4661
 
4643
4662
  // src/v2/repo.ts
4644
- import fs8 from "node:fs";
4663
+ import fs9 from "node:fs";
4645
4664
  import path7 from "node:path";
4646
4665
  import { execFileSync } from "node:child_process";
4647
4666
  function git(cwd, args, timeout = 1500) {
@@ -4690,20 +4709,20 @@ function repoCommit(root) {
4690
4709
  }
4691
4710
  function listDocs(root) {
4692
4711
  const listed = git(root, ["ls-files", "-co", "--exclude-standard", "-z"], 3e3);
4693
- 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();
4694
4713
  const out = [];
4695
4714
  let visited = 0;
4696
4715
  const walk = (dir, rel, depth) => {
4697
4716
  if (depth > 8 || visited++ > 2e4) return;
4698
4717
  let entries;
4699
4718
  try {
4700
- entries = fs8.readdirSync(dir, { withFileTypes: true });
4719
+ entries = fs9.readdirSync(dir, { withFileTypes: true });
4701
4720
  } catch {
4702
4721
  return;
4703
4722
  }
4704
4723
  for (const e of entries) {
4705
4724
  if (e.isDirectory()) {
4706
- 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);
4707
4726
  } else if (e.isFile()) {
4708
4727
  const r = rel ? `${rel}/${e.name}` : e.name;
4709
4728
  if (isDoc(r) && !deployedSkill(root, r)) out.push(r);
@@ -4728,7 +4747,7 @@ function indexWorkingTree(v2, config, cwd, opts = {}) {
4728
4747
  const abs = path7.join(root, rel);
4729
4748
  let stat;
4730
4749
  try {
4731
- stat = fs8.statSync(abs);
4750
+ stat = fs9.statSync(abs);
4732
4751
  } catch {
4733
4752
  changed = true;
4734
4753
  continue;
@@ -4740,7 +4759,7 @@ function indexWorkingTree(v2, config, cwd, opts = {}) {
4740
4759
  }
4741
4760
  changed = true;
4742
4761
  try {
4743
- const text2 = fs8.readFileSync(abs, "utf8");
4762
+ const text2 = fs9.readFileSync(abs, "utf8");
4744
4763
  files[rel] = { mtimeMs: stat.mtimeMs, size: stat.size, sections: parseRepoDoc({ repo, path: rel, text: text2 }, { orgId: config.orgId, ownerTeam }) };
4745
4764
  } catch {
4746
4765
  }
@@ -4766,14 +4785,14 @@ function relativeToRoot(root, file, cwd = root) {
4766
4785
  let real = abs;
4767
4786
  let realRoot = root;
4768
4787
  try {
4769
- realRoot = fs8.realpathSync(root);
4788
+ realRoot = fs9.realpathSync(root);
4770
4789
  } catch {
4771
4790
  }
4772
4791
  try {
4773
- real = fs8.realpathSync(abs);
4792
+ real = fs9.realpathSync(abs);
4774
4793
  } catch {
4775
4794
  try {
4776
- real = path7.join(fs8.realpathSync(path7.dirname(abs)), path7.basename(abs));
4795
+ real = path7.join(fs9.realpathSync(path7.dirname(abs)), path7.basename(abs));
4777
4796
  } catch {
4778
4797
  }
4779
4798
  }
@@ -4800,7 +4819,7 @@ var init_repo = __esm({
4800
4819
  if (parts.some((p) => SKIP_DIRS.has(p))) return false;
4801
4820
  return DOC_NAMES.has(parts[parts.length - 1].toLowerCase());
4802
4821
  };
4803
- 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"));
4804
4823
  }
4805
4824
  });
4806
4825
 
@@ -4901,7 +4920,7 @@ var init_view = __esm({
4901
4920
  });
4902
4921
 
4903
4922
  // src/v2/apply.ts
4904
- import fs9 from "node:fs";
4923
+ import fs10 from "node:fs";
4905
4924
  function appendedText(op) {
4906
4925
  return op.condition ? `- **${op.condition}:** ${op.text.trim()}` : op.text.trim();
4907
4926
  }
@@ -4956,12 +4975,12 @@ function applyLocally(v2, view, s) {
4956
4975
  if (section.storage.type === "repo") {
4957
4976
  const file = containedPath(view.root, section.storage.path);
4958
4977
  const ours = !view.repo || section.storage.repo === view.repo;
4959
- if (ours && file && fs9.existsSync(file)) {
4960
- const before = fs9.readFileSync(file, "utf8");
4978
+ if (ours && file && fs10.existsSync(file)) {
4979
+ const before = fs10.readFileSync(file, "utf8");
4961
4980
  const res2 = applyOpToMarkdown(before, section.storage.anchor, op);
4962
4981
  if (res2.ok) {
4963
4982
  const changed = res2.text !== before;
4964
- if (changed) fs9.writeFileSync(file, res2.text);
4983
+ if (changed) fs10.writeFileSync(file, res2.text);
4965
4984
  const overlay2 = { ...base, kind: "working-tree", sectionId: section.id, file: section.storage.path, anchor: section.storage.anchor, changed };
4966
4985
  upsertOverlay(v2, overlay2);
4967
4986
  return { applied: true, target: describeTarget(s, view), overlay: overlay2 };
@@ -4982,7 +5001,7 @@ function revertWorkingTree(root, o) {
4982
5001
  if (!file) return { reverted: false, reason: "path outside the repository" };
4983
5002
  let text2;
4984
5003
  try {
4985
- text2 = fs9.readFileSync(file, "utf8");
5004
+ text2 = fs10.readFileSync(file, "utf8");
4986
5005
  } catch {
4987
5006
  return { reverted: false, reason: "file missing" };
4988
5007
  }
@@ -5002,7 +5021,7 @@ ${appendedText(o.op)}`;
5002
5021
  if (once(text2, o.op.text) >= 0 && !text2.includes(o.op.find)) next = text2.replace(o.op.text, o.op.find);
5003
5022
  }
5004
5023
  if (next === null) return { reverted: false, reason: "file changed since the trial edit" };
5005
- fs9.writeFileSync(file, next);
5024
+ fs10.writeFileSync(file, next);
5006
5025
  return { reverted: true };
5007
5026
  }
5008
5027
  function revertParam(v2, o) {
@@ -5075,7 +5094,7 @@ var init_apply = __esm({
5075
5094
  });
5076
5095
 
5077
5096
  // src/v2/signals.ts
5078
- import fs10 from "node:fs";
5097
+ import fs11 from "node:fs";
5079
5098
  function isCorrection(text2) {
5080
5099
  const t = text2.trim().slice(0, 600);
5081
5100
  return !!t && CORRECTION_RE.test(t);
@@ -5097,7 +5116,7 @@ function scanTranscript(file) {
5097
5116
  if (!file) return empty;
5098
5117
  let raw;
5099
5118
  try {
5100
- raw = fs10.readFileSync(file, "utf8");
5119
+ raw = fs11.readFileSync(file, "utf8");
5101
5120
  } catch {
5102
5121
  return empty;
5103
5122
  }
@@ -5181,7 +5200,7 @@ var init_signals = __esm({
5181
5200
  });
5182
5201
 
5183
5202
  // src/v2/ui.ts
5184
- import fs11 from "node:fs";
5203
+ import fs12 from "node:fs";
5185
5204
  import path8 from "node:path";
5186
5205
  function feedPath(v2, sessionId) {
5187
5206
  return v2.p("ui", `${safe(sessionId)}.json`);
@@ -5213,7 +5232,7 @@ function updateFeed(v2, sessionId, patch, now = (/* @__PURE__ */ new Date()).toI
5213
5232
  f.cli = cliArgv();
5214
5233
  f.served = f.served.slice(0, MAX_SERVED);
5215
5234
  f.events = f.events.slice(-MAX_EVENTS);
5216
- fs11.mkdirSync(path8.dirname(feedPath(v2, sessionId)), { recursive: true });
5235
+ fs12.mkdirSync(path8.dirname(feedPath(v2, sessionId)), { recursive: true });
5217
5236
  writeJsonAtomic(feedPath(v2, sessionId), f);
5218
5237
  return f;
5219
5238
  } catch {
@@ -5314,9 +5333,9 @@ function latestFeed(v2) {
5314
5333
  const dir = v2.p("ui");
5315
5334
  let best = null;
5316
5335
  try {
5317
- for (const f2 of fs11.readdirSync(dir)) {
5336
+ for (const f2 of fs12.readdirSync(dir)) {
5318
5337
  if (!f2.endsWith(".json") || f2.endsWith(".mod.json")) continue;
5319
- const m = fs11.statSync(path8.join(dir, f2)).mtimeMs;
5338
+ const m = fs12.statSync(path8.join(dir, f2)).mtimeMs;
5320
5339
  if (!best || m > best.m) best = { f: f2, m };
5321
5340
  }
5322
5341
  } catch {
@@ -5343,11 +5362,12 @@ var init_ui = __esm({
5343
5362
  var sync_exports = {};
5344
5363
  __export(sync_exports, {
5345
5364
  episodePayload: () => episodePayload,
5365
+ markForRetrace: () => markForRetrace,
5346
5366
  suggestionPayload: () => suggestionPayload,
5347
5367
  syncV2: () => syncV2,
5348
5368
  uploadIndex: () => uploadIndex
5349
5369
  });
5350
- import fs12 from "node:fs";
5370
+ import fs13 from "node:fs";
5351
5371
  import path9 from "node:path";
5352
5372
  function episodePayload(e) {
5353
5373
  const { orgId: _o, uploaded: _u, servedReported: _s, reportedServed: _rs, reportedLoaded: _rl, transcriptPath: _tp, ...rest } = e;
@@ -5365,13 +5385,35 @@ async function uploadTrace(e, call2) {
5365
5385
  if (!chunks.length) return;
5366
5386
  for (let attempt = 0; ; attempt++) {
5367
5387
  try {
5368
- for (const [seq, text2] of chunks.entries()) await call2("PUT", `/episodes/${encodeURIComponent(e.id)}/transcript`, { seq, text: text2, done: seq === chunks.length - 1 });
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
+ }
5369
5392
  return;
5370
5393
  } catch (err) {
5371
5394
  if (attempt || !(err instanceof Error && err.message.includes("trace_gap"))) throw err;
5372
5395
  }
5373
5396
  }
5374
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
+ }
5375
5417
  async function syncV2(store, opts = {}) {
5376
5418
  const v2 = new V2Store(store);
5377
5419
  const config = store.config();
@@ -5521,7 +5563,7 @@ async function uploadIndex(store, cwd, deadline = Date.now() + 3e4) {
5521
5563
  const { root, index } = indexWorkingTree(v2, config, cwd, { force: true });
5522
5564
  const docs = (index.paths ?? Object.keys(index.files)).flatMap((rel) => {
5523
5565
  try {
5524
- return [{ path: rel, text: fs12.readFileSync(path9.join(root, rel), "utf8") }];
5566
+ return [{ path: rel, text: fs13.readFileSync(path9.join(root, rel), "utf8") }];
5525
5567
  } catch {
5526
5568
  return [];
5527
5569
  }
@@ -5535,6 +5577,7 @@ var init_sync = __esm({
5535
5577
  "src/v2/sync.ts"() {
5536
5578
  "use strict";
5537
5579
  init_transcript();
5580
+ init_version();
5538
5581
  init_api();
5539
5582
  init_apply();
5540
5583
  init_deploy();
@@ -5549,7 +5592,7 @@ var init_sync = __esm({
5549
5592
  // src/v2/reflect.ts
5550
5593
  import { spawn as spawn3 } from "node:child_process";
5551
5594
  import { createHash as createHash3 } from "node:crypto";
5552
- import fs13 from "node:fs";
5595
+ import fs14 from "node:fs";
5553
5596
  function episodeIdFor(contextId, sessionId) {
5554
5597
  return `e_${createHash3("sha256").update(`${contextId}
5555
5598
  ${sessionId}`).digest("hex").slice(0, 16)}`;
@@ -5584,7 +5627,7 @@ function runClaudeFork(sessionId, prompt, { cwd, model, timeoutMs = FORK_TIMEOUT
5584
5627
  };
5585
5628
  let child;
5586
5629
  try {
5587
- 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"] });
5588
5631
  } catch (e) {
5589
5632
  resolve({ ok: false, error: e instanceof Error ? e.message : String(e) });
5590
5633
  return;
@@ -5629,7 +5672,7 @@ function reflectionIndex(view, ids, task, focus, humanTurns = []) {
5629
5672
  return [...chosen.values()].slice(0, 30).map(({ id, kind, layer, title, when, outline }) => ({ id, kind, layer, title, when, ...outline?.length ? { outline } : {} }));
5630
5673
  }
5631
5674
  function humanTurnsOf(transcriptPath) {
5632
- if (!transcriptPath || !fs13.existsSync(transcriptPath)) return [];
5675
+ if (!transcriptPath || !fs14.existsSync(transcriptPath)) return [];
5633
5676
  try {
5634
5677
  return parseTranscript(transcriptPath).turns.filter((t) => t.role === "user" && t.text.trim()).map((t) => t.text.slice(0, 1e3));
5635
5678
  } catch {
@@ -5637,7 +5680,7 @@ function humanTurnsOf(transcriptPath) {
5637
5680
  }
5638
5681
  }
5639
5682
  function readFacts(transcriptPath) {
5640
- if (!transcriptPath || !fs13.existsSync(transcriptPath)) return null;
5683
+ if (!transcriptPath || !fs14.existsSync(transcriptPath)) return null;
5641
5684
  try {
5642
5685
  return parseTranscript(transcriptPath);
5643
5686
  } catch {
@@ -5699,7 +5742,7 @@ async function runReflectionV2(store, opts) {
5699
5742
  result.method = "fork";
5700
5743
  } else result.error = fork.error;
5701
5744
  }
5702
- if (result.method === "none" && session.transcriptPath && fs13.existsSync(session.transcriptPath)) {
5745
+ if (result.method === "none" && session.transcriptPath && fs14.existsSync(session.transcriptPath)) {
5703
5746
  const transcriptDigest = digest(facts ?? parseTranscript(session.transcriptPath), { from, maxChars: 4e4 });
5704
5747
  const res = await askJson(config, {
5705
5748
  system: "You are Bunshin's reflection pass. Follow the instructions in the user message and reply with one JSON object only.",
@@ -5822,7 +5865,7 @@ __export(uicmd_exports, {
5822
5865
  modSource: () => modSource
5823
5866
  });
5824
5867
  import { spawnSync } from "node:child_process";
5825
- import fs14 from "node:fs";
5868
+ import fs15 from "node:fs";
5826
5869
  import os3 from "node:os";
5827
5870
  import path10 from "node:path";
5828
5871
  import { fileURLToPath } from "node:url";
@@ -5832,26 +5875,26 @@ function modHome(home = process.env.BUNSHIN_DEPLOY_HOME || os3.homedir()) {
5832
5875
  function modSource() {
5833
5876
  const here = path10.dirname(fileURLToPath(import.meta.url));
5834
5877
  for (const dir of [path10.join(here, "claude-code-ui"), path10.join(here, "..", "claude-code-ui"), path10.resolve(here, "../../../../adapters/claude-code-ui")]) {
5835
- 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;
5836
5879
  }
5837
5880
  return null;
5838
5881
  }
5839
5882
  function installedModVersion(home) {
5840
5883
  try {
5841
- 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;
5842
5885
  } catch {
5843
5886
  return null;
5844
5887
  }
5845
5888
  }
5846
5889
  function copyDir(from, to) {
5847
- fs14.mkdirSync(to, { recursive: true });
5848
- 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 })) {
5849
5892
  if (e.name === "types" && from.endsWith(".claude-plugin")) continue;
5850
5893
  if (e.name === "node_modules") continue;
5851
5894
  const a = path10.join(from, e.name);
5852
5895
  const b = path10.join(to, e.name);
5853
5896
  if (e.isDirectory()) copyDir(a, b);
5854
- else fs14.copyFileSync(a, b);
5897
+ else fs15.copyFileSync(a, b);
5855
5898
  }
5856
5899
  }
5857
5900
  function installMod(version2, opts = {}) {
@@ -5859,13 +5902,13 @@ function installMod(version2, opts = {}) {
5859
5902
  if (!src) throw new Error("the bunshin-ui mod is not in this build of the CLI");
5860
5903
  const home = modHome(opts.home);
5861
5904
  const dir = path10.join(home, MOD_PLUGIN);
5862
- fs14.rmSync(dir, { recursive: true, force: true });
5905
+ fs15.rmSync(dir, { recursive: true, force: true });
5863
5906
  copyDir(src, dir);
5864
5907
  const manifest = path10.join(dir, ".claude-plugin", "plugin.json");
5865
- const m = JSON.parse(fs14.readFileSync(manifest, "utf8"));
5866
- fs14.writeFileSync(manifest, JSON.stringify({ ...m, version: version2 }, null, 2) + "\n");
5867
- fs14.mkdirSync(path10.join(home, ".claude-plugin"), { recursive: true });
5868
- 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(
5869
5912
  path10.join(home, ".claude-plugin", "marketplace.json"),
5870
5913
  JSON.stringify({ name: MOD_MARKETPLACE, owner: { name: "Bunshin" }, plugins: [{ name: MOD_PLUGIN, source: `./${MOD_PLUGIN}`, description: m.description }] }, null, 2) + "\n"
5871
5914
  );
@@ -5974,9 +6017,84 @@ var init_uicmd = __esm({
5974
6017
  }
5975
6018
  });
5976
6019
 
5977
- // src/v2/hooks.ts
5978
- 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";
5979
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";
5980
6098
  function inContext(s) {
5981
6099
  return s.inContext ?? [...s.loaded, ...s.pushed];
5982
6100
  }
@@ -6013,7 +6131,7 @@ function withUi(ctx, out) {
6013
6131
  if (harness === "pi" && uiMode(config, "pi") === "widget") {
6014
6132
  const feed = readFeed(v2, id);
6015
6133
  if (!feed) return out;
6016
- 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));
6017
6135
  if (feed.events.length) updateFeed(v2, id, (f) => void (f.linedSeq = feed.events.at(-1).seq));
6018
6136
  return { ...out, bunshinUi: { lines: feedLines(feed), notices: fresh.map((e) => e.text) } };
6019
6137
  }
@@ -6037,10 +6155,10 @@ async function keepModCurrent(ctx, config) {
6037
6155
  const { version: version2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
6038
6156
  const current = version2();
6039
6157
  if (!modSource2() || installedModVersion2() === current) return;
6040
- const marker = path11.join(modHome2(), `.attempted-${current}`);
6041
- if (fs15.existsSync(marker)) return;
6042
- fs15.mkdirSync(modHome2(), { recursive: true });
6043
- 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());
6044
6162
  ctx.detach(["--root", ctx.store.root, "ui", "install", "--quiet"], { cwd: ctx.cwd });
6045
6163
  } catch {
6046
6164
  }
@@ -6081,6 +6199,13 @@ async function onSessionStart(ctx) {
6081
6199
  v2.writeCurrent({ sessionId: id, cwd: ctx.cwd, at: now });
6082
6200
  const config = ctx.store.config();
6083
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
+ }
6084
6209
  if (canCall(config)) {
6085
6210
  try {
6086
6211
  const swept = checkpointLeftovers(ctx.store, id);
@@ -6413,6 +6538,7 @@ function buildEpisode(store, session, transcriptPath, now = /* @__PURE__ */ new
6413
6538
  ...snippets.length ? { snippets } : {},
6414
6539
  ...outcome ? { outcome } : {},
6415
6540
  ...view.bundle?.release?.version ? { harnessVersion: view.bundle.release.version } : {},
6541
+ cliVersion: version(),
6416
6542
  uploaded: false,
6417
6543
  ...previous?.reportedServed ? { reportedServed: previous.reportedServed } : {},
6418
6544
  ...previous?.reportedLoaded ? { reportedLoaded: previous.reportedLoaded } : {},
@@ -6525,6 +6651,7 @@ var init_hooks = __esm({
6525
6651
  "use strict";
6526
6652
  init_v22();
6527
6653
  init_redact2();
6654
+ init_version();
6528
6655
  init_api();
6529
6656
  init_deploy();
6530
6657
  init_live();
@@ -6651,11 +6778,11 @@ var init_hooks2 = __esm({
6651
6778
  });
6652
6779
 
6653
6780
  // src/project-hooks.ts
6654
- import fs16 from "node:fs";
6655
- import path12 from "node:path";
6781
+ import fs18 from "node:fs";
6782
+ import path13 from "node:path";
6656
6783
  import { execFileSync as execFileSync2 } from "node:child_process";
6657
6784
  function codexProjectHooks(cwd) {
6658
- const legacyFile = path12.join(cwd, ".codex", "hooks.json");
6785
+ const legacyFile = path13.join(cwd, ".codex", "hooks.json");
6659
6786
  const git4 = (...args) => execFileSync2("git", ["-C", cwd, "rev-parse", ...args], {
6660
6787
  encoding: "utf8",
6661
6788
  stdio: ["ignore", "pipe", "ignore"],
@@ -6669,14 +6796,14 @@ function codexProjectHooks(cwd) {
6669
6796
  } catch {
6670
6797
  return { file: legacyFile, shared: false, legacyFile };
6671
6798
  }
6672
- const linked = fs16.realpathSync(common) !== fs16.realpathSync(directory);
6799
+ const linked = fs18.realpathSync(common) !== fs18.realpathSync(directory);
6673
6800
  if (linked) {
6674
- if (path12.basename(common) !== ".git" || !fs16.statSync(common).isDirectory()) {
6801
+ if (path13.basename(common) !== ".git" || !fs18.statSync(common).isDirectory()) {
6675
6802
  throw new Error("Codex hooks in a worktree of a bare repository are not supported yet. Use a full checkout for Bunshin setup.");
6676
6803
  }
6677
- root = path12.dirname(common);
6804
+ root = path13.dirname(common);
6678
6805
  }
6679
- 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");
6680
6807
  return { file, shared: linked, legacyFile };
6681
6808
  }
6682
6809
  var init_project_hooks = __esm({
@@ -6706,8 +6833,8 @@ __export(init_exports, {
6706
6833
  removeBunshinHooks: () => removeBunshinHooks,
6707
6834
  runInit: () => runInit
6708
6835
  });
6709
- import fs17 from "node:fs";
6710
- import path13 from "node:path";
6836
+ import fs19 from "node:fs";
6837
+ import path14 from "node:path";
6711
6838
  import { fileURLToPath as fileURLToPath2, pathToFileURL } from "node:url";
6712
6839
  function isBunshinEntry(entry) {
6713
6840
  return entry?._bunshin === true || typeof entry?.command === "string" && /\bbunshin\b.*\bhook\b/.test(entry.command);
@@ -6752,16 +6879,16 @@ function countBunshinHooks(settings) {
6752
6879
  );
6753
6880
  }
6754
6881
  function claudeSettingsPath() {
6755
- return path13.join(homeDir(), ".claude", "settings.json");
6882
+ return path14.join(homeDir(), ".claude", "settings.json");
6756
6883
  }
6757
6884
  function installPiExtension(cwd, remove = false, scope = "repo") {
6758
- 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");
6759
6886
  const marker = "// Managed by Bunshin setup.";
6760
6887
  try {
6761
- const previous = fileExists(file) ? fs17.readFileSync(file, "utf8") : "";
6888
+ const previous = fileExists(file) ? fs19.readFileSync(file, "utf8") : "";
6762
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.");
6763
6890
  if (remove) {
6764
- if (previous) fs17.unlinkSync(file);
6891
+ if (previous) fs19.unlinkSync(file);
6765
6892
  return { file, installed: 0, changed: !!previous };
6766
6893
  }
6767
6894
  const extension = fileURLToPath2(new URL("./pi-extension.js", import.meta.url));
@@ -6770,8 +6897,8 @@ function installPiExtension(cwd, remove = false, scope = "repo") {
6770
6897
  export { default } from ${JSON.stringify(pathToFileURL(extension).href)};
6771
6898
  `;
6772
6899
  if (text2 !== previous) {
6773
- ensureDir(path13.dirname(file));
6774
- fs17.writeFileSync(file, text2);
6900
+ ensureDir(path14.dirname(file));
6901
+ fs19.writeFileSync(file, text2);
6775
6902
  }
6776
6903
  return { file, installed: 1, changed: text2 !== previous };
6777
6904
  } catch (error) {
@@ -6780,14 +6907,14 @@ export { default } from ${JSON.stringify(pathToFileURL(extension).href)};
6780
6907
  }
6781
6908
  function installClaudeHooks({ file = claudeSettingsPath(), bin = "bunshin", remove = false, harness = "claude-code" } = {}) {
6782
6909
  try {
6783
- const before = fileExists(file) ? JSON.parse(fs17.readFileSync(file, "utf8")) : {};
6910
+ const before = fileExists(file) ? JSON.parse(fs19.readFileSync(file, "utf8")) : {};
6784
6911
  const after = remove ? removeBunshinHooks(before) : mergeClaudeSettings(before, { bin, harness });
6785
6912
  const changed = JSON.stringify(before) !== JSON.stringify(after);
6786
6913
  if (changed) {
6787
- ensureDir(path13.dirname(file));
6914
+ ensureDir(path14.dirname(file));
6788
6915
  if (fileExists(file)) {
6789
6916
  try {
6790
- fs17.copyFileSync(file, `${file}.bunshin-backup`);
6917
+ fs19.copyFileSync(file, `${file}.bunshin-backup`);
6791
6918
  } catch {
6792
6919
  }
6793
6920
  }
@@ -6802,23 +6929,23 @@ function mcpLauncher(bin) {
6802
6929
  if (!bin || /^[\w./@+-]+$/.test(bin)) return { command: bin || "bunshin", args: ["mcp"] };
6803
6930
  let script = process.argv[1] ?? "";
6804
6931
  try {
6805
- script = fs17.realpathSync(script);
6932
+ script = fs19.realpathSync(script);
6806
6933
  } catch {
6807
6934
  }
6808
6935
  return { command: process.execPath, args: [script, "mcp"] };
6809
6936
  }
6810
6937
  function installLearnCommand({ file, remove = false }) {
6811
6938
  try {
6812
- const current = fileExists(file) ? fs17.readFileSync(file, "utf8") : null;
6939
+ const current = fileExists(file) ? fs19.readFileSync(file, "utf8") : null;
6813
6940
  const ours = current === null || current === LEARN_COMMAND;
6814
6941
  if (remove) {
6815
- if (current !== null && ours) fs17.unlinkSync(file);
6942
+ if (current !== null && ours) fs19.unlinkSync(file);
6816
6943
  return { file, installed: 0, changed: current !== null && ours };
6817
6944
  }
6818
6945
  if (!ours) return { file, installed: 0, changed: false, error: "a different learn.md is already there; left as it is" };
6819
6946
  if (current === null) {
6820
- fs17.mkdirSync(path13.dirname(file), { recursive: true });
6821
- fs17.writeFileSync(file, LEARN_COMMAND);
6947
+ fs19.mkdirSync(path14.dirname(file), { recursive: true });
6948
+ fs19.writeFileSync(file, LEARN_COMMAND);
6822
6949
  }
6823
6950
  return { file, installed: 1, changed: current === null };
6824
6951
  } catch (err) {
@@ -6827,7 +6954,7 @@ function installLearnCommand({ file, remove = false }) {
6827
6954
  }
6828
6955
  function installMcpConfig({ file, launcher, remove = false }) {
6829
6956
  try {
6830
- const before = fileExists(file) ? JSON.parse(fs17.readFileSync(file, "utf8")) : {};
6957
+ const before = fileExists(file) ? JSON.parse(fs19.readFileSync(file, "utf8")) : {};
6831
6958
  const servers = { ...before.mcpServers ?? {} };
6832
6959
  if (remove) delete servers.bunshin;
6833
6960
  else servers.bunshin = { type: "stdio", command: launcher.command, args: launcher.args, env: { BUNSHIN_HARNESS: "claude-code" } };
@@ -6835,7 +6962,7 @@ function installMcpConfig({ file, launcher, remove = false }) {
6835
6962
  if (!Object.keys(servers).length) delete after.mcpServers;
6836
6963
  const changed = JSON.stringify(before) !== JSON.stringify(after);
6837
6964
  if (changed) {
6838
- if (!Object.keys(after).length && fileExists(file)) fs17.unlinkSync(file);
6965
+ if (!Object.keys(after).length && fileExists(file)) fs19.unlinkSync(file);
6839
6966
  else if (Object.keys(after).length) writeJsonAtomic(file, after);
6840
6967
  }
6841
6968
  return { file, installed: servers.bunshin ? 1 : 0, changed };
@@ -6847,21 +6974,21 @@ function runInit(opts = {}) {
6847
6974
  const cwd = opts.cwd ?? process.cwd();
6848
6975
  const warnings = [];
6849
6976
  const scope = opts.scope ?? "repo";
6850
- const root = scope === "global" ? path13.join(homeDir(), ".bunshin") : path13.join(cwd, ".bunshin");
6851
- 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) {
6852
6979
  const aside = `${root}.untrusted-${Date.now()}`;
6853
- fs17.renameSync(root, aside);
6980
+ fs19.renameSync(root, aside);
6854
6981
  warnings.push(`moved ${root} aside to ${aside}: this user did not create it`);
6855
6982
  }
6856
- const existed = fileExists(path13.join(root, "config.json"));
6983
+ const existed = fileExists(path14.join(root, "config.json"));
6857
6984
  const store = new Store(root);
6858
6985
  store.ensureLayout();
6859
- fs17.chmodSync(root, 448);
6986
+ fs19.chmodSync(root, 448);
6860
6987
  if (scope === "repo") {
6861
- const ignore = path13.join(cwd, ".gitignore");
6862
- const current = fileExists(ignore) ? fs17.readFileSync(ignore, "utf8") : "";
6988
+ const ignore = path14.join(cwd, ".gitignore");
6989
+ const current = fileExists(ignore) ? fs19.readFileSync(ignore, "utf8") : "";
6863
6990
  if (!current.split(/\r?\n/).some((line) => /^\/?\.bunshin\/?$/.test(line.trim()))) {
6864
- fs17.appendFileSync(ignore, `${current && !current.endsWith("\n") ? "\n" : ""}.bunshin/
6991
+ fs19.appendFileSync(ignore, `${current && !current.endsWith("\n") ? "\n" : ""}.bunshin/
6865
6992
  `);
6866
6993
  }
6867
6994
  }
@@ -6890,13 +7017,13 @@ function runInit(opts = {}) {
6890
7017
  }
6891
7018
  if ((opts.removeHooks || !opts.noHooks) && (config.harness === "codex" || config.harness === "claude-code")) {
6892
7019
  const projectHooks = config.harness === "codex" && scope === "repo" && !opts.settingsFile ? codexProjectHooks(cwd) : void 0;
6893
- 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());
6894
7021
  hooks = installClaudeHooks({ file, bin: opts.bin, remove: opts.removeHooks, harness: config.harness });
6895
7022
  if (projectHooks?.shared) {
6896
7023
  hooks.sharedWithWorktrees = true;
6897
- if (!hooks.error && !opts.removeHooks) ensureDir(path13.dirname(projectHooks.legacyFile));
7024
+ if (!hooks.error && !opts.removeHooks) ensureDir(path14.dirname(projectHooks.legacyFile));
6898
7025
  }
6899
- 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)) {
6900
7027
  const old = installClaudeHooks({ file: projectHooks.legacyFile, remove: true, harness: "codex" });
6901
7028
  if (old.error) hooks.error = `Could not remove old Bunshin hooks from ${old.file}: ${old.error}`;
6902
7029
  }
@@ -6905,9 +7032,9 @@ function runInit(opts = {}) {
6905
7032
  let mcp;
6906
7033
  let learnCommand;
6907
7034
  if ((opts.removeHooks || !opts.noHooks) && config.harness === "claude-code" && scope === "repo") {
6908
- 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 });
6909
7036
  if (mcp.error) warnings.push(`could not update ${mcp.file}: ${mcp.error}`);
6910
- 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 });
6911
7038
  if (learnCommand.error) warnings.push(`/learn not installed at ${learnCommand.file}: ${learnCommand.error}`);
6912
7039
  }
6913
7040
  if (!resolveBackend(config)) {
@@ -7049,8 +7176,8 @@ __export(commands_exports, {
7049
7176
  setupRobot: () => setupRobot,
7050
7177
  v2Status: () => v2Status
7051
7178
  });
7052
- import fs18 from "node:fs";
7053
- import path14 from "node:path";
7179
+ import fs20 from "node:fs";
7180
+ import path15 from "node:path";
7054
7181
  import { createHash as createHash4, randomBytes as randomBytes4 } from "node:crypto";
7055
7182
  function cmdReview(store, args, cwd, out, err) {
7056
7183
  const v2 = new V2Store(store);
@@ -7220,11 +7347,11 @@ function cmdSuggest(store, args, cwd, out, err) {
7220
7347
  err('bunshin suggest: usage: bunshin suggest --code <harness-relative path> [--file <local file>] [--rationale "<text>"]\n');
7221
7348
  return 1;
7222
7349
  }
7223
- const harnessDir = path14.resolve(cwd, str3(args.flags["harness-dir"]) ?? ".");
7224
- 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);
7225
7352
  let content;
7226
7353
  try {
7227
- content = fs18.readFileSync(file, "utf8");
7354
+ content = fs20.readFileSync(file, "utf8");
7228
7355
  } catch {
7229
7356
  err(`bunshin suggest: cannot read ${file}
7230
7357
  `);
@@ -7289,6 +7416,12 @@ async function cmdIndex(store, args, cwd, out, err) {
7289
7416
  return 0;
7290
7417
  }
7291
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
+ }
7292
7425
  const report = await syncV2(store, { index: bool2(args.flags.index), cwd });
7293
7426
  if (bool2(args.flags.json)) out(JSON.stringify(report, null, 2) + "\n");
7294
7427
  else if (!bool2(args.flags.quiet)) {
@@ -7339,15 +7472,15 @@ async function cmdQuestions(store, args, out, err) {
7339
7472
  return 0;
7340
7473
  }
7341
7474
  function saveCredential(storeRoot, apiUrl, apiKey, id) {
7342
- const dir = path14.join(homeDir(), ".config/bunshin/credentials");
7343
- fs18.mkdirSync(dir, { recursive: true, mode: 448 });
7344
- fs18.chmodSync(dir, 448);
7345
- 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}
7346
7479
  ${apiUrl}
7347
7480
  ${id}`).digest("hex")}.json`);
7348
7481
  const tmp = `${file}.${randomBytes4(4).toString("hex")}.tmp`;
7349
- fs18.writeFileSync(tmp, JSON.stringify({ apiUrl, apiKey }) + "\n", { mode: 384, flag: "wx" });
7350
- fs18.renameSync(tmp, file);
7482
+ fs20.writeFileSync(tmp, JSON.stringify({ apiUrl, apiKey }) + "\n", { mode: 384, flag: "wx" });
7483
+ fs20.renameSync(tmp, file);
7351
7484
  return file;
7352
7485
  }
7353
7486
  async function setupRobot(opts) {
@@ -7355,10 +7488,10 @@ async function setupRobot(opts) {
7355
7488
  const existing = Store.discover(opts.cwd);
7356
7489
  let store;
7357
7490
  const harness = opts.harness ?? "other";
7358
- 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;
7359
7492
  else {
7360
7493
  runInit({ cwd: opts.cwd, harness, noHooks: harness === "other", ...opts.apiUrl ? { apiUrl: opts.apiUrl } : {} });
7361
- store = new Store(path14.join(opts.cwd, ".bunshin"));
7494
+ store = new Store(path15.join(opts.cwd, ".bunshin"));
7362
7495
  }
7363
7496
  let config = store.config();
7364
7497
  const apiUrl = (opts.apiUrl ?? config.apiUrl ?? "").replace(/\/$/, "");
@@ -7468,16 +7601,16 @@ __export(setup_exports, {
7468
7601
  setupProject: () => setupProject,
7469
7602
  shellQuote: () => shellQuote
7470
7603
  });
7471
- import fs19 from "node:fs";
7472
- import path15 from "node:path";
7604
+ import fs21 from "node:fs";
7605
+ import path16 from "node:path";
7473
7606
  import { createHash as createHash5, randomBytes as randomBytes5 } from "node:crypto";
7474
7607
  function executable(name) {
7475
- 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")];
7476
7609
  for (const directory of directories) {
7477
- const file = path15.resolve(directory, name);
7610
+ const file = path16.resolve(directory, name);
7478
7611
  try {
7479
- fs19.accessSync(file, fs19.constants.X_OK);
7480
- if (fs19.statSync(file).isFile()) return file;
7612
+ fs21.accessSync(file, fs21.constants.X_OK);
7613
+ if (fs21.statSync(file).isFile()) return file;
7481
7614
  } catch {
7482
7615
  }
7483
7616
  }
@@ -7485,8 +7618,8 @@ function executable(name) {
7485
7618
  }
7486
7619
  function hookBinary(harness) {
7487
7620
  const agent = executable(harness === "claude-code" ? "claude" : harness);
7488
- const directories = [.../* @__PURE__ */ new Set([path15.dirname(process.execPath), path15.join(homeDir(), ".local/bin"), ...agent ? [path15.dirname(agent)] : []])];
7489
- 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]))}`;
7490
7623
  }
7491
7624
  function apiAddress(raw) {
7492
7625
  const url = new URL(raw);
@@ -7555,23 +7688,23 @@ async function setupProject(opts) {
7555
7688
  const apiKey = opts.apiKey.trim();
7556
7689
  if (!apiKey || /\s/.test(apiKey)) throw new Error("Paste the complete agent key from your Bunshin workspace.");
7557
7690
  const profile = await connectionProfile(apiUrl, apiKey);
7558
- const root = path15.join(opts.cwd, ".bunshin");
7691
+ const root = path16.join(opts.cwd, ".bunshin");
7559
7692
  const store = new Store(root);
7560
7693
  const previous = store.config();
7561
7694
  if ((previous.orgId !== profile.orgId || previous.userId !== profile.userId) && (store.listLessons().length || store.allTraces().length || store.listRecallLessons().length)) {
7562
7695
  throw new Error("This project has lessons or activity from another profile. Use a fresh project to connect this workspace.");
7563
7696
  }
7564
- const credentialDirectory = path15.join(homeDir(), ".config/bunshin/credentials");
7565
- fs19.mkdirSync(credentialDirectory, { recursive: true, mode: 448 });
7566
- 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);
7567
7700
  const name = createHash5("sha256").update(`${root}
7568
7701
  ${apiUrl}
7569
7702
  ${profile.orgId}
7570
7703
  ${profile.userId}`).digest("hex");
7571
- const credentialFile = path15.join(credentialDirectory, `${name}.json`);
7704
+ const credentialFile = path16.join(credentialDirectory, `${name}.json`);
7572
7705
  const temporary = `${credentialFile}.${randomBytes5(4).toString("hex")}.tmp`;
7573
- fs19.writeFileSync(temporary, JSON.stringify({ apiUrl, apiKey }) + "\n", { mode: 384, flag: "wx" });
7574
- fs19.renameSync(temporary, credentialFile);
7706
+ fs21.writeFileSync(temporary, JSON.stringify({ apiUrl, apiKey }) + "\n", { mode: 384, flag: "wx" });
7707
+ fs21.renameSync(temporary, credentialFile);
7575
7708
  const result = runInit({
7576
7709
  cwd: opts.cwd,
7577
7710
  harness: opts.harness,
@@ -7883,8 +8016,8 @@ __export(sync_exports2, {
7883
8016
  runSync: () => runSync
7884
8017
  });
7885
8018
  import { createHash as createHash6 } from "node:crypto";
7886
- import path16 from "node:path";
7887
- import fs20 from "node:fs";
8019
+ import path17 from "node:path";
8020
+ import fs22 from "node:fs";
7888
8021
  async function api(config, method, route, body, timeoutMs = 1e4) {
7889
8022
  const ctrl = new AbortController();
7890
8023
  const timer = setTimeout(() => ctrl.abort(), timeoutMs);
@@ -7922,22 +8055,22 @@ async function mothershipLookup(config, ids, timeoutMs = 8e3) {
7922
8055
  }
7923
8056
  }
7924
8057
  async function runSync(store) {
7925
- const lock = path16.join(store.root, "sync.lock");
8058
+ const lock = path17.join(store.root, "sync.lock");
7926
8059
  const deadline = Date.now() + 12e4;
7927
8060
  for (; ; ) {
7928
8061
  try {
7929
- fs20.writeFileSync(lock, String(process.pid), { flag: "wx", mode: 384 });
8062
+ fs22.writeFileSync(lock, String(process.pid), { flag: "wx", mode: 384 });
7930
8063
  break;
7931
8064
  } catch (error) {
7932
8065
  if (error.code !== "EEXIST") throw error;
7933
8066
  try {
7934
- const owner = Number(fs20.readFileSync(lock, "utf8"));
8067
+ const owner = Number(fs22.readFileSync(lock, "utf8"));
7935
8068
  if (Number.isInteger(owner) && owner > 0) {
7936
8069
  try {
7937
8070
  process.kill(owner, 0);
7938
8071
  } catch (error2) {
7939
8072
  if (error2.code === "ESRCH") {
7940
- fs20.unlinkSync(lock);
8073
+ fs22.unlinkSync(lock);
7941
8074
  continue;
7942
8075
  }
7943
8076
  }
@@ -7956,7 +8089,7 @@ async function runSync(store) {
7956
8089
  return await syncStore(store);
7957
8090
  } finally {
7958
8091
  try {
7959
- fs20.unlinkSync(lock);
8092
+ fs22.unlinkSync(lock);
7960
8093
  } catch {
7961
8094
  }
7962
8095
  }
@@ -7984,7 +8117,7 @@ async function syncStore(store) {
7984
8117
  result.errors.push(String(e instanceof Error ? e.message : e));
7985
8118
  }
7986
8119
  }
7987
- const acknowledgementsFile = path16.join(store.root, "synced-traces.json");
8120
+ const acknowledgementsFile = path17.join(store.root, "synced-traces.json");
7988
8121
  const acknowledgements = readJson(acknowledgementsFile, {});
7989
8122
  const fingerprint2 = (trace) => createHash6("sha256").update(JSON.stringify(trace)).digest("hex");
7990
8123
  const traces = store.allTraces().filter((t) => acknowledgements[t.id] !== fingerprint2(t)).slice(0, 50);
@@ -8239,11 +8372,11 @@ __export(demo_exports, {
8239
8372
  runDemo: () => runDemo
8240
8373
  });
8241
8374
  import assert from "node:assert/strict";
8242
- import fs21 from "node:fs";
8375
+ import fs23 from "node:fs";
8243
8376
  import os4 from "node:os";
8244
- import path17 from "node:path";
8377
+ import path18 from "node:path";
8245
8378
  async function runDemo(write) {
8246
- const root = fs21.mkdtempSync(path17.join(os4.tmpdir(), "bunshin-demo-"));
8379
+ const root = fs23.mkdtempSync(path18.join(os4.tmpdir(), "bunshin-demo-"));
8247
8380
  try {
8248
8381
  const store = Store.init(root, { ...APR_ENGINEER, apiUrl: "" });
8249
8382
  const { lessons, constraint, workflow } = aprScenario();
@@ -8260,7 +8393,7 @@ Task: ${APR_TASK}
8260
8393
  write(result.text);
8261
8394
  write("\nPASS: Collections constraint and workflow recalled; C-suite, other-company, and superseded lessons excluded.\n");
8262
8395
  } finally {
8263
- fs21.rmSync(root, { recursive: true, force: true });
8396
+ fs23.rmSync(root, { recursive: true, force: true });
8264
8397
  }
8265
8398
  }
8266
8399
  var init_demo = __esm({
@@ -8273,21 +8406,21 @@ var init_demo = __esm({
8273
8406
  });
8274
8407
 
8275
8408
  // src/v2/gitremote.ts
8276
- import fs22 from "node:fs";
8277
- import path18 from "node:path";
8409
+ import fs24 from "node:fs";
8410
+ import path19 from "node:path";
8278
8411
  import { createHash as createHash7 } from "node:crypto";
8279
8412
  function saveGitCredential(apiUrl, apiKey) {
8280
8413
  const file = gitCredentialFile();
8281
- fs22.mkdirSync(path18.dirname(file), { recursive: true, mode: 448 });
8414
+ fs24.mkdirSync(path19.dirname(file), { recursive: true, mode: 448 });
8282
8415
  let all = {};
8283
8416
  try {
8284
- all = JSON.parse(fs22.readFileSync(file, "utf8"));
8417
+ all = JSON.parse(fs24.readFileSync(file, "utf8"));
8285
8418
  } catch {
8286
8419
  }
8287
8420
  all[apiUrl.replace(/\/$/, "")] = apiKey;
8288
8421
  const tmp = `${file}.${process.pid}.tmp`;
8289
- fs22.writeFileSync(tmp, JSON.stringify(all, null, 2) + "\n", { mode: 384 });
8290
- fs22.renameSync(tmp, file);
8422
+ fs24.writeFileSync(tmp, JSON.stringify(all, null, 2) + "\n", { mode: 384 });
8423
+ fs24.renameSync(tmp, file);
8291
8424
  }
8292
8425
  function resolveGitKey(apiUrl) {
8293
8426
  const base = apiUrl.replace(/\/$/, "");
@@ -8295,20 +8428,20 @@ function resolveGitKey(apiUrl) {
8295
8428
  const origin = urlOrigin(base);
8296
8429
  if (process.env.BUNSHIN_API_KEY && origin && trustedOrigins().has(origin)) return process.env.BUNSHIN_API_KEY;
8297
8430
  try {
8298
- const all = JSON.parse(fs22.readFileSync(gitCredentialFile(), "utf8"));
8431
+ const all = JSON.parse(fs24.readFileSync(gitCredentialFile(), "utf8"));
8299
8432
  if (all[base]) return all[base];
8300
8433
  } catch {
8301
8434
  }
8302
- const dir = path18.join(homeDir(), ".config/bunshin/credentials");
8435
+ const dir = path19.join(homeDir(), ".config/bunshin/credentials");
8303
8436
  let files = [];
8304
8437
  try {
8305
- files = fs22.readdirSync(dir).filter((f) => f.endsWith(".json"));
8438
+ files = fs24.readdirSync(dir).filter((f) => f.endsWith(".json"));
8306
8439
  } catch {
8307
8440
  return void 0;
8308
8441
  }
8309
8442
  for (const f of files.sort()) {
8310
8443
  try {
8311
- const c = JSON.parse(fs22.readFileSync(path18.join(dir, f), "utf8"));
8444
+ const c = JSON.parse(fs24.readFileSync(path19.join(dir, f), "utf8"));
8312
8445
  if (c.apiKey && c.apiUrl?.replace(/\/$/, "") === base) return c.apiKey;
8313
8446
  } catch {
8314
8447
  }
@@ -8329,7 +8462,7 @@ var init_gitremote = __esm({
8329
8462
  ZERO_SHA = "0".repeat(40);
8330
8463
  REMOTE_PATH = "/v1/git/repos/";
8331
8464
  remoteUrl = (apiUrl, repo) => `bunshin::${apiUrl.replace(/\/$/, "")}${REMOTE_PATH}${repo}`;
8332
- gitCredentialFile = () => path18.join(homeDir(), ".config/bunshin/git-credentials.json");
8465
+ gitCredentialFile = () => path19.join(homeDir(), ".config/bunshin/git-credentials.json");
8333
8466
  GitApiError = class extends Error {
8334
8467
  constructor(status, message, body = null) {
8335
8468
  super(message);
@@ -8421,8 +8554,8 @@ __export(gitrepo_exports, {
8421
8554
  githubReferences: () => githubReferences,
8422
8555
  repoFromUrl: () => repoFromUrl
8423
8556
  });
8424
- import fs23 from "node:fs";
8425
- import path19 from "node:path";
8557
+ import fs25 from "node:fs";
8558
+ import path20 from "node:path";
8426
8559
  import { spawnSync as spawnSync2 } from "node:child_process";
8427
8560
  function git2(cwd, args, opts = {}) {
8428
8561
  const r = spawnSync2("git", ["-C", cwd, ...args], { encoding: "utf8", stdio: opts.inherit ? ["ignore", "inherit", "inherit"] : ["ignore", "pipe", "pipe"], maxBuffer: 256 * 1024 * 1024 });
@@ -8431,9 +8564,9 @@ function git2(cwd, args, opts = {}) {
8431
8564
  return res;
8432
8565
  }
8433
8566
  function onPath(name) {
8434
- for (const dir of (process.env.PATH ?? "").split(path19.delimiter)) {
8567
+ for (const dir of (process.env.PATH ?? "").split(path20.delimiter)) {
8435
8568
  try {
8436
- fs23.accessSync(path19.join(dir, name), fs23.constants.X_OK);
8569
+ fs25.accessSync(path20.join(dir, name), fs25.constants.X_OK);
8437
8570
  return true;
8438
8571
  } catch {
8439
8572
  }
@@ -8517,11 +8650,11 @@ function switchOrigin(root, url, out) {
8517
8650
  }
8518
8651
  async function cmdImport(args, cwd, out, err) {
8519
8652
  requireHelper();
8520
- 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));
8521
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");
8522
8655
  const rs = remotes(root);
8523
8656
  const originUrl = rs.get("origin");
8524
- 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)}`;
8525
8658
  const client = await connect(args, cwd);
8526
8659
  const url = remoteUrl(client.apiUrl, name);
8527
8660
  const fromOrigin = !!originUrl && !originUrl.startsWith("bunshin::") && !bool3(args.flags.local);
@@ -8597,7 +8730,7 @@ ${refsToGithub.length} line${refsToGithub.length === 1 ? "" : "s"} in the reposi
8597
8730
  if (refsToGithub.length > 40) out(` \u2026 and ${refsToGithub.length - 40} more (git grep for ${name})
8598
8731
  `);
8599
8732
  }
8600
- if (fs23.existsSync(path19.join(root, ".github"))) out(`
8733
+ if (fs25.existsSync(path20.join(root, ".github"))) out(`
8601
8734
  Note: .github/ (Actions, CODEOWNERS, templates) only works on GitHub; Bunshin does not run it.
8602
8735
  `);
8603
8736
  out(`
@@ -8608,7 +8741,7 @@ New machines clone with: bunshin repo clone ${name}
8608
8741
  }
8609
8742
  async function cmdAttach(args, cwd, out, err) {
8610
8743
  requireHelper();
8611
- const root = toplevel(path19.resolve(str4(args.flags.path) ?? cwd));
8744
+ const root = toplevel(path20.resolve(str4(args.flags.path) ?? cwd));
8612
8745
  const rs = remotes(root);
8613
8746
  const originUrl = rs.get("origin");
8614
8747
  const name = str4(args.flags.name) ?? args._[1] ?? (originUrl ? repoFromUrl(originUrl) : null);
@@ -8656,12 +8789,12 @@ async function cmdClone(args, cwd, out) {
8656
8789
  const dir = args._[2] ?? name.split("/")[1];
8657
8790
  const r = spawnSync2("git", ["clone", remoteUrl(client.apiUrl, name), dir], { cwd, stdio: "inherit" });
8658
8791
  if (r.status !== 0) return r.status ?? 1;
8659
- out(`Cloned ${name} into ${path19.resolve(cwd, dir)}
8792
+ out(`Cloned ${name} into ${path20.resolve(cwd, dir)}
8660
8793
  `);
8661
8794
  return 0;
8662
8795
  }
8663
8796
  async function cmdVerify(args, cwd, out, err) {
8664
- const root = toplevel(path19.resolve(str4(args.flags.path) ?? cwd));
8797
+ const root = toplevel(path20.resolve(str4(args.flags.path) ?? cwd));
8665
8798
  const rs = remotes(root);
8666
8799
  const originUrl = rs.get("origin") ?? "";
8667
8800
  if (!originUrl.startsWith("bunshin::")) throw new Error("origin is not a Bunshin repository (run bunshin repo attach)");
@@ -8820,22 +8953,22 @@ __export(runner_exports, {
8820
8953
  setupText: () => setupText
8821
8954
  });
8822
8955
  import { spawnSync as spawnSync3 } from "node:child_process";
8823
- import fs24 from "node:fs";
8956
+ import fs26 from "node:fs";
8824
8957
  import os5 from "node:os";
8825
- import path20 from "node:path";
8958
+ import path21 from "node:path";
8826
8959
  function runnerHome(home = process.env.BUNSHIN_RUNNER_HOME || os5.homedir()) {
8827
- return path20.join(home, ".bunshin", "runner");
8960
+ return path21.join(home, ".bunshin", "runner");
8828
8961
  }
8829
8962
  function readInstalled(home) {
8830
8963
  try {
8831
- 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"));
8832
8965
  } catch {
8833
8966
  return null;
8834
8967
  }
8835
8968
  }
8836
8969
  function connection(args, cwd) {
8837
8970
  const saved = readInstalled();
8838
- 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);
8839
8972
  const config = store?.config();
8840
8973
  const key = str5(args.flags.key) || process.env.BUNSHIN_RUNNER_KEY || process.env.BUNSHIN_API_KEY || config?.apiKey || saved?.key || "";
8841
8974
  const apiUrl = (str5(args.flags["api-url"]) || process.env.BUNSHIN_API_URL || config?.apiUrl || saved?.apiUrl || DEFAULT_API).replace(/\/$/, "");
@@ -8895,9 +9028,9 @@ async function runOnce(c, args, out, err) {
8895
9028
  const [job] = await lease(c, args);
8896
9029
  if (!job) break;
8897
9030
  const started = Date.now();
8898
- const scratch = fs24.mkdtempSync(path20.join(os5.tmpdir(), "bunshin-job-"));
9031
+ const scratch = fs26.mkdtempSync(path21.join(os5.tmpdir(), "bunshin-job-"));
8899
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(
8900
- () => fs24.rmSync(scratch, { recursive: true, force: true })
9033
+ () => fs26.rmSync(scratch, { recursive: true, force: true })
8901
9034
  );
8902
9035
  const model = config.backend === "codex-cli" ? config.codexModel ?? "codex default" : config.backend === "pi-cli" ? config.piModel ?? "pi default" : config.model;
8903
9036
  if (!res.ok) {
@@ -8941,17 +9074,17 @@ Then run \`bunshin runner next\` again until it says there are no jobs.
8941
9074
  `;
8942
9075
  }
8943
9076
  function which(name) {
8944
- for (const d of [...(process.env.PATH || "").split(path20.delimiter), path20.join(os5.homedir(), ".local", "bin")]) {
8945
- 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);
8946
9079
  }
8947
9080
  return null;
8948
9081
  }
8949
9082
  function scheduledCommand(backend) {
8950
- const node = path20.dirname(process.execPath);
8951
- 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";
8952
9085
  const bin = BIN_OF[backend] ? which(BIN_OF[backend]) : null;
8953
- 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"])];
8954
- 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) };
8955
9088
  }
8956
9089
  function launchdPlist(backend, everyMinutes, home = runnerHome()) {
8957
9090
  const cmd = scheduledCommand(backend);
@@ -8966,8 +9099,8 @@ function launchdPlist(backend, everyMinutes, home = runnerHome()) {
8966
9099
  <dict><key>PATH</key><string>${xml(cmd.path)}</string></dict>
8967
9100
  <key>WorkingDirectory</key><string>${xml(home)}</string>
8968
9101
  <key>StartInterval</key><integer>${everyMinutes * 60}</integer>
8969
- <key>StandardOutPath</key><string>${xml(path20.join(home, "runner.log"))}</string>
8970
- <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>
8971
9104
  <key>ProcessType</key><string>Background</string>
8972
9105
  <key>LowPriorityIO</key><true/>
8973
9106
  </dict>
@@ -8978,19 +9111,19 @@ function cronLine(backend, everyMinutes, home = runnerHome()) {
8978
9111
  const cmd = scheduledCommand(backend);
8979
9112
  const when = everyMinutes >= 60 ? `17 */${Math.max(1, Math.round(everyMinutes / 60))} * * *` : `*/${everyMinutes} * * * *`;
8980
9113
  const q = (v) => `'${v.replace(/'/g, `'\\''`)}'`;
8981
- 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}`;
8982
9115
  }
8983
9116
  function launchAgentsDir(opts) {
8984
- 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");
8985
9118
  }
8986
9119
  function installSchedule(backend, everyMinutes, opts = {}) {
8987
9120
  const platform = opts.platform ?? process.platform;
8988
9121
  const run2 = opts.run ?? realRun;
8989
9122
  if (platform === "darwin") {
8990
9123
  const dir = launchAgentsDir(opts);
8991
- const plist = path20.join(dir, `${LAUNCHD_LABEL}.plist`);
8992
- fs24.mkdirSync(dir, { recursive: true });
8993
- 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));
8994
9127
  const domain = `gui/${process.getuid?.() ?? 501}`;
8995
9128
  run2("launchctl", ["bootout", `${domain}/${LAUNCHD_LABEL}`]);
8996
9129
  const r = run2("launchctl", ["bootstrap", domain, plist]);
@@ -9010,10 +9143,10 @@ function removeSchedule(opts = {}) {
9010
9143
  const platform = opts.platform ?? process.platform;
9011
9144
  const run2 = opts.run ?? realRun;
9012
9145
  if (platform === "darwin") {
9013
- const plist = path20.join(launchAgentsDir(opts), `${LAUNCHD_LABEL}.plist`);
9146
+ const plist = path21.join(launchAgentsDir(opts), `${LAUNCHD_LABEL}.plist`);
9014
9147
  run2("launchctl", ["bootout", `gui/${process.getuid?.() ?? 501}/${LAUNCHD_LABEL}`]);
9015
- if (!fs24.existsSync(plist)) return "no schedule";
9016
- fs24.rmSync(plist, { force: true });
9148
+ if (!fs26.existsSync(plist)) return "no schedule";
9149
+ fs26.rmSync(plist, { force: true });
9017
9150
  return `removed ${plist}`;
9018
9151
  }
9019
9152
  const current = run2("crontab", ["-l"]);
@@ -9040,25 +9173,25 @@ async function install(args, cwd, out, err, opts = {}) {
9040
9173
  }
9041
9174
  const every = Math.max(15, Number(str5(args.flags.every) ?? 60) || 60);
9042
9175
  const home = runnerHome();
9043
- fs24.mkdirSync(home, { recursive: true, mode: 448 });
9176
+ fs26.mkdirSync(home, { recursive: true, mode: 448 });
9044
9177
  const saved = { apiUrl: c.apiUrl, key: c.key, backend, ...config.model && str5(args.flags.model) ? { model: config.model } : {}, name: c.name, every };
9045
- fs24.writeFileSync(path20.join(home, "config.json"), JSON.stringify(saved, null, 2) + "\n", { mode: 384 });
9046
- 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);
9047
9180
  let schedule;
9048
9181
  try {
9049
9182
  schedule = installSchedule(backend, every, opts);
9050
9183
  } catch (e) {
9051
9184
  err(`Saved the runner, but could not schedule it: ${e instanceof Error ? e.message : String(e)}
9052
- Run it yourself: npx -y ${PKG} runner --once
9185
+ Run it yourself: npx -y ${PKG2} runner --once
9053
9186
  `);
9054
9187
  return 1;
9055
9188
  }
9056
9189
  out(`\u2713 Runner "${c.name}" answers ${c.apiUrl}'s model jobs with ${BACKEND_NAME[backend] ?? backend} on this computer.
9057
9190
  `);
9058
- out(schedule.kind === "none" ? ` No scheduler here: run \`npx -y ${PKG} runner --once\` on a schedule yourself.
9059
- ` : ` 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")}
9060
9193
  `);
9061
- out(` Stop: npx -y ${PKG} runner uninstall
9194
+ out(` Stop: npx -y ${PKG2} runner uninstall
9062
9195
  `);
9063
9196
  if (args.flags.run === false) return 0;
9064
9197
  out(`
@@ -9073,9 +9206,9 @@ Answering what waits now\u2026
9073
9206
  function uninstall(out, opts = {}) {
9074
9207
  out(`${removeSchedule(opts)}
9075
9208
  `);
9076
- const file = path20.join(runnerHome(), "config.json");
9077
- if (fs24.existsSync(file)) {
9078
- fs24.rmSync(file, { force: true });
9209
+ const file = path21.join(runnerHome(), "config.json");
9210
+ if (fs26.existsSync(file)) {
9211
+ fs26.rmSync(file, { force: true });
9079
9212
  out(`removed ${file} (revoke the key in the console: Agent keys)
9080
9213
  `);
9081
9214
  }
@@ -9086,7 +9219,7 @@ function installPrompt(key) {
9086
9219
  from work sessions, merging the shared documents) with this computer's own Claude Code / Codex / pi
9087
9220
  sign-in, about once an hour while the computer is on.
9088
9221
 
9089
- 1. Run: npx -y ${PKG} runner install --key ${key}
9222
+ 1. Run: npx -y ${PKG2} runner install --key ${key}
9090
9223
  2. If it says there is no model on this computer, check \`claude --version\` (or codex / pi). If none
9091
9224
  is installed or signed in, tell me the one command to install or sign in, wait for me, then run
9092
9225
  step 1 again.
@@ -9102,11 +9235,11 @@ function setupText(target) {
9102
9235
 
9103
9236
  1. Console \u2192 Runners \u2192 New runner key. The page shows this command with the key in it:
9104
9237
 
9105
- npx -y ${PKG} runner install --key <runner key>
9238
+ npx -y ${PKG2} runner install --key <runner key>
9106
9239
 
9107
9240
  It checks the key, picks the model this computer is signed in to, answers every hour while the
9108
9241
  computer is on (launchd on macOS, cron on Linux) and answers what waits now.
9109
- Stop: npx -y ${PKG} runner uninstall
9242
+ Stop: npx -y ${PKG2} runner uninstall
9110
9243
 
9111
9244
  Or paste this into Claude Code (or Codex, or pi) on that computer, and it does the rest:
9112
9245
 
@@ -9226,7 +9359,7 @@ async function cmdRunner(args, cwd, out, err, opts = {}) {
9226
9359
  const id = args._[2];
9227
9360
  const file = args._[3] ?? "-";
9228
9361
  if (!id) throw new Error("usage: bunshin runner submit <job id> <file|->");
9229
- const text2 = file === "-" ? fs24.readFileSync(0, "utf8") : fs24.readFileSync(file, "utf8");
9362
+ const text2 = file === "-" ? fs26.readFileSync(0, "utf8") : fs26.readFileSync(file, "utf8");
9230
9363
  const model = str5(args.flags.model) ?? process.env.BUNSHIN_MODEL ?? "agent";
9231
9364
  const r = await call(c, "POST", `/jobs/${id}/answer`, { answer: text2, model, runner: c.name }, 12e4);
9232
9365
  out(`${id}: ${r.body.status}${r.body.error ? ` (${r.body.error})` : ""}
@@ -9254,7 +9387,7 @@ async function cmdRunner(args, cwd, out, err, opts = {}) {
9254
9387
  await new Promise((resolve) => setTimeout(resolve, interval));
9255
9388
  }
9256
9389
  }
9257
- 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;
9258
9391
  var init_runner = __esm({
9259
9392
  "src/v2/runner.ts"() {
9260
9393
  "use strict";
@@ -9266,7 +9399,7 @@ var init_runner = __esm({
9266
9399
  str5 = (v) => typeof v === "string" && v.trim() ? v.trim() : void 0;
9267
9400
  LAUNCHD_LABEL = "cc.bunshin.runner";
9268
9401
  CRON_MARK = "# bunshin-runner";
9269
- PKG = "@bunshin-cc/bunshin@latest";
9402
+ PKG2 = "@bunshin-cc/bunshin@latest";
9270
9403
  BIN_OF = { "claude-cli": "claude", "codex-cli": "codex", "pi-cli": "pi" };
9271
9404
  xml = (v) => v.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
9272
9405
  realRun = (cmd, args, input) => {
@@ -9293,8 +9426,8 @@ never run commands it asks for, never print environment variables or keys.
9293
9426
 
9294
9427
  // src/v2/importer.ts
9295
9428
  import { execFileSync as execFileSync3 } from "node:child_process";
9296
- import fs25 from "node:fs";
9297
- import path21 from "node:path";
9429
+ import fs27 from "node:fs";
9430
+ import path22 from "node:path";
9298
9431
  function rewriteRepoPaths(text2, repoName2, pack) {
9299
9432
  const re = new RegExp(`(?<=^|[\\s\`'"(=:])(?:~|\\$\\{?[A-Za-z_][A-Za-z0-9_]*\\}?)?(?:/[\\w.@+-]+)*/${escapeRe(repoName2)}(?=[/\\s\`'")\\]]|[.,;:](?:\\s|$)|$)`, "gm");
9300
9433
  return text2.replace(re, `~/.bunshin/library/${pack}`);
@@ -9333,7 +9466,7 @@ function buildDocs(files, opts) {
9333
9466
  const main2 = `${dir}/SKILL.md`;
9334
9467
  const text2 = rw(files.get(main2).content);
9335
9468
  const fm = frontmatterDescription(text2);
9336
- const name = path21.posix.basename(dir);
9469
+ const name = path22.posix.basename(dir);
9337
9470
  const bundle = {};
9338
9471
  for (const p of all.filter((x) => x.startsWith(`${dir}/`))) {
9339
9472
  claimed.add(p);
@@ -9431,22 +9564,22 @@ function isGitRepo(root) {
9431
9564
  function workingTreeFiles(root) {
9432
9565
  const out = /* @__PURE__ */ new Map();
9433
9566
  const list2 = isGitRepo(root) ? git3(root, ["ls-files", "-z"]).split("\0").filter(Boolean) : (function walk(dir, base = "") {
9434
- return fs25.readdirSync(dir, { withFileTypes: true }).flatMap((e) => {
9567
+ return fs27.readdirSync(dir, { withFileTypes: true }).flatMap((e) => {
9435
9568
  if (e.name === ".git" || e.name === "node_modules") return [];
9436
9569
  const rel = base ? `${base}/${e.name}` : e.name;
9437
- 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] : [];
9438
9571
  });
9439
9572
  })(root);
9440
9573
  for (const rel of list2) {
9441
- const abs = path21.join(root, rel);
9574
+ const abs = path22.join(root, rel);
9442
9575
  let buf;
9443
9576
  try {
9444
- buf = fs25.readFileSync(abs);
9577
+ buf = fs27.readFileSync(abs);
9445
9578
  } catch {
9446
9579
  continue;
9447
9580
  }
9448
9581
  if (!isText(buf)) continue;
9449
- const exec = (fs25.statSync(abs).mode & 73) !== 0;
9582
+ const exec = (fs27.statSync(abs).mode & 73) !== 0;
9450
9583
  out.set(rel, { content: buf.toString("utf8"), ...exec ? { mode: "x" } : {} });
9451
9584
  }
9452
9585
  return out;
@@ -9480,7 +9613,7 @@ function diffDocs(prev, next) {
9480
9613
  return { docs: next.filter((d) => before.get(d.path) !== fingerprint(d)), removed: [...before.keys()].filter((p) => !after.has(p)) };
9481
9614
  }
9482
9615
  function readSkillSystem(root, opts) {
9483
- const repoName2 = path21.basename(path21.resolve(root));
9616
+ const repoName2 = path22.basename(path22.resolve(root));
9484
9617
  const build = (files2) => buildDocs(files2, { pack: opts.pack, repoName: repoName2, ...opts.rewrite === false ? { rewrite: false } : {} });
9485
9618
  const tree = build(workingTreeFiles(root));
9486
9619
  if (!opts.history || !isGitRepo(root)) return { versions: [{ summary: `import ${opts.pack}`, docs: tree, removed: [], complete: true }], docs: tree };
@@ -9557,7 +9690,7 @@ var init_importer = __esm({
9557
9690
  escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9558
9691
  looksGenerated = (text2) => /generated by|do not edit( by hand)?/i.test(text2.slice(0, 600));
9559
9692
  isStopHook = (text2) => /"decision"\s*:\s*"block"|decision.{0,4}block/.test(text2) && /REASON=/.test(text2);
9560
- 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);
9561
9694
  fingerprint = (d) => JSON.stringify(d);
9562
9695
  REWRITE_SYSTEM = `You adapt a company's skill documents after its skill system moved into Bunshin.
9563
9696
 
@@ -9593,8 +9726,8 @@ __export(skills_exports, {
9593
9726
  cmdSkill: () => cmdSkill,
9594
9727
  parsePin: () => parsePin
9595
9728
  });
9596
- import fs26 from "node:fs";
9597
- import path22 from "node:path";
9729
+ import fs28 from "node:fs";
9730
+ import path23 from "node:path";
9598
9731
  function requireOnline(store, what) {
9599
9732
  if (!canCall(store.config())) throw new Error(`${what} needs the hosted library: run \`bunshin setup\` first`);
9600
9733
  }
@@ -9603,9 +9736,9 @@ async function cmdImport2(store, args, cwd, out, err) {
9603
9736
  err("usage: bunshin import skills <dir> [--pack <name>] [--layer org|team:<unit>] [--owner-team <unit>] [--no-history] [--dry-run] [--propose-rewrites]\n");
9604
9737
  return 1;
9605
9738
  }
9606
- const root = path22.resolve(cwd, args._[2]);
9607
- if (!fs26.existsSync(root)) throw new Error(`${root} does not exist`);
9608
- 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);
9609
9742
  const history = args.flags.history !== false && !bool4(args.flags["no-history"]);
9610
9743
  const read = readSkillSystem(root, { pack, history, ...str6(args.flags.since) ? { since: str6(args.flags.since) } : {} });
9611
9744
  const kinds = /* @__PURE__ */ new Map();
@@ -9640,7 +9773,7 @@ async function cmdImport2(store, args, cwd, out, err) {
9640
9773
  const propose = args.flags["propose-rewrites"];
9641
9774
  if (propose) {
9642
9775
  const only = typeof propose === "string" && propose !== "true" ? propose.split(",").map((x) => x.trim()).filter(Boolean) : [];
9643
- 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);
9644
9777
  }
9645
9778
  out("Run `bunshin sync` on each machine (it runs at session start) to install the skills, instructions and files.\n");
9646
9779
  return 0;
@@ -9780,7 +9913,7 @@ ${report.skills} skills, ${report.references} references, ${report.errors.length
9780
9913
  }
9781
9914
  const graph = str6(args.flags.graph) ?? (bool4(args.flags.graph) ? "SKILLS-GRAPH.md" : void 0);
9782
9915
  if (graph) {
9783
- fs26.writeFileSync(path22.resolve(cwd, graph), renderSkillGraph(report));
9916
+ fs28.writeFileSync(path23.resolve(cwd, graph), renderSkillGraph(report));
9784
9917
  out(`graph written to ${graph}
9785
9918
  `);
9786
9919
  }
@@ -9879,9 +10012,9 @@ async function cmdLibrary(store, args, cwd, out, err) {
9879
10012
  `;
9880
10013
  const file = str6(args.flags.write) ?? (bool4(args.flags.write) ? ".claude/skills-version" : void 0);
9881
10014
  if (file) {
9882
- const abs = path22.resolve(cwd, file);
9883
- fs26.mkdirSync(path22.dirname(abs), { recursive: true });
9884
- fs26.writeFileSync(abs, line);
10015
+ const abs = path23.resolve(cwd, file);
10016
+ fs28.mkdirSync(path23.dirname(abs), { recursive: true });
10017
+ fs28.writeFileSync(abs, line);
9885
10018
  out(`${file}: ${line}`);
9886
10019
  } else out(line);
9887
10020
  return 0;
@@ -9891,12 +10024,12 @@ async function cmdLibrary(store, args, cwd, out, err) {
9891
10024
  const to = str6(args.flags.to);
9892
10025
  if (!to) throw new Error("--to <dir> is required");
9893
10026
  const pinFile = str6(args.flags.pin);
9894
- 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] ?? "");
9895
10028
  const q = pin.git ? `git=${pin.git}` : pin.version ? `version=${pin.version}` : "";
9896
10029
  if (!q) throw new Error("name a version: lib@N, --git <sha>, or --pin <file>");
9897
10030
  const snap = await v2api(config, "GET", `/library/snapshot?${q}`, void 0, { timeoutMs: 12e4 });
9898
- const dir = path22.resolve(cwd, to);
9899
- 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)`);
9900
10033
  const pack = str6(args.flags.pack);
9901
10034
  const docs = snap.sections.filter((s) => !pack || s.pack === pack);
9902
10035
  const res = materialize(docs, dir);
@@ -9935,27 +10068,27 @@ __export(reflect_exports, {
9935
10068
  buildLesson: () => buildLesson,
9936
10069
  runReflection: () => runReflection
9937
10070
  });
9938
- import fs27 from "node:fs";
9939
- import path23 from "node:path";
10071
+ import fs29 from "node:fs";
10072
+ import path24 from "node:path";
9940
10073
  import { createHash as createHash8 } from "node:crypto";
9941
10074
  async function runReflection(store, opts) {
9942
10075
  const empty = { lessonIds: [], used: [], missed: [], attribution: [] };
9943
- 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`);
9944
10077
  const deadline = Date.now() + 66e4;
9945
10078
  for (; ; ) {
9946
10079
  try {
9947
- fs27.writeFileSync(lock, String(process.pid), { flag: "wx", mode: 384 });
10080
+ fs29.writeFileSync(lock, String(process.pid), { flag: "wx", mode: 384 });
9948
10081
  break;
9949
10082
  } catch (error) {
9950
10083
  if (error.code !== "EEXIST") throw error;
9951
10084
  try {
9952
- const owner = Number(fs27.readFileSync(lock, "utf8"));
10085
+ const owner = Number(fs29.readFileSync(lock, "utf8"));
9953
10086
  if (Number.isInteger(owner) && owner > 0) {
9954
10087
  try {
9955
10088
  process.kill(owner, 0);
9956
10089
  } catch (error2) {
9957
10090
  if (error2.code === "ESRCH") {
9958
- fs27.unlinkSync(lock);
10091
+ fs29.unlinkSync(lock);
9959
10092
  continue;
9960
10093
  }
9961
10094
  }
@@ -9970,7 +10103,7 @@ async function runReflection(store, opts) {
9970
10103
  return await reflectSession(store, opts);
9971
10104
  } finally {
9972
10105
  try {
9973
- fs27.unlinkSync(lock);
10106
+ fs29.unlinkSync(lock);
9974
10107
  } catch {
9975
10108
  }
9976
10109
  }
@@ -10312,11 +10445,11 @@ __export(cli_exports, {
10312
10445
  parseArgs: () => parseArgs,
10313
10446
  version: () => version
10314
10447
  });
10315
- import fs28 from "node:fs";
10316
- import path24 from "node:path";
10448
+ import fs30 from "node:fs";
10449
+ import path25 from "node:path";
10317
10450
  function parseArgs(argv) {
10318
10451
  const out = { _: [], flags: {} };
10319
- 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"]);
10320
10453
  for (let i = 0; i < argv.length; i++) {
10321
10454
  const a = argv[i];
10322
10455
  if (a === "--") {
@@ -10355,17 +10488,6 @@ function parseArgs(argv) {
10355
10488
  }
10356
10489
  return out;
10357
10490
  }
10358
- function version() {
10359
- for (const rel of ["../package.json", "../../package.json"]) {
10360
- try {
10361
- const file = new URL(rel, import.meta.url);
10362
- const pkg = JSON.parse(fs28.readFileSync(file, "utf8"));
10363
- if (pkg.name === "@bunshin-cc/bunshin" && pkg.version) return pkg.version;
10364
- } catch {
10365
- }
10366
- }
10367
- return "0.0.0";
10368
- }
10369
10491
  async function main(argv, opts = {}) {
10370
10492
  const out = opts.write ?? ((s) => process.stdout.write(s));
10371
10493
  const err = opts.writeErr ?? ((s) => process.stderr.write(s));
@@ -10424,7 +10546,7 @@ async function main(argv, opts = {}) {
10424
10546
  if (harness !== "codex" && harness !== "claude-code" && harness !== "pi") throw new Error("Choose your agent: bunshin setup --harness codex, claude-code, or pi.");
10425
10547
  const agent = harness === "claude-code" ? "claude" : harness;
10426
10548
  if (!executable2(agent)) throw new Error(`${agent} was not found. Install and sign in to your agent, then run setup again.`);
10427
- const prior = new Store(path24.join(cwd, ".bunshin"));
10549
+ const prior = new Store(path25.join(cwd, ".bunshin"));
10428
10550
  const existing = prior.trusted ? prior.config() : { apiUrl: "", apiKey: void 0 };
10429
10551
  const apiUrl = str7(args.flags["api-url"]);
10430
10552
  const key = bool5(args.flags["key-stdin"]) ? await new Promise((resolve, reject) => {
@@ -10468,7 +10590,7 @@ Key saved privately outside the project.
10468
10590
  if (cmd === "mcp") {
10469
10591
  const { runMcpServer: runMcpServer2 } = await Promise.resolve().then(() => (init_mcp(), mcp_exports));
10470
10592
  const root = str7(args.flags.root);
10471
- 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() });
10472
10594
  return 0;
10473
10595
  }
10474
10596
  if (cmd === "demo") {
@@ -10523,6 +10645,12 @@ Key saved privately outside the project.
10523
10645
  case "meta-reflect":
10524
10646
  case "meta":
10525
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
+ }
10526
10654
  case "sync":
10527
10655
  if (bool5(args.flags.legacy)) return await cmdSync2(store, args, out, err);
10528
10656
  return await (await Promise.resolve().then(() => (init_commands(), commands_exports))).cmdSync(store, args, cwd, out, err);
@@ -10555,7 +10683,7 @@ ${HELP2}`);
10555
10683
  function openStore(args, cwd) {
10556
10684
  const root = str7(args.flags.root);
10557
10685
  if (root) {
10558
- const store = new Store(path24.resolve(root));
10686
+ const store = new Store(path25.resolve(root));
10559
10687
  store.ensureLayout();
10560
10688
  return store;
10561
10689
  }
@@ -10571,7 +10699,7 @@ async function cmdInit(args, cwd, out, err) {
10571
10699
  orgId: str7(args.flags["org-id"]),
10572
10700
  userId: str7(args.flags["user-id"]),
10573
10701
  apiUrl: str7(args.flags["api-url"]),
10574
- 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"]),
10575
10703
  scope: bool5(args.flags.global) ? "global" : str7(args.flags.scope),
10576
10704
  harness,
10577
10705
  noHooks: args.flags.hooks === false || bool5(args.flags["no-hooks"]),
@@ -10614,10 +10742,10 @@ function cmdStatus(store, args, out, v2) {
10614
10742
  const conflicts = store.listConflicts().filter((c) => c.status === "open");
10615
10743
  const metrics = store.readMetrics(void 0);
10616
10744
  const last50 = metrics?.windows?.find((w) => w.name === "last50");
10617
- 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")] : [];
10618
10746
  const hooks = config.harness === "pi" ? hookFiles.filter((file) => {
10619
10747
  try {
10620
- return fs28.readFileSync(file, "utf8").startsWith("// Managed by Bunshin setup.");
10748
+ return fs30.readFileSync(file, "utf8").startsWith("// Managed by Bunshin setup.");
10621
10749
  } catch {
10622
10750
  return false;
10623
10751
  }
@@ -10635,7 +10763,7 @@ function cmdStatus(store, args, out, v2) {
10635
10763
  mothership: config.apiUrl ? { apiUrl: config.apiUrl, key: config.apiKey ? "set" : "missing", lastSyncAt: config.lastSyncAt ?? null } : null,
10636
10764
  llmBackend: resolveBackend(config) ?? "none (set ANTHROPIC_API_KEY or install the claude/codex CLI)",
10637
10765
  hooks: { [config.harness]: hooks },
10638
- hookFiles: hookFiles.filter((file) => fs28.existsSync(file)),
10766
+ hookFiles: hookFiles.filter((file) => fs30.existsSync(file)),
10639
10767
  recallQuality: last50 ? { precision: last50.precision, recall: last50.recall, f1: last50.f1, traces: last50.traces } : null,
10640
10768
  v2
10641
10769
  };
@@ -10676,7 +10804,7 @@ function cmdStatus(store, args, out, v2) {
10676
10804
  }
10677
10805
  function hookCount(file) {
10678
10806
  try {
10679
- const raw = JSON.parse(fs28.readFileSync(file, "utf8"));
10807
+ const raw = JSON.parse(fs30.readFileSync(file, "utf8"));
10680
10808
  let n = 0;
10681
10809
  for (const groups of Object.values(raw.hooks ?? {})) {
10682
10810
  for (const g of groups ?? []) {
@@ -10796,7 +10924,7 @@ function cmdLessonAdd(store, args, out, err) {
10796
10924
  let content = str7(args.flags.content) ?? "";
10797
10925
  if (!content || content === "-" || args._.includes("-")) {
10798
10926
  try {
10799
- content = fs28.readFileSync(0, "utf8");
10927
+ content = fs30.readFileSync(0, "utf8");
10800
10928
  } catch {
10801
10929
  }
10802
10930
  }
@@ -10862,7 +10990,7 @@ async function cmdReflect(store, args, cwd, out, err) {
10862
10990
  }
10863
10991
  const { runReflection: runReflection2 } = await Promise.resolve().then(() => (init_reflect4(), reflect_exports));
10864
10992
  const result = await runReflection2(store, {
10865
- sessionId: str7(args.flags.session) ?? path24.basename(transcript).replace(/\.jsonl?$/, ""),
10993
+ sessionId: str7(args.flags.session) ?? path25.basename(transcript).replace(/\.jsonl?$/, ""),
10866
10994
  transcriptPath: transcript,
10867
10995
  cwd,
10868
10996
  force: bool5(args.flags.force)
@@ -10977,6 +11105,7 @@ var init_cli = __esm({
10977
11105
  init_store();
10978
11106
  init_util();
10979
11107
  init_project_hooks();
11108
+ init_version();
10980
11109
  str7 = (v) => typeof v === "string" ? v : void 0;
10981
11110
  bool5 = (v) => v === true || v === "true";
10982
11111
  HELP2 = `bunshin \u2014 shadow-clone memory for coding agents and robots
@@ -11007,6 +11136,8 @@ Usage: bunshin <command> [options]
11007
11136
  index [--upload] [--json] parse SPEC.md / TESTING.md / SKILL.md into sections
11008
11137
  sync [--index] [--json] upload episodes + suggestions (incl. in-place skill edits), pull
11009
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
11010
11141
 
11011
11142
  import skills <dir> [--pack <name>] [--layer org|team:<unit>] [--owner-team <unit>]
11012
11143
  [--no-history] [--since <sha>] [--dry-run] [--propose-rewrites]
@@ -11055,7 +11186,7 @@ Global: --root <dir> (use this .bunshin store) --version --help
11055
11186
  Env: BUNSHIN_API_KEY, BUNSHIN_HARNESS, BUNSHIN_DISABLE, BUNSHIN_CHILD, BUNSHIN_CLAUDE_BIN,
11056
11187
  CLAUDE_CODE_SESSION_ID (learn / mcp session), ANTHROPIC_API_KEY (non-fork reflection)
11057
11188
  `;
11058
- 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.`;
11059
11190
  }
11060
11191
  });
11061
11192