@bunshin-cc/bunshin 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/bin.js +193 -24
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -2674,6 +2674,17 @@ ${d.text.replace(/\n*$/, "\n")}`),
2674
2674
  ];
2675
2675
  return parts.join("\n");
2676
2676
  }
2677
+ function removeBlock(file) {
2678
+ const text2 = readText(file);
2679
+ if (text2 === null) return false;
2680
+ const start = text2.indexOf(BLOCK_BEGIN);
2681
+ const end = text2.indexOf(BLOCK_END);
2682
+ if (start < 0 || end < start) return false;
2683
+ const rest = (text2.slice(0, start).replace(/\s*$/, "") + "\n" + text2.slice(end + BLOCK_END.length).replace(/^\s*/, "")).trim();
2684
+ if (rest) writeTextAtomic(file, rest + "\n");
2685
+ else fs5.unlinkSync(file);
2686
+ return true;
2687
+ }
2677
2688
  function writeBlock(file, block2) {
2678
2689
  const text2 = readText(file) ?? "";
2679
2690
  const start = text2.indexOf(BLOCK_BEGIN);
@@ -2984,9 +2995,9 @@ function deployLocked(v2, config, ctx, bundle, opts, p, harnesses, now) {
2984
2995
  }
2985
2996
  }
2986
2997
  }
2987
- const blockDocs = plan.instructions.map((s) => ({ id: s.id, version: s.version, text: next[s.id]?.pending ? readBlock(p.claudeMd)?.get(s.id) ?? s.body : s.body }));
2998
+ const blockDocs = config.instructionsFile ? plan.instructions.map((s) => ({ id: s.id, version: s.version, text: next[s.id]?.pending ? readBlock(p.claudeMd)?.get(s.id) ?? s.body : s.body })) : [];
2988
2999
  if (harnesses.has("claude-code") && (blockDocs.length || readBlock(p.claudeMd))) {
2989
- if (writeBlock(p.claudeMd, renderBlock(blockDocs, ""))) report.written++;
3000
+ if (blockDocs.length ? writeBlock(p.claudeMd, renderBlock(blockDocs, "")) : removeBlock(p.claudeMd)) report.written++;
2990
3001
  }
2991
3002
  if (harnesses.has("codex") && (blockDocs.length || plan.skills.length || readBlock(p.agentsMd))) {
2992
3003
  if (writeBlock(p.agentsMd, renderBlock(blockDocs, skillIndex(plan.skills, p)))) report.written++;
@@ -3352,7 +3363,7 @@ function repoCommit(root) {
3352
3363
  }
3353
3364
  function listDocs(root) {
3354
3365
  const listed = git(root, ["ls-files", "-co", "--exclude-standard", "-z"], 3e3);
3355
- if (listed !== null) return [...new Set(listed.split("\0").filter(Boolean).filter(isDoc))].filter((rel) => fs7.existsSync(path6.join(root, rel))).sort();
3366
+ if (listed !== null) return [...new Set(listed.split("\0").filter(Boolean).filter(isDoc))].filter((rel) => fs7.existsSync(path6.join(root, rel)) && !deployedSkill(root, rel)).sort();
3356
3367
  const out = [];
3357
3368
  let visited = 0;
3358
3369
  const walk = (dir, rel, depth) => {
@@ -3365,10 +3376,10 @@ function listDocs(root) {
3365
3376
  }
3366
3377
  for (const e of entries) {
3367
3378
  if (e.isDirectory()) {
3368
- if (!SKIP_DIRS.has(e.name)) walk(path6.join(dir, e.name), rel ? `${rel}/${e.name}` : e.name, depth + 1);
3379
+ if (!SKIP_DIRS.has(e.name) && !fs7.existsSync(path6.join(dir, e.name, ".git"))) walk(path6.join(dir, e.name), rel ? `${rel}/${e.name}` : e.name, depth + 1);
3369
3380
  } else if (e.isFile()) {
3370
3381
  const r = rel ? `${rel}/${e.name}` : e.name;
3371
- if (isDoc(r)) out.push(r);
3382
+ if (isDoc(r) && !deployedSkill(root, r)) out.push(r);
3372
3383
  }
3373
3384
  }
3374
3385
  };
@@ -3448,7 +3459,7 @@ function relativeToRoot(root, file, cwd = root) {
3448
3459
  }
3449
3460
  return null;
3450
3461
  }
3451
- var DOC_NAMES, SKIP_DIRS, LIST_TTL_MS, rootCache, isDoc;
3462
+ var DOC_NAMES, SKIP_DIRS, LIST_TTL_MS, rootCache, isDoc, deployedSkill;
3452
3463
  var init_repo = __esm({
3453
3464
  "src/v2/repo.ts"() {
3454
3465
  "use strict";
@@ -3462,6 +3473,7 @@ var init_repo = __esm({
3462
3473
  if (parts.some((p) => SKIP_DIRS.has(p))) return false;
3463
3474
  return DOC_NAMES.has(parts[parts.length - 1].toLowerCase());
3464
3475
  };
3476
+ deployedSkill = (root, rel) => /(^|\/)SKILL\.md$/i.test(rel) && fs7.existsSync(path6.join(root, path6.posix.dirname(rel), ".bunshin.json"));
3465
3477
  }
3466
3478
  });
3467
3479
 
@@ -4515,6 +4527,7 @@ __export(commands_exports, {
4515
4527
  cmdRecall: () => cmdRecall,
4516
4528
  cmdReflectFork: () => cmdReflectFork,
4517
4529
  cmdReview: () => cmdReview,
4530
+ cmdReviewDecide: () => cmdReviewDecide,
4518
4531
  cmdSuggest: () => cmdSuggest,
4519
4532
  cmdSync: () => cmdSync,
4520
4533
  robotAttrsFromFlags: () => robotAttrsFromFlags,
@@ -4556,7 +4569,7 @@ function cmdReview(store, args, cwd, out, err) {
4556
4569
  `);
