@bunshin-cc/bunshin 0.3.3 → 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 +159 -17
- 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++;
|
|
@@ -7127,8 +7138,8 @@ async function cmdImport2(store, args, cwd, out, err) {
|
|
|
7127
7138
|
if (batches.length > 1) out(` batch ${i + 1}/${batches.length}: ${last.versions.length} versions${last.skipped.length ? `, ${last.skipped.length} already imported` : ""}
|
|
7128
7139
|
`);
|
|
7129
7140
|
}
|
|
7130
|
-
const
|
|
7131
|
-
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}.
|
|
7132
7143
|
`);
|
|
7133
7144
|
if (history) out("Old commit pins resolve with `bunshin library checkout --git <sha>`.\n");
|
|
7134
7145
|
const propose = args.flags["propose-rewrites"];
|
|
@@ -7316,8 +7327,8 @@ function sinceCutoff(since) {
|
|
|
7316
7327
|
}
|
|
7317
7328
|
function parsePin(text2) {
|
|
7318
7329
|
const t = text2.trim();
|
|
7319
|
-
const
|
|
7320
|
-
if (
|
|
7330
|
+
const lib2 = /lib@(\d+)/.exec(t) ?? /^(\d+)$/.exec(t);
|
|
7331
|
+
if (lib2) return { version: Number(lib2[1]) };
|
|
7321
7332
|
const sha2 = /\b([0-9a-f]{7,40})\b/i.exec(t);
|
|
7322
7333
|
return sha2 ? { git: sha2[1].toLowerCase() } : {};
|
|
7323
7334
|
}
|
|
@@ -7826,6 +7837,94 @@ init_v22();
|
|
|
7826
7837
|
init_transcript();
|
|
7827
7838
|
init_api();
|
|
7828
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
|
|
7829
7928
|
init_reflect2();
|
|
7830
7929
|
init_repo();
|
|
7831
7930
|
|
|
@@ -7973,6 +8072,10 @@ async function onSessionStart(ctx) {
|
|
|
7973
8072
|
}
|
|
7974
8073
|
}
|
|
7975
8074
|
const bundle = v2.readBundle();
|
|
8075
|
+
v2.updateSession(id, ctx.cwd, (s) => {
|
|
8076
|
+
s.live = baselineOf(bundle);
|
|
8077
|
+
});
|
|
8078
|
+
const instructions = renderInstructions(bundle);
|
|
7976
8079
|
const lines = [];
|
|
7977
8080
|
for (const d of (bundle?.digest ?? []).slice(0, 5)) lines.push(`- ${d}`);
|
|
7978
8081
|
const proposed = v2.listSuggestions().filter((s) => s.status === "proposed").length;
|
|
@@ -7982,9 +8085,37 @@ async function onSessionStart(ctx) {
|
|
|
7982
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>"\`):`);
|
|
7983
8086
|
for (const q of questions) lines.push(`- [${q.id}] ${q.question}${q.options.length ? ` Options: ${q.options.join(" | ")}` : ""}`);
|
|
7984
8087
|
}
|
|
7985
|
-
|
|
7986
|
-
|
|
7987
|
-
|
|
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
|
+
}
|
|
7988
8119
|
}
|
|
7989
8120
|
function hintLine(titles, harness) {
|
|
7990
8121
|
const quoted = titles.map((t) => `"${t}"`).join(", ");
|
|
@@ -8001,7 +8132,13 @@ function onPrompt(ctx) {
|
|
|
8001
8132
|
s.lastPrompt = prompt.slice(0, 4e3);
|
|
8002
8133
|
s.lastActivityAt = now;
|
|
8003
8134
|
});
|
|
8004
|
-
|
|
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({});
|
|
8005
8142
|
const config = ctx.store.config();
|
|
8006
8143
|
const view = localView(v2, config, ctx.cwd);
|
|
8007
8144
|
const injected = [...session.loaded, ...session.pushed];
|
|
@@ -8013,16 +8150,16 @@ function onPrompt(ctx) {
|
|
|
8013
8150
|
addUnique(s.loaded, sec.id);
|
|
8014
8151
|
});
|
|
8015
8152
|
v2.log("v2-auto", { session: session.sessionId, section: sec.id, reason: res.reason });
|
|
8016
|
-
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) })));
|
|
8017
8154
|
}
|
|
8018
8155
|
const fresh = res.hint.filter((h) => !session.hinted?.includes(h.id)).slice(0, 3);
|
|
8019
|
-
if (!fresh.length) return {};
|
|
8156
|
+
if (!fresh.length) return withLive({});
|
|
8020
8157
|
v2.updateSession(session.sessionId, ctx.cwd, (s) => {
|
|
8021
8158
|
s.hinted = s.hinted ?? [];
|
|
8022
8159
|
addUnique(s.hinted, ...fresh.map((h) => h.id));
|
|
8023
8160
|
addUnique(s.served, ...fresh.map((h) => h.id));
|
|
8024
8161
|
});
|
|
8025
|
-
return additional("UserPromptSubmit", hintLine(fresh.map((h) => h.title), config.harness));
|
|
8162
|
+
return withLive(additional("UserPromptSubmit", hintLine(fresh.map((h) => h.title), config.harness)));
|
|
8026
8163
|
}
|
|
8027
8164
|
function toolPaths(input) {
|
|
8028
8165
|
if (!input || typeof input !== "object") return [];
|
|
@@ -8038,16 +8175,21 @@ function onPreToolUse(ctx) {
|
|
|
8038
8175
|
const session = touch(v2, ctx, (s) => {
|
|
8039
8176
|
s.lastActivityAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8040
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
|
+
};
|
|
8041
8183
|
const raw = toolPaths(ctx.input.tool_input);
|
|
8042
|
-
if (!raw.length) return {};
|
|
8184
|
+
if (!raw.length) return withLive({});
|
|
8043
8185
|
const config = ctx.store.config();
|
|
8044
8186
|
const view = localView(v2, config, ctx.cwd);
|
|
8045
8187
|
const paths = raw.map((p) => relativeToRoot(view.root, p, ctx.cwd)).filter((p) => !!p);
|
|
8046
8188
|
const text2 = [session.task, session.lastPrompt].filter(Boolean).join("\n");
|
|
8047
|
-
if (!paths.length && !text2) return {};
|
|
8189
|
+
if (!paths.length && !text2) return withLive({});
|
|
8048
8190
|
const seen = /* @__PURE__ */ new Set([...session.pushed, ...session.loaded]);
|
|
8049
8191
|
const hits = push(view.sections, view.reader, { paths, text: text2 }).filter((h) => !seen.has(h.section.id)).slice(0, MAX_PUSH);
|
|
8050
|
-
if (!hits.length) return {};
|
|
8192
|
+
if (!hits.length) return withLive({});
|
|
8051
8193
|
const ids = hits.map((h) => h.section.id);
|
|
8052
8194
|
v2.updateSession(session.sessionId, ctx.cwd, (s) => {
|
|
8053
8195
|
addUnique(s.pushed, ...ids);
|
|
@@ -8056,7 +8198,7 @@ function onPreToolUse(ctx) {
|
|
|
8056
8198
|
});
|
|
8057
8199
|
v2.log("v2-push", { session: session.sessionId, tool: ctx.input.tool_name, hits: hits.map((h) => `${h.section.id} ${h.trigger}`) });
|
|
8058
8200
|
const deployed = deployEnabled(config, view.ctx);
|
|
8059
|
-
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")));
|
|
8060
8202
|
}
|
|
8061
8203
|
function onStop(ctx) {
|
|
8062
8204
|
const v2 = new V2Store(ctx.store);
|