@bunshin-cc/bunshin 0.3.1 → 0.3.3
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 +64 -9
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -3352,7 +3352,7 @@ function repoCommit(root) {
|
|
|
3352
3352
|
}
|
|
3353
3353
|
function listDocs(root) {
|
|
3354
3354
|
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();
|
|
3355
|
+
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
3356
|
const out = [];
|
|
3357
3357
|
let visited = 0;
|
|
3358
3358
|
const walk = (dir, rel, depth) => {
|
|
@@ -3365,10 +3365,10 @@ function listDocs(root) {
|
|
|
3365
3365
|
}
|
|
3366
3366
|
for (const e of entries) {
|
|
3367
3367
|
if (e.isDirectory()) {
|
|
3368
|
-
if (!SKIP_DIRS.has(e.name)) walk(path6.join(dir, e.name), rel ? `${rel}/${e.name}` : e.name, depth + 1);
|
|
3368
|
+
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
3369
|
} else if (e.isFile()) {
|
|
3370
3370
|
const r = rel ? `${rel}/${e.name}` : e.name;
|
|
3371
|
-
if (isDoc(r)) out.push(r);
|
|
3371
|
+
if (isDoc(r) && !deployedSkill(root, r)) out.push(r);
|
|
3372
3372
|
}
|
|
3373
3373
|
}
|
|
3374
3374
|
};
|
|
@@ -3448,7 +3448,7 @@ function relativeToRoot(root, file, cwd = root) {
|
|
|
3448
3448
|
}
|
|
3449
3449
|
return null;
|
|
3450
3450
|
}
|
|
3451
|
-
var DOC_NAMES, SKIP_DIRS, LIST_TTL_MS, rootCache, isDoc;
|
|
3451
|
+
var DOC_NAMES, SKIP_DIRS, LIST_TTL_MS, rootCache, isDoc, deployedSkill;
|
|
3452
3452
|
var init_repo = __esm({
|
|
3453
3453
|
"src/v2/repo.ts"() {
|
|
3454
3454
|
"use strict";
|
|
@@ -3462,6 +3462,7 @@ var init_repo = __esm({
|
|
|
3462
3462
|
if (parts.some((p) => SKIP_DIRS.has(p))) return false;
|
|
3463
3463
|
return DOC_NAMES.has(parts[parts.length - 1].toLowerCase());
|
|
3464
3464
|
};
|
|
3465
|
+
deployedSkill = (root, rel) => /(^|\/)SKILL\.md$/i.test(rel) && fs7.existsSync(path6.join(root, path6.posix.dirname(rel), ".bunshin.json"));
|
|
3465
3466
|
}
|
|
3466
3467
|
});
|
|
3467
3468
|
|
|
@@ -4175,11 +4176,13 @@ var init_exports = {};
|
|
|
4175
4176
|
__export(init_exports, {
|
|
4176
4177
|
CODEX_HOOK_EVENTS: () => CODEX_HOOK_EVENTS,
|
|
4177
4178
|
HOOK_EVENTS: () => HOOK_EVENTS,
|
|
4179
|
+
LEARN_COMMAND: () => LEARN_COMMAND,
|
|
4178
4180
|
PRE_TOOL_MATCHER: () => PRE_TOOL_MATCHER,
|
|
4179
4181
|
bunshinHookEntry: () => bunshinHookEntry,
|
|
4180
4182
|
claudeSettingsPath: () => claudeSettingsPath,
|
|
4181
4183
|
countBunshinHooks: () => countBunshinHooks,
|
|
4182
4184
|
installClaudeHooks: () => installClaudeHooks,
|
|
4185
|
+
installLearnCommand: () => installLearnCommand,
|
|
4183
4186
|
installMcpConfig: () => installMcpConfig,
|
|
4184
4187
|
installPiExtension: () => installPiExtension,
|
|
4185
4188
|
isBunshinEntry: () => isBunshinEntry,
|
|
@@ -4290,6 +4293,24 @@ function mcpLauncher(bin) {
|
|
|
4290
4293
|
}
|
|
4291
4294
|
return { command: process.execPath, args: [script, "mcp"] };
|
|
4292
4295
|
}
|
|
4296
|
+
function installLearnCommand({ file, remove = false }) {
|
|
4297
|
+
try {
|
|
4298
|
+
const current = fileExists(file) ? fs13.readFileSync(file, "utf8") : null;
|
|
4299
|
+
const ours = current === null || current === LEARN_COMMAND;
|
|
4300
|
+
if (remove) {
|
|
4301
|
+
if (current !== null && ours) fs13.unlinkSync(file);
|
|
4302
|
+
return { file, installed: 0, changed: current !== null && ours };
|
|
4303
|
+
}
|
|
4304
|
+
if (!ours) return { file, installed: 0, changed: false, error: "a different learn.md is already there; left as it is" };
|
|
4305
|
+
if (current === null) {
|
|
4306
|
+
fs13.mkdirSync(path10.dirname(file), { recursive: true });
|
|
4307
|
+
fs13.writeFileSync(file, LEARN_COMMAND);
|
|
4308
|
+
}
|
|
4309
|
+
return { file, installed: 1, changed: current === null };
|
|
4310
|
+
} catch (err) {
|
|
4311
|
+
return { file, installed: 0, changed: false, error: err instanceof Error ? err.message : String(err) };
|
|
4312
|
+
}
|
|
4313
|
+
}
|
|
4293
4314
|
function installMcpConfig({ file, launcher, remove = false }) {
|
|
4294
4315
|
try {
|
|
4295
4316
|
const before = fileExists(file) ? JSON.parse(fs13.readFileSync(file, "utf8")) : {};
|
|
@@ -4362,15 +4383,18 @@ function runInit(opts = {}) {
|
|
|
4362
4383
|
if (hooks.error) warnings.push(`could not update ${hooks.file}: ${hooks.error}`);
|
|
4363
4384
|
}
|
|
4364
4385
|
let mcp;
|
|
4386
|
+
let learnCommand;
|
|
4365
4387
|
if ((opts.removeHooks || !opts.noHooks) && config.harness === "claude-code" && scope === "repo") {
|
|
4366
4388
|
mcp = installMcpConfig({ file: path10.join(cwd, ".mcp.json"), launcher: opts.mcp ?? mcpLauncher(opts.bin), remove: opts.removeHooks });
|
|
4367
4389
|
if (mcp.error) warnings.push(`could not update ${mcp.file}: ${mcp.error}`);
|
|
4390
|
+
learnCommand = installLearnCommand({ file: path10.join(cwd, ".claude", "commands", "learn.md"), remove: opts.removeHooks });
|
|
4391
|
+
if (learnCommand.error) warnings.push(`/learn not installed at ${learnCommand.file}: ${learnCommand.error}`);
|
|
4368
4392
|
}
|
|
4369
4393
|
if (!resolveBackend(config)) {
|
|
4370
4394
|
warnings.push("No model backend found. Install and sign in to Codex or Claude Code before using automatic learning.");
|
|
4371
4395
|
}
|
|
4372
4396
|
store.log("init", { root, scope, role: config.role, orgUnit: config.orgUnit, hooks: hooks?.installed ?? 0 });
|
|
4373
|
-
return { root, config, created: !existed, hooks, ...mcp ? { mcp } : {}, warnings };
|
|
4397
|
+
return { root, config, created: !existed, hooks, ...mcp ? { mcp } : {}, ...learnCommand ? { learnCommand } : {}, warnings };
|
|
4374
4398
|
}
|
|
4375
4399
|
function otherHarnessInstructions() {
|
|
4376
4400
|
return [
|
|
@@ -4386,7 +4410,7 @@ function otherHarnessInstructions() {
|
|
|
4386
4410
|
" Claude Code plugin users need no settings.json wiring \u2014 the plugin ships hooks/hooks.json."
|
|
4387
4411
|
].join("\n");
|
|
4388
4412
|
}
|
|
4389
|
-
var PRE_TOOL_MATCHER, HOOK_EVENTS, CODEX_HOOK_EVENTS;
|
|
4413
|
+
var PRE_TOOL_MATCHER, HOOK_EVENTS, CODEX_HOOK_EVENTS, LEARN_COMMAND;
|
|
4390
4414
|
var init_init = __esm({
|
|
4391
4415
|
"src/init.ts"() {
|
|
4392
4416
|
"use strict";
|
|
@@ -4410,6 +4434,7 @@ var init_init = __esm({
|
|
|
4410
4434
|
{ event: "PreCompact", arg: "pre-compact", timeout: 10 },
|
|
4411
4435
|
{ event: "SessionEnd", arg: "session-end", timeout: 3 }
|
|
4412
4436
|
];
|
|
4437
|
+
LEARN_COMMAND = '---\ndescription: Capture what this session learned into the Bunshin library now (applied locally as a trial, submitted for the nightly review)\nargument-hint: "[what to capture, e.g. \\"the ledger must be reconciled before closing\\"]"\nallowed-tools: Bash(bunshin learn:*)\n---\n\nCapture knowledge from this session into Bunshin.\n\nRun exactly this with the Bash tool (it takes up to two minutes; use a timeout of 180000 ms):\n\n```bash\nbunshin learn "$ARGUMENTS"\n```\n\n`bunshin learn` forks this conversation (`claude -p --resume <this session> --fork-session`),\nso the reflection sees everything you saw without re-sending the transcript. It applies what it\nfinds locally as a trial and queues it for the nightly review:\n\n- edits to this repo\'s SPEC.md / TESTING.md / SKILL.md land in the working tree (they show up\n in `git diff`);\n- knowledge with no repo home becomes a local overlay on the Bunshin library;\n- per-machine values stay private.\n\nRelay its output to the user as it is. Each line reads\n`captured: <summary> \u2192 <section or file#anchor>`. If it prints `nothing new to capture.`, say so\nin one line; that is the normal outcome. If it reports an error, show it and stop; do not retry\nin a loop, and do not edit the library files by hand.\n\nIf the user named nothing specific, run it without arguments. Do not continue with other work in\nthe same reply unless the user asked you to.\n';
|
|
4413
4438
|
}
|
|
4414
4439
|
});
|
|
4415
4440
|
|
|
@@ -4491,6 +4516,7 @@ __export(commands_exports, {
|
|
|
4491
4516
|
cmdRecall: () => cmdRecall,
|
|
4492
4517
|
cmdReflectFork: () => cmdReflectFork,
|
|
4493
4518
|
cmdReview: () => cmdReview,
|
|
4519
|
+
cmdReviewDecide: () => cmdReviewDecide,
|
|
4494
4520
|
cmdSuggest: () => cmdSuggest,
|
|
4495
4521
|
cmdSync: () => cmdSync,
|
|
4496
4522
|
robotAttrsFromFlags: () => robotAttrsFromFlags,
|
|
@@ -4532,7 +4558,7 @@ function cmdReview(store, args, cwd, out, err) {
|
|
|
4532
4558
|
`);
|
|
4533
4559
|
}
|
|
4534
4560
|
out(`
|
|
4535
|
-
bunshin review apply <id> | submit <id> | skip <id> | apply-all
|
|
4561
|
+
bunshin review apply <id> | submit <id> | skip <id> | apply-all (owners: approve <id> | reject <id> [--note])
|
|
4536
4562
|
`);
|
|
4537
4563
|
return 0;
|
|
4538
4564
|
}
|
|
@@ -4574,6 +4600,29 @@ bunshin review apply <id> | submit <id> | skip <id> | apply-all
|
|
|
4574
4600
|
`);
|
|
4575
4601
|
return 1;
|
|
4576
4602
|
}
|
|
4603
|
+
async function cmdReviewDecide(store, args, out, err) {
|
|
4604
|
+
const sub = args._[1];
|
|
4605
|
+
const id = args._[2];
|
|
4606
|
+
if (!id) {
|
|
4607
|
+
err(`bunshin review ${sub}: a suggestion id is required (see the console's Review, or bunshin review list --all)
|
|
4608
|
+
`);
|
|
4609
|
+
return 1;
|
|
4610
|
+
}
|
|
4611
|
+
const config = store.config();
|
|
4612
|
+
if (!canCall(config)) {
|
|
4613
|
+
err(`bunshin review ${sub}: this project is not connected to Bunshin
|
|
4614
|
+
`);
|
|
4615
|
+
return 1;
|
|
4616
|
+
}
|
|
4617
|
+
const note = str(args.flags.note);
|
|
4618
|
+
const res = await v2api(config, "POST", `/suggestions/${encodeURIComponent(id)}/review`, {
|
|
4619
|
+
approve: sub === "approve",
|
|
4620
|
+
...note ? { note } : {}
|
|
4621
|
+
});
|
|
4622
|
+
out(`${res.suggestion.id}: ${res.suggestion.status}${res.suggestion.decision?.reason ? ` (${res.suggestion.decision.reason})` : ""}
|
|
4623
|
+
`);
|
|
4624
|
+
return 0;
|
|
4625
|
+
}
|
|
4577
4626
|
async function cmdRecall(store, args, cwd, out, err) {
|
|
4578
4627
|
const abstract = args._.slice(1).join(" ").trim() || str(args.flags.abstract) || "";
|
|
4579
4628
|
if (!abstract) {
|
|
@@ -8320,8 +8369,9 @@ Usage: bunshin <command> [options]
|
|
|
8320
8369
|
print one document (or one part of it) and mark it loaded
|
|
8321
8370
|
learn [focus...] capture what this session learned now (fork reflection),
|
|
8322
8371
|
apply it locally as a trial and submit it
|
|
8323
|
-
review [list [--all]|apply <id>|submit <id>|skip <id>|apply-all]
|
|
8324
|
-
suggestions awaiting review (apply = local trial + submit
|
|
8372
|
+
review [list [--all]|apply <id>|submit <id>|skip <id>|apply-all|approve <id>|reject <id> [--note <text>]]
|
|
8373
|
+
suggestions awaiting review (apply = local trial + submit;
|
|
8374
|
+
approve / reject = owner decision: merge now, or keep it out)
|
|
8325
8375
|
index [--upload] [--json] parse SPEC.md / TESTING.md / SKILL.md into sections
|
|
8326
8376
|
sync [--index] [--json] upload episodes + suggestions (incl. in-place skill edits), pull
|
|
8327
8377
|
the library, install skills / instructions / files, settle trials
|
|
@@ -8440,6 +8490,10 @@ Key saved privately outside the project.
|
|
|
8440
8490
|
`);
|
|
8441
8491
|
if (result.hooks?.error) throw new Error(`Connected, but hooks could not be configured: ${result.hooks.error}. Fix the file and run setup again.`);
|
|
8442
8492
|
if (result.hooks) out(`${result.hooks.installed} ${harness === "pi" ? "extension" : "hooks"} configured in ${result.hooks.file}
|
|
8493
|
+
`);
|
|
8494
|
+
if (result.learnCommand?.installed) out(`/learn installed in ${result.learnCommand.file}
|
|
8495
|
+
`);
|
|
8496
|
+
else if (result.learnCommand?.error) out(`/learn not installed: ${result.learnCommand.error} (${result.learnCommand.file})
|
|
8443
8497
|
`);
|
|
8444
8498
|
if (result.hooks?.sharedWithWorktrees) out("Codex shares these hooks with the repository\u2019s linked worktrees. Each connected worktree keeps its own Bunshin store.\n");
|
|
8445
8499
|
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 five Bunshin hooks.\n");
|
|
@@ -8487,6 +8541,7 @@ Key saved privately outside the project.
|
|
|
8487
8541
|
case "suggest":
|
|
8488
8542
|
return (await Promise.resolve().then(() => (init_commands(), commands_exports))).cmdSuggest(store, args, cwd, out, err);
|
|
8489
8543
|
case "review":
|
|
8544
|
+
if (args._[1] === "approve" || args._[1] === "reject") return await (await Promise.resolve().then(() => (init_commands(), commands_exports))).cmdReviewDecide(store, args, out, err);
|
|
8490
8545
|
return (await Promise.resolve().then(() => (init_commands(), commands_exports))).cmdReview(store, args, cwd, out, err);
|
|
8491
8546
|
case "questions":
|
|
8492
8547
|
case "question":
|