4557
4570
  }
4558
4571
  out(`
4559
- bunshin review apply <id> | submit <id> | skip <id> | apply-all
4572
+ bunshin review apply <id> | submit <id> | skip <id> | apply-all (owners: approve <id> | reject <id> [--note])
4560
4573
  `);
4561
4574
  return 0;
4562
4575
  }
@@ -4598,6 +4611,29 @@ bunshin review apply <id> | submit <id> | skip <id> | apply-all
4598
4611
  `);
4599
4612
  return 1;
4600
4613
  }
4614
+ async function cmdReviewDecide(store, args, out, err) {
4615
+ const sub = args._[1];
4616
+ const id = args._[2];
4617
+ if (!id) {
4618
+ err(`bunshin review ${sub}: a suggestion id is required (see the console's Review, or bunshin review list --all)
4619
+ `);
4620
+ return 1;
4621
+ }
4622
+ const config = store.config();
4623
+ if (!canCall(config)) {
4624
+ err(`bunshin review ${sub}: this project is not connected to Bunshin
4625
+ `);
4626
+ return 1;
4627
+ }
4628
+ const note = str(args.flags.note);
4629
+ const res = await v2api(config, "POST", `/suggestions/${encodeURIComponent(id)}/review`, {
4630
+ approve: sub === "approve",
4631
+ ...note ? { note } : {}
4632
+ });
4633
+ out(`${res.suggestion.id}: ${res.suggestion.status}${res.suggestion.decision?.reason ? ` (${res.suggestion.decision.reason})` : ""}
4634
+ `);
4635
+ return 0;
4636
+ }
4601
4637
  async function cmdRecall(store, args, cwd, out, err) {
4602
4638
  const abstract = args._.slice(1).join(" ").trim() || str(args.flags.abstract) || "";
4603
4639
  if (!abstract) {
@@ -7102,8 +7138,8 @@ async function cmdImport2(store, args, cwd, out, err) {
7102
7138
  if (batches.length > 1) out(` batch ${i + 1}/${batches.length}: ${last.versions.length} versions${last.skipped.length ? `, ${last.skipped.length} already imported` : ""}
7103
7139
  `);
7104
7140
  }
7105
- const lib = last?.versions.at(-1)?.version;
7106
- out(`Imported into the library: ${written} document revisions${lib ? `, now lib@${lib}` : ""}; ${last?.documents ?? 0} documents active in ${pack}.
7141
+ const lib2 = last?.versions.at(-1)?.version;
7142
+ out(`Imported into the library: ${written} document revisions${lib2 ? `, now lib@${lib2}` : ""}; ${last?.documents ?? 0} documents active in ${pack}.
7107
7143
  `);
7108
7144
  if (history) out("Old commit pins resolve with `bunshin library checkout --git <sha>`.\n");
7109
7145
  const propose = args.flags["propose-rewrites"];
@@ -7291,8 +7327,8 @@ function sinceCutoff(since) {
7291
7327
  }
7292
7328
  function parsePin(text2) {
7293
7329
  const t = text2.trim();
7294
- const lib = /lib@(\d+)/.exec(t) ?? /^(\d+)$/.exec(t);
7295
- if (lib) return { version: Number(lib[1]) };
7330
+ const lib2 = /lib@(\d+)/.exec(t) ?? /^(\d+)$/.exec(t);
7331
+ if (lib2) return { version: Number(lib2[1]) };
7296
7332
  const sha2 = /\b([0-9a-f]{7,40})\b/i.exec(t);
7297
7333
  return sha2 ? { git: sha2[1].toLowerCase() } : {};
7298
7334
  }
@@ -7801,6 +7837,94 @@ init_v22();
7801
7837
  init_transcript();
7802
7838
  init_api();
7803
7839
  init_deploy();
7840
+
7841
+ // src/v2/live.ts
7842
+ init_api();
7843
+ var LIVE_REFRESH_MS = 5 * 6e4;
7844
+ var MAX_DIFF_LINES = 80;
7845
+ var instructionDocs = (bundle) => (bundle?.sections ?? []).filter((s) => s.delivery === "always" && s.status === "active");
7846
+ var skillDocs = (bundle) => (bundle?.sections ?? []).filter((s) => !!s.skill && s.status === "active");
7847
+ function baselineOf(bundle) {
7848
+ return {
7849
+ ...bundle?.libraryVersion !== void 0 ? { library: bundle.libraryVersion } : {},
7850
+ skills: Object.fromEntries(skillDocs(bundle).map((s) => [s.id, { version: s.version, name: s.skill.name }])),
7851
+ instructions: Object.fromEntries(instructionDocs(bundle).map((s) => [s.id, s.body]))
7852
+ };
7853
+ }
7854
+ var lib = (v) => v === void 0 ? "the library" : `lib@${v}`;
7855
+ function renderInstructions(bundle) {
7856
+ const docs = instructionDocs(bundle);
7857
+ if (!docs.length) return "";
7858
+ return [
7859
+ `## Standing instructions (your organization, from Bunshin ${lib(bundle?.libraryVersion)})`,
7860
+ "Follow these as you would CLAUDE.md. Bunshin re-delivers them after /clear and compaction, and tells you when they change.",
7861
+ "",
7862
+ ...docs.map((d) => d.body.trim())
7863
+ ].join("\n");
7864
+ }
7865
+ function lineDiff(before, after, max = MAX_DIFF_LINES) {
7866
+ const a = before.split("\n");
7867
+ const b = after.split("\n");
7868
+ if (a.length * b.length > 4e6) return [`(the text changed substantially; ${b.length} lines now)`];
7869
+ const dp = Array.from({ length: a.length + 1 }, () => new Uint32Array(b.length + 1));
7870
+ for (let i2 = a.length - 1; i2 >= 0; i2--) for (let j2 = b.length - 1; j2 >= 0; j2--) dp[i2][j2] = a[i2] === b[j2] ? dp[i2 + 1][j2 + 1] + 1 : Math.max(dp[i2 + 1][j2], dp[i2][j2 + 1]);
7871
+ const out = [];
7872
+ let i = 0;
7873
+ let j = 0;
7874
+ while (i < a.length || j < b.length) {
7875
+ if (i < a.length && j < b.length && a[i] === b[j]) {
7876
+ i++;
7877
+ j++;
7878
+ } else if (j < b.length && (i === a.length || dp[i][j + 1] >= dp[i + 1][j])) {
7879
+ if (b[j].trim()) out.push(`+ ${b[j]}`);
7880
+ j++;
7881
+ } else {
7882
+ if (a[i].trim()) out.push(`- ${a[i]}`);
7883
+ i++;
7884
+ }
7885
+ }
7886
+ return out.length > max ? [...out.slice(0, max), `\u2026 ${out.length - max} more changed lines`] : out;
7887
+ }
7888
+ function changesSince(base, bundle, skillPath) {
7889
+ const next = baselineOf(bundle);
7890
+ if (!bundle || next.library === base.library) return { text: "", next: base };
7891
+ const lines = [];
7892
+ const now = instructionDocs(bundle);
7893
+ for (const d of now) {
7894
+ const old = base.instructions[d.id];
7895
+ if (old === d.body) continue;
7896
+ if (old === void 0) {
7897
+ lines.push(`New standing instructions ("${d.title}"):`, d.body.trim(), "");
7898
+ continue;
7899
+ }
7900
+ const diff = lineDiff(old, d.body);
7901
+ if (diff.length) lines.push(`Standing instructions changed ("${d.title}"); follow the new text:`, ...diff, "");
7902
+ }
7903
+ for (const id of Object.keys(base.instructions)) if (!now.some((d) => d.id === id)) lines.push(`Standing instructions withdrawn (${id}): stop following them.`, "");
7904
+ const skillLines = [];
7905
+ for (const s of skillDocs(bundle)) {
7906
+ const old = base.skills[s.id];
7907
+ const where = skillPath(s.skill.name);
7908
+ const read = where ? `re-read \`${where}\` before using it` : `load it with \`bunshin load ${s.id}\` before using it`;
7909
+ if (!old) skillLines.push(`- new skill \`${s.skill.name}\`: ${s.skill.description.replace(/\s+/g, " ").trim()} (${where ? `\`${where}\`` : `\`bunshin load ${s.id}\``})`);
7910
+ else if (old.version !== s.version) skillLines.push(`- \`${s.skill.name}\` updated (v${old.version} \u2192 v${s.version}): ${read}`);
7911
+ }
7912
+ for (const [id, old] of Object.entries(base.skills)) if (!next.skills[id]) skillLines.push(`- \`${old.name}\` was retired: don't use it`);
7913
+ if (skillLines.length) lines.push("Skills:", ...skillLines);
7914
+ if (!lines.length) return { text: "", next };
7915
+ return { text: [`## Bunshin: the library changed during this session (${lib(base.library)} \u2192 ${lib(next.library)})`, ...lines].join("\n").trim(), next };
7916
+ }
7917
+ function refreshInBackground(v2, store, detach2, cwd, now = Date.now()) {
7918
+ if (!canCall(store.config())) return false;
7919
+ const state = v2.readSyncState();
7920
+ const last = Math.max(Date.parse(state.lastAttemptAt ?? "") || 0, Date.parse(state.lastSyncAt ?? "") || 0);
7921
+ if (now - last < LIVE_REFRESH_MS) return false;
7922
+ v2.writeSyncState({ ...state, lastAttemptAt: new Date(now).toISOString() });
7923
+ detach2(["--root", store.root, "sync", "--quiet"], { cwd });
7924
+ return true;
7925
+ }
7926
+
7927
+ // src/v2/hooks.ts
7804
7928
  init_reflect2();
