@bunshin-cc/bunshin 0.9.0 → 0.10.1
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 +710 -403
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -155,8 +155,8 @@ async function askClaudeCli(opts) {
|
|
|
155
155
|
return raw;
|
|
156
156
|
}
|
|
157
157
|
async function askCodexCli(opts) {
|
|
158
|
-
const
|
|
159
|
-
const outFile = path.join(
|
|
158
|
+
const dir2 = fs.mkdtempSync(path.join(os.tmpdir(), "bunshin-codex-"));
|
|
159
|
+
const outFile = path.join(dir2, "answer.txt");
|
|
160
160
|
const args = ["exec", "--skip-git-repo-check", "--ephemeral", "-s", "read-only", "-o", outFile];
|
|
161
161
|
if (opts.model) args.push("-m", opts.model);
|
|
162
162
|
if (opts.effort) args.push("-c", `model_reasoning_effort="${opts.effort}"`);
|
|
@@ -170,7 +170,7 @@ ${opts.user}`;
|
|
|
170
170
|
await run("codex", args, { stdin: prompt, cwd: opts.cwd, timeoutMs: opts.timeoutMs ?? 3e5, backend: "codex-cli" });
|
|
171
171
|
return fs.readFileSync(outFile, "utf8");
|
|
172
172
|
} finally {
|
|
173
|
-
fs.rmSync(
|
|
173
|
+
fs.rmSync(dir2, { recursive: true, force: true });
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
176
|
async function askPiCli(opts) {
|
|
@@ -312,8 +312,8 @@ import path2 from "node:path";
|
|
|
312
312
|
function newId(prefix) {
|
|
313
313
|
return `${prefix}_${randomBytes(4).toString("hex")}`;
|
|
314
314
|
}
|
|
315
|
-
function ensureDir(
|
|
316
|
-
fs2.mkdirSync(
|
|
315
|
+
function ensureDir(dir2, mode = 448) {
|
|
316
|
+
fs2.mkdirSync(dir2, { recursive: true, mode });
|
|
317
317
|
}
|
|
318
318
|
function realish(p) {
|
|
319
319
|
const abs = path2.resolve(p);
|
|
@@ -450,9 +450,9 @@ function trustStore(root) {
|
|
|
450
450
|
const real = realish(root);
|
|
451
451
|
const list2 = trustedStores();
|
|
452
452
|
if (list2.includes(real)) return;
|
|
453
|
-
const
|
|
454
|
-
fs3.mkdirSync(
|
|
455
|
-
fs3.chmodSync(
|
|
453
|
+
const dir2 = configDir();
|
|
454
|
+
fs3.mkdirSync(dir2, { recursive: true, mode: 448 });
|
|
455
|
+
fs3.chmodSync(dir2, 448);
|
|
456
456
|
const file = trustFile();
|
|
457
457
|
const tmp = `${file}.${process.pid}.${randomBytes2(3).toString("hex")}.tmp`;
|
|
458
458
|
fs3.writeFileSync(tmp, JSON.stringify({ stores: [...list2, real] }, null, 2) + "\n", { mode: 384 });
|
|
@@ -616,13 +616,13 @@ var init_store = __esm({
|
|
|
616
616
|
}
|
|
617
617
|
static candidates(cwd) {
|
|
618
618
|
const out = [];
|
|
619
|
-
let
|
|
619
|
+
let dir2 = path4.resolve(cwd);
|
|
620
620
|
for (; ; ) {
|
|
621
|
-
const candidate = path4.join(
|
|
621
|
+
const candidate = path4.join(dir2, ".bunshin");
|
|
622
622
|
if (fileExists(path4.join(candidate, "config.json"))) out.push(candidate);
|
|
623
|
-
const parent = path4.dirname(
|
|
624
|
-
if (parent ===
|
|
625
|
-
|
|
623
|
+
const parent = path4.dirname(dir2);
|
|
624
|
+
if (parent === dir2) return out;
|
|
625
|
+
dir2 = parent;
|
|
626
626
|
}
|
|
627
627
|
}
|
|
628
628
|
static init(root, config) {
|
|
@@ -1200,10 +1200,10 @@ function isLocalLayer(layer) {
|
|
|
1200
1200
|
return kind === "user" || kind === "robot";
|
|
1201
1201
|
}
|
|
1202
1202
|
function withScope(layers, scope) {
|
|
1203
|
-
const
|
|
1204
|
-
if (!
|
|
1203
|
+
const path29 = scopePath(scope?.tree, scope?.source);
|
|
1204
|
+
if (!path29.length) return layers;
|
|
1205
1205
|
const i = layers.findIndex((l) => isLocalLayer(l));
|
|
1206
|
-
return i < 0 ? [...layers, ...
|
|
1206
|
+
return i < 0 ? [...layers, ...path29] : [...layers.slice(0, i), ...path29, ...layers.slice(i)];
|
|
1207
1207
|
}
|
|
1208
1208
|
function readerFor(ctx, ownerClearance, scope) {
|
|
1209
1209
|
if (ctx.kind === "robot" && !scope?.source) scope = { ...scope, source: `robot:${ctx.id}` };
|
|
@@ -1250,11 +1250,11 @@ function baseAffinity(section, reader) {
|
|
|
1250
1250
|
const kind = layerKind(section.layer);
|
|
1251
1251
|
if (kind === "group" || kind === "source") {
|
|
1252
1252
|
if (!reader.layers.includes(section.layer)) return 0.05;
|
|
1253
|
-
const
|
|
1253
|
+
const path29 = reader.layers.filter((l) => {
|
|
1254
1254
|
const k = layerKind(l);
|
|
1255
1255
|
return k === "group" || k === "source";
|
|
1256
1256
|
});
|
|
1257
|
-
return 0.6 + 0.4 * ((
|
|
1257
|
+
return 0.6 + 0.4 * ((path29.indexOf(section.layer) + 1) / path29.length);
|
|
1258
1258
|
}
|
|
1259
1259
|
if (isLocalLayer(section.layer)) return reader.layers.includes(section.layer) ? 1 : 0;
|
|
1260
1260
|
return 0.5;
|
|
@@ -1454,19 +1454,19 @@ var init_document = __esm({
|
|
|
1454
1454
|
function repoLayer(repo) {
|
|
1455
1455
|
return `source:${repo.includes("/") ? "repo" : "folder"}:${repo}`;
|
|
1456
1456
|
}
|
|
1457
|
-
function repoSectionId(repo,
|
|
1458
|
-
return anchor ? `repo:${repo}/${
|
|
1457
|
+
function repoSectionId(repo, path29, anchor = "") {
|
|
1458
|
+
return anchor ? `repo:${repo}/${path29}#${anchor}` : `repo:${repo}/${path29}`;
|
|
1459
1459
|
}
|
|
1460
|
-
function docKind(
|
|
1461
|
-
const base =
|
|
1460
|
+
function docKind(path29) {
|
|
1461
|
+
const base = path29.split("/").pop()?.toLowerCase() ?? "";
|
|
1462
1462
|
if (base === "testing.md") return "verification";
|
|
1463
1463
|
if (base === "skill.md") return "skill";
|
|
1464
1464
|
return "spec";
|
|
1465
1465
|
}
|
|
1466
|
-
function defaultPaths(
|
|
1467
|
-
const
|
|
1468
|
-
if (!
|
|
1469
|
-
return [`${
|
|
1466
|
+
function defaultPaths(path29) {
|
|
1467
|
+
const dir2 = path29.split("/").slice(0, -1).join("/");
|
|
1468
|
+
if (!dir2 || dir2.startsWith(".claude/") || dir2.startsWith(".codex/")) return [];
|
|
1469
|
+
return [`${dir2}/**`];
|
|
1470
1470
|
}
|
|
1471
1471
|
function parseRepoDoc(doc, opts) {
|
|
1472
1472
|
const now = opts.now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -1609,11 +1609,11 @@ function lintSkills(sections) {
|
|
|
1609
1609
|
}
|
|
1610
1610
|
const state = /* @__PURE__ */ new Map();
|
|
1611
1611
|
const cycles = /* @__PURE__ */ new Set();
|
|
1612
|
-
const dfs = (n,
|
|
1612
|
+
const dfs = (n, path29) => {
|
|
1613
1613
|
state.set(n, 1);
|
|
1614
1614
|
for (const d of edges[n] ?? []) {
|
|
1615
|
-
if (state.get(d) === 1) cycles.add([...
|
|
1616
|
-
else if (!state.get(d)) dfs(d, [...
|
|
1615
|
+
if (state.get(d) === 1) cycles.add([...path29.slice(path29.indexOf(d)), d].join(" -> "));
|
|
1616
|
+
else if (!state.get(d)) dfs(d, [...path29, d]);
|
|
1617
1617
|
}
|
|
1618
1618
|
state.set(n, 2);
|
|
1619
1619
|
};
|
|
@@ -1700,8 +1700,8 @@ function parsePythonSkills(text2) {
|
|
|
1700
1700
|
}
|
|
1701
1701
|
return { skills, constants };
|
|
1702
1702
|
}
|
|
1703
|
-
function harnessSectionId(
|
|
1704
|
-
return `harness:${
|
|
1703
|
+
function harnessSectionId(path29, symbol) {
|
|
1704
|
+
return `harness:${path29}#${symbol}`;
|
|
1705
1705
|
}
|
|
1706
1706
|
var DEF_RE;
|
|
1707
1707
|
var init_python = __esm({
|
|
@@ -1827,8 +1827,8 @@ function globToRegExp(glob) {
|
|
|
1827
1827
|
globCache.set(glob, out);
|
|
1828
1828
|
return out;
|
|
1829
1829
|
}
|
|
1830
|
-
function matchesGlob(
|
|
1831
|
-
const p =
|
|
1830
|
+
function matchesGlob(path29, glob) {
|
|
1831
|
+
const p = path29.replace(/^\.\//, "").replace(/\\/g, "/");
|
|
1832
1832
|
return globToRegExp(glob.replace(/^\.\//, "")).test(p);
|
|
1833
1833
|
}
|
|
1834
1834
|
var DEFAULT_EMBEDDERS, globCache;
|
|
@@ -1920,10 +1920,10 @@ function push(sections, reader, query) {
|
|
|
1920
1920
|
for (const s of recallable(sections, reader).filter((x) => reachesReader(x, reader))) {
|
|
1921
1921
|
const triggers = [...(s.triggers ?? []).filter((t) => t.anchor), { anchor: "", paths: s.paths, entities: s.entities, skills: s.skills }];
|
|
1922
1922
|
for (const t of triggers) {
|
|
1923
|
-
const
|
|
1924
|
-
const entity =
|
|
1925
|
-
const skill =
|
|
1926
|
-
const trigger =
|
|
1923
|
+
const path29 = paths.find((p) => t.paths.some((g) => matchesGlob(p, g)));
|
|
1924
|
+
const entity = path29 ? void 0 : t.entities.find((e) => entities.has(e) || text2 && text2.includes(e));
|
|
1925
|
+
const skill = path29 || entity ? void 0 : skills.find((k) => t.skills.includes(k));
|
|
1926
|
+
const trigger = path29 ? `path:${path29}` : entity ? `entity:${entity}` : skill ? `skill:${skill}` : "";
|
|
1927
1927
|
if (trigger) {
|
|
1928
1928
|
hits.set(s.id, { section: s, trigger, anchor: t.anchor });
|
|
1929
1929
|
break;
|
|
@@ -2078,6 +2078,53 @@ var init_engine = __esm({
|
|
|
2078
2078
|
}
|
|
2079
2079
|
});
|
|
2080
2080
|
|
|
2081
|
+
// ../core/src/v2/recall/router.ts
|
|
2082
|
+
function routerMessage(prompt, ctx, inContext2) {
|
|
2083
|
+
const lines = ["Session context:"];
|
|
2084
|
+
lines.push(ctx.first ? `- First prompt of this session: ${ctx.first.slice(0, 600)}` : "- This is the first prompt of the session.");
|
|
2085
|
+
if (ctx.recent?.length) lines.push("- Recent user prompts (oldest first):", ...ctx.recent.slice(-3).map((r) => ` \xB7 ${r.slice(0, 300)}`));
|
|
2086
|
+
if (ctx.reply) lines.push(`- Agent's last reply: ${ctx.reply.slice(0, 1500)}`);
|
|
2087
|
+
lines.push(`- Already in the agent's context: ${inContext2.length ? inContext2.join(", ") : "nothing from the library"}`);
|
|
2088
|
+
return `${lines.join("\n")}
|
|
2089
|
+
|
|
2090
|
+
Latest user prompt (verbatim, may be truncated):
|
|
2091
|
+
<<<
|
|
2092
|
+
${prompt.slice(0, 6e3)}
|
|
2093
|
+
>>>
|
|
2094
|
+
Return the JSON line.`;
|
|
2095
|
+
}
|
|
2096
|
+
function parseRouterAnswer(text2, items, skip = []) {
|
|
2097
|
+
const m = /\{[\s\S]*\}/.exec(text2);
|
|
2098
|
+
if (!m) return null;
|
|
2099
|
+
let o;
|
|
2100
|
+
try {
|
|
2101
|
+
o = JSON.parse(m[0]);
|
|
2102
|
+
} catch {
|
|
2103
|
+
return null;
|
|
2104
|
+
}
|
|
2105
|
+
const skipped = new Set(skip);
|
|
2106
|
+
const byId = new Map(items.filter((i) => !skipped.has(i.id)).map((i) => [i.id, i]));
|
|
2107
|
+
const ids = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && byId.has(x)) : [];
|
|
2108
|
+
const serve = typeof o.serve === "string" && byId.has(o.serve) ? o.serve : void 0;
|
|
2109
|
+
const rules = [...new Set(ids(o.rules).filter((id) => byId.get(id).lesson && id !== serve))].slice(0, MAX_RULES);
|
|
2110
|
+
const hint = ids(o.hints).filter((id) => id !== serve && !rules.includes(id)).slice(0, 2);
|
|
2111
|
+
return { ...serve ? { serve } : {}, rules, hint };
|
|
2112
|
+
}
|
|
2113
|
+
var MAX_RULES, RULES;
|
|
2114
|
+
var init_router = __esm({
|
|
2115
|
+
"../core/src/v2/recall/router.ts"() {
|
|
2116
|
+
"use strict";
|
|
2117
|
+
MAX_RULES = 3;
|
|
2118
|
+
RULES = `You are the prompt-time recall router for an engineering team's agent library. A coding agent is about to act on the user's latest prompt. You see the prompt plus session context: the session's first prompt, recent user prompts, the agent's last reply, and the items already in the agent's context. Decide what, if anything, to inject now.
|
|
2119
|
+
|
|
2120
|
+
- "serve": at most ONE skill or document, injected in full (long). Serve only when this prompt starts or clearly redirects work that the item is the procedure for, and it is not already in context. Follow-ups, approvals, status questions, chatter, and continuing work already under way need none.
|
|
2121
|
+
- "rules": at most ${MAX_RULES} LESSONS (short corrections or preferences learned from this team before), injected in full. Include a lesson only when what the agent will DO in response to this prompt \u2014 including the steps the work it starts normally leads to (a ticket ends in a PR and a report; a fix ends in evidence and a merge) \u2014 is the lesson's situation, even if the user does not mention the lesson. Not for shared topic words, not for preferences that would not change the agent's next actions, not if already in context. When unsure, include none.
|
|
2122
|
+
- "hints": up to 2 other possibly relevant document ids, shown as titles only.
|
|
2123
|
+
|
|
2124
|
+
Only use ids from the library. Output exactly one line of JSON and nothing else: {"serve": "<id or null>", "rules": ["<lesson id>", ...], "hints": ["<id>", ...]}`;
|
|
2125
|
+
}
|
|
2126
|
+
});
|
|
2127
|
+
|
|
2081
2128
|
// ../core/src/v2/guidelines.ts
|
|
2082
2129
|
function reflectionGuidelines(sections, reader, opts = {}) {
|
|
2083
2130
|
const covering = opts.layer ? /* @__PURE__ */ new Set(["org", opts.layer, ...opts.tree ? ancestorScopes(opts.tree, opts.layer) : []]) : void 0;
|
|
@@ -3161,6 +3208,7 @@ var init_v2 = __esm({
|
|
|
3161
3208
|
init_python();
|
|
3162
3209
|
init_text_index();
|
|
3163
3210
|
init_engine();
|
|
3211
|
+
init_router();
|
|
3164
3212
|
init_reflect();
|
|
3165
3213
|
init_digest();
|
|
3166
3214
|
init_reflect_context();
|
|
@@ -3325,17 +3373,17 @@ function listDocs(root) {
|
|
|
3325
3373
|
if (listed !== null) return [...new Set(listed.split("\0").filter(Boolean).filter(isDoc))].filter((rel) => fs6.existsSync(path5.join(root, rel)) && !deployedSkill(root, rel)).sort();
|
|
3326
3374
|
const out = [];
|
|
3327
3375
|
let visited = 0;
|
|
3328
|
-
const walk = (
|
|
3376
|
+
const walk = (dir2, rel, depth) => {
|
|
3329
3377
|
if (depth > 8 || visited++ > 2e4) return;
|
|
3330
3378
|
let entries;
|
|
3331
3379
|
try {
|
|
3332
|
-
entries = fs6.readdirSync(
|
|
3380
|
+
entries = fs6.readdirSync(dir2, { withFileTypes: true });
|
|
3333
3381
|
} catch {
|
|
3334
3382
|
return;
|
|
3335
3383
|
}
|
|
3336
3384
|
for (const e of entries) {
|
|
3337
3385
|
if (e.isDirectory()) {
|
|
3338
|
-
if (!SKIP_DIRS.has(e.name) && !fs6.existsSync(path5.join(
|
|
3386
|
+
if (!SKIP_DIRS.has(e.name) && !fs6.existsSync(path5.join(dir2, e.name, ".git"))) walk(path5.join(dir2, e.name), rel ? `${rel}/${e.name}` : e.name, depth + 1);
|
|
3339
3387
|
} else if (e.isFile()) {
|
|
3340
3388
|
const r = rel ? `${rel}/${e.name}` : e.name;
|
|
3341
3389
|
if (isDoc(r) && !deployedSkill(root, r)) out.push(r);
|
|
@@ -4074,6 +4122,14 @@ var init_src = __esm({
|
|
|
4074
4122
|
});
|
|
4075
4123
|
|
|
4076
4124
|
// src/v2/store.ts
|
|
4125
|
+
var store_exports = {};
|
|
4126
|
+
__export(store_exports, {
|
|
4127
|
+
V2Store: () => V2Store,
|
|
4128
|
+
addUnique: () => addUnique,
|
|
4129
|
+
fileSafe: () => fileSafe,
|
|
4130
|
+
hexId: () => hexId,
|
|
4131
|
+
normalizeSession: () => normalizeSession
|
|
4132
|
+
});
|
|
4077
4133
|
import fs8 from "node:fs";
|
|
4078
4134
|
import path7 from "node:path";
|
|
4079
4135
|
import { createHash as createHash2, randomBytes as randomBytes3 } from "node:crypto";
|
|
@@ -4192,12 +4248,12 @@ var init_store2 = __esm({
|
|
|
4192
4248
|
return s;
|
|
4193
4249
|
}
|
|
4194
4250
|
listSessions() {
|
|
4195
|
-
const
|
|
4196
|
-
if (!fileExists(
|
|
4251
|
+
const dir2 = this.p("sessions");
|
|
4252
|
+
if (!fileExists(dir2)) return [];
|
|
4197
4253
|
const out = [];
|
|
4198
|
-
for (const f of fs8.readdirSync(
|
|
4254
|
+
for (const f of fs8.readdirSync(dir2)) {
|
|
4199
4255
|
if (!f.endsWith(".json") || f === "current.json") continue;
|
|
4200
|
-
const raw = readJson(path7.join(
|
|
4256
|
+
const raw = readJson(path7.join(dir2, f), null);
|
|
4201
4257
|
if (raw?.sessionId) out.push(normalizeSession(raw.sessionId, raw));
|
|
4202
4258
|
}
|
|
4203
4259
|
return out;
|
|
@@ -4295,12 +4351,12 @@ var init_store2 = __esm({
|
|
|
4295
4351
|
}
|
|
4296
4352
|
}
|
|
4297
4353
|
listDir(name) {
|
|
4298
|
-
const
|
|
4299
|
-
if (!fileExists(
|
|
4354
|
+
const dir2 = this.p(name);
|
|
4355
|
+
if (!fileExists(dir2)) return [];
|
|
4300
4356
|
const out = [];
|
|
4301
|
-
for (const f of fs8.readdirSync(
|
|
4357
|
+
for (const f of fs8.readdirSync(dir2)) {
|
|
4302
4358
|
if (!f.endsWith(".json")) continue;
|
|
4303
|
-
const v = readJson(path7.join(
|
|
4359
|
+
const v = readJson(path7.join(dir2, f), null);
|
|
4304
4360
|
if (v) out.push(v);
|
|
4305
4361
|
}
|
|
4306
4362
|
return out;
|
|
@@ -4374,8 +4430,8 @@ function removeFile(file) {
|
|
|
4374
4430
|
} catch {
|
|
4375
4431
|
}
|
|
4376
4432
|
}
|
|
4377
|
-
function pruneDirs(
|
|
4378
|
-
let d =
|
|
4433
|
+
function pruneDirs(dir2, stop) {
|
|
4434
|
+
let d = dir2;
|
|
4379
4435
|
while (d.startsWith(stop) && d !== stop) {
|
|
4380
4436
|
try {
|
|
4381
4437
|
if (fs9.readdirSync(d).length) return;
|
|
@@ -4386,16 +4442,16 @@ function pruneDirs(dir, stop) {
|
|
|
4386
4442
|
d = path8.dirname(d);
|
|
4387
4443
|
}
|
|
4388
4444
|
}
|
|
4389
|
-
function listFiles(
|
|
4445
|
+
function listFiles(dir2, base = dir2) {
|
|
4390
4446
|
let entries;
|
|
4391
4447
|
try {
|
|
4392
|
-
entries = fs9.readdirSync(
|
|
4448
|
+
entries = fs9.readdirSync(dir2, { withFileTypes: true });
|
|
4393
4449
|
} catch {
|
|
4394
4450
|
return [];
|
|
4395
4451
|
}
|
|
4396
4452
|
const out = [];
|
|
4397
4453
|
for (const e of entries) {
|
|
4398
|
-
const abs = path8.join(
|
|
4454
|
+
const abs = path8.join(dir2, e.name);
|
|
4399
4455
|
if (e.isDirectory()) out.push(...listFiles(abs, base));
|
|
4400
4456
|
else if (e.isFile()) out.push(path8.relative(base, abs).split(path8.sep).join("/"));
|
|
4401
4457
|
}
|
|
@@ -4403,8 +4459,8 @@ function listFiles(dir, base = dir) {
|
|
|
4403
4459
|
}
|
|
4404
4460
|
function childDir(root, name) {
|
|
4405
4461
|
if (!name || !SAFE_NAME.test(name)) return null;
|
|
4406
|
-
const
|
|
4407
|
-
return path8.dirname(path8.resolve(
|
|
4462
|
+
const dir2 = path8.join(root, name);
|
|
4463
|
+
return path8.dirname(path8.resolve(dir2)) === path8.resolve(root) && insideRoot(root, dir2) ? dir2 : null;
|
|
4408
4464
|
}
|
|
4409
4465
|
function layerRank(layer, tree) {
|
|
4410
4466
|
const kind = layerKind(layer);
|
|
@@ -4433,17 +4489,17 @@ function planDeploy(bundle, p, harnesses) {
|
|
|
4433
4489
|
const contents = /* @__PURE__ */ new Map();
|
|
4434
4490
|
const dirs = [];
|
|
4435
4491
|
for (const root of roots) {
|
|
4436
|
-
const
|
|
4437
|
-
const body =
|
|
4438
|
-
if (!
|
|
4492
|
+
const dir2 = childDir(root, name);
|
|
4493
|
+
const body = dir2 && safeJoin(dir2, "SKILL.md");
|
|
4494
|
+
if (!dir2 || !body) {
|
|
4439
4495
|
warnings.push(`skipped skill ${name} in ${root}: its folder leads outside ${root}`);
|
|
4440
4496
|
continue;
|
|
4441
4497
|
}
|
|
4442
|
-
dirs.push(
|
|
4498
|
+
dirs.push(dir2);
|
|
4443
4499
|
targets.push({ abs: body, rel: "SKILL.md", role: "body" });
|
|
4444
4500
|
contents.set(body, s.body);
|
|
4445
4501
|
for (const [rel, f] of Object.entries(s.skill.files ?? {})) {
|
|
4446
|
-
const abs = safeJoin(
|
|
4502
|
+
const abs = safeJoin(dir2, rel);
|
|
4447
4503
|
if (!abs || rel === "SKILL.md" || rel === MANIFEST) continue;
|
|
4448
4504
|
targets.push({ abs, rel, role: "file", ...f.mode ? { mode: f.mode } : {} });
|
|
4449
4505
|
contents.set(abs, f.content);
|
|
@@ -4583,10 +4639,10 @@ function captureDoc(c, doc, out) {
|
|
|
4583
4639
|
if (chosen) byRel.set(key, chosen);
|
|
4584
4640
|
}
|
|
4585
4641
|
if (doc.kind === "skill") {
|
|
4586
|
-
for (const
|
|
4587
|
-
for (const rel of listFiles(
|
|
4642
|
+
for (const dir2 of doc.dirs ?? []) {
|
|
4643
|
+
for (const rel of listFiles(dir2)) {
|
|
4588
4644
|
if (rel === "SKILL.md" || rel === MANIFEST || seen.has(`file:${rel}`)) continue;
|
|
4589
|
-
const abs = path8.join(
|
|
4645
|
+
const abs = path8.join(dir2, rel);
|
|
4590
4646
|
const content = readText(abs);
|
|
4591
4647
|
if (content === null || Buffer.byteLength(content) > MAX_FILE) continue;
|
|
4592
4648
|
byRel.set(`file:${rel}`, { target: { abs, rel, role: "file", sha: "", ...isExecutable(abs) ? { mode: "x" } : {} }, content });
|
|
@@ -4630,23 +4686,23 @@ function captureNewSkills(c, state, roots, owned, incoming, out, layer, only) {
|
|
|
4630
4686
|
continue;
|
|
4631
4687
|
}
|
|
4632
4688
|
for (const name of names) {
|
|
4633
|
-
const
|
|
4634
|
-
if (owned.has(
|
|
4635
|
-
const skillMd = readText(path8.join(
|
|
4689
|
+
const dir2 = path8.join(root, name);
|
|
4690
|
+
if (owned.has(dir2) || incoming.has(name) || only && name !== only) continue;
|
|
4691
|
+
const skillMd = readText(path8.join(dir2, "SKILL.md"));
|
|
4636
4692
|
if (skillMd === null) continue;
|
|
4637
4693
|
let born = 0;
|
|
4638
4694
|
try {
|
|
4639
|
-
const st = fs9.statSync(path8.join(
|
|
4695
|
+
const st = fs9.statSync(path8.join(dir2, "SKILL.md"));
|
|
4640
4696
|
born = Math.min(st.birthtimeMs || st.mtimeMs, st.mtimeMs);
|
|
4641
4697
|
} catch {
|
|
4642
4698
|
continue;
|
|
4643
4699
|
}
|
|
4644
4700
|
if (!(born >= since - BORN_SLACK_MS) && !only) continue;
|
|
4645
4701
|
const files = {};
|
|
4646
|
-
for (const rel of listFiles(
|
|
4702
|
+
for (const rel of listFiles(dir2).filter((r) => !r.split("/").some((seg) => seg.startsWith(".")))) {
|
|
4647
4703
|
if (rel === "SKILL.md" || rel === MANIFEST) continue;
|
|
4648
|
-
const content = readText(path8.join(
|
|
4649
|
-
if (content !== null && Buffer.byteLength(content) <= MAX_FILE) files[rel] = { content, ...isExecutable(path8.join(
|
|
4704
|
+
const content = readText(path8.join(dir2, rel));
|
|
4705
|
+
if (content !== null && Buffer.byteLength(content) <= MAX_FILE) files[rel] = { content, ...isExecutable(path8.join(dir2, rel)) ? { mode: "x" } : {} };
|
|
4650
4706
|
}
|
|
4651
4707
|
const hash = sha(JSON.stringify([skillMd, files]));
|
|
4652
4708
|
if (state.created[name]?.hash === hash) continue;
|
|
@@ -4767,11 +4823,11 @@ function deployLocked(v2, config, ctx, bundle, opts, p, harnesses, now) {
|
|
|
4767
4823
|
const prevByAbs = new Map((prev?.targets ?? []).map((t) => [`${t.abs}#${t.role}`, t]));
|
|
4768
4824
|
const foreign = /* @__PURE__ */ new Set();
|
|
4769
4825
|
if (d.kind === "skill" && !prev) {
|
|
4770
|
-
for (const
|
|
4771
|
-
const cur = readText(path8.join(
|
|
4826
|
+
for (const dir2 of d.dirs ?? []) {
|
|
4827
|
+
const cur = readText(path8.join(dir2, "SKILL.md"));
|
|
4772
4828
|
if (cur === null || cur === d.section.body || state.created[d.name]) continue;
|
|
4773
|
-
foreign.add(
|
|
4774
|
-
report.warnings.push(`${
|
|
4829
|
+
foreign.add(dir2);
|
|
4830
|
+
report.warnings.push(`${dir2} is not managed by Bunshin; the library's ${d.name} skill was not installed there`);
|
|
4775
4831
|
}
|
|
4776
4832
|
delete state.created[d.name];
|
|
4777
4833
|
}
|
|
@@ -4780,7 +4836,7 @@ function deployLocked(v2, config, ctx, bundle, opts, p, harnesses, now) {
|
|
|
4780
4836
|
targets.push({ ...t, sha: sha(neutralizeMarkers(d.section.body).replace(/\n*$/, "\n")) });
|
|
4781
4837
|
continue;
|
|
4782
4838
|
}
|
|
4783
|
-
if ([...foreign].some((
|
|
4839
|
+
if ([...foreign].some((dir2) => t.abs.startsWith(dir2 + path8.sep))) continue;
|
|
4784
4840
|
if (!(d.dirs ?? (d.root ? [d.root] : [])).some((r) => insideRoot(r, t.abs))) {
|
|
4785
4841
|
report.warnings.push(`${t.abs} not written: it leads outside its folder`);
|
|
4786
4842
|
continue;
|
|
@@ -4810,20 +4866,20 @@ function deployLocked(v2, config, ctx, bundle, opts, p, harnesses, now) {
|
|
|
4810
4866
|
}
|
|
4811
4867
|
}
|
|
4812
4868
|
if (d.kind === "skill") {
|
|
4813
|
-
for (const
|
|
4814
|
-
if ((d.dirs ?? []).includes(
|
|
4815
|
-
removeFile(path8.join(
|
|
4816
|
-
pruneDirs(
|
|
4817
|
-
}
|
|
4818
|
-
for (const
|
|
4819
|
-
const manifest = safeJoin(
|
|
4820
|
-
if (foreign.has(
|
|
4869
|
+
for (const dir2 of prev?.dirs ?? []) {
|
|
4870
|
+
if ((d.dirs ?? []).includes(dir2) || !removable(dir2)) continue;
|
|
4871
|
+
removeFile(path8.join(dir2, MANIFEST));
|
|
4872
|
+
pruneDirs(dir2, path8.dirname(dir2));
|
|
4873
|
+
}
|
|
4874
|
+
for (const dir2 of d.dirs ?? []) {
|
|
4875
|
+
const manifest = safeJoin(dir2, MANIFEST);
|
|
4876
|
+
if (foreign.has(dir2) || !manifest || !fs9.existsSync(path8.join(dir2, "SKILL.md"))) continue;
|
|
4821
4877
|
writeJsonAtomic(manifest, { sectionId: id, name: d.name, version: d.section.version, libraryVersion: bundle.libraryVersion ?? null, managedBy: "bunshin sync" });
|
|
4822
4878
|
}
|
|
4823
4879
|
report.skills++;
|
|
4824
4880
|
} else if (d.kind === "pack") report.packFiles += targets.length;
|
|
4825
4881
|
if (d.kind === "instructions") report.instructions++;
|
|
4826
|
-
const dirs = d.dirs?.filter((
|
|
4882
|
+
const dirs = d.dirs?.filter((dir2) => !foreign.has(dir2));
|
|
4827
4883
|
next[id] = { sectionId: id, kind: d.kind, name: d.name, version: d.section.version, targets, ...dirs ? { dirs } : {} };
|
|
4828
4884
|
}
|
|
4829
4885
|
for (const [id, doc] of Object.entries(state.docs)) {
|
|
@@ -4840,14 +4896,14 @@ function deployLocked(v2, config, ctx, bundle, opts, p, harnesses, now) {
|
|
|
4840
4896
|
report.removed++;
|
|
4841
4897
|
}
|
|
4842
4898
|
}
|
|
4843
|
-
for (const
|
|
4844
|
-
if (!removable(
|
|
4845
|
-
removeFile(path8.join(
|
|
4846
|
-
pruneDirs(
|
|
4847
|
-
if (fs9.existsSync(
|
|
4848
|
-
const left = listFiles(
|
|
4849
|
-
if (left.length) report.warnings.push(`${
|
|
4850
|
-
else pruneDirs(
|
|
4899
|
+
for (const dir2 of doc.dirs ?? []) {
|
|
4900
|
+
if (!removable(dir2)) continue;
|
|
4901
|
+
removeFile(path8.join(dir2, MANIFEST));
|
|
4902
|
+
pruneDirs(dir2, path8.dirname(dir2));
|
|
4903
|
+
if (fs9.existsSync(dir2)) {
|
|
4904
|
+
const left = listFiles(dir2);
|
|
4905
|
+
if (left.length) report.warnings.push(`${dir2} kept: it has files Bunshin did not write`);
|
|
4906
|
+
else pruneDirs(dir2, path8.dirname(dir2));
|
|
4851
4907
|
}
|
|
4852
4908
|
}
|
|
4853
4909
|
}
|
|
@@ -4883,9 +4939,9 @@ function deployLocked(v2, config, ctx, bundle, opts, p, harnesses, now) {
|
|
|
4883
4939
|
const packs = new Set(plan.skills.concat([...plan.docs.values()].map((d) => d.section)).map((s) => s.pack).filter((x) => !!x));
|
|
4884
4940
|
if (harnesses.has("claude-code")) {
|
|
4885
4941
|
for (const pack of packs) {
|
|
4886
|
-
const
|
|
4887
|
-
if (!
|
|
4888
|
-
const link = path8.join(
|
|
4942
|
+
const dir2 = childDir(p.library, pack);
|
|
4943
|
+
if (!dir2 || !insideRoot(dir2, path8.join(dir2, ".claude", "x"))) continue;
|
|
4944
|
+
const link = path8.join(dir2, ".claude", "skills");
|
|
4889
4945
|
try {
|
|
4890
4946
|
fs9.lstatSync(link);
|
|
4891
4947
|
if (fs9.readlinkSync(link) !== p.claudeSkills) {
|
|
@@ -4940,10 +4996,10 @@ function deployedState(config, paths = deployPaths(process.env, config.orgId)) {
|
|
|
4940
4996
|
const s = readState(paths, config.orgId);
|
|
4941
4997
|
return { ...s.libraryVersion ? { libraryVersion: s.libraryVersion } : {}, linked: s.linked ?? [], docs: Object.values(s.docs) };
|
|
4942
4998
|
}
|
|
4943
|
-
function materialize(sections,
|
|
4999
|
+
function materialize(sections, dir2) {
|
|
4944
5000
|
let files = 0;
|
|
4945
5001
|
const put = (rel, content, mode) => {
|
|
4946
|
-
const abs = safeJoin(
|
|
5002
|
+
const abs = safeJoin(dir2, rel);
|
|
4947
5003
|
if (!abs) return;
|
|
4948
5004
|
ensureDir(path8.dirname(abs));
|
|
4949
5005
|
writeFile(abs, content, mode);
|
|
@@ -4970,20 +5026,20 @@ function linkSkills(p, root, harnesses, names) {
|
|
|
4970
5026
|
for (const h of harnesses) {
|
|
4971
5027
|
const spec = LINK_DIRS[h];
|
|
4972
5028
|
if (!spec) continue;
|
|
4973
|
-
const
|
|
5029
|
+
const dir2 = path8.join(abs, spec.rel);
|
|
4974
5030
|
const canonical = path8.resolve(p[spec.key]);
|
|
4975
5031
|
const want = new Set(names.filter((n) => SAFE_NAME.test(n) && fs9.existsSync(path8.join(canonical, n, "SKILL.md"))));
|
|
4976
5032
|
let entries = [];
|
|
4977
5033
|
try {
|
|
4978
|
-
entries = fs9.readdirSync(
|
|
5034
|
+
entries = fs9.readdirSync(dir2, { withFileTypes: true });
|
|
4979
5035
|
} catch {
|
|
4980
5036
|
}
|
|
4981
5037
|
for (const e of entries) {
|
|
4982
5038
|
if (!e.isSymbolicLink()) continue;
|
|
4983
|
-
const link = path8.join(
|
|
5039
|
+
const link = path8.join(dir2, e.name);
|
|
4984
5040
|
let target = "";
|
|
4985
5041
|
try {
|
|
4986
|
-
target = path8.resolve(
|
|
5042
|
+
target = path8.resolve(dir2, fs9.readlinkSync(link));
|
|
4987
5043
|
} catch {
|
|
4988
5044
|
continue;
|
|
4989
5045
|
}
|
|
@@ -4997,7 +5053,7 @@ function linkSkills(p, root, harnesses, names) {
|
|
|
4997
5053
|
out.removed++;
|
|
4998
5054
|
}
|
|
4999
5055
|
for (const name of want) {
|
|
5000
|
-
const link = path8.join(
|
|
5056
|
+
const link = path8.join(dir2, name);
|
|
5001
5057
|
try {
|
|
5002
5058
|
fs9.lstatSync(link);
|
|
5003
5059
|
out.warnings.push(`${link} is the project's own; the library's ${name} skill was not linked there`);
|
|
@@ -5005,7 +5061,7 @@ function linkSkills(p, root, harnesses, names) {
|
|
|
5005
5061
|
} catch {
|
|
5006
5062
|
}
|
|
5007
5063
|
try {
|
|
5008
|
-
ensureDir(
|
|
5064
|
+
ensureDir(dir2);
|
|
5009
5065
|
fs9.symlinkSync(path8.join(canonical, name), link, "dir");
|
|
5010
5066
|
excluded.push(path8.join(spec.rel, name));
|
|
5011
5067
|
out.linked++;
|
|
@@ -5013,7 +5069,7 @@ function linkSkills(p, root, harnesses, names) {
|
|
|
5013
5069
|
out.warnings.push(`${link} not linked: ${err instanceof Error ? err.message : String(err)}`);
|
|
5014
5070
|
}
|
|
5015
5071
|
}
|
|
5016
|
-
pruneDirs(
|
|
5072
|
+
pruneDirs(dir2, abs);
|
|
5017
5073
|
}
|
|
5018
5074
|
out.kept = excluded.length > 0;
|
|
5019
5075
|
gitExclude(abs, excluded);
|
|
@@ -5918,19 +5974,19 @@ function renderFeed(f) {
|
|
|
5918
5974
|
return out.join("\n").trimEnd() + "\n";
|
|
5919
5975
|
}
|
|
5920
5976
|
function latestFeed(v2) {
|
|
5921
|
-
const
|
|
5977
|
+
const dir2 = v2.p("ui");
|
|
5922
5978
|
let best = null;
|
|
5923
5979
|
try {
|
|
5924
|
-
for (const f2 of fs13.readdirSync(
|
|
5980
|
+
for (const f2 of fs13.readdirSync(dir2)) {
|
|
5925
5981
|
if (!f2.endsWith(".json") || f2.endsWith(".mod.json")) continue;
|
|
5926
|
-
const m = fs13.statSync(path9.join(
|
|
5982
|
+
const m = fs13.statSync(path9.join(dir2, f2)).mtimeMs;
|
|
5927
5983
|
if (!best || m > best.m) best = { f: f2, m };
|
|
5928
5984
|
}
|
|
5929
5985
|
} catch {
|
|
5930
5986
|
return null;
|
|
5931
5987
|
}
|
|
5932
5988
|
if (!best) return null;
|
|
5933
|
-
const f = readJson(path9.join(
|
|
5989
|
+
const f = readJson(path9.join(dir2, best.f), null);
|
|
5934
5990
|
return f && f.v === UI_FEED_VERSION ? f : null;
|
|
5935
5991
|
}
|
|
5936
5992
|
var UI_FEED_VERSION, MAX_SERVED, MAX_EVENTS, MOD_FRESH_MS, DEFAULT_MODES;
|
|
@@ -6447,6 +6503,203 @@ var init_reflect3 = __esm({
|
|
|
6447
6503
|
}
|
|
6448
6504
|
});
|
|
6449
6505
|
|
|
6506
|
+
// src/v2/route.ts
|
|
6507
|
+
var route_exports = {};
|
|
6508
|
+
__export(route_exports, {
|
|
6509
|
+
ROUTER_MODEL: () => ROUTER_MODEL,
|
|
6510
|
+
lastReply: () => lastReply,
|
|
6511
|
+
promptContext: () => promptContext,
|
|
6512
|
+
readAnswer: () => readAnswer,
|
|
6513
|
+
readJob: () => readJob,
|
|
6514
|
+
routeJob: () => routeJob,
|
|
6515
|
+
sweepRouter: () => sweepRouter,
|
|
6516
|
+
writeJob: () => writeJob
|
|
6517
|
+
});
|
|
6518
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
6519
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
6520
|
+
import fs16 from "node:fs";
|
|
6521
|
+
import path11 from "node:path";
|
|
6522
|
+
function readJson2(file) {
|
|
6523
|
+
try {
|
|
6524
|
+
return JSON.parse(fs16.readFileSync(file, "utf8"));
|
|
6525
|
+
} catch {
|
|
6526
|
+
return null;
|
|
6527
|
+
}
|
|
6528
|
+
}
|
|
6529
|
+
function lastReply(transcriptPath, maxBytes = 256e3) {
|
|
6530
|
+
if (!transcriptPath) return void 0;
|
|
6531
|
+
try {
|
|
6532
|
+
const fd = fs16.openSync(transcriptPath, "r");
|
|
6533
|
+
try {
|
|
6534
|
+
const size = fs16.fstatSync(fd).size;
|
|
6535
|
+
const len = Math.min(size, maxBytes);
|
|
6536
|
+
const buf = Buffer.alloc(len);
|
|
6537
|
+
fs16.readSync(fd, buf, 0, len, size - len);
|
|
6538
|
+
const lines = buf.toString("utf8").split("\n");
|
|
6539
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
6540
|
+
let m;
|
|
6541
|
+
try {
|
|
6542
|
+
m = JSON.parse(lines[i]);
|
|
6543
|
+
} catch {
|
|
6544
|
+
continue;
|
|
6545
|
+
}
|
|
6546
|
+
if (m?.type !== "assistant" || m.isSidechain) continue;
|
|
6547
|
+
const c = m.message?.content;
|
|
6548
|
+
const text2 = Array.isArray(c) ? c.filter((b) => b?.type === "text").map((b) => String(b.text ?? "")).join("\n") : typeof c === "string" ? c : "";
|
|
6549
|
+
if (text2.trim()) return redact2(text2.trim()).slice(0, 1500);
|
|
6550
|
+
}
|
|
6551
|
+
} finally {
|
|
6552
|
+
fs16.closeSync(fd);
|
|
6553
|
+
}
|
|
6554
|
+
} catch {
|
|
6555
|
+
}
|
|
6556
|
+
return void 0;
|
|
6557
|
+
}
|
|
6558
|
+
function promptContext(session) {
|
|
6559
|
+
const recent = session.recentPrompts ?? [];
|
|
6560
|
+
if (!recent.length) return {};
|
|
6561
|
+
const reply = lastReply(session.transcriptPath);
|
|
6562
|
+
return { ...session.task ? { first: session.task.slice(0, 1e3) } : {}, recent, ...reply ? { reply } : {} };
|
|
6563
|
+
}
|
|
6564
|
+
async function routerIndex(store, v2, source) {
|
|
6565
|
+
const file = path11.join(dir(v2), `index-${createHash5("sha256").update(source).digest("hex").slice(0, 12)}.json`);
|
|
6566
|
+
const cached = readJson2(file);
|
|
6567
|
+
if (cached && Date.now() - Date.parse(cached.fetchedAt) < INDEX_TTL_MS) return cached;
|
|
6568
|
+
try {
|
|
6569
|
+
const res = await v2api(
|
|
6570
|
+
store.config(),
|
|
6571
|
+
"POST",
|
|
6572
|
+
"/recall/index",
|
|
6573
|
+
{ ...source ? { source } : {}, ...cached ? { have: cached.hash } : {} },
|
|
6574
|
+
{ timeoutMs: 15e3 }
|
|
6575
|
+
);
|
|
6576
|
+
const next = res.unchanged && cached ? { ...cached, fetchedAt: (/* @__PURE__ */ new Date()).toISOString() } : { hash: res.hash, system: res.system ?? "", items: res.items ?? [], source, fetchedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
6577
|
+
if (!next.system) throw new Error("the server sent no index");
|
|
6578
|
+
writeJsonAtomic(file, next);
|
|
6579
|
+
return next;
|
|
6580
|
+
} catch (err) {
|
|
6581
|
+
if (cached) return cached;
|
|
6582
|
+
throw err;
|
|
6583
|
+
}
|
|
6584
|
+
}
|
|
6585
|
+
function askClaude(systemFile, message, model, cwd) {
|
|
6586
|
+
const bin = process.env.BUNSHIN_CLAUDE_BIN || "claude";
|
|
6587
|
+
const args = [
|
|
6588
|
+
"-p",
|
|
6589
|
+
"--model",
|
|
6590
|
+
model,
|
|
6591
|
+
"--effort",
|
|
6592
|
+
"low",
|
|
6593
|
+
"--settings",
|
|
6594
|
+
JSON.stringify({ disableAllHooks: true }),
|
|
6595
|
+
"--setting-sources",
|
|
6596
|
+
"",
|
|
6597
|
+
"--tools",
|
|
6598
|
+
"",
|
|
6599
|
+
"--no-session-persistence",
|
|
6600
|
+
"--exclude-dynamic-system-prompt-sections",
|
|
6601
|
+
"--system-prompt-file",
|
|
6602
|
+
systemFile,
|
|
6603
|
+
"--output-format",
|
|
6604
|
+
"json",
|
|
6605
|
+
message
|
|
6606
|
+
];
|
|
6607
|
+
return new Promise((resolve, reject) => {
|
|
6608
|
+
const child = spawn4(bin, args, { cwd, env: modelEnv("claude-cli"), stdio: ["ignore", "pipe", "pipe"] });
|
|
6609
|
+
let out = "";
|
|
6610
|
+
let err = "";
|
|
6611
|
+
let done = false;
|
|
6612
|
+
const finish = (fn) => {
|
|
6613
|
+
if (done) return;
|
|
6614
|
+
done = true;
|
|
6615
|
+
clearTimeout(timer);
|
|
6616
|
+
child.kill("SIGTERM");
|
|
6617
|
+
fn();
|
|
6618
|
+
};
|
|
6619
|
+
const timer = setTimeout(() => finish(() => reject(new Error(`claude took over ${CLAUDE_TIMEOUT_MS / 1e3} s`))), CLAUDE_TIMEOUT_MS);
|
|
6620
|
+
child.stdout.on("data", (d) => {
|
|
6621
|
+
out += d.toString();
|
|
6622
|
+
try {
|
|
6623
|
+
const parsed = JSON.parse(out);
|
|
6624
|
+
finish(() => resolve(parsed));
|
|
6625
|
+
} catch {
|
|
6626
|
+
}
|
|
6627
|
+
});
|
|
6628
|
+
child.stderr.on("data", (d) => err += d.toString());
|
|
6629
|
+
child.on("error", (e) => finish(() => reject(e)));
|
|
6630
|
+
child.on("close", (code2) => finish(() => reject(new Error(`claude exited ${code2}: ${(err || out).trim().slice(0, 300)}`))));
|
|
6631
|
+
});
|
|
6632
|
+
}
|
|
6633
|
+
async function routeJob(store, sessionId, seq) {
|
|
6634
|
+
const v2 = new V2Store(store);
|
|
6635
|
+
const config = store.config();
|
|
6636
|
+
const job = readJob(v2, sessionId);
|
|
6637
|
+
if (!job || job.seq !== seq || job.cancelled) return null;
|
|
6638
|
+
const started = Date.parse(job.at) || Date.now();
|
|
6639
|
+
const write = (a) => {
|
|
6640
|
+
const answer = { seq, at: (/* @__PURE__ */ new Date()).toISOString(), ms: Date.now() - started, ...a };
|
|
6641
|
+
writeJsonAtomic(answerFile(v2, sessionId, seq), answer);
|
|
6642
|
+
const dense = (readJob(v2, sessionId) ?? job).dense;
|
|
6643
|
+
v2.log("v2-router", { session: sessionId, seq, serve: answer.serve ?? null, rules: answer.rules ?? [], hint: answer.hint ?? [], ms: answer.ms, modelMs: answer.modelMs, error: answer.error, dense: dense?.serve ?? null, denseHint: dense?.hint ?? [], usage: answer.usage });
|
|
6644
|
+
return answer;
|
|
6645
|
+
};
|
|
6646
|
+
try {
|
|
6647
|
+
const session = v2.readSession(sessionId);
|
|
6648
|
+
if (!session?.lastPrompt) return write({ error: "no prompt in the session file" });
|
|
6649
|
+
if (!canCall(config)) return write({ error: "offline: the router's index comes from the server" });
|
|
6650
|
+
const index = await routerIndex(store, v2, session.source ?? "");
|
|
6651
|
+
const systemFile = path11.join(dir(v2), `system-${index.hash}.txt`);
|
|
6652
|
+
if (!fs16.existsSync(systemFile)) fs16.writeFileSync(systemFile, index.system);
|
|
6653
|
+
const cwd = path11.join(dir(v2), "cwd");
|
|
6654
|
+
fs16.mkdirSync(cwd, { recursive: true });
|
|
6655
|
+
const inContext2 = session.inContext ?? [...session.loaded, ...session.pushed];
|
|
6656
|
+
const titles = new Map((v2.readBundle()?.sections ?? []).map((s) => [s.id, s.skill?.name ?? s.title]));
|
|
6657
|
+
const message = routerMessage(session.lastPrompt, promptContext(session), inContext2.map((id) => titles.get(id)).filter((t) => !!t));
|
|
6658
|
+
const res = await askClaude(systemFile, message, config.recall.routerModel || process.env.BUNSHIN_ROUTER_MODEL || ROUTER_MODEL, cwd);
|
|
6659
|
+
const u = res.usage ?? {};
|
|
6660
|
+
const usage = { input: u.input_tokens ?? 0, cacheRead: u.cache_read_input_tokens ?? 0, cacheWrite: u.cache_creation_input_tokens ?? 0, output: u.output_tokens ?? 0, ...u.output_tokens_details?.thinking_tokens ? { thinking: u.output_tokens_details.thinking_tokens } : {} };
|
|
6661
|
+
const meta = { ...res.duration_ms ? { modelMs: res.duration_ms } : {}, usage };
|
|
6662
|
+
if (res.is_error) return write({ ...meta, error: `claude: ${(res.result ?? "error").slice(0, 300)}` });
|
|
6663
|
+
const picks = parseRouterAnswer(res.result ?? "", index.items, inContext2);
|
|
6664
|
+
if (!picks) return write({ ...meta, error: `not JSON: ${(res.result ?? "").slice(0, 120)}` });
|
|
6665
|
+
return write({ ...picks, ...meta });
|
|
6666
|
+
} catch (err) {
|
|
6667
|
+
return write({ error: (err instanceof Error ? err.message : String(err)).slice(0, 300) });
|
|
6668
|
+
}
|
|
6669
|
+
}
|
|
6670
|
+
function sweepRouter(v2, now = Date.now()) {
|
|
6671
|
+
try {
|
|
6672
|
+
for (const f of fs16.readdirSync(dir(v2))) {
|
|
6673
|
+
if (!/\.(job|answer)\.json$/.test(f)) continue;
|
|
6674
|
+
const p = path11.join(dir(v2), f);
|
|
6675
|
+
if (now - fs16.statSync(p).mtimeMs > 864e5) fs16.rmSync(p, { force: true });
|
|
6676
|
+
}
|
|
6677
|
+
} catch {
|
|
6678
|
+
}
|
|
6679
|
+
}
|
|
6680
|
+
var ROUTER_MODEL, INDEX_TTL_MS, CLAUDE_TIMEOUT_MS, dir, safe2, jobFile, answerFile, readJob, writeJob, readAnswer;
|
|
6681
|
+
var init_route = __esm({
|
|
6682
|
+
"src/v2/route.ts"() {
|
|
6683
|
+
"use strict";
|
|
6684
|
+
init_v22();
|
|
6685
|
+
init_llm();
|
|
6686
|
+
init_redact2();
|
|
6687
|
+
init_util();
|
|
6688
|
+
init_api();
|
|
6689
|
+
init_store2();
|
|
6690
|
+
ROUTER_MODEL = "claude-haiku-5-5";
|
|
6691
|
+
INDEX_TTL_MS = 10 * 6e4;
|
|
6692
|
+
CLAUDE_TIMEOUT_MS = 6e4;
|
|
6693
|
+
dir = (v2) => path11.join(v2.dir, "router");
|
|
6694
|
+
safe2 = (id) => id.replace(/[^\w.-]/g, "_");
|
|
6695
|
+
jobFile = (v2, session) => path11.join(dir(v2), `${safe2(session)}.job.json`);
|
|
6696
|
+
answerFile = (v2, session, seq) => path11.join(dir(v2), `${safe2(session)}.${seq}.answer.json`);
|
|
6697
|
+
readJob = (v2, session) => readJson2(jobFile(v2, session));
|
|
6698
|
+
writeJob = (v2, session, job) => writeJsonAtomic(jobFile(v2, session), job);
|
|
6699
|
+
readAnswer = (v2, session, seq) => readJson2(answerFile(v2, session, seq));
|
|
6700
|
+
}
|
|
6701
|
+
});
|
|
6702
|
+
|
|
6450
6703
|
// src/v2/uicmd.ts
|
|
6451
6704
|
var uicmd_exports = {};
|
|
6452
6705
|
__export(uicmd_exports, {
|
|
@@ -6459,51 +6712,51 @@ __export(uicmd_exports, {
|
|
|
6459
6712
|
modSource: () => modSource
|
|
6460
6713
|
});
|
|
6461
6714
|
import { spawnSync } from "node:child_process";
|
|
6462
|
-
import
|
|
6715
|
+
import fs17 from "node:fs";
|
|
6463
6716
|
import os3 from "node:os";
|
|
6464
|
-
import
|
|
6717
|
+
import path12 from "node:path";
|
|
6465
6718
|
import { fileURLToPath } from "node:url";
|
|
6466
6719
|
function modHome(home = process.env.BUNSHIN_DEPLOY_HOME || os3.homedir()) {
|
|
6467
|
-
return
|
|
6720
|
+
return path12.join(home, ".bunshin", "claude-code-ui");
|
|
6468
6721
|
}
|
|
6469
6722
|
function modSource() {
|
|
6470
|
-
const here =
|
|
6471
|
-
for (const
|
|
6472
|
-
if (
|
|
6723
|
+
const here = path12.dirname(fileURLToPath(import.meta.url));
|
|
6724
|
+
for (const dir2 of [path12.join(here, "claude-code-ui"), path12.join(here, "..", "claude-code-ui"), path12.resolve(here, "../../../../adapters/claude-code-ui")]) {
|
|
6725
|
+
if (fs17.existsSync(path12.join(dir2, ".claude-plugin", "plugin.json"))) return dir2;
|
|
6473
6726
|
}
|
|
6474
6727
|
return null;
|
|
6475
6728
|
}
|
|
6476
6729
|
function installedModVersion(home) {
|
|
6477
6730
|
try {
|
|
6478
|
-
return JSON.parse(
|
|
6731
|
+
return JSON.parse(fs17.readFileSync(path12.join(modHome(home), MOD_PLUGIN, ".claude-plugin", "plugin.json"), "utf8")).version ?? null;
|
|
6479
6732
|
} catch {
|
|
6480
6733
|
return null;
|
|
6481
6734
|
}
|
|
6482
6735
|
}
|
|
6483
6736
|
function copyDir(from, to) {
|
|
6484
|
-
|
|
6485
|
-
for (const e of
|
|
6737
|
+
fs17.mkdirSync(to, { recursive: true });
|
|
6738
|
+
for (const e of fs17.readdirSync(from, { withFileTypes: true })) {
|
|
6486
6739
|
if (e.name === "types" && from.endsWith(".claude-plugin")) continue;
|
|
6487
6740
|
if (e.name === "node_modules") continue;
|
|
6488
|
-
const a =
|
|
6489
|
-
const b =
|
|
6741
|
+
const a = path12.join(from, e.name);
|
|
6742
|
+
const b = path12.join(to, e.name);
|
|
6490
6743
|
if (e.isDirectory()) copyDir(a, b);
|
|
6491
|
-
else
|
|
6744
|
+
else fs17.copyFileSync(a, b);
|
|
6492
6745
|
}
|
|
6493
6746
|
}
|
|
6494
6747
|
function installMod(version2, opts = {}) {
|
|
6495
6748
|
const src = modSource();
|
|
6496
6749
|
if (!src) throw new Error("the bunshin-ui mod is not in this build of the CLI");
|
|
6497
6750
|
const home = modHome(opts.home);
|
|
6498
|
-
const
|
|
6499
|
-
|
|
6500
|
-
copyDir(src,
|
|
6501
|
-
const manifest =
|
|
6502
|
-
const m = JSON.parse(
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6751
|
+
const dir2 = path12.join(home, MOD_PLUGIN);
|
|
6752
|
+
fs17.rmSync(dir2, { recursive: true, force: true });
|
|
6753
|
+
copyDir(src, dir2);
|
|
6754
|
+
const manifest = path12.join(dir2, ".claude-plugin", "plugin.json");
|
|
6755
|
+
const m = JSON.parse(fs17.readFileSync(manifest, "utf8"));
|
|
6756
|
+
fs17.writeFileSync(manifest, JSON.stringify({ ...m, version: version2 }, null, 2) + "\n");
|
|
6757
|
+
fs17.mkdirSync(path12.join(home, ".claude-plugin"), { recursive: true });
|
|
6758
|
+
fs17.writeFileSync(
|
|
6759
|
+
path12.join(home, ".claude-plugin", "marketplace.json"),
|
|
6507
6760
|
JSON.stringify({ name: MOD_MARKETPLACE, owner: { name: "Bunshin" }, plugins: [{ name: MOD_PLUGIN, source: `./${MOD_PLUGIN}`, description: m.description }] }, null, 2) + "\n"
|
|
6508
6761
|
);
|
|
6509
6762
|
const run2 = opts.run ?? spawnSync;
|
|
@@ -6517,7 +6770,7 @@ function installMod(version2, opts = {}) {
|
|
|
6517
6770
|
};
|
|
6518
6771
|
if (!step(["plugin", "marketplace", "add", home])) step(["plugin", "marketplace", "update", MOD_MARKETPLACE]);
|
|
6519
6772
|
if (!step(["plugin", "install", `${MOD_PLUGIN}@${MOD_MARKETPLACE}`, "--scope", "user"])) step(["plugin", "update", `${MOD_PLUGIN}@${MOD_MARKETPLACE}`]);
|
|
6520
|
-
return { dir, version: version2, steps };
|
|
6773
|
+
return { dir: dir2, version: version2, steps };
|
|
6521
6774
|
}
|
|
6522
6775
|
async function cmdUi(store, args, out, err, version2) {
|
|
6523
6776
|
const sub = args._[1];
|
|
@@ -6538,7 +6791,7 @@ async function cmdUi(store, args, out, err, version2) {
|
|
|
6538
6791
|
`);
|
|
6539
6792
|
for (const s of res.steps) out(` ${s.ok ? "\u2713" : "\xB7"} ${s.command}${s.ok ? "" : `: ${s.output.split("\n")[0] ?? ""}`}
|
|
6540
6793
|
`);
|
|
6541
|
-
out(installed ? "The mod loads in new Claude Code sessions (and after /reload-plugins): a Bunshin band above the prompt, /bunshin, toasts.\nUntil it runs, Bunshin prints one short line per event instead.\n" : "Could not install with `claude plugin`; install it yourself:\n claude plugin marketplace add " +
|
|
6794
|
+
out(installed ? "The mod loads in new Claude Code sessions (and after /reload-plugins): a Bunshin band above the prompt, /bunshin, toasts.\nUntil it runs, Bunshin prints one short line per event instead.\n" : "Could not install with `claude plugin`; install it yourself:\n claude plugin marketplace add " + path12.dirname(res.dir) + "\n claude plugin install bunshin-ui@bunshin-local --scope user\n");
|
|
6542
6795
|
}
|
|
6543
6796
|
return installed || !failed.length ? 0 : 1;
|
|
6544
6797
|
}
|
|
@@ -6619,19 +6872,19 @@ __export(update_exports, {
|
|
|
6619
6872
|
updateCheckDue: () => updateCheckDue,
|
|
6620
6873
|
updateNotice: () => updateNotice
|
|
6621
6874
|
});
|
|
6622
|
-
import
|
|
6623
|
-
import
|
|
6875
|
+
import fs18 from "node:fs";
|
|
6876
|
+
import path13 from "node:path";
|
|
6624
6877
|
function readState2() {
|
|
6625
6878
|
try {
|
|
6626
|
-
return JSON.parse(
|
|
6879
|
+
return JSON.parse(fs18.readFileSync(stateFile(), "utf8"));
|
|
6627
6880
|
} catch {
|
|
6628
6881
|
return {};
|
|
6629
6882
|
}
|
|
6630
6883
|
}
|
|
6631
6884
|
function writeState(s) {
|
|
6632
6885
|
try {
|
|
6633
|
-
|
|
6634
|
-
|
|
6886
|
+
fs18.mkdirSync(path13.dirname(stateFile()), { recursive: true });
|
|
6887
|
+
fs18.writeFileSync(stateFile(), JSON.stringify(s, null, 2) + "\n");
|
|
6635
6888
|
} catch {
|
|
6636
6889
|
}
|
|
6637
6890
|
}
|
|
@@ -6681,14 +6934,14 @@ var init_update = __esm({
|
|
|
6681
6934
|
PKG = "@bunshin-cc/bunshin";
|
|
6682
6935
|
LATEST_URL = `https://registry.npmjs.org/${PKG.replace("/", "%2f")}/latest`;
|
|
6683
6936
|
DAY = 864e5;
|
|
6684
|
-
stateFile = () =>
|
|
6937
|
+
stateFile = () => path13.join(homeDir(), ".bunshin", "update.json");
|
|
6685
6938
|
disabled = () => process.env.BUNSHIN_NO_UPDATE_CHECK === "1" || process.env.BUNSHIN_NO_UPDATE_CHECK === "true";
|
|
6686
6939
|
}
|
|
6687
6940
|
});
|
|
6688
6941
|
|
|
6689
6942
|
// src/v2/hooks.ts
|
|
6690
|
-
import
|
|
6691
|
-
import
|
|
6943
|
+
import fs19 from "node:fs";
|
|
6944
|
+
import path14 from "node:path";
|
|
6692
6945
|
function inContext(s) {
|
|
6693
6946
|
return s.inContext ?? [...s.loaded, ...s.pushed];
|
|
6694
6947
|
}
|
|
@@ -6755,10 +7008,10 @@ async function keepModCurrent(ctx, config) {
|
|
|
6755
7008
|
const { version: version2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
|
|
6756
7009
|
const current = version2();
|
|
6757
7010
|
if (!modSource2() || installedModVersion2() === current) return;
|
|
6758
|
-
const marker =
|
|
6759
|
-
if (
|
|
6760
|
-
|
|
6761
|
-
|
|
7011
|
+
const marker = path14.join(modHome2(), `.attempted-${current}`);
|
|
7012
|
+
if (fs19.existsSync(marker)) return;
|
|
7013
|
+
fs19.mkdirSync(modHome2(), { recursive: true });
|
|
7014
|
+
fs19.writeFileSync(marker, (/* @__PURE__ */ new Date()).toISOString());
|
|
6762
7015
|
ctx.detach(["--root", ctx.store.root, "ui", "install", "--quiet"], { cwd: ctx.cwd });
|
|
6763
7016
|
} catch {
|
|
6764
7017
|
}
|
|
@@ -6887,35 +7140,6 @@ function skillHint(sec, others) {
|
|
|
6887
7140
|
const also = others.length ? ` Also possibly relevant: ${others.map((t) => `"${t}"`).join(", ")}.` : "";
|
|
6888
7141
|
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}`;
|
|
6889
7142
|
}
|
|
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
7143
|
async function remoteAuto(ctx, prompt, exclude, source, context) {
|
|
6920
7144
|
const config = ctx.store.config();
|
|
6921
7145
|
if (!canCall(config) || !config.recall.useMothership) return null;
|
|
@@ -6939,6 +7163,14 @@ function invokedSkill(prompt, sections) {
|
|
|
6939
7163
|
const name = m[1].split(":").pop().toLowerCase();
|
|
6940
7164
|
return sections.find((s) => s.skill && s.skill.name.toLowerCase() === name);
|
|
6941
7165
|
}
|
|
7166
|
+
function startRoute(v2, ctx, sessionId, now) {
|
|
7167
|
+
const prev = readJob(v2, sessionId);
|
|
7168
|
+
if (prev && !prev.delivered && !prev.cancelled) v2.log("v2-router-undelivered", { session: sessionId, seq: prev.seq, answered: !!readAnswer(v2, sessionId, prev.seq) });
|
|
7169
|
+
const job = { seq: (prev?.seq ?? 0) + 1, at: now };
|
|
7170
|
+
writeJob(v2, sessionId, job);
|
|
7171
|
+
ctx.detach(["--root", ctx.store.root, "route", "--session", sessionId, "--seq", String(job.seq), "--quiet"], { cwd: ctx.cwd });
|
|
7172
|
+
return job;
|
|
7173
|
+
}
|
|
6942
7174
|
async function onPrompt(ctx) {
|
|
6943
7175
|
const prompt = ctx.input.prompt ?? "";
|
|
6944
7176
|
if (prompt.trimStart().startsWith(REFLECT_MARKER)) return {};
|
|
@@ -6960,7 +7192,9 @@ async function onPrompt(ctx) {
|
|
|
6960
7192
|
const config = ctx.store.config();
|
|
6961
7193
|
const view = localView(v2, config, ctx.cwd, session.source !== void 0 ? { source: session.source } : {});
|
|
6962
7194
|
const injected = inContext(session);
|
|
7195
|
+
const job = config.recall.router === "claude-cli" && canCall(config) ? startRoute(v2, ctx, session.sessionId, now) : void 0;
|
|
6963
7196
|
const named = invokedSkill(prompt, view.sections);
|
|
7197
|
+
if (named && job) writeJob(v2, session.sessionId, { ...job, cancelled: true });
|
|
6964
7198
|
if (named) {
|
|
6965
7199
|
const why = `you ran /${named.skill.name}`;
|
|
6966
7200
|
const deployed = deployEnabled(config, view.ctx);
|
|
@@ -6987,6 +7221,12 @@ async function onPrompt(ctx) {
|
|
|
6987
7221
|
if (reply) context.reply = reply;
|
|
6988
7222
|
const remote = await remoteAuto(ctx, prompt, injected, session.source, Object.keys(context).length ? context : void 0);
|
|
6989
7223
|
const local = (id) => view.sections.find((s) => s.id === id);
|
|
7224
|
+
if (job && !remote?.router) {
|
|
7225
|
+
const dense = remote ? { serve: remote.section?.id, hint: (remote.hint ?? []).map((h) => h.id), reason: remote.reason } : { serve: res.section?.id, hint: res.hint.map((h) => h.id), reason: res.reason };
|
|
7226
|
+
writeJob(v2, session.sessionId, { ...job, dense: { ...dense, hint: dense.hint.slice(0, 3) } });
|
|
7227
|
+
return withLive({});
|
|
7228
|
+
}
|
|
7229
|
+
if (job) writeJob(v2, session.sessionId, { ...job, cancelled: true });
|
|
6990
7230
|
if (remote) {
|
|
6991
7231
|
const why = remote.router ? remote.reason ?? `router ${remote.router}` : `dense ${remote.reason ?? ""}`.trim();
|
|
6992
7232
|
if (remote.section) {
|
|
@@ -7059,15 +7299,69 @@ function toolPaths(input) {
|
|
|
7059
7299
|
}
|
|
7060
7300
|
return [...new Set(out)];
|
|
7061
7301
|
}
|
|
7062
|
-
function
|
|
7302
|
+
async function routedText(v2, ctx, session) {
|
|
7303
|
+
const config = ctx.store.config();
|
|
7304
|
+
if (config.recall.router !== "claude-cli") return "";
|
|
7305
|
+
const job = readJob(v2, session.sessionId);
|
|
7306
|
+
if (!job || job.delivered || job.cancelled) return "";
|
|
7307
|
+
const answer = readAnswer(v2, session.sessionId, job.seq);
|
|
7308
|
+
if (!answer) return "";
|
|
7309
|
+
const fallback = !!answer.error;
|
|
7310
|
+
writeJob(v2, session.sessionId, { ...job, delivered: { at: (/* @__PURE__ */ new Date()).toISOString(), ...ctx.input.tool_name ? { tool: ctx.input.tool_name } : {}, ...fallback ? { fallback } : {} } });
|
|
7311
|
+
const seen = new Set(inContext(session));
|
|
7312
|
+
const view = localView(v2, config, ctx.cwd, session.source !== void 0 ? { source: session.source } : {});
|
|
7313
|
+
const find = async (id) => {
|
|
7314
|
+
const here = view.sections.find((s) => s.id === id);
|
|
7315
|
+
if (here || !canCall(config)) return here;
|
|
7316
|
+
try {
|
|
7317
|
+
return (await v2api(config, "GET", `/sections/${encodeURIComponent(id)}`, void 0, { timeoutMs: Math.max(300, Math.min(2e3, ctx.deadline - Date.now() - 300)) })).section;
|
|
7318
|
+
} catch {
|
|
7319
|
+
return void 0;
|
|
7320
|
+
}
|
|
7321
|
+
};
|
|
7322
|
+
const serveId = fallback ? job.dense?.serve : answer.serve;
|
|
7323
|
+
const fresh = (ids2) => [...new Set(ids2.filter((id) => !!id && !seen.has(id)))];
|
|
7324
|
+
const [doc] = await Promise.all(fresh([serveId]).map(find));
|
|
7325
|
+
const rules = (await Promise.all(fresh(fallback ? [] : answer.rules ?? []).filter((id) => id !== doc?.id).map(find))).filter((s) => !!s);
|
|
7326
|
+
const hints = fresh(fallback ? job.dense?.hint ?? [] : answer.hint ?? []).filter((id) => id !== doc?.id && !session.hinted?.includes(id));
|
|
7327
|
+
const hintTitles = hints.map((id) => view.sections.find((s) => s.id === id)?.title).filter((t) => !!t);
|
|
7328
|
+
v2.log("v2-router-delivered", { session: session.sessionId, seq: job.seq, tool: ctx.input.tool_name, afterMs: Date.now() - Date.parse(job.at), serve: doc?.id ?? null, rules: rules.map((r) => r.id), hint: hints, fallback, dense: job.dense?.serve ?? null, ...answer.error ? { error: answer.error } : {} });
|
|
7329
|
+
const ids = [...doc ? [doc.id] : [], ...rules.map((r) => r.id)];
|
|
7330
|
+
if (!ids.length && !hintTitles.length) return "";
|
|
7331
|
+
v2.updateSession(session.sessionId, ctx.cwd, (s) => {
|
|
7332
|
+
addUnique(s.served, ...ids, ...hints);
|
|
7333
|
+
addUnique(s.loaded, ...ids);
|
|
7334
|
+
markInContext(s, ...ids);
|
|
7335
|
+
s.hinted = s.hinted ?? [];
|
|
7336
|
+
addUnique(s.hinted, ...hints);
|
|
7337
|
+
});
|
|
7338
|
+
const why = fallback ? "your request matches it" : "the router picked it for the user's latest request";
|
|
7339
|
+
updateFeed(v2, session.sessionId, (f) => {
|
|
7340
|
+
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" });
|
|
7341
|
+
if (doc) addServed(f, { id: doc.id, title: doc.title, kind: doc.kind, ...doc.skill ? { skill: doc.skill.name } : {}, how: "auto", why });
|
|
7342
|
+
if (doc) addEvent(f, "served", `served ${label({ ...doc.skill ? { skill: doc.skill.name } : {}, title: doc.title, kind: doc.kind })} \u2014 ${fallback ? "dense (router failed)" : "router"}`);
|
|
7343
|
+
if (rules.length) addEvent(f, "served", `lessons: ${rules.map((r) => r.title).join(", ")}`);
|
|
7344
|
+
});
|
|
7345
|
+
return [
|
|
7346
|
+
doc ? renderSection(doc, why, { deployed: !!doc.skill && deployEnabled(config, view.ctx) }) : "",
|
|
7347
|
+
...rules.map((r) => renderSection(r, "a lesson learned before applies to this work", { deployed: false })),
|
|
7348
|
+
hintTitles.length ? hintLine(hintTitles, config.harness) : ""
|
|
7349
|
+
].filter(Boolean).join("\n\n");
|
|
7350
|
+
}
|
|
7351
|
+
async function onPreToolUse(ctx) {
|
|
7063
7352
|
const v2 = new V2Store(ctx.store);
|
|
7064
7353
|
const session = touch(v2, ctx, (s) => {
|
|
7065
7354
|
s.lastActivityAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7066
7355
|
});
|
|
7067
7356
|
const live = liveUpdate(v2, ctx, session);
|
|
7357
|
+
let routed = "";
|
|
7358
|
+
try {
|
|
7359
|
+
routed = await routedText(v2, ctx, session);
|
|
7360
|
+
} catch {
|
|
7361
|
+
}
|
|
7068
7362
|
const withLive = (out) => {
|
|
7069
|
-
|
|
7070
|
-
return
|
|
7363
|
+
const text3 = [live, routed, out.hookSpecificOutput?.additionalContext ?? ""].filter(Boolean).join("\n\n");
|
|
7364
|
+
return text3 === (out.hookSpecificOutput?.additionalContext ?? "") ? out : additional("PreToolUse", text3);
|
|
7071
7365
|
};
|
|
7072
7366
|
const raw = toolPaths(ctx.input.tool_input);
|
|
7073
7367
|
if (!raw.length) return withLive({});
|
|
@@ -7076,7 +7370,7 @@ function onPreToolUse(ctx) {
|
|
|
7076
7370
|
const paths = raw.map((p) => relativeToRoot(view.root, p, ctx.cwd)).filter((p) => !!p);
|
|
7077
7371
|
const text2 = [session.task, session.lastPrompt].filter(Boolean).join("\n");
|
|
7078
7372
|
if (!paths.length && !text2) return withLive({});
|
|
7079
|
-
const seen = new Set(inContext(session));
|
|
7373
|
+
const seen = new Set(inContext(v2.readSession(session.sessionId) ?? session));
|
|
7080
7374
|
const hits = push(view.sections, view.reader, { paths, text: text2 }).filter((h) => !seen.has(h.section.id)).slice(0, MAX_PUSH);
|
|
7081
7375
|
if (!hits.length) return withLive({});
|
|
7082
7376
|
const ids = hits.map((h) => h.section.id);
|
|
@@ -7327,8 +7621,10 @@ var init_hooks = __esm({
|
|
|
7327
7621
|
init_signals();
|
|
7328
7622
|
init_store2();
|
|
7329
7623
|
init_ui();
|
|
7624
|
+
init_route();
|
|
7330
7625
|
init_source();
|
|
7331
7626
|
init_view();
|
|
7627
|
+
init_route();
|
|
7332
7628
|
NUDGE_TEXT = "This looks worth capturing. Consider /learn.";
|
|
7333
7629
|
NUDGE_INTERVAL_MS = 60 * 6e4;
|
|
7334
7630
|
CHECKPOINT_MS = 30 * 6e4;
|
|
@@ -7342,8 +7638,8 @@ var init_hooks = __esm({
|
|
|
7342
7638
|
});
|
|
7343
7639
|
|
|
7344
7640
|
// src/hooks.ts
|
|
7345
|
-
import
|
|
7346
|
-
import
|
|
7641
|
+
import fs20 from "node:fs";
|
|
7642
|
+
import path15 from "node:path";
|
|
7347
7643
|
function normalizeEvent(raw) {
|
|
7348
7644
|
return EVENT_NAMES[String(raw ?? "").trim().toLowerCase().replace(/_/g, "-")] ?? null;
|
|
7349
7645
|
}
|
|
@@ -7411,19 +7707,19 @@ async function handleHook(rawEvent, input, opts = {}) {
|
|
|
7411
7707
|
}
|
|
7412
7708
|
function deferToProject(cwd, env = process.env) {
|
|
7413
7709
|
if (env.BUNSHIN_HOOK_SCOPE !== "user") return false;
|
|
7414
|
-
let
|
|
7710
|
+
let dir2 = path15.resolve(cwd);
|
|
7415
7711
|
for (let i = 0; i < 40; i++) {
|
|
7416
7712
|
for (const f of [".claude/settings.local.json", ".claude/settings.json", ".codex/hooks.json"]) {
|
|
7417
7713
|
try {
|
|
7418
|
-
const text2 =
|
|
7714
|
+
const text2 = fs20.readFileSync(path15.join(dir2, f), "utf8");
|
|
7419
7715
|
if (/\bbunshin\b[^"]*\bhook\b/.test(text2) && !/BUNSHIN_HOOK_SCOPE=user/.test(text2)) return true;
|
|
7420
7716
|
} catch {
|
|
7421
7717
|
}
|
|
7422
7718
|
}
|
|
7423
|
-
if (
|
|
7424
|
-
const up =
|
|
7425
|
-
if (up ===
|
|
7426
|
-
|
|
7719
|
+
if (fs20.existsSync(path15.join(dir2, ".git"))) return false;
|
|
7720
|
+
const up = path15.dirname(dir2);
|
|
7721
|
+
if (up === dir2) return false;
|
|
7722
|
+
dir2 = up;
|
|
7427
7723
|
}
|
|
7428
7724
|
return false;
|
|
7429
7725
|
}
|
|
@@ -7469,11 +7765,11 @@ var init_hooks2 = __esm({
|
|
|
7469
7765
|
});
|
|
7470
7766
|
|
|
7471
7767
|
// src/project-hooks.ts
|
|
7472
|
-
import
|
|
7473
|
-
import
|
|
7768
|
+
import fs21 from "node:fs";
|
|
7769
|
+
import path16 from "node:path";
|
|
7474
7770
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
7475
7771
|
function codexProjectHooks(cwd) {
|
|
7476
|
-
const legacyFile =
|
|
7772
|
+
const legacyFile = path16.join(cwd, ".codex", "hooks.json");
|
|
7477
7773
|
const git5 = (...args) => execFileSync4("git", ["-C", cwd, "rev-parse", ...args], {
|
|
7478
7774
|
encoding: "utf8",
|
|
7479
7775
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -7487,14 +7783,14 @@ function codexProjectHooks(cwd) {
|
|
|
7487
7783
|
} catch {
|
|
7488
7784
|
return { file: legacyFile, shared: false, legacyFile };
|
|
7489
7785
|
}
|
|
7490
|
-
const linked =
|
|
7786
|
+
const linked = fs21.realpathSync(common) !== fs21.realpathSync(directory);
|
|
7491
7787
|
if (linked) {
|
|
7492
|
-
if (
|
|
7788
|
+
if (path16.basename(common) !== ".git" || !fs21.statSync(common).isDirectory()) {
|
|
7493
7789
|
throw new Error("Codex hooks in a worktree of a bare repository are not supported yet. Use a full checkout for Bunshin setup.");
|
|
7494
7790
|
}
|
|
7495
|
-
root =
|
|
7791
|
+
root = path16.dirname(common);
|
|
7496
7792
|
}
|
|
7497
|
-
const file =
|
|
7793
|
+
const file = fs21.realpathSync(root) === fs21.realpathSync(cwd) ? legacyFile : path16.join(root, ".codex", "hooks.json");
|
|
7498
7794
|
return { file, shared: linked, legacyFile };
|
|
7499
7795
|
}
|
|
7500
7796
|
var init_project_hooks = __esm({
|
|
@@ -7506,6 +7802,7 @@ var init_project_hooks = __esm({
|
|
|
7506
7802
|
// src/init.ts
|
|
7507
7803
|
var init_exports = {};
|
|
7508
7804
|
__export(init_exports, {
|
|
7805
|
+
ALL_TOOLS_MATCHER: () => ALL_TOOLS_MATCHER,
|
|
7509
7806
|
BORROW_COMMAND: () => BORROW_COMMAND,
|
|
7510
7807
|
CODEX_HOOK_EVENTS: () => CODEX_HOOK_EVENTS,
|
|
7511
7808
|
HOOK_EVENTS: () => HOOK_EVENTS,
|
|
@@ -7527,8 +7824,8 @@ __export(init_exports, {
|
|
|
7527
7824
|
runInit: () => runInit
|
|
7528
7825
|
});
|
|
7529
7826
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
7530
|
-
import
|
|
7531
|
-
import
|
|
7827
|
+
import fs22 from "node:fs";
|
|
7828
|
+
import path17 from "node:path";
|
|
7532
7829
|
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "node:url";
|
|
7533
7830
|
function isBunshinEntry(entry) {
|
|
7534
7831
|
return entry?._bunshin === true || typeof entry?.command === "string" && /\bbunshin\b.*\bhook\b/.test(entry.command);
|
|
@@ -7553,7 +7850,7 @@ function removeBunshinHooks(settings) {
|
|
|
7553
7850
|
else delete out.hooks;
|
|
7554
7851
|
return out;
|
|
7555
7852
|
}
|
|
7556
|
-
function mergeClaudeSettings(settings, { bin = "bunshin", harness = "claude-code", user = false } = {}) {
|
|
7853
|
+
function mergeClaudeSettings(settings, { bin = "bunshin", harness = "claude-code", user = false, allTools = false } = {}) {
|
|
7557
7854
|
const base = removeBunshinHooks(settings);
|
|
7558
7855
|
const hooks = { ...base.hooks ?? {} };
|
|
7559
7856
|
for (const { event, arg, timeout, matcher } of harness === "codex" ? CODEX_HOOK_EVENTS : HOOK_EVENTS) {
|
|
@@ -7561,7 +7858,8 @@ function mergeClaudeSettings(settings, { bin = "bunshin", harness = "claude-code
|
|
|
7561
7858
|
const hook = bunshinHookEntry(arg, timeout, bin);
|
|
7562
7859
|
hook.command = `${user ? "BUNSHIN_HOOK_SCOPE=user " : ""}BUNSHIN_HARNESS=${harness} ${hook.command}`;
|
|
7563
7860
|
if (harness === "codex") delete hook._bunshin;
|
|
7564
|
-
|
|
7861
|
+
const m = matcher && allTools && event === "PreToolUse" ? ALL_TOOLS_MATCHER : matcher;
|
|
7862
|
+
groups.push(m ? { matcher: m, hooks: [hook] } : { hooks: [hook] });
|
|
7565
7863
|
hooks[event] = groups;
|
|
7566
7864
|
}
|
|
7567
7865
|
return { ...base, hooks };
|
|
@@ -7573,16 +7871,16 @@ function countBunshinHooks(settings) {
|
|
|
7573
7871
|
);
|
|
7574
7872
|
}
|
|
7575
7873
|
function claudeSettingsPath() {
|
|
7576
|
-
return
|
|
7874
|
+
return path17.join(homeDir(), ".claude", "settings.json");
|
|
7577
7875
|
}
|
|
7578
7876
|
function installPiExtension(cwd, remove = false, scope = "repo") {
|
|
7579
|
-
const file = scope === "global" ?
|
|
7877
|
+
const file = scope === "global" ? path17.join(homeDir(), ".pi/agent/extensions/bunshin.js") : path17.join(cwd, ".pi/extensions/bunshin.js");
|
|
7580
7878
|
const marker = "// Managed by Bunshin setup.";
|
|
7581
7879
|
try {
|
|
7582
|
-
const previous = fileExists(file) ?
|
|
7880
|
+
const previous = fileExists(file) ? fs22.readFileSync(file, "utf8") : "";
|
|
7583
7881
|
if (previous && !previous.startsWith(marker)) throw new Error("An existing Bunshin extension file is not managed by setup; preserve or move it before continuing.");
|
|
7584
7882
|
if (remove) {
|
|
7585
|
-
if (previous)
|
|
7883
|
+
if (previous) fs22.unlinkSync(file);
|
|
7586
7884
|
return { file, installed: 0, changed: !!previous };
|
|
7587
7885
|
}
|
|
7588
7886
|
const extension = fileURLToPath2(new URL("./pi-extension.js", import.meta.url));
|
|
@@ -7591,24 +7889,24 @@ function installPiExtension(cwd, remove = false, scope = "repo") {
|
|
|
7591
7889
|
export { default } from ${JSON.stringify(pathToFileURL(extension).href)};
|
|
7592
7890
|
`;
|
|
7593
7891
|
if (text2 !== previous) {
|
|
7594
|
-
ensureDir(
|
|
7595
|
-
|
|
7892
|
+
ensureDir(path17.dirname(file));
|
|
7893
|
+
fs22.writeFileSync(file, text2);
|
|
7596
7894
|
}
|
|
7597
7895
|
return { file, installed: 1, changed: text2 !== previous };
|
|
7598
7896
|
} catch (error) {
|
|
7599
7897
|
return { file, installed: 0, changed: false, error: error instanceof Error ? error.message : String(error) };
|
|
7600
7898
|
}
|
|
7601
7899
|
}
|
|
7602
|
-
function installClaudeHooks({ file = claudeSettingsPath(), bin = "bunshin", remove = false, harness = "claude-code", user = false } = {}) {
|
|
7900
|
+
function installClaudeHooks({ file = claudeSettingsPath(), bin = "bunshin", remove = false, harness = "claude-code", user = false, allTools = false } = {}) {
|
|
7603
7901
|
try {
|
|
7604
|
-
const before = fileExists(file) ? JSON.parse(
|
|
7605
|
-
const after = remove ? removeBunshinHooks(before) : mergeClaudeSettings(before, { bin, harness, user });
|
|
7902
|
+
const before = fileExists(file) ? JSON.parse(fs22.readFileSync(file, "utf8")) : {};
|
|
7903
|
+
const after = remove ? removeBunshinHooks(before) : mergeClaudeSettings(before, { bin, harness, user, allTools });
|
|
7606
7904
|
const changed = JSON.stringify(before) !== JSON.stringify(after);
|
|
7607
7905
|
if (changed) {
|
|
7608
|
-
ensureDir(
|
|
7906
|
+
ensureDir(path17.dirname(file));
|
|
7609
7907
|
if (fileExists(file)) {
|
|
7610
7908
|
try {
|
|
7611
|
-
|
|
7909
|
+
fs22.copyFileSync(file, `${file}.bunshin-backup`);
|
|
7612
7910
|
} catch {
|
|
7613
7911
|
}
|
|
7614
7912
|
}
|
|
@@ -7623,23 +7921,23 @@ function mcpLauncher(bin) {
|
|
|
7623
7921
|
if (!bin || /^[\w./@+-]+$/.test(bin)) return { command: bin || "bunshin", args: ["mcp"] };
|
|
7624
7922
|
let script = process.argv[1] ?? "";
|
|
7625
7923
|
try {
|
|
7626
|
-
script =
|
|
7924
|
+
script = fs22.realpathSync(script);
|
|
7627
7925
|
} catch {
|
|
7628
7926
|
}
|
|
7629
7927
|
return { command: process.execPath, args: [script, "mcp"] };
|
|
7630
7928
|
}
|
|
7631
7929
|
function installLearnCommand({ file, remove = false, content = LEARN_COMMAND }) {
|
|
7632
7930
|
try {
|
|
7633
|
-
const current = fileExists(file) ?
|
|
7931
|
+
const current = fileExists(file) ? fs22.readFileSync(file, "utf8") : null;
|
|
7634
7932
|
const ours = current === null || current === content;
|
|
7635
7933
|
if (remove) {
|
|
7636
|
-
if (current !== null && ours)
|
|
7934
|
+
if (current !== null && ours) fs22.unlinkSync(file);
|
|
7637
7935
|
return { file, installed: 0, changed: current !== null && ours };
|
|
7638
7936
|
}
|
|
7639
|
-
if (!ours) return { file, installed: 0, changed: false, error: `a different ${
|
|
7937
|
+
if (!ours) return { file, installed: 0, changed: false, error: `a different ${path17.basename(file)} is already there; left as it is` };
|
|
7640
7938
|
if (current === null) {
|
|
7641
|
-
|
|
7642
|
-
|
|
7939
|
+
fs22.mkdirSync(path17.dirname(file), { recursive: true });
|
|
7940
|
+
fs22.writeFileSync(file, content);
|
|
7643
7941
|
}
|
|
7644
7942
|
return { file, installed: 1, changed: current === null };
|
|
7645
7943
|
} catch (err) {
|
|
@@ -7648,7 +7946,7 @@ function installLearnCommand({ file, remove = false, content = LEARN_COMMAND })
|
|
|
7648
7946
|
}
|
|
7649
7947
|
function installMcpConfig({ file, launcher, remove = false }) {
|
|
7650
7948
|
try {
|
|
7651
|
-
const before = fileExists(file) ? JSON.parse(
|
|
7949
|
+
const before = fileExists(file) ? JSON.parse(fs22.readFileSync(file, "utf8")) : {};
|
|
7652
7950
|
const servers = { ...before.mcpServers ?? {} };
|
|
7653
7951
|
if (remove) delete servers.bunshin;
|
|
7654
7952
|
else servers.bunshin = { type: "stdio", command: launcher.command, args: launcher.args, env: { BUNSHIN_HARNESS: "claude-code" } };
|
|
@@ -7656,7 +7954,7 @@ function installMcpConfig({ file, launcher, remove = false }) {
|
|
|
7656
7954
|
if (!Object.keys(servers).length) delete after.mcpServers;
|
|
7657
7955
|
const changed = JSON.stringify(before) !== JSON.stringify(after);
|
|
7658
7956
|
if (changed) {
|
|
7659
|
-
if (!Object.keys(after).length && fileExists(file))
|
|
7957
|
+
if (!Object.keys(after).length && fileExists(file)) fs22.unlinkSync(file);
|
|
7660
7958
|
else if (Object.keys(after).length) writeJsonAtomic(file, after);
|
|
7661
7959
|
}
|
|
7662
7960
|
return { file, installed: servers.bunshin ? 1 : 0, changed };
|
|
@@ -7665,7 +7963,7 @@ function installMcpConfig({ file, launcher, remove = false }) {
|
|
|
7665
7963
|
}
|
|
7666
7964
|
}
|
|
7667
7965
|
function installUserMcp({ launcher, remove = false, claude = "claude" }) {
|
|
7668
|
-
const file =
|
|
7966
|
+
const file = path17.join(homeDir(), ".claude.json");
|
|
7669
7967
|
const run2 = (args) => execFileSync5(claude, args, { stdio: ["ignore", "pipe", "pipe"], timeout: 2e4, env: { ...process.env, BUNSHIN_CHILD: "1" } }).toString();
|
|
7670
7968
|
try {
|
|
7671
7969
|
let had = false;
|
|
@@ -7691,22 +7989,22 @@ function runInit(opts = {}) {
|
|
|
7691
7989
|
const cwd = opts.cwd ?? process.cwd();
|
|
7692
7990
|
const warnings = [];
|
|
7693
7991
|
const scope = opts.scope ?? "repo";
|
|
7694
|
-
const root = scope === "global" ?
|
|
7695
|
-
if (
|
|
7992
|
+
const root = scope === "global" ? path17.join(homeDir(), ".bunshin") : path17.join(cwd, ".bunshin");
|
|
7993
|
+
if (fs22.existsSync(root) && !new Store(root).trusted && fs22.readdirSync(root).length) {
|
|
7696
7994
|
const aside = `${root}.untrusted-${Date.now()}`;
|
|
7697
|
-
|
|
7995
|
+
fs22.renameSync(root, aside);
|
|
7698
7996
|
warnings.push(`moved ${root} aside to ${aside}: this user did not create it`);
|
|
7699
7997
|
}
|
|
7700
|
-
const existed = fileExists(
|
|
7998
|
+
const existed = fileExists(path17.join(root, "config.json"));
|
|
7701
7999
|
const store = new Store(root);
|
|
7702
8000
|
store.ensureLayout();
|
|
7703
|
-
|
|
8001
|
+
fs22.chmodSync(root, 448);
|
|
7704
8002
|
const hookScope = opts.hooks ?? (scope === "global" ? "user" : (existed && store.trusted ? store.config().hooks : void 0) ?? "project");
|
|
7705
|
-
if (scope === "repo" && (hookScope === "project" || fileExists(
|
|
7706
|
-
const ignore =
|
|
7707
|
-
const current = fileExists(ignore) ?
|
|
8003
|
+
if (scope === "repo" && (hookScope === "project" || fileExists(path17.join(cwd, ".git")))) {
|
|
8004
|
+
const ignore = path17.join(cwd, ".gitignore");
|
|
8005
|
+
const current = fileExists(ignore) ? fs22.readFileSync(ignore, "utf8") : "";
|
|
7708
8006
|
if (!current.split(/\r?\n/).some((line) => /^\/?\.bunshin\/?$/.test(line.trim()))) {
|
|
7709
|
-
|
|
8007
|
+
fs22.appendFileSync(ignore, `${current && !current.endsWith("\n") ? "\n" : ""}.bunshin/
|
|
7710
8008
|
`);
|
|
7711
8009
|
}
|
|
7712
8010
|
}
|
|
@@ -7736,17 +8034,17 @@ function runInit(opts = {}) {
|
|
|
7736
8034
|
}
|
|
7737
8035
|
if ((opts.removeHooks || !opts.noHooks) && (config.harness === "codex" || config.harness === "claude-code")) {
|
|
7738
8036
|
const projectHooks = config.harness === "codex" && hookScope === "project" && !opts.settingsFile ? codexProjectHooks(cwd) : void 0;
|
|
7739
|
-
const file = opts.settingsFile ?? (config.harness === "codex" ? projectHooks?.file ??
|
|
7740
|
-
hooks = installClaudeHooks({ file, bin: opts.bin, remove: opts.removeHooks, harness: config.harness, user: hookScope === "user" && !opts.settingsFile });
|
|
8037
|
+
const file = opts.settingsFile ?? (config.harness === "codex" ? projectHooks?.file ?? path17.join(process.env.CODEX_HOME ?? path17.join(homeDir(), ".codex"), "hooks.json") : hookScope === "project" ? path17.join(cwd, ".claude", "settings.local.json") : claudeSettingsPath());
|
|
8038
|
+
hooks = installClaudeHooks({ file, bin: opts.bin, remove: opts.removeHooks, harness: config.harness, user: hookScope === "user" && !opts.settingsFile, allTools: config.recall.router === "claude-cli" });
|
|
7741
8039
|
if (!opts.removeHooks && !opts.settingsFile && hookScope === "user" && config.harness === "claude-code") {
|
|
7742
|
-
const local =
|
|
7743
|
-
if (fileExists(local) &&
|
|
8040
|
+
const local = path17.join(cwd, ".claude", "settings.local.json");
|
|
8041
|
+
if (fileExists(local) && path17.resolve(local) !== path17.resolve(file)) installClaudeHooks({ file: local, remove: true });
|
|
7744
8042
|
}
|
|
7745
8043
|
if (projectHooks?.shared) {
|
|
7746
8044
|
hooks.sharedWithWorktrees = true;
|
|
7747
|
-
if (!hooks.error && !opts.removeHooks) ensureDir(
|
|
8045
|
+
if (!hooks.error && !opts.removeHooks) ensureDir(path17.dirname(projectHooks.legacyFile));
|
|
7748
8046
|
}
|
|
7749
|
-
if (!hooks.error && projectHooks && projectHooks.file !== projectHooks.legacyFile && fileExists(projectHooks.legacyFile) &&
|
|
8047
|
+
if (!hooks.error && projectHooks && projectHooks.file !== projectHooks.legacyFile && fileExists(projectHooks.legacyFile) && fs22.realpathSync(projectHooks.file) !== fs22.realpathSync(projectHooks.legacyFile)) {
|
|
7750
8048
|
const old = installClaudeHooks({ file: projectHooks.legacyFile, remove: true, harness: "codex" });
|
|
7751
8049
|
if (old.error) hooks.error = `Could not remove old Bunshin hooks from ${old.file}: ${old.error}`;
|
|
7752
8050
|
}
|
|
@@ -7756,15 +8054,15 @@ function runInit(opts = {}) {
|
|
|
7756
8054
|
let learnCommand;
|
|
7757
8055
|
if ((opts.removeHooks || !opts.noHooks) && config.harness === "claude-code") {
|
|
7758
8056
|
const user = hookScope === "user";
|
|
7759
|
-
mcp = user ? installUserMcp({ launcher: opts.mcp ?? mcpLauncher(opts.bin), remove: opts.removeHooks }) : installMcpConfig({ file:
|
|
8057
|
+
mcp = user ? installUserMcp({ launcher: opts.mcp ?? mcpLauncher(opts.bin), remove: opts.removeHooks }) : installMcpConfig({ file: path17.join(cwd, ".mcp.json"), launcher: opts.mcp ?? mcpLauncher(opts.bin), remove: opts.removeHooks });
|
|
7760
8058
|
if (mcp.error) warnings.push(`could not update ${mcp.file}: ${mcp.error}`);
|
|
7761
|
-
learnCommand = installLearnCommand({ file:
|
|
8059
|
+
learnCommand = installLearnCommand({ file: path17.join(user ? path17.join(homeDir(), ".claude") : path17.join(cwd, ".claude"), "commands", "learn.md"), remove: opts.removeHooks });
|
|
7762
8060
|
if (learnCommand.error) warnings.push(`/learn not installed at ${learnCommand.file}: ${learnCommand.error}`);
|
|
7763
8061
|
}
|
|
7764
8062
|
let borrowCommand;
|
|
7765
8063
|
if (opts.removeHooks || !opts.noHooks) {
|
|
7766
8064
|
const user = hookScope === "user";
|
|
7767
|
-
const file = config.harness === "claude-code" ?
|
|
8065
|
+
const file = config.harness === "claude-code" ? path17.join(user ? path17.join(homeDir(), ".claude") : path17.join(cwd, ".claude"), "commands", "borrow.md") : config.harness === "codex" ? path17.join(process.env.CODEX_HOME ?? path17.join(homeDir(), ".codex"), "prompts", "borrow.md") : config.harness === "pi" ? user ? path17.join(homeDir(), ".pi", "agent", "prompts", "borrow.md") : path17.join(cwd, ".pi", "prompts", "borrow.md") : null;
|
|
7768
8066
|
if (file) {
|
|
7769
8067
|
borrowCommand = installLearnCommand({ file, remove: opts.removeHooks, content: BORROW_COMMAND });
|
|
7770
8068
|
if (borrowCommand.error) warnings.push(`/borrow not installed at ${borrowCommand.file}: ${borrowCommand.error}`);
|
|
@@ -7790,7 +8088,7 @@ function otherHarnessInstructions() {
|
|
|
7790
8088
|
" Claude Code plugin users need no settings.json wiring \u2014 the plugin ships hooks/hooks.json."
|
|
7791
8089
|
].join("\n");
|
|
7792
8090
|
}
|
|
7793
|
-
var PRE_TOOL_MATCHER, HOOK_EVENTS, CODEX_HOOK_EVENTS, LEARN_COMMAND, BORROW_COMMAND;
|
|
8091
|
+
var PRE_TOOL_MATCHER, ALL_TOOLS_MATCHER, HOOK_EVENTS, CODEX_HOOK_EVENTS, LEARN_COMMAND, BORROW_COMMAND;
|
|
7794
8092
|
var init_init = __esm({
|
|
7795
8093
|
"src/init.ts"() {
|
|
7796
8094
|
"use strict";
|
|
@@ -7800,6 +8098,7 @@ var init_init = __esm({
|
|
|
7800
8098
|
init_project_hooks();
|
|
7801
8099
|
init_trust();
|
|
7802
8100
|
PRE_TOOL_MATCHER = "Read|Edit|Write|MultiEdit|NotebookEdit";
|
|
8101
|
+
ALL_TOOLS_MATCHER = "*";
|
|
7803
8102
|
HOOK_EVENTS = [
|
|
7804
8103
|
{ event: "SessionStart", arg: "session-start", timeout: 10 },
|
|
7805
8104
|
{ event: "UserPromptSubmit", arg: "user-prompt-submit", timeout: 10 },
|
|
@@ -7921,9 +8220,9 @@ __export(commands_exports, {
|
|
|
7921
8220
|
setupRobot: () => setupRobot,
|
|
7922
8221
|
v2Status: () => v2Status
|
|
7923
8222
|
});
|
|
7924
|
-
import
|
|
7925
|
-
import
|
|
7926
|
-
import { createHash as
|
|
8223
|
+
import fs23 from "node:fs";
|
|
8224
|
+
import path18 from "node:path";
|
|
8225
|
+
import { createHash as createHash6, randomBytes as randomBytes4 } from "node:crypto";
|
|
7927
8226
|
function cmdReview(store, args, cwd, out, err) {
|
|
7928
8227
|
const v2 = new V2Store(store);
|
|
7929
8228
|
const sub = args._[1] ?? "list";
|
|
@@ -8093,11 +8392,11 @@ function cmdSuggest(store, args, cwd, out, err) {
|
|
|
8093
8392
|
err('bunshin suggest: usage: bunshin suggest --code <harness-relative path> [--file <local file>] [--rationale "<text>"]\n');
|
|
8094
8393
|
return 1;
|
|
8095
8394
|
}
|
|
8096
|
-
const harnessDir =
|
|
8097
|
-
const file = str3(args.flags.file) ?
|
|
8395
|
+
const harnessDir = path18.resolve(cwd, str3(args.flags["harness-dir"]) ?? ".");
|
|
8396
|
+
const file = str3(args.flags.file) ? path18.resolve(cwd, str3(args.flags.file)) : path18.join(harnessDir, rel);
|
|
8098
8397
|
let content;
|
|
8099
8398
|
try {
|
|
8100
|
-
content =
|
|
8399
|
+
content = fs23.readFileSync(file, "utf8");
|
|
8101
8400
|
} catch {
|
|
8102
8401
|
err(`bunshin suggest: cannot read ${file}
|
|
8103
8402
|
`);
|
|
@@ -8218,15 +8517,15 @@ async function cmdQuestions(store, args, out, err) {
|
|
|
8218
8517
|
return 0;
|
|
8219
8518
|
}
|
|
8220
8519
|
function saveCredential(storeRoot, apiUrl, apiKey, id) {
|
|
8221
|
-
const
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
const file =
|
|
8520
|
+
const dir2 = path18.join(homeDir(), ".config/bunshin/credentials");
|
|
8521
|
+
fs23.mkdirSync(dir2, { recursive: true, mode: 448 });
|
|
8522
|
+
fs23.chmodSync(dir2, 448);
|
|
8523
|
+
const file = path18.join(dir2, `${createHash6("sha256").update(`${storeRoot}
|
|
8225
8524
|
${apiUrl}
|
|
8226
8525
|
${id}`).digest("hex")}.json`);
|
|
8227
8526
|
const tmp = `${file}.${randomBytes4(4).toString("hex")}.tmp`;
|
|
8228
|
-
|
|
8229
|
-
|
|
8527
|
+
fs23.writeFileSync(tmp, JSON.stringify({ apiUrl, apiKey }) + "\n", { mode: 384, flag: "wx" });
|
|
8528
|
+
fs23.renameSync(tmp, file);
|
|
8230
8529
|
return file;
|
|
8231
8530
|
}
|
|
8232
8531
|
async function setupRobot(opts) {
|
|
@@ -8234,10 +8533,10 @@ async function setupRobot(opts) {
|
|
|
8234
8533
|
const existing = Store.discover(opts.cwd);
|
|
8235
8534
|
let store;
|
|
8236
8535
|
const harness = opts.harness ?? "other";
|
|
8237
|
-
if (existing &&
|
|
8536
|
+
if (existing && path18.resolve(path18.dirname(existing.root)) === path18.resolve(opts.cwd) && harness === "other") store = existing;
|
|
8238
8537
|
else {
|
|
8239
8538
|
runInit({ cwd: opts.cwd, harness, noHooks: harness === "other", ...opts.apiUrl ? { apiUrl: opts.apiUrl } : {} });
|
|
8240
|
-
store = new Store(
|
|
8539
|
+
store = new Store(path18.join(opts.cwd, ".bunshin"));
|
|
8241
8540
|
}
|
|
8242
8541
|
let config = store.config();
|
|
8243
8542
|
const apiUrl = (opts.apiUrl ?? config.apiUrl ?? "").replace(/\/$/, "");
|
|
@@ -8391,16 +8690,16 @@ __export(setup_exports, {
|
|
|
8391
8690
|
setupProject: () => setupProject,
|
|
8392
8691
|
shellQuote: () => shellQuote
|
|
8393
8692
|
});
|
|
8394
|
-
import
|
|
8395
|
-
import
|
|
8396
|
-
import { createHash as
|
|
8693
|
+
import fs24 from "node:fs";
|
|
8694
|
+
import path19 from "node:path";
|
|
8695
|
+
import { createHash as createHash7, randomBytes as randomBytes5 } from "node:crypto";
|
|
8397
8696
|
function executable(name) {
|
|
8398
|
-
const directories = [...(process.env.PATH ?? "").split(
|
|
8697
|
+
const directories = [...(process.env.PATH ?? "").split(path19.delimiter), path19.join(homeDir(), ".local/bin")];
|
|
8399
8698
|
for (const directory of directories) {
|
|
8400
|
-
const file =
|
|
8699
|
+
const file = path19.resolve(directory, name);
|
|
8401
8700
|
try {
|
|
8402
|
-
|
|
8403
|
-
if (
|
|
8701
|
+
fs24.accessSync(file, fs24.constants.X_OK);
|
|
8702
|
+
if (fs24.statSync(file).isFile()) return file;
|
|
8404
8703
|
} catch {
|
|
8405
8704
|
}
|
|
8406
8705
|
}
|
|
@@ -8408,8 +8707,8 @@ function executable(name) {
|
|
|
8408
8707
|
}
|
|
8409
8708
|
function hookBinary(harness) {
|
|
8410
8709
|
const agent = executable(harness === "claude-code" ? "claude" : harness);
|
|
8411
|
-
const directories = [.../* @__PURE__ */ new Set([
|
|
8412
|
-
return `PATH=${shellQuote(directories.join(
|
|
8710
|
+
const directories = [.../* @__PURE__ */ new Set([path19.dirname(process.execPath), path19.join(homeDir(), ".local/bin"), ...agent ? [path19.dirname(agent)] : []])];
|
|
8711
|
+
return `PATH=${shellQuote(directories.join(path19.delimiter))}:"$PATH" ${shellQuote(process.execPath)} ${shellQuote(fs24.realpathSync(process.argv[1]))}`;
|
|
8413
8712
|
}
|
|
8414
8713
|
function apiAddress(raw) {
|
|
8415
8714
|
const url = new URL(raw);
|
|
@@ -8478,23 +8777,23 @@ async function setupProject(opts) {
|
|
|
8478
8777
|
const apiKey = opts.apiKey.trim();
|
|
8479
8778
|
if (!apiKey || /\s/.test(apiKey)) throw new Error("Paste the complete agent key from your Bunshin workspace.");
|
|
8480
8779
|
const profile = await connectionProfile(apiUrl, apiKey);
|
|
8481
|
-
const root =
|
|
8780
|
+
const root = path19.join(opts.cwd, ".bunshin");
|
|
8482
8781
|
const store = new Store(root);
|
|
8483
8782
|
const previous = store.config();
|
|
8484
8783
|
if ((previous.orgId !== profile.orgId || previous.userId !== profile.userId) && (store.listLessons().length || store.allTraces().length || store.listRecallLessons().length)) {
|
|
8485
8784
|
throw new Error("This project has lessons or activity from another profile. Use a fresh project to connect this workspace.");
|
|
8486
8785
|
}
|
|
8487
|
-
const credentialDirectory =
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
const name =
|
|
8786
|
+
const credentialDirectory = path19.join(homeDir(), ".config/bunshin/credentials");
|
|
8787
|
+
fs24.mkdirSync(credentialDirectory, { recursive: true, mode: 448 });
|
|
8788
|
+
fs24.chmodSync(credentialDirectory, 448);
|
|
8789
|
+
const name = createHash7("sha256").update(`${root}
|
|
8491
8790
|
${apiUrl}
|
|
8492
8791
|
${profile.orgId}
|
|
8493
8792
|
${profile.userId}`).digest("hex");
|
|
8494
|
-
const credentialFile =
|
|
8793
|
+
const credentialFile = path19.join(credentialDirectory, `${name}.json`);
|
|
8495
8794
|
const temporary = `${credentialFile}.${randomBytes5(4).toString("hex")}.tmp`;
|
|
8496
|
-
|
|
8497
|
-
|
|
8795
|
+
fs24.writeFileSync(temporary, JSON.stringify({ apiUrl, apiKey }) + "\n", { mode: 384, flag: "wx" });
|
|
8796
|
+
fs24.renameSync(temporary, credentialFile);
|
|
8498
8797
|
const result = runInit({
|
|
8499
8798
|
cwd: opts.cwd,
|
|
8500
8799
|
harness: opts.harness,
|
|
@@ -8846,9 +9145,9 @@ __export(sync_exports2, {
|
|
|
8846
9145
|
resolveConflict: () => resolveConflict,
|
|
8847
9146
|
runSync: () => runSync
|
|
8848
9147
|
});
|
|
8849
|
-
import { createHash as
|
|
8850
|
-
import
|
|
8851
|
-
import
|
|
9148
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
9149
|
+
import path20 from "node:path";
|
|
9150
|
+
import fs25 from "node:fs";
|
|
8852
9151
|
async function api(config, method, route, body, timeoutMs = 1e4) {
|
|
8853
9152
|
const ctrl = new AbortController();
|
|
8854
9153
|
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
@@ -8886,22 +9185,22 @@ async function mothershipLookup(config, ids, timeoutMs = 8e3) {
|
|
|
8886
9185
|
}
|
|
8887
9186
|
}
|
|
8888
9187
|
async function runSync(store) {
|
|
8889
|
-
const lock =
|
|
9188
|
+
const lock = path20.join(store.root, "sync.lock");
|
|
8890
9189
|
const deadline = Date.now() + 12e4;
|
|
8891
9190
|
for (; ; ) {
|
|
8892
9191
|
try {
|
|
8893
|
-
|
|
9192
|
+
fs25.writeFileSync(lock, String(process.pid), { flag: "wx", mode: 384 });
|
|
8894
9193
|
break;
|
|
8895
9194
|
} catch (error) {
|
|
8896
9195
|
if (error.code !== "EEXIST") throw error;
|
|
8897
9196
|
try {
|
|
8898
|
-
const owner = Number(
|
|
9197
|
+
const owner = Number(fs25.readFileSync(lock, "utf8"));
|
|
8899
9198
|
if (Number.isInteger(owner) && owner > 0) {
|
|
8900
9199
|
try {
|
|
8901
9200
|
process.kill(owner, 0);
|
|
8902
9201
|
} catch (error2) {
|
|
8903
9202
|
if (error2.code === "ESRCH") {
|
|
8904
|
-
|
|
9203
|
+
fs25.unlinkSync(lock);
|
|
8905
9204
|
continue;
|
|
8906
9205
|
}
|
|
8907
9206
|
}
|
|
@@ -8920,7 +9219,7 @@ async function runSync(store) {
|
|
|
8920
9219
|
return await syncStore(store);
|
|
8921
9220
|
} finally {
|
|
8922
9221
|
try {
|
|
8923
|
-
|
|
9222
|
+
fs25.unlinkSync(lock);
|
|
8924
9223
|
} catch {
|
|
8925
9224
|
}
|
|
8926
9225
|
}
|
|
@@ -8948,9 +9247,9 @@ async function syncStore(store) {
|
|
|
8948
9247
|
result.errors.push(String(e instanceof Error ? e.message : e));
|
|
8949
9248
|
}
|
|
8950
9249
|
}
|
|
8951
|
-
const acknowledgementsFile =
|
|
9250
|
+
const acknowledgementsFile = path20.join(store.root, "synced-traces.json");
|
|
8952
9251
|
const acknowledgements = readJson(acknowledgementsFile, {});
|
|
8953
|
-
const fingerprint2 = (trace) =>
|
|
9252
|
+
const fingerprint2 = (trace) => createHash8("sha256").update(JSON.stringify(trace)).digest("hex");
|
|
8954
9253
|
const traces = store.allTraces().filter((t) => acknowledgements[t.id] !== fingerprint2(t)).slice(0, 50);
|
|
8955
9254
|
for (const t of traces) {
|
|
8956
9255
|
try {
|
|
@@ -9203,11 +9502,11 @@ __export(demo_exports, {
|
|
|
9203
9502
|
runDemo: () => runDemo
|
|
9204
9503
|
});
|
|
9205
9504
|
import assert from "node:assert/strict";
|
|
9206
|
-
import
|
|
9505
|
+
import fs26 from "node:fs";
|
|
9207
9506
|
import os4 from "node:os";
|
|
9208
|
-
import
|
|
9507
|
+
import path21 from "node:path";
|
|
9209
9508
|
async function runDemo(write) {
|
|
9210
|
-
const root =
|
|
9509
|
+
const root = fs26.mkdtempSync(path21.join(os4.tmpdir(), "bunshin-demo-"));
|
|
9211
9510
|
try {
|
|
9212
9511
|
const store = Store.init(root, { ...APR_ENGINEER, apiUrl: "" });
|
|
9213
9512
|
const { lessons, constraint, workflow } = aprScenario();
|
|
@@ -9224,7 +9523,7 @@ Task: ${APR_TASK}
|
|
|
9224
9523
|
write(result.text);
|
|
9225
9524
|
write("\nPASS: Collections constraint and workflow recalled; C-suite, other-company, and superseded lessons excluded.\n");
|
|
9226
9525
|
} finally {
|
|
9227
|
-
|
|
9526
|
+
fs26.rmSync(root, { recursive: true, force: true });
|
|
9228
9527
|
}
|
|
9229
9528
|
}
|
|
9230
9529
|
var init_demo = __esm({
|
|
@@ -9237,21 +9536,21 @@ var init_demo = __esm({
|
|
|
9237
9536
|
});
|
|
9238
9537
|
|
|
9239
9538
|
// src/v2/gitremote.ts
|
|
9240
|
-
import
|
|
9241
|
-
import
|
|
9242
|
-
import { createHash as
|
|
9539
|
+
import fs27 from "node:fs";
|
|
9540
|
+
import path22 from "node:path";
|
|
9541
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
9243
9542
|
function saveGitCredential(apiUrl, apiKey) {
|
|
9244
9543
|
const file = gitCredentialFile();
|
|
9245
|
-
|
|
9544
|
+
fs27.mkdirSync(path22.dirname(file), { recursive: true, mode: 448 });
|
|
9246
9545
|
let all = {};
|
|
9247
9546
|
try {
|
|
9248
|
-
all = JSON.parse(
|
|
9547
|
+
all = JSON.parse(fs27.readFileSync(file, "utf8"));
|
|
9249
9548
|
} catch {
|
|
9250
9549
|
}
|
|
9251
9550
|
all[apiUrl.replace(/\/$/, "")] = apiKey;
|
|
9252
9551
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
9253
|
-
|
|
9254
|
-
|
|
9552
|
+
fs27.writeFileSync(tmp, JSON.stringify(all, null, 2) + "\n", { mode: 384 });
|
|
9553
|
+
fs27.renameSync(tmp, file);
|
|
9255
9554
|
}
|
|
9256
9555
|
function resolveGitKey(apiUrl) {
|
|
9257
9556
|
const base = apiUrl.replace(/\/$/, "");
|
|
@@ -9259,20 +9558,20 @@ function resolveGitKey(apiUrl) {
|
|
|
9259
9558
|
const origin = urlOrigin(base);
|
|
9260
9559
|
if (process.env.BUNSHIN_API_KEY && origin && trustedOrigins().has(origin)) return process.env.BUNSHIN_API_KEY;
|
|
9261
9560
|
try {
|
|
9262
|
-
const all = JSON.parse(
|
|
9561
|
+
const all = JSON.parse(fs27.readFileSync(gitCredentialFile(), "utf8"));
|
|
9263
9562
|
if (all[base]) return all[base];
|
|
9264
9563
|
} catch {
|
|
9265
9564
|
}
|
|
9266
|
-
const
|
|
9565
|
+
const dir2 = path22.join(homeDir(), ".config/bunshin/credentials");
|
|
9267
9566
|
let files = [];
|
|
9268
9567
|
try {
|
|
9269
|
-
files =
|
|
9568
|
+
files = fs27.readdirSync(dir2).filter((f) => f.endsWith(".json"));
|
|
9270
9569
|
} catch {
|
|
9271
9570
|
return void 0;
|
|
9272
9571
|
}
|
|
9273
9572
|
for (const f of files.sort()) {
|
|
9274
9573
|
try {
|
|
9275
|
-
const c = JSON.parse(
|
|
9574
|
+
const c = JSON.parse(fs27.readFileSync(path22.join(dir2, f), "utf8"));
|
|
9276
9575
|
if (c.apiKey && c.apiUrl?.replace(/\/$/, "") === base) return c.apiKey;
|
|
9277
9576
|
} catch {
|
|
9278
9577
|
}
|
|
@@ -9280,7 +9579,7 @@ function resolveGitKey(apiUrl) {
|
|
|
9280
9579
|
return void 0;
|
|
9281
9580
|
}
|
|
9282
9581
|
function refsDigest(refs) {
|
|
9283
|
-
return
|
|
9582
|
+
return createHash9("sha256").update(
|
|
9284
9583
|
[...refs].sort((a, b) => a.ref.localeCompare(b.ref)).map((r) => `${r.sha} ${r.ref}`).join("\n")
|
|
9285
9584
|
).digest("hex").slice(0, 12);
|
|
9286
9585
|
}
|
|
@@ -9293,7 +9592,7 @@ var init_gitremote = __esm({
|
|
|
9293
9592
|
ZERO_SHA = "0".repeat(40);
|
|
9294
9593
|
REMOTE_PATH = "/v1/git/repos/";
|
|
9295
9594
|
remoteUrl = (apiUrl, repo) => `bunshin::${apiUrl.replace(/\/$/, "")}${REMOTE_PATH}${repo}`;
|
|
9296
|
-
gitCredentialFile = () =>
|
|
9595
|
+
gitCredentialFile = () => path22.join(homeDir(), ".config/bunshin/git-credentials.json");
|
|
9297
9596
|
GitApiError = class extends Error {
|
|
9298
9597
|
constructor(status, message, body = null) {
|
|
9299
9598
|
super(message);
|
|
@@ -9385,8 +9684,8 @@ __export(gitrepo_exports, {
|
|
|
9385
9684
|
githubReferences: () => githubReferences,
|
|
9386
9685
|
repoFromUrl: () => repoFromUrl
|
|
9387
9686
|
});
|
|
9388
|
-
import
|
|
9389
|
-
import
|
|
9687
|
+
import fs28 from "node:fs";
|
|
9688
|
+
import path23 from "node:path";
|
|
9390
9689
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
9391
9690
|
function git3(cwd, args, opts = {}) {
|
|
9392
9691
|
const r = spawnSync2("git", ["-C", cwd, ...args], { encoding: "utf8", stdio: opts.inherit ? ["ignore", "inherit", "inherit"] : ["ignore", "pipe", "pipe"], maxBuffer: 256 * 1024 * 1024 });
|
|
@@ -9395,9 +9694,9 @@ function git3(cwd, args, opts = {}) {
|
|
|
9395
9694
|
return res;
|
|
9396
9695
|
}
|
|
9397
9696
|
function onPath(name) {
|
|
9398
|
-
for (const
|
|
9697
|
+
for (const dir2 of (process.env.PATH ?? "").split(path23.delimiter)) {
|
|
9399
9698
|
try {
|
|
9400
|
-
|
|
9699
|
+
fs28.accessSync(path23.join(dir2, name), fs28.constants.X_OK);
|
|
9401
9700
|
return true;
|
|
9402
9701
|
} catch {
|
|
9403
9702
|
}
|
|
@@ -9448,9 +9747,9 @@ async function connect(args, cwd, prompt = true, reuseKey = true) {
|
|
|
9448
9747
|
if (!reuseKey || !process.env.BUNSHIN_API_KEY || key !== process.env.BUNSHIN_API_KEY) saveGitCredential(apiUrl, key);
|
|
9449
9748
|
return client;
|
|
9450
9749
|
}
|
|
9451
|
-
function toplevel(
|
|
9452
|
-
const r = git3(
|
|
9453
|
-
if (!r.ok) throw new Error(`${
|
|
9750
|
+
function toplevel(dir2) {
|
|
9751
|
+
const r = git3(dir2, ["rev-parse", "--show-toplevel"], { allowFail: true });
|
|
9752
|
+
if (!r.ok) throw new Error(`${dir2} is not inside a git repository`);
|
|
9454
9753
|
return r.out.trim();
|
|
9455
9754
|
}
|
|
9456
9755
|
function githubReferences(root, repo) {
|
|
@@ -9481,11 +9780,11 @@ function switchOrigin(root, url, out) {
|
|
|
9481
9780
|
}
|
|
9482
9781
|
async function cmdImport(args, cwd, out, err) {
|
|
9483
9782
|
requireHelper();
|
|
9484
|
-
const root = toplevel(
|
|
9783
|
+
const root = toplevel(path23.resolve(str4(args.flags.path) ?? args._[1] ?? cwd));
|
|
9485
9784
|
if (git3(root, ["rev-parse", "--is-shallow-repository"]).out.trim() === "true") throw new Error("this clone is shallow; fetch the full history first: git fetch --unshallow");
|
|
9486
9785
|
const rs = remotes(root);
|
|
9487
9786
|
const originUrl = rs.get("origin");
|
|
9488
|
-
const name = str4(args.flags.name) ?? (originUrl && !originUrl.startsWith("bunshin::") ? repoFromUrl(originUrl) : null) ?? `${
|
|
9787
|
+
const name = str4(args.flags.name) ?? (originUrl && !originUrl.startsWith("bunshin::") ? repoFromUrl(originUrl) : null) ?? `${path23.basename(path23.dirname(root))}/${path23.basename(root)}`;
|
|
9489
9788
|
const client = await connect(args, cwd);
|
|
9490
9789
|
const url = remoteUrl(client.apiUrl, name);
|
|
9491
9790
|
const fromOrigin = !!originUrl && !originUrl.startsWith("bunshin::") && !bool3(args.flags.local);
|
|
@@ -9561,7 +9860,7 @@ ${refsToGithub.length} line${refsToGithub.length === 1 ? "" : "s"} in the reposi
|
|
|
9561
9860
|
if (refsToGithub.length > 40) out(` \u2026 and ${refsToGithub.length - 40} more (git grep for ${name})
|
|
9562
9861
|
`);
|
|
9563
9862
|
}
|
|
9564
|
-
if (
|
|
9863
|
+
if (fs28.existsSync(path23.join(root, ".github"))) out(`
|
|
9565
9864
|
Note: .github/ (Actions, CODEOWNERS, templates) only works on GitHub; Bunshin does not run it.
|
|
9566
9865
|
`);
|
|
9567
9866
|
out(`
|
|
@@ -9572,7 +9871,7 @@ New machines clone with: bunshin repo clone ${name}
|
|
|
9572
9871
|
}
|
|
9573
9872
|
async function cmdAttach(args, cwd, out, err) {
|
|
9574
9873
|
requireHelper();
|
|
9575
|
-
const root = toplevel(
|
|
9874
|
+
const root = toplevel(path23.resolve(str4(args.flags.path) ?? cwd));
|
|
9576
9875
|
const rs = remotes(root);
|
|
9577
9876
|
const originUrl = rs.get("origin");
|
|
9578
9877
|
const name = str4(args.flags.name) ?? args._[1] ?? (originUrl ? repoFromUrl(originUrl) : null);
|
|
@@ -9617,15 +9916,15 @@ async function cmdClone(args, cwd, out) {
|
|
|
9617
9916
|
const name = args._[1];
|
|
9618
9917
|
if (!name) throw new Error("usage: bunshin repo clone owner/name [directory]");
|
|
9619
9918
|
const client = await connect(args, cwd);
|
|
9620
|
-
const
|
|
9621
|
-
const r = spawnSync2("git", ["clone", remoteUrl(client.apiUrl, name),
|
|
9919
|
+
const dir2 = args._[2] ?? name.split("/")[1];
|
|
9920
|
+
const r = spawnSync2("git", ["clone", remoteUrl(client.apiUrl, name), dir2], { cwd, stdio: "inherit" });
|
|
9622
9921
|
if (r.status !== 0) return r.status ?? 1;
|
|
9623
|
-
out(`Cloned ${name} into ${
|
|
9922
|
+
out(`Cloned ${name} into ${path23.resolve(cwd, dir2)}
|
|
9624
9923
|
`);
|
|
9625
9924
|
return 0;
|
|
9626
9925
|
}
|
|
9627
9926
|
async function cmdVerify(args, cwd, out, err) {
|
|
9628
|
-
const root = toplevel(
|
|
9927
|
+
const root = toplevel(path23.resolve(str4(args.flags.path) ?? cwd));
|
|
9629
9928
|
const rs = remotes(root);
|
|
9630
9929
|
const originUrl = rs.get("origin") ?? "";
|
|
9631
9930
|
if (!originUrl.startsWith("bunshin::")) throw new Error("origin is not a Bunshin repository (run bunshin repo attach)");
|
|
@@ -9788,22 +10087,22 @@ __export(runner_exports, {
|
|
|
9788
10087
|
setupText: () => setupText
|
|
9789
10088
|
});
|
|
9790
10089
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
9791
|
-
import
|
|
10090
|
+
import fs29 from "node:fs";
|
|
9792
10091
|
import os5 from "node:os";
|
|
9793
|
-
import
|
|
10092
|
+
import path24 from "node:path";
|
|
9794
10093
|
function runnerHome(home = process.env.BUNSHIN_RUNNER_HOME || os5.homedir()) {
|
|
9795
|
-
return
|
|
10094
|
+
return path24.join(home, ".bunshin", "runner");
|
|
9796
10095
|
}
|
|
9797
10096
|
function readInstalled(home) {
|
|
9798
10097
|
try {
|
|
9799
|
-
return JSON.parse(
|
|
10098
|
+
return JSON.parse(fs29.readFileSync(path24.join(runnerHome(home), "config.json"), "utf8"));
|
|
9800
10099
|
} catch {
|
|
9801
10100
|
return null;
|
|
9802
10101
|
}
|
|
9803
10102
|
}
|
|
9804
10103
|
function connection(args, cwd) {
|
|
9805
10104
|
const saved = readInstalled();
|
|
9806
|
-
const store = saved &&
|
|
10105
|
+
const store = saved && path24.resolve(cwd).startsWith(runnerHome()) ? null : Store.discover(cwd);
|
|
9807
10106
|
const config = store?.config();
|
|
9808
10107
|
const key = str5(args.flags.key) || process.env.BUNSHIN_RUNNER_KEY || process.env.BUNSHIN_API_KEY || config?.apiKey || saved?.key || "";
|
|
9809
10108
|
const apiUrl = (str5(args.flags["api-url"]) || process.env.BUNSHIN_API_URL || config?.apiUrl || saved?.apiUrl || DEFAULT_API).replace(/\/$/, "");
|
|
@@ -9863,9 +10162,9 @@ async function runOnce(c, args, out, err) {
|
|
|
9863
10162
|
const [job] = await lease(c, args);
|
|
9864
10163
|
if (!job) break;
|
|
9865
10164
|
const started = Date.now();
|
|
9866
|
-
const scratch =
|
|
10165
|
+
const scratch = fs29.mkdtempSync(path24.join(os5.tmpdir(), "bunshin-job-"));
|
|
9867
10166
|
const res = await askJson(config, { system: job.system, user: job.prompt, schema: job.schema, enforceSchema: false, timeoutMs: 15 * 6e4, maxTokens: 16e3, cwd: scratch }).finally(
|
|
9868
|
-
() =>
|
|
10167
|
+
() => fs29.rmSync(scratch, { recursive: true, force: true })
|
|
9869
10168
|
);
|
|
9870
10169
|
const model = config.backend === "codex-cli" ? config.codexModel ?? "codex default" : config.backend === "pi-cli" ? config.piModel ?? "pi default" : config.model;
|
|
9871
10170
|
if (!res.ok) {
|
|
@@ -9955,24 +10254,24 @@ function renderCheck(id, c) {
|
|
|
9955
10254
|
}
|
|
9956
10255
|
function logAnswer(entry) {
|
|
9957
10256
|
try {
|
|
9958
|
-
|
|
9959
|
-
|
|
10257
|
+
fs29.mkdirSync(runnerHome(), { recursive: true });
|
|
10258
|
+
fs29.appendFileSync(path24.join(runnerHome(), "log.jsonl"), `${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), ...entry })}
|
|
9960
10259
|
`);
|
|
9961
10260
|
} catch {
|
|
9962
10261
|
}
|
|
9963
10262
|
}
|
|
9964
10263
|
function which(name) {
|
|
9965
|
-
for (const d of [...(process.env.PATH || "").split(
|
|
9966
|
-
if (d &&
|
|
10264
|
+
for (const d of [...(process.env.PATH || "").split(path24.delimiter), path24.join(os5.homedir(), ".local", "bin")]) {
|
|
10265
|
+
if (d && fs29.existsSync(path24.join(d, name))) return path24.join(d, name);
|
|
9967
10266
|
}
|
|
9968
10267
|
return null;
|
|
9969
10268
|
}
|
|
9970
10269
|
function scheduledCommand(backend) {
|
|
9971
|
-
const node =
|
|
9972
|
-
const npx =
|
|
10270
|
+
const node = path24.dirname(process.execPath);
|
|
10271
|
+
const npx = fs29.existsSync(path24.join(node, "npx")) ? path24.join(node, "npx") : which("npx") ?? "npx";
|
|
9973
10272
|
const bin = BIN_OF[backend] ? which(BIN_OF[backend]) : null;
|
|
9974
|
-
const dirs = [.../* @__PURE__ */ new Set([node, ...bin ? [
|
|
9975
|
-
return { argv: [npx, "-y", PKG2, "runner", "--once"], path: dirs.join(
|
|
10273
|
+
const dirs = [.../* @__PURE__ */ new Set([node, ...bin ? [path24.dirname(bin)] : [], path24.join(os5.homedir(), ".local", "bin"), "/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin", "/usr/sbin", "/sbin"])];
|
|
10274
|
+
return { argv: [npx, "-y", PKG2, "runner", "--once"], path: dirs.join(path24.delimiter) };
|
|
9976
10275
|
}
|
|
9977
10276
|
function launchdPlist(backend, everyMinutes, home = runnerHome()) {
|
|
9978
10277
|
const cmd = scheduledCommand(backend);
|
|
@@ -9987,8 +10286,8 @@ function launchdPlist(backend, everyMinutes, home = runnerHome()) {
|
|
|
9987
10286
|
<dict><key>PATH</key><string>${xml(cmd.path)}</string></dict>
|
|
9988
10287
|
<key>WorkingDirectory</key><string>${xml(home)}</string>
|
|
9989
10288
|
<key>StartInterval</key><integer>${everyMinutes * 60}</integer>
|
|
9990
|
-
<key>StandardOutPath</key><string>${xml(
|
|
9991
|
-
<key>StandardErrorPath</key><string>${xml(
|
|
10289
|
+
<key>StandardOutPath</key><string>${xml(path24.join(home, "runner.log"))}</string>
|
|
10290
|
+
<key>StandardErrorPath</key><string>${xml(path24.join(home, "runner.log"))}</string>
|
|
9992
10291
|
<key>ProcessType</key><string>Background</string>
|
|
9993
10292
|
<key>LowPriorityIO</key><true/>
|
|
9994
10293
|
</dict>
|
|
@@ -9999,19 +10298,19 @@ function cronLine(backend, everyMinutes, home = runnerHome()) {
|
|
|
9999
10298
|
const cmd = scheduledCommand(backend);
|
|
10000
10299
|
const when = everyMinutes >= 60 ? `17 */${Math.max(1, Math.round(everyMinutes / 60))} * * *` : `*/${everyMinutes} * * * *`;
|
|
10001
10300
|
const q = (v) => `'${v.replace(/'/g, `'\\''`)}'`;
|
|
10002
|
-
return `${when} cd ${q(home)} && PATH=${q(cmd.path)} ${cmd.argv.map(q).join(" ")} >> ${q(
|
|
10301
|
+
return `${when} cd ${q(home)} && PATH=${q(cmd.path)} ${cmd.argv.map(q).join(" ")} >> ${q(path24.join(home, "runner.log"))} 2>&1 ${CRON_MARK}`;
|
|
10003
10302
|
}
|
|
10004
10303
|
function launchAgentsDir(opts) {
|
|
10005
|
-
return opts.launchAgents ??
|
|
10304
|
+
return opts.launchAgents ?? path24.join(process.env.BUNSHIN_RUNNER_HOME || os5.homedir(), "Library", "LaunchAgents");
|
|
10006
10305
|
}
|
|
10007
10306
|
function installSchedule(backend, everyMinutes, opts = {}) {
|
|
10008
10307
|
const platform = opts.platform ?? process.platform;
|
|
10009
10308
|
const run2 = opts.run ?? realRun;
|
|
10010
10309
|
if (platform === "darwin") {
|
|
10011
|
-
const
|
|
10012
|
-
const plist =
|
|
10013
|
-
|
|
10014
|
-
|
|
10310
|
+
const dir2 = launchAgentsDir(opts);
|
|
10311
|
+
const plist = path24.join(dir2, `${LAUNCHD_LABEL}.plist`);
|
|
10312
|
+
fs29.mkdirSync(dir2, { recursive: true });
|
|
10313
|
+
fs29.writeFileSync(plist, launchdPlist(backend, everyMinutes));
|
|
10015
10314
|
const domain = `gui/${process.getuid?.() ?? 501}`;
|
|
10016
10315
|
run2("launchctl", ["bootout", `${domain}/${LAUNCHD_LABEL}`]);
|
|
10017
10316
|
const r = run2("launchctl", ["bootstrap", domain, plist]);
|
|
@@ -10031,10 +10330,10 @@ function removeSchedule(opts = {}) {
|
|
|
10031
10330
|
const platform = opts.platform ?? process.platform;
|
|
10032
10331
|
const run2 = opts.run ?? realRun;
|
|
10033
10332
|
if (platform === "darwin") {
|
|
10034
|
-
const plist =
|
|
10333
|
+
const plist = path24.join(launchAgentsDir(opts), `${LAUNCHD_LABEL}.plist`);
|
|
10035
10334
|
run2("launchctl", ["bootout", `gui/${process.getuid?.() ?? 501}/${LAUNCHD_LABEL}`]);
|
|
10036
|
-
if (!
|
|
10037
|
-
|
|
10335
|
+
if (!fs29.existsSync(plist)) return "no schedule";
|
|
10336
|
+
fs29.rmSync(plist, { force: true });
|
|
10038
10337
|
return `removed ${plist}`;
|
|
10039
10338
|
}
|
|
10040
10339
|
const current = run2("crontab", ["-l"]);
|
|
@@ -10061,10 +10360,10 @@ async function install(args, cwd, out, err, opts = {}) {
|
|
|
10061
10360
|
}
|
|
10062
10361
|
const every = Math.max(15, Number(str5(args.flags.every) ?? 60) || 60);
|
|
10063
10362
|
const home = runnerHome();
|
|
10064
|
-
|
|
10363
|
+
fs29.mkdirSync(home, { recursive: true, mode: 448 });
|
|
10065
10364
|
const saved = { apiUrl: c.apiUrl, key: c.key, backend, ...config.model && str5(args.flags.model) ? { model: config.model } : {}, name: c.name, every };
|
|
10066
|
-
|
|
10067
|
-
|
|
10365
|
+
fs29.writeFileSync(path24.join(home, "config.json"), JSON.stringify(saved, null, 2) + "\n", { mode: 384 });
|
|
10366
|
+
fs29.chmodSync(path24.join(home, "config.json"), 384);
|
|
10068
10367
|
let schedule;
|
|
10069
10368
|
try {
|
|
10070
10369
|
schedule = installSchedule(backend, every, opts);
|
|
@@ -10077,7 +10376,7 @@ Run it yourself: npx -y ${PKG2} runner --once
|
|
|
10077
10376
|
out(`\u2713 Runner "${c.name}" answers ${c.apiUrl}'s model jobs with ${BACKEND_NAME[backend] ?? backend} on this computer.
|
|
10078
10377
|
`);
|
|
10079
10378
|
out(schedule.kind === "none" ? ` No scheduler here: run \`npx -y ${PKG2} runner --once\` on a schedule yourself.
|
|
10080
|
-
` : ` Every ${every} minutes while this computer is on (${schedule.kind}). Log: ${
|
|
10379
|
+
` : ` Every ${every} minutes while this computer is on (${schedule.kind}). Log: ${path24.join(home, "runner.log")}
|
|
10081
10380
|
`);
|
|
10082
10381
|
out(` Stop: npx -y ${PKG2} runner uninstall
|
|
10083
10382
|
`);
|
|
@@ -10094,9 +10393,9 @@ Answering what waits now\u2026
|
|
|
10094
10393
|
function uninstall(out, opts = {}) {
|
|
10095
10394
|
out(`${removeSchedule(opts)}
|
|
10096
10395
|
`);
|
|
10097
|
-
const file =
|
|
10098
|
-
if (
|
|
10099
|
-
|
|
10396
|
+
const file = path24.join(runnerHome(), "config.json");
|
|
10397
|
+
if (fs29.existsSync(file)) {
|
|
10398
|
+
fs29.rmSync(file, { force: true });
|
|
10100
10399
|
out(`removed ${file} (revoke the key in the console: Agent keys)
|
|
10101
10400
|
`);
|
|
10102
10401
|
}
|
|
@@ -10276,7 +10575,7 @@ async function cmdRunner(args, cwd, out, err, opts = {}) {
|
|
|
10276
10575
|
out(text2);
|
|
10277
10576
|
return 0;
|
|
10278
10577
|
}
|
|
10279
|
-
|
|
10578
|
+
fs29.writeFileSync(file, text2, { mode: 384 });
|
|
10280
10579
|
const left = Math.round((Date.parse(job.leaseUntil) - Date.now()) / 6e4);
|
|
10281
10580
|
out(
|
|
10282
10581
|
[
|
|
@@ -10294,7 +10593,7 @@ async function cmdRunner(args, cwd, out, err, opts = {}) {
|
|
|
10294
10593
|
const id = args._[2];
|
|
10295
10594
|
const file = args._[3] ?? "-";
|
|
10296
10595
|
if (!id) throw new Error("usage: bunshin runner validate <job id> <file|->");
|
|
10297
|
-
const text2 = file === "-" ?
|
|
10596
|
+
const text2 = file === "-" ? fs29.readFileSync(0, "utf8") : fs29.readFileSync(file, "utf8");
|
|
10298
10597
|
const r = await call(c, "POST", `/jobs/${id}/check`, { answer: text2, runner: c.name }, 12e4);
|
|
10299
10598
|
out(renderCheck(id, r.body));
|
|
10300
10599
|
return r.body.ok ? 0 : 1;
|
|
@@ -10303,7 +10602,7 @@ async function cmdRunner(args, cwd, out, err, opts = {}) {
|
|
|
10303
10602
|
const id = args._[2];
|
|
10304
10603
|
const file = args._[3] ?? "-";
|
|
10305
10604
|
if (!id) throw new Error("usage: bunshin runner submit <job id> <file|-> [--force]");
|
|
10306
|
-
const text2 = file === "-" ?
|
|
10605
|
+
const text2 = file === "-" ? fs29.readFileSync(0, "utf8") : fs29.readFileSync(file, "utf8");
|
|
10307
10606
|
const check = await call(c, "POST", `/jobs/${id}/check`, { answer: text2, runner: c.name }, 12e4).catch(() => null);
|
|
10308
10607
|
if (check?.body && (check.status < 400 || check.body.problems)) {
|
|
10309
10608
|
out(renderCheck(id, check.body));
|
|
@@ -10327,7 +10626,7 @@ async function cmdRunner(args, cwd, out, err, opts = {}) {
|
|
|
10327
10626
|
const since = Date.parse(str5(args.flags.since) ?? "") || Date.now() - 12 * 36e5;
|
|
10328
10627
|
const entries = (() => {
|
|
10329
10628
|
try {
|
|
10330
|
-
return
|
|
10629
|
+
return fs29.readFileSync(path24.join(runnerHome(), "log.jsonl"), "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l)).filter((e) => Date.parse(e.at) >= since);
|
|
10331
10630
|
} catch {
|
|
10332
10631
|
return [];
|
|
10333
10632
|
}
|
|
@@ -10397,8 +10696,8 @@ var init_runner = __esm({
|
|
|
10397
10696
|
|
|
10398
10697
|
// src/v2/importer.ts
|
|
10399
10698
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
10400
|
-
import
|
|
10401
|
-
import
|
|
10699
|
+
import fs30 from "node:fs";
|
|
10700
|
+
import path25 from "node:path";
|
|
10402
10701
|
function rewriteRepoPaths(text2, repoName2, pack) {
|
|
10403
10702
|
const re = new RegExp(`(?<=^|[\\s\`'"(=:])(?:~|\\$\\{?[A-Za-z_][A-Za-z0-9_]*\\}?)?(?:/[\\w.@+-]+)*/${escapeRe(repoName2)}(?=[/\\s\`'")\\]]|[.,;:](?:\\s|$)|$)`, "gm");
|
|
10404
10703
|
return text2.replace(re, `~/.bunshin/library/${pack}`);
|
|
@@ -10433,17 +10732,17 @@ function buildDocs(files, opts) {
|
|
|
10433
10732
|
const claimed = /* @__PURE__ */ new Set();
|
|
10434
10733
|
const all = [...files.keys()].sort();
|
|
10435
10734
|
const skillDirs = all.map(skillDirOf).filter((d) => !!d);
|
|
10436
|
-
for (const
|
|
10437
|
-
const main2 = `${
|
|
10735
|
+
for (const dir2 of skillDirs) {
|
|
10736
|
+
const main2 = `${dir2}/SKILL.md`;
|
|
10438
10737
|
const text2 = rw(files.get(main2).content);
|
|
10439
10738
|
const fm = frontmatterDescription(text2);
|
|
10440
|
-
const name =
|
|
10739
|
+
const name = path25.posix.basename(dir2);
|
|
10441
10740
|
const bundle = {};
|
|
10442
|
-
for (const p of all.filter((x) => x.startsWith(`${
|
|
10741
|
+
for (const p of all.filter((x) => x.startsWith(`${dir2}/`))) {
|
|
10443
10742
|
claimed.add(p);
|
|
10444
10743
|
if (p === main2) continue;
|
|
10445
10744
|
const f = files.get(p);
|
|
10446
|
-
bundle[p.slice(
|
|
10745
|
+
bundle[p.slice(dir2.length + 1)] = { content: rw(f.content), ...f.mode ? { mode: f.mode } : {} };
|
|
10447
10746
|
}
|
|
10448
10747
|
const description = fm.description.replace(/\s+/g, " ").trim() || firstSentence(text2) || name;
|
|
10449
10748
|
const { name: _n, description: _d, ...rest } = fm.data;
|
|
@@ -10470,9 +10769,9 @@ function buildDocs(files, opts) {
|
|
|
10470
10769
|
if (/\.md$/i.test(p) && looksGenerated(files.get(p).content)) claimed.add(p);
|
|
10471
10770
|
}
|
|
10472
10771
|
const dirs = new Set(all.filter((x) => x.includes("/") && !claimed.has(x)).map((x) => x.split("/")[0]));
|
|
10473
|
-
for (const
|
|
10474
|
-
if (skillParents.has(
|
|
10475
|
-
const members = all.filter((x) => x.startsWith(`${
|
|
10772
|
+
for (const dir2 of [...dirs].sort()) {
|
|
10773
|
+
if (skillParents.has(dir2) && all.filter((x) => x.startsWith(`${dir2}/`) && !claimed.has(x)).length === 0) continue;
|
|
10774
|
+
const members = all.filter((x) => x.startsWith(`${dir2}/`) && !claimed.has(x));
|
|
10476
10775
|
if (!members.length) continue;
|
|
10477
10776
|
const readme = members.find((x) => /^[^/]+\/README(\.md)?$/i.test(x));
|
|
10478
10777
|
const bundle = {};
|
|
@@ -10483,15 +10782,15 @@ function buildDocs(files, opts) {
|
|
|
10483
10782
|
bundle[p] = { content: rw(f.content), ...f.mode ? { mode: f.mode } : {} };
|
|
10484
10783
|
}
|
|
10485
10784
|
const tool = members.some((p) => files.get(p).mode === "x" || SCRIPT.test(p));
|
|
10486
|
-
const body = readme ? rw(files.get(readme).content) : `# ${
|
|
10785
|
+
const body = readme ? rw(files.get(readme).content) : `# ${dir2}
|
|
10487
10786
|
|
|
10488
10787
|
${members.map((p) => `- \`${p}\``).join("\n")}
|
|
10489
10788
|
`;
|
|
10490
10789
|
docs.push({
|
|
10491
|
-
path: readme ?? `${
|
|
10790
|
+
path: readme ?? `${dir2}/#index`,
|
|
10492
10791
|
kind: tool ? "tool" : "reference",
|
|
10493
|
-
title:
|
|
10494
|
-
when: readme ? firstSentence(body) || `The ${
|
|
10792
|
+
title: dir2,
|
|
10793
|
+
when: readme ? firstSentence(body) || `The ${dir2} ${tool ? "tool" : "folder"}` : `The ${dir2} ${tool ? "tool" : "folder"} of ${opts.pack}`,
|
|
10495
10794
|
body,
|
|
10496
10795
|
files: bundle
|
|
10497
10796
|
});
|
|
@@ -10534,23 +10833,23 @@ function isGitRepo(root) {
|
|
|
10534
10833
|
}
|
|
10535
10834
|
function workingTreeFiles(root) {
|
|
10536
10835
|
const out = /* @__PURE__ */ new Map();
|
|
10537
|
-
const list2 = isGitRepo(root) ? git4(root, ["ls-files", "-z"]).split("\0").filter(Boolean) : (function walk(
|
|
10538
|
-
return
|
|
10836
|
+
const list2 = isGitRepo(root) ? git4(root, ["ls-files", "-z"]).split("\0").filter(Boolean) : (function walk(dir2, base = "") {
|
|
10837
|
+
return fs30.readdirSync(dir2, { withFileTypes: true }).flatMap((e) => {
|
|
10539
10838
|
if (e.name === ".git" || e.name === "node_modules") return [];
|
|
10540
10839
|
const rel = base ? `${base}/${e.name}` : e.name;
|
|
10541
|
-
return e.isDirectory() ? walk(
|
|
10840
|
+
return e.isDirectory() ? walk(path25.join(dir2, e.name), rel) : e.isFile() ? [rel] : [];
|
|
10542
10841
|
});
|
|
10543
10842
|
})(root);
|
|
10544
10843
|
for (const rel of list2) {
|
|
10545
|
-
const abs =
|
|
10844
|
+
const abs = path25.join(root, rel);
|
|
10546
10845
|
let buf;
|
|
10547
10846
|
try {
|
|
10548
|
-
buf =
|
|
10847
|
+
buf = fs30.readFileSync(abs);
|
|
10549
10848
|
} catch {
|
|
10550
10849
|
continue;
|
|
10551
10850
|
}
|
|
10552
10851
|
if (!isText(buf)) continue;
|
|
10553
|
-
const exec = (
|
|
10852
|
+
const exec = (fs30.statSync(abs).mode & 73) !== 0;
|
|
10554
10853
|
out.set(rel, { content: buf.toString("utf8"), ...exec ? { mode: "x" } : {} });
|
|
10555
10854
|
}
|
|
10556
10855
|
return out;
|
|
@@ -10584,7 +10883,7 @@ function diffDocs(prev, next) {
|
|
|
10584
10883
|
return { docs: next.filter((d) => before.get(d.path) !== fingerprint(d)), removed: [...before.keys()].filter((p) => !after.has(p)) };
|
|
10585
10884
|
}
|
|
10586
10885
|
function readSkillSystem(root, opts) {
|
|
10587
|
-
const repoName2 =
|
|
10886
|
+
const repoName2 = path25.basename(path25.resolve(root));
|
|
10588
10887
|
const build = (files2) => buildDocs(files2, { pack: opts.pack, repoName: repoName2, ...opts.rewrite === false ? { rewrite: false } : {} });
|
|
10589
10888
|
const tree = build(workingTreeFiles(root));
|
|
10590
10889
|
if (!opts.history || !isGitRepo(root)) return { versions: [{ summary: `import ${opts.pack}`, docs: tree, removed: [], complete: true }], docs: tree };
|
|
@@ -10661,7 +10960,7 @@ var init_importer = __esm({
|
|
|
10661
10960
|
escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
10662
10961
|
looksGenerated = (text2) => /generated by|do not edit( by hand)?/i.test(text2.slice(0, 600));
|
|
10663
10962
|
isStopHook = (text2) => /"decision"\s*:\s*"block"|decision.{0,4}block/.test(text2) && /REASON=/.test(text2);
|
|
10664
|
-
isUpdateHook = (p, text2) => /auto-?update/i.test(
|
|
10963
|
+
isUpdateHook = (p, text2) => /auto-?update/i.test(path25.basename(p)) || /git (pull|fetch)/.test(text2) && /SessionStart|session start/i.test(text2);
|
|
10665
10964
|
fingerprint = (d) => JSON.stringify(d);
|
|
10666
10965
|
REWRITE_SYSTEM = `You adapt a company's skill documents after its skill system moved into Bunshin.
|
|
10667
10966
|
|
|
@@ -10697,8 +10996,8 @@ __export(skills_exports, {
|
|
|
10697
10996
|
cmdSkill: () => cmdSkill,
|
|
10698
10997
|
parsePin: () => parsePin
|
|
10699
10998
|
});
|
|
10700
|
-
import
|
|
10701
|
-
import
|
|
10999
|
+
import fs31 from "node:fs";
|
|
11000
|
+
import path26 from "node:path";
|
|
10702
11001
|
function requireOnline(store, what) {
|
|
10703
11002
|
if (!canCall(store.config())) throw new Error(`${what} needs the hosted library: run \`bunshin setup\` first`);
|
|
10704
11003
|
}
|
|
@@ -10707,9 +11006,9 @@ async function cmdImport2(store, args, cwd, out, err) {
|
|
|
10707
11006
|
err("usage: bunshin import skills <dir> [--pack <name>] [--layer org|team:<unit>] [--owner-team <unit>] [--no-history] [--dry-run] [--propose-rewrites]\n");
|
|
10708
11007
|
return 1;
|
|
10709
11008
|
}
|
|
10710
|
-
const root =
|
|
10711
|
-
if (!
|
|
10712
|
-
const pack = str6(args.flags.pack) ??
|
|
11009
|
+
const root = path26.resolve(cwd, args._[2]);
|
|
11010
|
+
if (!fs31.existsSync(root)) throw new Error(`${root} does not exist`);
|
|
11011
|
+
const pack = str6(args.flags.pack) ?? path26.basename(root);
|
|
10713
11012
|
const history = args.flags.history !== false && !bool4(args.flags["no-history"]);
|
|
10714
11013
|
const read = readSkillSystem(root, { pack, history, ...str6(args.flags.since) ? { since: str6(args.flags.since) } : {} });
|
|
10715
11014
|
const kinds = /* @__PURE__ */ new Map();
|
|
@@ -10744,7 +11043,7 @@ async function cmdImport2(store, args, cwd, out, err) {
|
|
|
10744
11043
|
const propose = args.flags["propose-rewrites"];
|
|
10745
11044
|
if (propose) {
|
|
10746
11045
|
const only = typeof propose === "string" && propose !== "true" ? propose.split(",").map((x) => x.trim()).filter(Boolean) : [];
|
|
10747
|
-
await proposeRewrites(store, read.docs.filter((d) => !only.length || only.some((o) => d.path.includes(o))), pack,
|
|
11046
|
+
await proposeRewrites(store, read.docs.filter((d) => !only.length || only.some((o) => d.path.includes(o))), pack, path26.basename(root), out, err);
|
|
10748
11047
|
}
|
|
10749
11048
|
out("Run `bunshin sync` on each machine (it runs at session start) to install the skills, instructions and files.\n");
|
|
10750
11049
|
return 0;
|
|
@@ -10884,7 +11183,7 @@ ${report.skills} skills, ${report.references} references, ${report.errors.length
|
|
|
10884
11183
|
}
|
|
10885
11184
|
const graph = str6(args.flags.graph) ?? (bool4(args.flags.graph) ? "SKILLS-GRAPH.md" : void 0);
|
|
10886
11185
|
if (graph) {
|
|
10887
|
-
|
|
11186
|
+
fs31.writeFileSync(path26.resolve(cwd, graph), renderSkillGraph(report));
|
|
10888
11187
|
out(`graph written to ${graph}
|
|
10889
11188
|
`);
|
|
10890
11189
|
}
|
|
@@ -10974,9 +11273,9 @@ async function cmdLibrary(store, args, cwd, out, err) {
|
|
|
10974
11273
|
`;
|
|
10975
11274
|
const file = str6(args.flags.write) ?? (bool4(args.flags.write) ? ".claude/skills-version" : void 0);
|
|
10976
11275
|
if (file) {
|
|
10977
|
-
const abs =
|
|
10978
|
-
|
|
10979
|
-
|
|
11276
|
+
const abs = path26.resolve(cwd, file);
|
|
11277
|
+
fs31.mkdirSync(path26.dirname(abs), { recursive: true });
|
|
11278
|
+
fs31.writeFileSync(abs, line);
|
|
10980
11279
|
out(`${file}: ${line}`);
|
|
10981
11280
|
} else out(line);
|
|
10982
11281
|
return 0;
|
|
@@ -10986,16 +11285,16 @@ async function cmdLibrary(store, args, cwd, out, err) {
|
|
|
10986
11285
|
const to = str6(args.flags.to);
|
|
10987
11286
|
if (!to) throw new Error("--to <dir> is required");
|
|
10988
11287
|
const pinFile = str6(args.flags.pin);
|
|
10989
|
-
const pin = str6(args.flags.git) ? { git: str6(args.flags.git) } : pinFile ? parsePin(
|
|
11288
|
+
const pin = str6(args.flags.git) ? { git: str6(args.flags.git) } : pinFile ? parsePin(fs31.readFileSync(path26.resolve(cwd, pinFile), "utf8")) : parsePin(args._[2] ?? "");
|
|
10990
11289
|
const q = pin.git ? `git=${pin.git}` : pin.version ? `version=${pin.version}` : "";
|
|
10991
11290
|
if (!q) throw new Error("name a version: lib@N, --git <sha>, or --pin <file>");
|
|
10992
11291
|
const snap = await v2api(config, "GET", `/library/snapshot?${q}`, void 0, { timeoutMs: 12e4 });
|
|
10993
|
-
const
|
|
10994
|
-
if (
|
|
11292
|
+
const dir2 = path26.resolve(cwd, to);
|
|
11293
|
+
if (fs31.existsSync(dir2) && fs31.readdirSync(dir2).length && !bool4(args.flags.force)) throw new Error(`${dir2} is not empty (use --force)`);
|
|
10995
11294
|
const pack = str6(args.flags.pack);
|
|
10996
11295
|
const docs = snap.sections.filter((s) => !pack || s.pack === pack);
|
|
10997
|
-
const res = materialize(docs,
|
|
10998
|
-
out(`lib@${snap.version}${pin.git ? ` (commit ${pin.git})` : ""}: ${res.files} files in ${
|
|
11296
|
+
const res = materialize(docs, dir2);
|
|
11297
|
+
out(`lib@${snap.version}${pin.git ? ` (commit ${pin.git})` : ""}: ${res.files} files in ${dir2}
|
|
10999
11298
|
`);
|
|
11000
11299
|
return 0;
|
|
11001
11300
|
}
|
|
@@ -11030,27 +11329,27 @@ __export(reflect_exports, {
|
|
|
11030
11329
|
buildLesson: () => buildLesson,
|
|
11031
11330
|
runReflection: () => runReflection
|
|
11032
11331
|
});
|
|
11033
|
-
import
|
|
11034
|
-
import
|
|
11035
|
-
import { createHash as
|
|
11332
|
+
import fs32 from "node:fs";
|
|
11333
|
+
import path27 from "node:path";
|
|
11334
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
11036
11335
|
async function runReflection(store, opts) {
|
|
11037
11336
|
const empty = { lessonIds: [], used: [], missed: [], attribution: [] };
|
|
11038
|
-
const lock =
|
|
11337
|
+
const lock = path27.join(store.root, `reflect-${createHash10("sha256").update(opts.sessionId).digest("hex")}.lock`);
|
|
11039
11338
|
const deadline = Date.now() + 66e4;
|
|
11040
11339
|
for (; ; ) {
|
|
11041
11340
|
try {
|
|
11042
|
-
|
|
11341
|
+
fs32.writeFileSync(lock, String(process.pid), { flag: "wx", mode: 384 });
|
|
11043
11342
|
break;
|
|
11044
11343
|
} catch (error) {
|
|
11045
11344
|
if (error.code !== "EEXIST") throw error;
|
|
11046
11345
|
try {
|
|
11047
|
-
const owner = Number(
|
|
11346
|
+
const owner = Number(fs32.readFileSync(lock, "utf8"));
|
|
11048
11347
|
if (Number.isInteger(owner) && owner > 0) {
|
|
11049
11348
|
try {
|
|
11050
11349
|
process.kill(owner, 0);
|
|
11051
11350
|
} catch (error2) {
|
|
11052
11351
|
if (error2.code === "ESRCH") {
|
|
11053
|
-
|
|
11352
|
+
fs32.unlinkSync(lock);
|
|
11054
11353
|
continue;
|
|
11055
11354
|
}
|
|
11056
11355
|
}
|
|
@@ -11065,7 +11364,7 @@ async function runReflection(store, opts) {
|
|
|
11065
11364
|
return await reflectSession(store, opts);
|
|
11066
11365
|
} finally {
|
|
11067
11366
|
try {
|
|
11068
|
-
|
|
11367
|
+
fs32.unlinkSync(lock);
|
|
11069
11368
|
} catch {
|
|
11070
11369
|
}
|
|
11071
11370
|
}
|
|
@@ -11407,8 +11706,8 @@ __export(cli_exports, {
|
|
|
11407
11706
|
parseArgs: () => parseArgs,
|
|
11408
11707
|
version: () => version
|
|
11409
11708
|
});
|
|
11410
|
-
import
|
|
11411
|
-
import
|
|
11709
|
+
import fs33 from "node:fs";
|
|
11710
|
+
import path28 from "node:path";
|
|
11412
11711
|
function parseArgs(argv) {
|
|
11413
11712
|
const out = { _: [], flags: {} };
|
|
11414
11713
|
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", "watch", "once", "allow-codex-shell", "retrace", "across"]);
|
|
@@ -11508,7 +11807,7 @@ async function main(argv, opts = {}) {
|
|
|
11508
11807
|
if (harness !== "codex" && harness !== "claude-code" && harness !== "pi") throw new Error("Choose your agent: bunshin setup --harness codex, claude-code, or pi.");
|
|
11509
11808
|
const agent = harness === "claude-code" ? "claude" : harness;
|
|
11510
11809
|
if (!executable2(agent)) throw new Error(`${agent} was not found. Install and sign in to your agent, then run setup again.`);
|
|
11511
|
-
const prior = new Store(
|
|
11810
|
+
const prior = new Store(path28.join(cwd, ".bunshin"));
|
|
11512
11811
|
const existing = prior.trusted ? prior.config() : { apiUrl: "", apiKey: void 0 };
|
|
11513
11812
|
const apiUrl = str7(args.flags["api-url"]);
|
|
11514
11813
|
const key = bool5(args.flags["key-stdin"]) ? await new Promise((resolve, reject) => {
|
|
@@ -11537,7 +11836,7 @@ async function main(argv, opts = {}) {
|
|
|
11537
11836
|
});
|
|
11538
11837
|
if (result.config.hooks === "user" && !only && !exclude && !bool5(args.flags.all) && process.stdin.isTTY && !bool5(args.flags["key-stdin"])) {
|
|
11539
11838
|
const { askSources: askSources2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
|
|
11540
|
-
await askSources2(new Store(
|
|
11839
|
+
await askSources2(new Store(path28.join(cwd, ".bunshin")), out);
|
|
11541
11840
|
}
|
|
11542
11841
|
out(`Connected as ${result.profile.role}${result.profile.orgUnit ? ` in ${result.profile.orgUnit}` : " (company-wide)"}.
|
|
11543
11842
|
`);
|
|
@@ -11574,7 +11873,7 @@ Key saved privately outside the project.
|
|
|
11574
11873
|
if (cmd === "mcp") {
|
|
11575
11874
|
const { runMcpServer: runMcpServer2 } = await Promise.resolve().then(() => (init_mcp(), mcp_exports));
|
|
11576
11875
|
const root = str7(args.flags.root);
|
|
11577
|
-
await runMcpServer2({ cwd, ...root ? { store: new Store(
|
|
11876
|
+
await runMcpServer2({ cwd, ...root ? { store: new Store(path28.resolve(root)) } : {}, version: version() });
|
|
11578
11877
|
return 0;
|
|
11579
11878
|
}
|
|
11580
11879
|
if (cmd === "demo") {
|
|
@@ -11637,6 +11936,14 @@ Key saved privately outside the project.
|
|
|
11637
11936
|
case "meta-reflect":
|
|
11638
11937
|
case "meta":
|
|
11639
11938
|
return await cmdMetaReflect(store, args, cwd, out);
|
|
11939
|
+
case "route": {
|
|
11940
|
+
const { routeJob: routeJob2, sweepRouter: sweepRouter2 } = await Promise.resolve().then(() => (init_route(), route_exports));
|
|
11941
|
+
const answer = await routeJob2(store, str7(args.flags.session) ?? "", Number(args.flags.seq));
|
|
11942
|
+
sweepRouter2(new (await Promise.resolve().then(() => (init_store2(), store_exports))).V2Store(store));
|
|
11943
|
+
if (!bool5(args.flags.quiet)) out(`${JSON.stringify(answer)}
|
|
11944
|
+
`);
|
|
11945
|
+
return answer && !answer.error ? 0 : 1;
|
|
11946
|
+
}
|
|
11640
11947
|
case "update-check": {
|
|
11641
11948
|
const latest = await (await Promise.resolve().then(() => (init_update(), update_exports))).checkForUpdate();
|
|
11642
11949
|
if (!bool5(args.flags.quiet)) out(latest ? `latest ${latest} (this is ${version()})
|
|
@@ -11678,7 +11985,7 @@ ${HELP2}`);
|
|
|
11678
11985
|
function openStore(args, cwd) {
|
|
11679
11986
|
const root = str7(args.flags.root);
|
|
11680
11987
|
if (root) {
|
|
11681
|
-
const store = new Store(
|
|
11988
|
+
const store = new Store(path28.resolve(root));
|
|
11682
11989
|
store.ensureLayout();
|
|
11683
11990
|
return store;
|
|
11684
11991
|
}
|
|
@@ -11694,7 +12001,7 @@ async function cmdInit(args, cwd, out, err) {
|
|
|
11694
12001
|
orgId: str7(args.flags["org-id"]),
|
|
11695
12002
|
userId: str7(args.flags["user-id"]),
|
|
11696
12003
|
apiUrl: str7(args.flags["api-url"]),
|
|
11697
|
-
apiKey: bool5(args.flags["key-stdin"]) ?
|
|
12004
|
+
apiKey: bool5(args.flags["key-stdin"]) ? fs33.readFileSync(0, "utf8").trim() || void 0 : str7(args.flags["api-key"]),
|
|
11698
12005
|
scope: bool5(args.flags.global) ? "global" : str7(args.flags.scope),
|
|
11699
12006
|
harness,
|
|
11700
12007
|
noHooks: args.flags.hooks === false || bool5(args.flags["no-hooks"]),
|
|
@@ -11737,10 +12044,10 @@ function cmdStatus(store, args, out, v2) {
|
|
|
11737
12044
|
const conflicts = store.listConflicts().filter((c) => c.status === "open");
|
|
11738
12045
|
const metrics = store.readMetrics(void 0);
|
|
11739
12046
|
const last50 = metrics?.windows?.find((w) => w.name === "last50");
|
|
11740
|
-
const hookFiles = config.harness === "codex" ? [.../* @__PURE__ */ new Set([
|
|
12047
|
+
const hookFiles = config.harness === "codex" ? [.../* @__PURE__ */ new Set([path28.join(process.env.CODEX_HOME ?? path28.join(homeDir(), ".codex"), "hooks.json"), codexProjectHooks(path28.dirname(store.root)).file])] : config.harness === "claude-code" ? [path28.join(homeDir(), ".claude", "settings.json"), path28.join(path28.dirname(store.root), ".claude", "settings.local.json")] : config.harness === "pi" ? [path28.join(homeDir(), ".pi/agent/extensions/bunshin.js"), path28.join(path28.dirname(store.root), ".pi/extensions/bunshin.js")] : [];
|
|
11741
12048
|
const hooks = config.harness === "pi" ? hookFiles.filter((file) => {
|
|
11742
12049
|
try {
|
|
11743
|
-
return
|
|
12050
|
+
return fs33.readFileSync(file, "utf8").startsWith("// Managed by Bunshin setup.");
|
|
11744
12051
|
} catch {
|
|
11745
12052
|
return false;
|
|
11746
12053
|
}
|
|
@@ -11758,7 +12065,7 @@ function cmdStatus(store, args, out, v2) {
|
|
|
11758
12065
|
mothership: config.apiUrl ? { apiUrl: config.apiUrl, key: config.apiKey ? "set" : "missing", lastSyncAt: config.lastSyncAt ?? null } : null,
|
|
11759
12066
|
llmBackend: resolveBackend(config) ?? "none (set ANTHROPIC_API_KEY or install the claude/codex CLI)",
|
|
11760
12067
|
hooks: { [config.harness]: hooks },
|
|
11761
|
-
hookFiles: hookFiles.filter((file) =>
|
|
12068
|
+
hookFiles: hookFiles.filter((file) => fs33.existsSync(file)),
|
|
11762
12069
|
recallQuality: last50 ? { precision: last50.precision, recall: last50.recall, f1: last50.f1, traces: last50.traces } : null,
|
|
11763
12070
|
v2
|
|
11764
12071
|
};
|
|
@@ -11801,7 +12108,7 @@ function cmdStatus(store, args, out, v2) {
|
|
|
11801
12108
|
}
|
|
11802
12109
|
function hookCount(file) {
|
|
11803
12110
|
try {
|
|
11804
|
-
const raw = JSON.parse(
|
|
12111
|
+
const raw = JSON.parse(fs33.readFileSync(file, "utf8"));
|
|
11805
12112
|
let n = 0;
|
|
11806
12113
|
for (const groups of Object.values(raw.hooks ?? {})) {
|
|
11807
12114
|
for (const g of groups ?? []) {
|
|
@@ -11921,7 +12228,7 @@ function cmdLessonAdd(store, args, out, err) {
|
|
|
11921
12228
|
let content = str7(args.flags.content) ?? "";
|
|
11922
12229
|
if (!content || content === "-" || args._.includes("-")) {
|
|
11923
12230
|
try {
|
|
11924
|
-
content =
|
|
12231
|
+
content = fs33.readFileSync(0, "utf8");
|
|
11925
12232
|
} catch {
|
|
11926
12233
|
}
|
|
11927
12234
|
}
|
|
@@ -11987,7 +12294,7 @@ async function cmdReflect(store, args, cwd, out, err) {
|
|
|
11987
12294
|
}
|
|
11988
12295
|
const { runReflection: runReflection2 } = await Promise.resolve().then(() => (init_reflect4(), reflect_exports));
|
|
11989
12296
|
const result = await runReflection2(store, {
|
|
11990
|
-
sessionId: str7(args.flags.session) ??
|
|
12297
|
+
sessionId: str7(args.flags.session) ?? path28.basename(transcript).replace(/\.jsonl?$/, ""),
|
|
11991
12298
|
transcriptPath: transcript,
|
|
11992
12299
|
cwd,
|
|
11993
12300
|
force: bool5(args.flags.force)
|
|
@@ -12190,7 +12497,7 @@ Global: --root <dir> (use this .bunshin store) --version --help
|
|
|
12190
12497
|
Env: BUNSHIN_API_KEY, BUNSHIN_HARNESS, BUNSHIN_DISABLE, BUNSHIN_CHILD, BUNSHIN_CLAUDE_BIN,
|
|
12191
12498
|
CLAUDE_CODE_SESSION_ID (learn / mcp session), ANTHROPIC_API_KEY (non-fork reflection)
|
|
12192
12499
|
`;
|
|
12193
|
-
untrustedNote = (root) => `${root} was not created by you (e.g. it is committed to the repository), so Bunshin ignores it. If it is yours, run \`bunshin setup\` (or \`bunshin init\`) in ${
|
|
12500
|
+
untrustedNote = (root) => `${root} was not created by you (e.g. it is committed to the repository), so Bunshin ignores it. If it is yours, run \`bunshin setup\` (or \`bunshin init\`) in ${path28.dirname(root)} to trust it.`;
|
|
12194
12501
|
}
|
|
12195
12502
|
});
|
|
12196
12503
|
|