@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.
- package/dist/bin.js +193 -24
- 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
|
|
7106
|
-
out(`Imported into the library: ${written} document revisions${
|
|
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
|
|
7295
|
-
if (
|
|
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
|
-
|
|
7961
|
-
|
|
7962
|
-
|
|
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
|
-
|
|
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":
|