7805
7929
  init_repo();
7806
7930
 
@@ -7948,6 +8072,10 @@ async function onSessionStart(ctx) {
7948
8072
  }
7949
8073
  }
7950
8074
  const bundle = v2.readBundle();
8075
+ v2.updateSession(id, ctx.cwd, (s) => {
8076
+ s.live = baselineOf(bundle);
8077
+ });
8078
+ const instructions = renderInstructions(bundle);
7951
8079
  const lines = [];
7952
8080
  for (const d of (bundle?.digest ?? []).slice(0, 5)) lines.push(`- ${d}`);
7953
8081
  const proposed = v2.listSuggestions().filter((s) => s.status === "proposed").length;
@@ -7957,9 +8085,37 @@ async function onSessionStart(ctx) {
7957
8085
  lines.push(`Open question${questions.length === 1 ? "" : "s"} for your team (ask the user when it fits; answer with \`bunshin questions answer <id> "<choice>"\`):`);
7958
8086
  for (const q of questions) lines.push(`- [${q.id}] ${q.question}${q.options.length ? ` Options: ${q.options.join(" | ")}` : ""}`);
7959
8087
  }
7960
- if (!lines.length) return {};
7961
- return additional("SessionStart", `## Bunshin
7962
- ${lines.join("\n")}`);
8088
+ const parts = [instructions, lines.length ? `## Bunshin
8089
+ ${lines.join("\n")}` : ""].filter(Boolean);
8090
+ if (!parts.length) return {};
8091
+ return additional("SessionStart", parts.join("\n\n"));
8092
+ }
8093
+ function liveUpdate(v2, ctx, session) {
8094
+ try {
8095
+ refreshInBackground(v2, ctx.store, ctx.detach, ctx.cwd);
8096
+ const bundle = v2.readBundle();
8097
+ if (!session.live) {
8098
+ v2.updateSession(session.sessionId, ctx.cwd, (s) => {
8099
+ s.live = baselineOf(bundle);
8100
+ });
8101
+ return "";
8102
+ }
8103
+ const config = ctx.store.config();
8104
+ const deployed = deployEnabled(config, localView(v2, config, ctx.cwd).ctx);
8105
+ const paths = deployed ? deployPaths() : null;
8106
+ const root = paths ? config.harness === "codex" ? paths.codexSkills : paths.claudeSkills : null;
8107
+ const skillPath = (name) => root ? `${root}/${name}/SKILL.md` : null;
8108
+ const { text: text2, next } = changesSince(session.live, bundle, skillPath);
8109
+ if (next !== session.live) {
8110
+ v2.updateSession(session.sessionId, ctx.cwd, (s) => {
8111
+ s.live = next;
8112
+ });
8113
+ }
8114
+ if (text2) v2.log("v2-live-update", { session: session.sessionId, from: session.live.library ?? null, to: next.library ?? null });
8115
+ return text2;
8116
+ } catch {
8117
+ return "";
8118
+ }
7963
8119
  }
