@bunshin-cc/bunshin 0.1.0 → 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.
Files changed (3) hide show
  1. package/README.md +3 -1
  2. package/dist/bin.js +41 -9
  3. 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
- Open a new Codex session in the same project, run `/hooks`, and review and trust the four Bunshin hooks. For Claude Code, use `bunshin setup --harness claude-code`, then start a new session in the project.
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;
@@ -957,7 +957,7 @@ async function connectionProfile(apiUrl, apiKey) {
957
957
  if (!response.ok) throw new Error(`Bunshin returned HTTP ${response.status}. No connection settings were changed.`);
958
958
  const body = await response.json();
959
959
  const profile = body.profile;
960
- if (!profile || ![profile.orgId, profile.userId, profile.role, profile.orgUnit].every((value) => typeof value === "string" && value.length > 0) || !["public", "org", "department", "team", "leadership", "csuite", "private"].includes(profile.clearance) || !Array.isArray(profile.responsibilities) || !profile.responsibilities.every((value) => typeof value === "string")) {
960
+ if (!profile || ![profile.orgId, profile.userId, profile.role].every((value) => typeof value === "string" && value.length > 0) || typeof profile.orgUnit !== "string" || !["public", "org", "department", "team", "leadership", "csuite", "private"].includes(profile.clearance) || !Array.isArray(profile.responsibilities) || !profile.responsibilities.every((value) => typeof value === "string")) {
961
961
  throw new Error("Bunshin returned an incomplete profile. No connection settings were changed.");
962
962
  }
963
963
  return profile;
@@ -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 = out.lessons.map((raw) => store.saveLesson(buildLesson(raw, store, opts), { reindex: false }).id);
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("Open a new Codex session in this project, run /hooks, and review and trust the four Bunshin hooks.\n");
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: no .bunshin store found. Run `bunshin init` first.\n");
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("\nOpen a new Codex session, run /hooks, and review and trust the four Bunshin hooks.\n");
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.0",
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": "0.1.0"
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
- "scripts": {
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
+ }