@bunshin-cc/bunshin 0.3.0 → 0.3.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/dist/bin.js +83 -8
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -2489,19 +2489,49 @@ import { createHash } from "node:crypto";
|
|
|
2489
2489
|
import fs5 from "node:fs";
|
|
2490
2490
|
import os from "node:os";
|
|
2491
2491
|
import path4 from "node:path";
|
|
2492
|
+
function deployWorkspace(env = process.env) {
|
|
2493
|
+
if (env.BUNSHIN_DEPLOY_WORKSPACE) return path4.resolve(env.BUNSHIN_DEPLOY_WORKSPACE);
|
|
2494
|
+
try {
|
|
2495
|
+
const ws = JSON.parse(fs5.readFileSync(settingsFile(env.BUNSHIN_DEPLOY_HOME || os.homedir()), "utf8")).workspace;
|
|
2496
|
+
return typeof ws === "string" && ws ? ws : null;
|
|
2497
|
+
} catch {
|
|
2498
|
+
return null;
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2492
2501
|
function deployPaths(env = process.env) {
|
|
2493
2502
|
const home = env.BUNSHIN_DEPLOY_HOME || os.homedir();
|
|
2503
|
+
const ws = deployWorkspace(env);
|
|
2494
2504
|
return {
|
|
2495
2505
|
home,
|
|
2496
|
-
claudeSkills: path4.join(home, ".claude", "skills"),
|
|
2497
|
-
codexSkills: path4.join(home, ".codex", "skills"),
|
|
2498
|
-
claudeMd: path4.join(home, ".claude", "CLAUDE.md"),
|
|
2499
|
-
agentsMd: path4.join(home, ".codex", "AGENTS.md"),
|
|
2506
|
+
claudeSkills: path4.join(ws ?? home, ".claude", "skills"),
|
|
2507
|
+
codexSkills: path4.join(ws ?? home, ".codex", "skills"),
|
|
2508
|
+
claudeMd: ws ? path4.join(ws, "CLAUDE.md") : path4.join(home, ".claude", "CLAUDE.md"),
|
|
2509
|
+
agentsMd: ws ? path4.join(ws, "AGENTS.md") : path4.join(home, ".codex", "AGENTS.md"),
|
|
2500
2510
|
library: path4.join(home, ".bunshin", "library"),
|
|
2501
2511
|
bin: path4.join(home, ".bunshin", "bin"),
|
|
2502
2512
|
state: path4.join(home, ".bunshin", "deploy")
|
|
2503
2513
|
};
|
|
2504
2514
|
}
|
|
2515
|
+
function setDeployWorkspace(dir, env = process.env) {
|
|
2516
|
+
const home = env.BUNSHIN_DEPLOY_HOME || os.homedir();
|
|
2517
|
+
const ws = dir ? path4.resolve(dir.replace(/^~(?=$|\/)/, home)) : null;
|
|
2518
|
+
if (ws && !fs5.statSync(ws, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`${ws} is not a folder`);
|
|
2519
|
+
const stateDir = path4.join(home, ".bunshin", "deploy");
|
|
2520
|
+
const deployed = fs5.existsSync(stateDir) && fs5.readdirSync(stateDir).some((f) => {
|
|
2521
|
+
if (f === "settings.json" || !f.endsWith(".json")) return false;
|
|
2522
|
+
try {
|
|
2523
|
+
return Object.keys(JSON.parse(fs5.readFileSync(path4.join(stateDir, f), "utf8")).docs ?? {}).length > 0;
|
|
2524
|
+
} catch {
|
|
2525
|
+
return false;
|
|
2526
|
+
}
|
|
2527
|
+
});
|
|
2528
|
+
if (deployed && ws !== deployWorkspace({ ...env, BUNSHIN_DEPLOY_WORKSPACE: "" })) {
|
|
2529
|
+
throw new Error("The library is already deployed to the current location. Set the workspace before the first sync on this machine.");
|
|
2530
|
+
}
|
|
2531
|
+
fs5.mkdirSync(path4.dirname(settingsFile(home)), { recursive: true });
|
|
2532
|
+
fs5.writeFileSync(settingsFile(home), JSON.stringify(ws ? { workspace: ws } : {}, null, 2) + "\n");
|
|
2533
|
+
return ws;
|
|
2534
|
+
}
|
|
2505
2535
|
function statePath(p, orgId) {
|
|
2506
2536
|
return path4.join(p.state, `${orgId.replace(/[^A-Za-z0-9._-]/g, "_") || "org"}.json`);
|
|
2507
2537
|
}
|
|
@@ -3037,13 +3067,14 @@ function materialize(sections, dir) {
|
|
|
3037
3067
|
}
|
|
3038
3068
|
return { files };
|
|
3039
3069
|
}
|
|
3040
|
-
var sha, BLOCK_BEGIN, BLOCK_END, MANIFEST, MAX_FILE, isExecutable, LAYER_RANK, docMarker;
|
|
3070
|
+
var settingsFile, sha, BLOCK_BEGIN, BLOCK_END, MANIFEST, MAX_FILE, isExecutable, LAYER_RANK, docMarker;
|
|
3041
3071
|
var init_deploy = __esm({
|
|
3042
3072
|
"src/v2/deploy.ts"() {
|
|
3043
3073
|
"use strict";
|
|
3044
3074
|
init_v22();
|
|
3045
3075
|
init_util();
|
|
3046
3076
|
init_store2();
|
|
3077
|
+
settingsFile = (home) => path4.join(home, ".bunshin", "deploy", "settings.json");
|
|
3047
3078
|
sha = (text2) => createHash("sha256").update(text2).digest("hex");
|
|
3048
3079
|
BLOCK_BEGIN = "<!-- bunshin:begin -->";
|
|
3049
3080
|
BLOCK_END = "<!-- bunshin:end -->";
|
|
@@ -4144,11 +4175,13 @@ var init_exports = {};
|
|
|
4144
4175
|
__export(init_exports, {
|
|
4145
4176
|
CODEX_HOOK_EVENTS: () => CODEX_HOOK_EVENTS,
|
|
4146
4177
|
HOOK_EVENTS: () => HOOK_EVENTS,
|
|
4178
|
+
LEARN_COMMAND: () => LEARN_COMMAND,
|
|
4147
4179
|
PRE_TOOL_MATCHER: () => PRE_TOOL_MATCHER,
|
|
4148
4180
|
bunshinHookEntry: () => bunshinHookEntry,
|
|
4149
4181
|
claudeSettingsPath: () => claudeSettingsPath,
|
|
4150
4182
|
countBunshinHooks: () => countBunshinHooks,
|
|
4151
4183
|
installClaudeHooks: () => installClaudeHooks,
|
|
4184
|
+
installLearnCommand: () => installLearnCommand,
|
|
4152
4185
|
installMcpConfig: () => installMcpConfig,
|
|
4153
4186
|
installPiExtension: () => installPiExtension,
|
|
4154
4187
|
isBunshinEntry: () => isBunshinEntry,
|
|
@@ -4259,6 +4292,24 @@ function mcpLauncher(bin) {
|
|
|
4259
4292
|
}
|
|
4260
4293
|
return { command: process.execPath, args: [script, "mcp"] };
|
|
4261
4294
|
}
|
|
4295
|
+
function installLearnCommand({ file, remove = false }) {
|
|
4296
|
+
try {
|
|
4297
|
+
const current = fileExists(file) ? fs13.readFileSync(file, "utf8") : null;
|
|
4298
|
+
const ours = current === null || current === LEARN_COMMAND;
|
|
4299
|
+
if (remove) {
|
|
4300
|
+
if (current !== null && ours) fs13.unlinkSync(file);
|
|
4301
|
+
return { file, installed: 0, changed: current !== null && ours };
|
|
4302
|
+
}
|
|
4303
|
+
if (!ours) return { file, installed: 0, changed: false, error: "a different learn.md is already there; left as it is" };
|
|
4304
|
+
if (current === null) {
|
|
4305
|
+
fs13.mkdirSync(path10.dirname(file), { recursive: true });
|
|
4306
|
+
fs13.writeFileSync(file, LEARN_COMMAND);
|
|
4307
|
+
}
|
|
4308
|
+
return { file, installed: 1, changed: current === null };
|
|
4309
|
+
} catch (err) {
|
|
4310
|
+
return { file, installed: 0, changed: false, error: err instanceof Error ? err.message : String(err) };
|
|
4311
|
+
}
|
|
4312
|
+
}
|
|
4262
4313
|
function installMcpConfig({ file, launcher, remove = false }) {
|
|
4263
4314
|
try {
|
|
4264
4315
|
const before = fileExists(file) ? JSON.parse(fs13.readFileSync(file, "utf8")) : {};
|
|
@@ -4331,15 +4382,18 @@ function runInit(opts = {}) {
|
|
|
4331
4382
|
if (hooks.error) warnings.push(`could not update ${hooks.file}: ${hooks.error}`);
|
|
4332
4383
|
}
|
|
4333
4384
|
let mcp;
|
|
4385
|
+
let learnCommand;
|
|
4334
4386
|
if ((opts.removeHooks || !opts.noHooks) && config.harness === "claude-code" && scope === "repo") {
|
|
4335
4387
|
mcp = installMcpConfig({ file: path10.join(cwd, ".mcp.json"), launcher: opts.mcp ?? mcpLauncher(opts.bin), remove: opts.removeHooks });
|
|
4336
4388
|
if (mcp.error) warnings.push(`could not update ${mcp.file}: ${mcp.error}`);
|
|
4389
|
+
learnCommand = installLearnCommand({ file: path10.join(cwd, ".claude", "commands", "learn.md"), remove: opts.removeHooks });
|
|
4390
|
+
if (learnCommand.error) warnings.push(`/learn not installed at ${learnCommand.file}: ${learnCommand.error}`);
|
|
4337
4391
|
}
|
|
4338
4392
|
if (!resolveBackend(config)) {
|
|
4339
4393
|
warnings.push("No model backend found. Install and sign in to Codex or Claude Code before using automatic learning.");
|
|
4340
4394
|
}
|
|
4341
4395
|
store.log("init", { root, scope, role: config.role, orgUnit: config.orgUnit, hooks: hooks?.installed ?? 0 });
|
|
4342
|
-
return { root, config, created: !existed, hooks, ...mcp ? { mcp } : {}, warnings };
|
|
4396
|
+
return { root, config, created: !existed, hooks, ...mcp ? { mcp } : {}, ...learnCommand ? { learnCommand } : {}, warnings };
|
|
4343
4397
|
}
|
|
4344
4398
|
function otherHarnessInstructions() {
|
|
4345
4399
|
return [
|
|
@@ -4355,7 +4409,7 @@ function otherHarnessInstructions() {
|
|
|
4355
4409
|
" Claude Code plugin users need no settings.json wiring \u2014 the plugin ships hooks/hooks.json."
|
|
4356
4410
|
].join("\n");
|
|
4357
4411
|
}
|
|
4358
|
-
var PRE_TOOL_MATCHER, HOOK_EVENTS, CODEX_HOOK_EVENTS;
|
|
4412
|
+
var PRE_TOOL_MATCHER, HOOK_EVENTS, CODEX_HOOK_EVENTS, LEARN_COMMAND;
|
|
4359
4413
|
var init_init = __esm({
|
|
4360
4414
|
"src/init.ts"() {
|
|
4361
4415
|
"use strict";
|
|
@@ -4379,6 +4433,7 @@ var init_init = __esm({
|
|
|
4379
4433
|
{ event: "PreCompact", arg: "pre-compact", timeout: 10 },
|
|
4380
4434
|
{ event: "SessionEnd", arg: "session-end", timeout: 3 }
|
|
4381
4435
|
];
|
|
4436
|
+
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';
|
|
4382
4437
|
}
|
|
4383
4438
|
});
|
|
4384
4439
|
|
|
@@ -7270,6 +7325,19 @@ async function cmdLibrary(store, args, cwd, out, err) {
|
|
|
7270
7325
|
files: ${p.library}
|
|
7271
7326
|
`);
|
|
7272
7327
|
for (const x of pending) out(` edited here, awaiting the nightly: ${x.name}
|
|
7328
|
+
`);
|
|
7329
|
+
return 0;
|
|
7330
|
+
}
|
|
7331
|
+
case "workspace": {
|
|
7332
|
+
const target = args._[2];
|
|
7333
|
+
if (bool3(args.flags.clear)) setDeployWorkspace(null);
|
|
7334
|
+
else if (target) setDeployWorkspace(target);
|
|
7335
|
+
const ws = deployWorkspace();
|
|
7336
|
+
const p = deployPaths();
|
|
7337
|
+
out(ws ? `workspace: ${ws}
|
|
7338
|
+
` : "workspace: none (skills and instructions are user-wide)\n");
|
|
7339
|
+
out(` skills: ${p.claudeSkills}, ${p.codexSkills}
|
|
7340
|
+
instructions: ${p.claudeMd}, ${p.agentsMd}
|
|
7273
7341
|
`);
|
|
7274
7342
|
return 0;
|
|
7275
7343
|
}
|
|
@@ -8202,7 +8270,7 @@ init_util();
|
|
|
8202
8270
|
init_project_hooks();
|
|
8203
8271
|
function parseArgs(argv) {
|
|
8204
8272
|
const out = { _: [], flags: {} };
|
|
8205
|
-
const valueless = /* @__PURE__ */ new Set(["json", "quiet", "force", "sync", "global", "help", "version", "dry-run", "offline", "apply", "no-hooks", "remove-hooks", "key-stdin", "debug", "verbose", "robot", "fork", "upload", "index", "all", "legacy", "depth", "local", "skip-github", "propose-rewrites"]);
|
|
8273
|
+
const valueless = /* @__PURE__ */ new Set(["json", "quiet", "force", "sync", "global", "help", "version", "dry-run", "offline", "apply", "no-hooks", "remove-hooks", "key-stdin", "debug", "verbose", "robot", "fork", "upload", "index", "all", "legacy", "depth", "local", "skip-github", "propose-rewrites", "clear"]);
|
|
8206
8274
|
for (let i = 0; i < argv.length; i++) {
|
|
8207
8275
|
const a = argv[i];
|
|
8208
8276
|
if (a === "--") {
|
|
@@ -8290,6 +8358,9 @@ Usage: bunshin <command> [options]
|
|
|
8290
8358
|
the deployed skills; push proposes local edits / a new skill now
|
|
8291
8359
|
library version|log|status|pin [--write [file]]|checkout <lib@N> --to <dir> [--git <sha>] [--pin <file>]
|
|
8292
8360
|
library versions (lib@N), history, and exact reproductions
|
|
8361
|
+
library workspace [<dir>|--clear]
|
|
8362
|
+
deploy skills and instructions into a workspace folder (e.g.
|
|
8363
|
+
~/Documents/dev) instead of ~/.claude and ~/.codex; set it before the first sync
|
|
8293
8364
|
questions [list|answer <id> "<choice>"] open questions for your team
|
|
8294
8365
|
suggest --code <harness path> [--file <local file>] [--rationale "<text>"]
|
|
8295
8366
|
queue a robot harness code change (save_skill) for the release pipeline
|
|
@@ -8393,6 +8464,10 @@ Key saved privately outside the project.
|
|
|
8393
8464
|
`);
|
|
8394
8465
|
if (result.hooks?.error) throw new Error(`Connected, but hooks could not be configured: ${result.hooks.error}. Fix the file and run setup again.`);
|
|
8395
8466
|
if (result.hooks) out(`${result.hooks.installed} ${harness === "pi" ? "extension" : "hooks"} configured in ${result.hooks.file}
|
|
8467
|
+
`);
|
|
8468
|
+
if (result.learnCommand?.installed) out(`/learn installed in ${result.learnCommand.file}
|
|
8469
|
+
`);
|
|
8470
|
+
else if (result.learnCommand?.error) out(`/learn not installed: ${result.learnCommand.error} (${result.learnCommand.file})
|
|
8396
8471
|
`);
|
|
8397
8472
|
if (result.hooks?.sharedWithWorktrees) out("Codex shares these hooks with the repository\u2019s linked worktrees. Each connected worktree keeps its own Bunshin store.\n");
|
|
8398
8473
|
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");
|