7964
8120
  function hintLine(titles, harness) {
7965
8121
  const quoted = titles.map((t) => `"${t}"`).join(", ");
@@ -7976,7 +8132,13 @@ function onPrompt(ctx) {
7976
8132
  s.lastPrompt = prompt.slice(0, 4e3);
7977
8133
  s.lastActivityAt = now;
7978
8134
  });
7979
- if (!prompt.trim()) return {};
8135
+ const live = liveUpdate(v2, ctx, session);
8136
+ const withLive = (out) => {
8137
+ if (!live) return out;
8138
+ const text2 = [live, out.hookSpecificOutput?.additionalContext ?? ""].filter(Boolean).join("\n\n");
8139
+ return additional("UserPromptSubmit", text2);
8140
+ };
8141
+ if (!prompt.trim()) return withLive({});
7980
8142
  const config = ctx.store.config();
7981
8143
  const view = localView(v2, config, ctx.cwd);
7982
8144
  const injected = [...session.loaded, ...session.pushed];
@@ -7988,16 +8150,16 @@ function onPrompt(ctx) {
7988
8150
  addUnique(s.loaded, sec.id);
7989
8151
  });
7990
8152
  v2.log("v2-auto", { session: session.sessionId, section: sec.id, reason: res.reason });
7991
- return additional("UserPromptSubmit", renderSection(sec, "your prompt matches it", { ...res.anchor ? { anchor: res.anchor } : {}, deployed: !!sec.skill && deployEnabled(config, view.ctx) }));
8153
+ return withLive(additional("UserPromptSubmit", renderSection(sec, "your prompt matches it", { ...res.anchor ? { anchor: res.anchor } : {}, deployed: !!sec.skill && deployEnabled(config, view.ctx) })));
7992
8154
  }
