@bunshin-cc/bunshin 0.1.1 → 0.1.2
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/README.md +3 -1
- package/dist/bin.js +40 -8
- package/package.json +11 -10
package/README.md
CHANGED
|
@@ -14,7 +14,9 @@ bunshin setup --harness codex
|
|
|
14
14
|
|
|
15
15
|
In Bunshin, open **Agent keys** and create a key for this computer. Paste it into the hidden setup prompt. Setup reads your role, team, and permissions from the workspace, saves the key in a private file outside the project, and configures four project hooks. It does not need a separate model API key.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Run `codex` interactively in the same project. Review and accept its project trust prompt, then run `/hooks` and review and trust the four Bunshin hooks. Project trust and hook trust are separate steps. If `/hooks` does not list Bunshin, check that you opened the connected project and trusted its configuration. This guide tests interactive sessions; `codex exec` is not a substitute for this setup check.
|
|
18
|
+
|
|
19
|
+
For Claude Code, use `bunshin setup --harness claude-code`, then start a new session in the project.
|
|
18
20
|
|
|
19
21
|
For Pi, use Node.js 22.19 or later and run `bunshin setup --harness pi`. It installs a project extension in `.pi/extensions/bunshin.js`. Start Pi in the project (or run `/reload` in an existing session), review its project resources, and check `/bunshin status`. The extension ships in this npm package; you do not need a second package or a repository checkout.
|
|
20
22
|
|
package/dist/bin.js
CHANGED
|
@@ -753,9 +753,9 @@ function mergeClaudeSettings(settings, { bin = "bunshin", harness = "claude-code
|
|
|
753
753
|
for (const { event, arg, timeout } of HOOK_EVENTS) {
|
|
754
754
|
const groups = Array.isArray(hooks[event]) ? [...hooks[event]] : [];
|
|
755
755
|
const hook = bunshinHookEntry(arg, timeout, bin);
|
|
756
|
+
hook.command = `BUNSHIN_HARNESS=${harness} ${hook.command}`;
|
|
756
757
|
if (harness === "codex") {
|
|
757
758
|
delete hook._bunshin;
|
|
758
|
-
hook.command = `BUNSHIN_HARNESS=codex ${hook.command}`;
|
|
759
759
|
if (event === "UserPromptSubmit") {
|
|
760
760
|
hook.async = true;
|
|
761
761
|
hook.timeout = 30;
|
|
@@ -1861,6 +1861,8 @@ var init_prompts = __esm({
|
|
|
1861
1861
|
|
|
1862
1862
|
2. IS THERE A REUSABLE WORKFLOW? If the session carried out a multi-step, end-to-end procedure that someone would plausibly need to repeat (release, migration, end-to-end test of a ticket, onboarding a service, an incident runbook), emit a WORKFLOW lesson: preconditions, ordered imperative steps each with how to verify it succeeded, and what "done" looks like. Write the steps so an agent with no memory of this session could execute them reliably; include exact commands, paths and checks where they were shown. Otherwise emit FACT lessons: short, one idea each, the trap and the fix.
|
|
1863
1863
|
|
|
1864
|
+
Workflow evidence is mandatory. Include verificationEvidence: exact, contiguous quotes from successful TOOL RESULT output in this excerpt showing that the procedure's checks actually ran. A user request, a plan, code printed in an answer, a tool invocation without its result, or the assistant saying "done" is not execution evidence. Do not turn a proposed procedure into a workflow, even if the proposal gives exact paths and commands. Every workflow needs at least two steps, a verification check for every step, and an explicit outcome. Without observed verification, omit the workflow. You may save a useful human-stated requirement or reported process as a FACT, clearly attributed as such; never imply that requested files already exist or that planned checks passed.
|
|
1865
|
+
|
|
1864
1866
|
3. ATTRIBUTION. For every served lesson listed, decide whether the agent ACTUALLY USED it. Default is false. On-topic is not used. Used means the agent's behaviour visibly depended on it: it followed the workflow, avoided the trap, cited the fact, chose differently because of it. Classify each served lesson exactly one way: used, irrelevant (did not apply to this task), redundant (applied but the agent already knew or the repo showed it), relevant-but-ignored (applied and would have helped, agent did not act on it). Then list MISSED lessons: ids from the full index that were relevant and would have helped but were not served.
|
|
1865
1867
|
|
|
1866
1868
|
Also give the session an outcome (success / failure / unknown) and whether the user had to correct the agent.
|
|
@@ -1902,7 +1904,8 @@ Content rules: write lessons for a stranger, name systems and teams explicitly,
|
|
|
1902
1904
|
preconditions: { type: "array", items: { type: "string" } },
|
|
1903
1905
|
steps: { type: "array", items: workflowStep },
|
|
1904
1906
|
outcome: { type: "string" },
|
|
1905
|
-
estimatedMinutes: { type: "integer" }
|
|
1907
|
+
estimatedMinutes: { type: "integer" },
|
|
1908
|
+
verificationEvidence: { type: "array", items: { type: "string" }, description: "For workflows, exact quotes from successful tool results showing completed verification. Not user requests or assistant claims." }
|
|
1906
1909
|
}
|
|
1907
1910
|
}
|
|
1908
1911
|
},
|
|
@@ -2433,13 +2436,42 @@ async function reflectSession(store, opts) {
|
|
|
2433
2436
|
return { ...empty, skipped: res.error };
|
|
2434
2437
|
}
|
|
2435
2438
|
const out = res.value;
|
|
2436
|
-
const lessonIds =
|
|
2439
|
+
const lessonIds = [];
|
|
2440
|
+
for (const raw of out.lessons) {
|
|
2441
|
+
const rejection = workflowRejection(raw, facts, excerpt);
|
|
2442
|
+
if (rejection) {
|
|
2443
|
+
store.log("reflect-workflow-rejected", { session: opts.sessionId, title: redact(raw.title), reason: rejection });
|
|
2444
|
+
continue;
|
|
2445
|
+
}
|
|
2446
|
+
const lesson = store.saveLesson(buildLesson(raw, store, opts), { reindex: false });
|
|
2447
|
+
lessonIds.push(lesson.id);
|
|
2448
|
+
if (lesson.kind === "workflow") store.appendTraceEvent(opts.sessionId, {
|
|
2449
|
+
type: "workflow-evidence",
|
|
2450
|
+
lessonId: lesson.id,
|
|
2451
|
+
quotes: raw.verificationEvidence.map(redact)
|
|
2452
|
+
});
|
|
2453
|
+
}
|
|
2437
2454
|
const { used, missed } = applyAttribution(store, opts.sessionId, out, served);
|
|
2438
2455
|
store.rebuildIndex();
|
|
2439
2456
|
store.writeSession(opts.sessionId, { ...store.readSession(opts.sessionId), reflectedMessages: facts.turns.length, lastReflectAt: nowIso() });
|
|
2440
2457
|
store.log("reflect", { session: opts.sessionId, lessons: lessonIds, used, missed, outcome: out.outcome, corrected: out.corrected });
|
|
2441
2458
|
return { lessonIds, used, missed, attribution: out.attribution ?? [] };
|
|
2442
2459
|
}
|
|
2460
|
+
function workflowRejection(raw, facts, excerpt) {
|
|
2461
|
+
if (raw.kind !== "workflow") return;
|
|
2462
|
+
if (!Array.isArray(raw.steps) || raw.steps.length < 2 || raw.steps.some((step) => !step.action?.trim() || !step.verify?.trim()) || !raw.outcome?.trim()) {
|
|
2463
|
+
return "workflow requires two steps, per-step checks, and an outcome";
|
|
2464
|
+
}
|
|
2465
|
+
const normalize = (text) => redact(text).replace(/\s+/g, " ").trim();
|
|
2466
|
+
const visible = normalize(excerpt);
|
|
2467
|
+
const results = facts.turns.filter((turn) => turn.role === "tool" && turn.tool === "result" && !turn.isError).map((turn) => normalize(turn.text));
|
|
2468
|
+
const evidence = raw.verificationEvidence;
|
|
2469
|
+
if (!Array.isArray(evidence) || !evidence.length || evidence.some((quote) => {
|
|
2470
|
+
if (typeof quote !== "string") return true;
|
|
2471
|
+
const text = normalize(quote);
|
|
2472
|
+
return !text || !visible.includes(text) || !results.some((result) => result.includes(text));
|
|
2473
|
+
})) return "workflow requires matching verification evidence from successful tool results";
|
|
2474
|
+
}
|
|
2443
2475
|
function countFacts(turns) {
|
|
2444
2476
|
return {
|
|
2445
2477
|
turns,
|
|
@@ -3026,7 +3058,7 @@ async function main(argv, opts = {}) {
|
|
|
3026
3058
|
}) : process.env.BUNSHIN_API_KEY || (!apiUrl || apiUrl === existing.apiUrl) && existing.apiKey || await readKey2();
|
|
3027
3059
|
out("Connecting to Bunshin\u2026\n");
|
|
3028
3060
|
const result = await setupProject2({ cwd, harness, apiKey: key, apiUrl, noHooks: bool(args.flags["no-hooks"]) });
|
|
3029
|
-
out(`Connected as ${result.profile.role} in ${result.profile.orgUnit}.
|
|
3061
|
+
out(`Connected as ${result.profile.role}${result.profile.orgUnit ? ` in ${result.profile.orgUnit}` : " (company-wide)"}.
|
|
3030
3062
|
`);
|
|
3031
3063
|
out(`Project: ${cwd}
|
|
3032
3064
|
Key saved privately outside the project.
|
|
@@ -3034,7 +3066,7 @@ Key saved privately outside the project.
|
|
|
3034
3066
|
if (result.hooks?.error) throw new Error(`Connected, but hooks could not be configured: ${result.hooks.error}. Fix the file and run setup again.`);
|
|
3035
3067
|
if (result.hooks) out(`${result.hooks.installed} ${harness === "pi" ? "extension" : "hooks"} configured in ${result.hooks.file}
|
|
3036
3068
|
`);
|
|
3037
|
-
if (harness === "codex" && result.hooks) out("
|
|
3069
|
+
if (harness === "codex" && result.hooks) out("Run codex interactively in this project. Review and accept project trust, then run /hooks and review and trust the four Bunshin hooks.\n");
|
|
3038
3070
|
else if (harness === "pi" && result.hooks) out("Open Pi in this project and approve its project extension. Check it with /bunshin status.\n");
|
|
3039
3071
|
else if (result.hooks) out("Open a new Claude Code session in this project to load the hooks.\n");
|
|
3040
3072
|
else out("Hooks are disabled. Run setup again without --no-hooks to configure them.\n");
|
|
@@ -3054,7 +3086,7 @@ Key saved privately outside the project.
|
|
|
3054
3086
|
}
|
|
3055
3087
|
const store = openStore(args, cwd);
|
|
3056
3088
|
if (!store) {
|
|
3057
|
-
err("bunshin:
|
|
3089
|
+
err("bunshin: this project is not connected. Run `bunshin setup --harness codex` (or use --harness claude-code / --harness pi). For offline use, run `bunshin init`.\n");
|
|
3058
3090
|
return 1;
|
|
3059
3091
|
}
|
|
3060
3092
|
try {
|
|
@@ -3138,7 +3170,7 @@ async function cmdInit(args, cwd, out, err) {
|
|
|
3138
3170
|
if (result.hooks.error) err(` hooks: ${result.hooks.error}
|
|
3139
3171
|
`);
|
|
3140
3172
|
}
|
|
3141
|
-
if (result.config.harness === "codex" && result.hooks?.installed) out("\
|
|
3173
|
+
if (result.config.harness === "codex" && result.hooks?.installed) out("\nRun codex interactively in this project. Review and accept project trust, then run /hooks and review and trust the four Bunshin hooks.\n");
|
|
3142
3174
|
if (!result.config.apiUrl) out("\nTo connect this project to your workspace, run `bunshin setup --harness " + result.config.harness + "`.\n");
|
|
3143
3175
|
for (const w of result.warnings) err(`! ${w}
|
|
3144
3176
|
`);
|
|
@@ -3172,7 +3204,7 @@ function cmdStatus(store, args, out) {
|
|
|
3172
3204
|
mothership: config.apiUrl ? { apiUrl: config.apiUrl, key: config.apiKey ? "set" : "missing", lastSyncAt: config.lastSyncAt ?? null } : null,
|
|
3173
3205
|
llmBackend: resolveBackend(config) ?? "none (set ANTHROPIC_API_KEY or install the claude/codex CLI)",
|
|
3174
3206
|
hooks: { [config.harness]: hooks },
|
|
3175
|
-
hookFiles,
|
|
3207
|
+
hookFiles: hookFiles.filter((file) => fs9.existsSync(file)),
|
|
3176
3208
|
recallQuality: last50 ? { precision: last50.precision, recall: last50.recall, f1: last50.f1, traces: last50.traces } : null
|
|
3177
3209
|
};
|
|
3178
3210
|
if (bool(args.flags.json)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bunshin-cc/bunshin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Shared memory for company AI agents. Connect Codex, Claude Code, or Pi to Bunshin.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,6 +14,13 @@
|
|
|
14
14
|
"engines": {
|
|
15
15
|
"node": ">=20"
|
|
16
16
|
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc -p tsconfig.json --noEmit && esbuild src/bin.ts --bundle --platform=node --target=node20 --format=esm --external:@anthropic-ai/sdk --external:yaml --outfile=dist/bin.js && esbuild ../../adapters/pi/index.ts --bundle --platform=node --target=node22 --format=esm --outfile=dist/pi-extension.js",
|
|
19
|
+
"dev": "tsx src/bin.ts",
|
|
20
|
+
"test": "vitest run",
|
|
21
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
22
|
+
"prepack": "pnpm build"
|
|
23
|
+
},
|
|
17
24
|
"dependencies": {
|
|
18
25
|
"@anthropic-ai/sdk": "^0.127.0",
|
|
19
26
|
"yaml": "^2.8.0"
|
|
@@ -24,7 +31,7 @@
|
|
|
24
31
|
"tsx": "^4.20.0",
|
|
25
32
|
"typescript": "^5.8.0",
|
|
26
33
|
"vitest": "^3.2.0",
|
|
27
|
-
"@bunshin/core": "
|
|
34
|
+
"@bunshin/core": "workspace:*"
|
|
28
35
|
},
|
|
29
36
|
"license": "MIT",
|
|
30
37
|
"homepage": "https://bunshin.cc",
|
|
@@ -47,11 +54,5 @@
|
|
|
47
54
|
"claude-code",
|
|
48
55
|
"memory",
|
|
49
56
|
"knowledge"
|
|
50
|
-
]
|
|
51
|
-
|
|
52
|
-
"build": "tsc -p tsconfig.json --noEmit && esbuild src/bin.ts --bundle --platform=node --target=node20 --format=esm --external:@anthropic-ai/sdk --external:yaml --outfile=dist/bin.js && esbuild ../../adapters/pi/index.ts --bundle --platform=node --target=node22 --format=esm --outfile=dist/pi-extension.js",
|
|
53
|
-
"dev": "tsx src/bin.ts",
|
|
54
|
-
"test": "vitest run",
|
|
55
|
-
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
56
|
-
}
|
|
57
|
-
}
|
|
57
|
+
]
|
|
58
|
+
}
|