@bunshin-cc/bunshin 0.8.1 → 0.9.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 +81 -16
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -2770,8 +2770,17 @@ A lesson is read by agents on other tasks, months from now. Write the rule, not
|
|
|
2770
2770
|
|
|
2771
2771
|
Every entry is a whole document (a skill, a spec, a policy). Prefer editing an existing document
|
|
2772
2772
|
over creating one, and aim the edit: "anchor" names the heading (from its headings list) the text
|
|
2773
|
-
belongs under, so it lands in the right part of the document.
|
|
2774
|
-
|
|
2773
|
+
belongs under, so it lands in the right part of the document.
|
|
2774
|
+
|
|
2775
|
+
Steering and approach lessons (a, e) are the exception: each is its own short lesson. Use op
|
|
2776
|
+
"create" with kind "rule": a title naming the behaviour, a "when" naming the situations it applies
|
|
2777
|
+
in (as the user would phrase a request there, even one that never mentions it), and a body of at
|
|
2778
|
+
most about 120 words: the rule, why, and when it was learned. Do not append such a lesson inside a
|
|
2779
|
+
long skill or procedure: a rule buried in a long document goes unfollowed even when the document is
|
|
2780
|
+
loaded, and the user ends up repeating it. If a short rule in the list below already says it, append
|
|
2781
|
+
to that rule instead. Traps, procedures and facts (b, c, d) still go into the document they belong to.
|
|
2782
|
+
|
|
2783
|
+
Library documents relevant to this session:
|
|
2775
2784
|
${index}
|
|
2776
2785
|
${input.skills?.length ? `
|
|
2777
2786
|
Skills in the library (target any of them by id, listed here or not):
|
|
@@ -2791,9 +2800,9 @@ A fact, command or habit of this project \u2192 this project. True of every proj
|
|
|
2791
2800
|
that group. True anywhere this workspace works \u2192 org. Lessons that come up again in other scopes
|
|
2792
2801
|
are widened by the nightly generalization pass, so a narrow scope is never a loss. Name it as
|
|
2793
2802
|
"layer" in the target of a new document (an edit keeps the document's own scope).
|
|
2794
|
-
` : ""}${input.profileId ? `- How THIS person works
|
|
2795
|
-
|
|
2796
|
-
|
|
2803
|
+
` : ""}${input.profileId ? `- How THIS person works in every task alike (tone, length, how they like to be asked) \u2192
|
|
2804
|
+
op "append" to their profile ${input.profileId}, anchor "how-i-work". A preference that applies in
|
|
2805
|
+
one kind of situation (how they want PRs, reports, deploys) is a short rule lesson in their layer.
|
|
2797
2806
|
` : ""}- Knowledge about one repo that its team owns \u2192 that repo's section (repo:... ids).
|
|
2798
2807
|
- Cross-repo facts, another team's rules, non-code processes \u2192 a Bunshin section at the right layer.
|
|
2799
2808
|
- If it only holds in some contexts, use op "append" with a "condition" (e.g. "objects over 50 g").
|
|
@@ -6878,13 +6887,42 @@ function skillHint(sec, others) {
|
|
|
6878
6887
|
const also = others.length ? ` Also possibly relevant: ${others.map((t) => `"${t}"`).join(", ")}.` : "";
|
|
6879
6888
|
return `Bunshin: the \`${sec.skill.name}\` skill may apply: ${sec.skill.description.replace(/\s+/g, " ").trim()} If it does, invoke it (Skill tool, or \`/${sec.skill.name}\`) before doing this by hand.${also}`;
|
|
6880
6889
|
}
|
|
6881
|
-
|
|
6890
|
+
function lastReply(transcriptPath, maxBytes = 256e3) {
|
|
6891
|
+
if (!transcriptPath) return void 0;
|
|
6892
|
+
try {
|
|
6893
|
+
const fd = fs18.openSync(transcriptPath, "r");
|
|
6894
|
+
try {
|
|
6895
|
+
const size = fs18.fstatSync(fd).size;
|
|
6896
|
+
const len = Math.min(size, maxBytes);
|
|
6897
|
+
const buf = Buffer.alloc(len);
|
|
6898
|
+
fs18.readSync(fd, buf, 0, len, size - len);
|
|
6899
|
+
const lines = buf.toString("utf8").split("\n");
|
|
6900
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
6901
|
+
let m;
|
|
6902
|
+
try {
|
|
6903
|
+
m = JSON.parse(lines[i]);
|
|
6904
|
+
} catch {
|
|
6905
|
+
continue;
|
|
6906
|
+
}
|
|
6907
|
+
if (m?.type !== "assistant" || m.isSidechain) continue;
|
|
6908
|
+
const c = m.message?.content;
|
|
6909
|
+
const text2 = Array.isArray(c) ? c.filter((b) => b?.type === "text").map((b) => String(b.text ?? "")).join("\n") : typeof c === "string" ? c : "";
|
|
6910
|
+
if (text2.trim()) return redact2(text2.trim()).slice(0, 1500);
|
|
6911
|
+
}
|
|
6912
|
+
} finally {
|
|
6913
|
+
fs18.closeSync(fd);
|
|
6914
|
+
}
|
|
6915
|
+
} catch {
|
|
6916
|
+
}
|
|
6917
|
+
return void 0;
|
|
6918
|
+
}
|
|
6919
|
+
async function remoteAuto(ctx, prompt, exclude, source, context) {
|
|
6882
6920
|
const config = ctx.store.config();
|
|
6883
6921
|
if (!canCall(config) || !config.recall.useMothership) return null;
|
|
6884
6922
|
const budget = Math.min(2500, ctx.deadline - Date.now() - 600);
|
|
6885
6923
|
if (budget < 300) return null;
|
|
6886
6924
|
try {
|
|
6887
|
-
const res = await v2api(config, "POST", "/recall/auto", { prompt: redact2(prompt).slice(0, 8e3), exclude, ...source ? { source } : {} }, { timeoutMs: budget });
|
|
6925
|
+
const res = await v2api(config, "POST", "/recall/auto", { prompt: redact2(prompt).slice(0, 8e3), exclude, ...source ? { source } : {}, ...context ? { context } : {} }, { timeoutMs: budget });
|
|
6888
6926
|
return res.available ? res : null;
|
|
6889
6927
|
} catch {
|
|
6890
6928
|
return null;
|
|
@@ -6908,6 +6946,7 @@ async function onPrompt(ctx) {
|
|
|
6908
6946
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6909
6947
|
const session = touch(v2, ctx, (s) => {
|
|
6910
6948
|
if (!s.task && prompt.trim()) s.task = redact2(prompt.trim()).slice(0, 2e3);
|
|
6949
|
+
if (s.lastPrompt && prompt.trim()) s.recentPrompts = [...s.recentPrompts ?? [], s.lastPrompt.slice(0, 600)].slice(-3);
|
|
6911
6950
|
s.lastPrompt = redact2(prompt).slice(0, 4e3);
|
|
6912
6951
|
s.lastActivityAt = now;
|
|
6913
6952
|
});
|
|
@@ -6939,19 +6978,45 @@ async function onPrompt(ctx) {
|
|
|
6939
6978
|
return withLive(deployed || !fresh2 ? {} : additional("UserPromptSubmit", renderSection(named, why, { deployed: false })));
|
|
6940
6979
|
}
|
|
6941
6980
|
const res = auto(view.sections, view.reader, prompt, { exclude: injected });
|
|
6942
|
-
const
|
|
6981
|
+
const recent = session.recentPrompts ?? [];
|
|
6982
|
+
const context = {
|
|
6983
|
+
...recent.length && session.task ? { first: session.task.slice(0, 1e3) } : {},
|
|
6984
|
+
...recent.length ? { recent } : {}
|
|
6985
|
+
};
|
|
6986
|
+
const reply = recent.length ? lastReply(ctx.input.transcript_path ?? session.transcriptPath) : void 0;
|
|
6987
|
+
if (reply) context.reply = reply;
|
|
6988
|
+
const remote = await remoteAuto(ctx, prompt, injected, session.source, Object.keys(context).length ? context : void 0);
|
|
6989
|
+
const local = (id) => view.sections.find((s) => s.id === id);
|
|
6943
6990
|
if (remote) {
|
|
6944
|
-
const
|
|
6991
|
+
const why = remote.router ? remote.reason ?? `router ${remote.router}` : `dense ${remote.reason ?? ""}`.trim();
|
|
6945
6992
|
if (remote.section) {
|
|
6946
6993
|
res.section = local(remote.section.id) ?? remote.section;
|
|
6947
6994
|
res.anchor = void 0;
|
|
6948
|
-
res.reason =
|
|
6995
|
+
res.reason = why;
|
|
6949
6996
|
} else {
|
|
6950
6997
|
res.section = void 0;
|
|
6951
6998
|
res.hint = (remote.hint ?? []).map((h) => ({ id: h.id, title: local(h.id)?.title ?? h.title }));
|
|
6952
|
-
res.reason =
|
|
6999
|
+
res.reason = why;
|
|
6953
7000
|
}
|
|
6954
7001
|
}
|
|
7002
|
+
const rules = (remote?.rules ?? []).filter((r) => !injected.includes(r.id) && r.id !== res.section?.id).map((r) => local(r.id) ?? r);
|
|
7003
|
+
let lessons = "";
|
|
7004
|
+
if (rules.length) {
|
|
7005
|
+
v2.updateSession(session.sessionId, ctx.cwd, (s) => {
|
|
7006
|
+
for (const r of rules) {
|
|
7007
|
+
addUnique(s.served, r.id);
|
|
7008
|
+
addUnique(s.loaded, r.id);
|
|
7009
|
+
markInContext(s, r.id);
|
|
7010
|
+
}
|
|
7011
|
+
});
|
|
7012
|
+
v2.log("v2-auto-rules", { session: session.sessionId, sections: rules.map((r) => r.id), reason: res.reason });
|
|
7013
|
+
updateFeed(v2, session.sessionId, (f) => {
|
|
7014
|
+
for (const r of [...rules].reverse()) addServed(f, { id: r.id, title: r.title, kind: r.kind, how: "auto", why: "a lesson learned before applies here" });
|
|
7015
|
+
addEvent(f, "served", `lessons: ${rules.map((r) => r.title).join(", ")}`);
|
|
7016
|
+
});
|
|
7017
|
+
lessons = rules.map((r) => renderSection(r, "a lesson learned before applies to this work", { deployed: false })).join("\n\n");
|
|
7018
|
+
}
|
|
7019
|
+
const withLessons = (text2) => withLive(additional("UserPromptSubmit", [text2, lessons].filter(Boolean).join("\n\n")));
|
|
6955
7020
|
if (res.section) {
|
|
6956
7021
|
const sec = res.section;
|
|
6957
7022
|
v2.updateSession(session.sessionId, ctx.cwd, (s) => {
|
|
@@ -6965,10 +7030,10 @@ async function onPrompt(ctx) {
|
|
|
6965
7030
|
addServed(f, { id: sec.id, title: sec.title, kind: sec.kind, ...sec.skill ? { skill: sec.skill.name } : {}, how: "auto", why });
|
|
6966
7031
|
addEvent(f, "served", `served ${label({ ...sec.skill ? { skill: sec.skill.name } : {}, title: sec.title, kind: sec.kind })} \u2014 ${why}`);
|
|
6967
7032
|
});
|
|
6968
|
-
return
|
|
7033
|
+
return withLessons(renderSection(sec, "your prompt matches it", { ...res.anchor ? { anchor: res.anchor } : {}, deployed: !!sec.skill && deployEnabled(config, view.ctx) }));
|
|
6969
7034
|
}
|
|
6970
|
-
const fresh = res.hint.filter((h) => !session.hinted?.includes(h.id)).slice(0, 3);
|
|
6971
|
-
if (!fresh.length) return
|
|
7035
|
+
const fresh = res.hint.filter((h) => !session.hinted?.includes(h.id) && !rules.some((r) => r.id === h.id)).slice(0, 3);
|
|
7036
|
+
if (!fresh.length) return withLessons("");
|
|
6972
7037
|
v2.updateSession(session.sessionId, ctx.cwd, (s) => {
|
|
6973
7038
|
s.hinted = s.hinted ?? [];
|
|
6974
7039
|
addUnique(s.hinted, ...fresh.map((h) => h.id));
|
|
@@ -6982,8 +7047,8 @@ async function onPrompt(ctx) {
|
|
|
6982
7047
|
addEvent(f, "hint", `possibly relevant: ${fresh.map((h) => h.title).join(", ")}`);
|
|
6983
7048
|
});
|
|
6984
7049
|
const topSkill = view.sections.find((s) => s.id === fresh[0].id && s.skill);
|
|
6985
|
-
if (topSkill && deployEnabled(config, view.ctx)) return
|
|
6986
|
-
return
|
|
7050
|
+
if (topSkill && deployEnabled(config, view.ctx)) return withLessons(skillHint(topSkill, fresh.slice(1).map((h) => h.title)));
|
|
7051
|
+
return withLessons(hintLine(fresh.map((h) => h.title), config.harness));
|
|
6987
7052
|
}
|
|
6988
7053
|
function toolPaths(input) {
|
|
6989
7054
|
if (!input || typeof input !== "object") return [];
|