7993
8155
  const fresh = res.hint.filter((h) => !session.hinted?.includes(h.id)).slice(0, 3);
7994
- if (!fresh.length) return {};
8156
+ if (!fresh.length) return withLive({});
7995
8157
  v2.updateSession(session.sessionId, ctx.cwd, (s) => {
7996
8158
  s.hinted = s.hinted ?? [];
7997
8159
  addUnique(s.hinted, ...fresh.map((h) => h.id));
7998
8160
  addUnique(s.served, ...fresh.map((h) => h.id));
7999
8161
  });
8000
- return additional("UserPromptSubmit", hintLine(fresh.map((h) => h.title), config.harness));
8162
+ return withLive(additional("UserPromptSubmit", hintLine(fresh.map((h) => h.title), config.harness)));
8001
8163
  }
8002
8164
  function toolPaths(input) {
8003
8165
  if (!input || typeof input !== "object") return [];
@@ -8013,16 +8175,21 @@ function onPreToolUse(ctx) {
8013
8175
  const session = touch(v2, ctx, (s) => {
8014
8176
  s.lastActivityAt = (/* @__PURE__ */ new Date()).toISOString();
8015
8177
  });
8178
+ const live = liveUpdate(v2, ctx, session);
8179
+ const withLive = (out) => {
8180
+ if (!live) return out;
8181
+ return additional("PreToolUse", [live, out.hookSpecificOutput?.additionalContext ?? ""].filter(Boolean).join("\n\n"));
8182
+ };
8016
8183
  const raw = toolPaths(ctx.input.tool_input);
8017
- if (!raw.length) return {};
8184
+ if (!raw.length) return withLive({});
8018
8185
  const config = ctx.store.config();
8019
8186
  const view = localView(v2, config, ctx.cwd);
8020
8187
  const paths = raw.map((p) => relativeToRoot(view.root, p, ctx.cwd)).filter((p) => !!p);
8021
8188
  const text2 = [session.task, session.lastPrompt].filter(Boolean).join("\n");
8022
- if (!paths.length && !text2) return {};
8189
+ if (!paths.length && !text2) return withLive({});
8023
8190
  const seen = /* @__PURE__ */ new Set([...session.pushed, ...session.loaded]);
8024
8191
  const hits = push(view.sections, view.reader, { paths, text: text2 }).filter((h) => !seen.has(h.section.id)).slice(0, MAX_PUSH);
8025
- if (!hits.length) return {};
8192
+ if (!hits.length) return withLive({});
8026
8193
  const ids = hits.map((h) => h.section.id);
8027
8194
  v2.updateSession(session.sessionId, ctx.cwd, (s) => {
8028
8195
  addUnique(s.pushed, ...ids);
@@ -8031,7 +8198,7 @@ function onPreToolUse(ctx) {
8031
8198
  });
8032
8199
  v2.log("v2-push", { session: session.sessionId, tool: ctx.input.tool_name, hits: hits.map((h) => `${h.section.id} ${h.trigger}`) });
8033
8200
  const deployed = deployEnabled(config, view.ctx);
8034
- return additional("PreToolUse", hits.map((h) => renderSection(h.section, h.trigger, { ...h.anchor ? { anchor: h.anchor } : {}, deployed: deployed && !!h.section.skill })).join("\n\n"));
8201
+ return withLive(additional("PreToolUse", hits.map((h) => renderSection(h.section, h.trigger, { ...h.anchor ? { anchor: h.anchor } : {}, deployed: deployed && !!h.section.skill })).join("\n\n")));
8035
8202
  }
8036
8203
  function onStop(ctx) {
8037
8204
  const v2 = new V2Store(ctx.store);
@@ -8344,8 +8511,9 @@ Usage: bunshin <command> [options]
8344
8511
  print one document (or one part of it) and mark it loaded
8345
8512
  learn [focus...] capture what this session learned now (fork reflection),
8346
8513
  apply it locally as a trial and submit it
8347
- review [list [--all]|apply <id>|submit <id>|skip <id>|apply-all]
8348
- suggestions awaiting review (apply = local trial + submit)
8514
+ review [list [--all]|apply <id>|submit <id>|skip <id>|apply-all|approve <id>|reject <id> [--note <text>]]
8515
+ suggestions awaiting review (apply = local trial + submit;
8516
+ approve / reject = owner decision: merge now, or keep it out)
8349
8517
  index [--upload] [--json] parse SPEC.md / TESTING.md / SKILL.md into sections
8350
8518
  sync [--index] [--json] upload episodes + suggestions (incl. in-place skill edits), pull
8351
8519
  the library, install skills / instructions / files, settle trials
@@ -8515,6 +8683,7 @@ Key saved privately outside the project.
8515
8683
  case "suggest":
8516
8684
  return (await Promise.resolve().then(() => (init_commands(), commands_exports))).cmdSuggest(store, args, cwd, out, err);
8517
8685
  case "review":
8686
+ if (args._[1] === "approve" || args._[1] === "reject") return await (await Promise.resolve().then(() => (init_commands(), commands_exports))).cmdReviewDecide(store, args, out, err);
8518
8687
  return (await Promise.resolve().then(() => (init_commands(), commands_exports))).cmdReview(store, args, cwd, out, err);
8519
8688
  case "questions":
8520
8689
  case "question":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bunshin-cc/bunshin",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "Shared memory for company AI agents. Connect Codex, Claude Code, or Pi to Bunshin.",
5
5
  "type": "module",
6
6
  "bin": {