@bunshin-cc/bunshin 0.6.5 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/bin.js +838 -391
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -1344,17 +1344,17 @@ var init_document = __esm({
|
|
|
1344
1344
|
});
|
|
1345
1345
|
|
|
1346
1346
|
// ../core/src/v2/sections/markdown.ts
|
|
1347
|
-
function repoSectionId(repo,
|
|
1348
|
-
return anchor ? `repo:${repo}/${
|
|
1347
|
+
function repoSectionId(repo, path28, anchor = "") {
|
|
1348
|
+
return anchor ? `repo:${repo}/${path28}#${anchor}` : `repo:${repo}/${path28}`;
|
|
1349
1349
|
}
|
|
1350
|
-
function docKind(
|
|
1351
|
-
const base =
|
|
1350
|
+
function docKind(path28) {
|
|
1351
|
+
const base = path28.split("/").pop()?.toLowerCase() ?? "";
|
|
1352
1352
|
if (base === "testing.md") return "verification";
|
|
1353
1353
|
if (base === "skill.md") return "skill";
|
|
1354
1354
|
return "spec";
|
|
1355
1355
|
}
|
|
1356
|
-
function defaultPaths(
|
|
1357
|
-
const dir =
|
|
1356
|
+
function defaultPaths(path28) {
|
|
1357
|
+
const dir = path28.split("/").slice(0, -1).join("/");
|
|
1358
1358
|
if (!dir || dir.startsWith(".claude/") || dir.startsWith(".codex/")) return [];
|
|
1359
1359
|
return [`${dir}/**`];
|
|
1360
1360
|
}
|
|
@@ -1499,11 +1499,11 @@ function lintSkills(sections) {
|
|
|
1499
1499
|
}
|
|
1500
1500
|
const state = /* @__PURE__ */ new Map();
|
|
1501
1501
|
const cycles = /* @__PURE__ */ new Set();
|
|
1502
|
-
const dfs = (n,
|
|
1502
|
+
const dfs = (n, path28) => {
|
|
1503
1503
|
state.set(n, 1);
|
|
1504
1504
|
for (const d of edges[n] ?? []) {
|
|
1505
|
-
if (state.get(d) === 1) cycles.add([...
|
|
1506
|
-
else if (!state.get(d)) dfs(d, [...
|
|
1505
|
+
if (state.get(d) === 1) cycles.add([...path28.slice(path28.indexOf(d)), d].join(" -> "));
|
|
1506
|
+
else if (!state.get(d)) dfs(d, [...path28, d]);
|
|
1507
1507
|
}
|
|
1508
1508
|
state.set(n, 2);
|
|
1509
1509
|
};
|
|
@@ -1590,8 +1590,8 @@ function parsePythonSkills(text2) {
|
|
|
1590
1590
|
}
|
|
1591
1591
|
return { skills, constants };
|
|
1592
1592
|
}
|
|
1593
|
-
function harnessSectionId(
|
|
1594
|
-
return `harness:${
|
|
1593
|
+
function harnessSectionId(path28, symbol) {
|
|
1594
|
+
return `harness:${path28}#${symbol}`;
|
|
1595
1595
|
}
|
|
1596
1596
|
var DEF_RE;
|
|
1597
1597
|
var init_python = __esm({
|
|
@@ -1717,8 +1717,8 @@ function globToRegExp(glob) {
|
|
|
1717
1717
|
globCache.set(glob, out);
|
|
1718
1718
|
return out;
|
|
1719
1719
|
}
|
|
1720
|
-
function matchesGlob(
|
|
1721
|
-
const p =
|
|
1720
|
+
function matchesGlob(path28, glob) {
|
|
1721
|
+
const p = path28.replace(/^\.\//, "").replace(/\\/g, "/");
|
|
1722
1722
|
return globToRegExp(glob.replace(/^\.\//, "")).test(p);
|
|
1723
1723
|
}
|
|
1724
1724
|
var DEFAULT_EMBEDDERS, globCache;
|
|
@@ -1809,10 +1809,10 @@ function push(sections, reader, query) {
|
|
|
1809
1809
|
for (const s of recallable(sections, reader).filter((x) => reachesReader(x, reader))) {
|
|
1810
1810
|
const triggers = [...(s.triggers ?? []).filter((t) => t.anchor), { anchor: "", paths: s.paths, entities: s.entities, skills: s.skills }];
|
|
1811
1811
|
for (const t of triggers) {
|
|
1812
|
-
const
|
|
1813
|
-
const entity =
|
|
1814
|
-
const skill =
|
|
1815
|
-
const trigger =
|
|
1812
|
+
const path28 = paths.find((p) => t.paths.some((g) => matchesGlob(p, g)));
|
|
1813
|
+
const entity = path28 ? void 0 : t.entities.find((e) => entities.has(e) || text2 && text2.includes(e));
|
|
1814
|
+
const skill = path28 || entity ? void 0 : skills.find((k) => t.skills.includes(k));
|
|
1815
|
+
const trigger = path28 ? `path:${path28}` : entity ? `entity:${entity}` : skill ? `skill:${skill}` : "";
|
|
1816
1816
|
if (trigger) {
|
|
1817
1817
|
hits.set(s.id, { section: s, trigger, anchor: t.anchor });
|
|
1818
1818
|
break;
|
|
@@ -2060,17 +2060,6 @@ function identLike(rest) {
|
|
|
2060
2060
|
return humps.length > 0 && m[1].length / humps.length >= 3;
|
|
2061
2061
|
});
|
|
2062
2062
|
}
|
|
2063
|
-
function luhnOk(digits) {
|
|
2064
|
-
let total = 0;
|
|
2065
|
-
let alt = false;
|
|
2066
|
-
for (let i = digits.length - 1; i >= 0; i--) {
|
|
2067
|
-
let d = digits.charCodeAt(i) - 48;
|
|
2068
|
-
if (alt && (d *= 2) > 9) d -= 9;
|
|
2069
|
-
total += d;
|
|
2070
|
-
alt = !alt;
|
|
2071
|
-
}
|
|
2072
|
-
return total % 10 === 0;
|
|
2073
|
-
}
|
|
2074
2063
|
function issuerOk(d) {
|
|
2075
2064
|
const n = d.length;
|
|
2076
2065
|
if (d[0] === "4") return n === 13 || n === 16 || n === 19;
|
|
@@ -2082,23 +2071,44 @@ function issuerOk(d) {
|
|
|
2082
2071
|
if (/^(?:50|5[6-8]|6\d)/.test(d)) return n >= 12 && n <= 19;
|
|
2083
2072
|
return false;
|
|
2084
2073
|
}
|
|
2074
|
+
function luhnDigits(d, n) {
|
|
2075
|
+
let total = 0;
|
|
2076
|
+
let alt = false;
|
|
2077
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
2078
|
+
let x = d[i];
|
|
2079
|
+
if (alt && (x *= 2) > 9) x -= 9;
|
|
2080
|
+
total += x;
|
|
2081
|
+
alt = !alt;
|
|
2082
|
+
}
|
|
2083
|
+
return total % 10 === 0;
|
|
2084
|
+
}
|
|
2085
2085
|
function cardPrefix(s) {
|
|
2086
|
-
const
|
|
2087
|
-
if (/^3[1-3]|^2[019]/.test(digits)) return 0;
|
|
2088
|
-
const sep = s.search(/[ -]/);
|
|
2089
|
-
if (sep > 0 && sep !== 4 && !(sep >= 13 && sep <= 19)) return 0;
|
|
2086
|
+
const d = CARD_DIGITS;
|
|
2090
2087
|
const ends = [];
|
|
2091
|
-
|
|
2088
|
+
const counts = [];
|
|
2089
|
+
let digits = 0;
|
|
2090
|
+
let sep = -1;
|
|
2092
2091
|
for (let i = 0, start = 0; i <= s.length; i++) {
|
|
2093
|
-
|
|
2092
|
+
const c = i < s.length ? s.charCodeAt(i) : -1;
|
|
2093
|
+
if (c >= 48 && c <= 57) {
|
|
2094
|
+
if (digits < d.length) d[digits] = c - 48;
|
|
2095
|
+
digits++;
|
|
2096
|
+
continue;
|
|
2097
|
+
}
|
|
2098
|
+
if (sep < 0 && c !== -1) sep = i;
|
|
2094
2099
|
if (ends.length && i - start < 3) break;
|
|
2095
|
-
|
|
2096
|
-
|
|
2100
|
+
ends.push(i);
|
|
2101
|
+
counts.push(digits);
|
|
2097
2102
|
start = i + 1;
|
|
2098
2103
|
}
|
|
2104
|
+
if (d[0] === 3 && d[1] >= 1 && d[1] <= 3 || d[0] === 2 && (d[1] === 0 || d[1] === 1 || d[1] === 9)) return 0;
|
|
2105
|
+
if (sep > 0 && sep !== 4 && !(sep >= 13 && sep <= 19)) return 0;
|
|
2099
2106
|
for (let k = ends.length - 1; k >= 0; k--) {
|
|
2100
|
-
const
|
|
2101
|
-
if (count
|
|
2107
|
+
const count = counts[k];
|
|
2108
|
+
if (count < 13 || count > 19 || !luhnDigits(d, count)) continue;
|
|
2109
|
+
let str8 = "";
|
|
2110
|
+
for (let j = 0; j < count; j++) str8 += String.fromCharCode(48 + d[j]);
|
|
2111
|
+
if (issuerOk(str8)) return ends[k];
|
|
2102
2112
|
}
|
|
2103
2113
|
return 0;
|
|
2104
2114
|
}
|
|
@@ -2181,7 +2191,7 @@ function redactDeep(value, opts = {}) {
|
|
|
2181
2191
|
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, redactDeep(v, opts)]));
|
|
2182
2192
|
return value;
|
|
2183
2193
|
}
|
|
2184
|
-
var REDACTED, MARKER_RE, isMarked, words2, KEY_FALSE, SCHEMA_KEY, PASS_WORD, PASS_STAT, nameCache, KEYWORD_VALUES, PLACEHOLDER_RE, CODE_RE, STRONG, HASH_WORD, COUNT_NAME, RESOURCE_TAIL, UUID_RE, PATH_RE, credentialish, ASSET_EXT, NOREPLY_RE, NAME, Q, VALUE, ARGS, STRONG_LAST, KEY_FILE, plainValue, PATTERNS;
|
|
2194
|
+
var REDACTED, MARKER_RE, isMarked, words2, KEY_FALSE, SCHEMA_KEY, PASS_WORD, PASS_STAT, nameCache, KEYWORD_VALUES, PLACEHOLDER_RE, CODE_RE, STRONG, HASH_WORD, COUNT_NAME, RESOURCE_TAIL, UUID_RE, PATH_RE, credentialish, CARD_DIGITS, ASSET_EXT, NOREPLY_RE, NAME, Q, VALUE, ARGS, STRONG_LAST, KEY_FILE, plainValue, PATTERNS;
|
|
2185
2195
|
var init_redact = __esm({
|
|
2186
2196
|
"../core/src/redact.ts"() {
|
|
2187
2197
|
"use strict";
|
|
@@ -2257,6 +2267,7 @@ var init_redact = __esm({
|
|
|
2257
2267
|
UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2258
2268
|
PATH_RE = /^(?:~|\.{1,2})?\/[\w.@%+~/-]*$|^[A-Za-z]:\\/;
|
|
2259
2269
|
credentialish = (v) => /[\d\W_]/.test(v) || v.length >= 16 && /[a-z]/.test(v) && /[A-Z]/.test(v);
|
|
2270
|
+
CARD_DIGITS = new Uint8Array(64);
|
|
2260
2271
|
ASSET_EXT = /^\.(?:m?js|cjs|jsx|tsx?|css|scss|map|json|html?|wasm|woff2?|ttf|otf|eot|png|jpe?g|gif|svg|webp|avif|ico|bmp|mp[34]|webm|wav|ogg|pdf|txt|md|zip|gz|br|tgz|tar|whl|jar|bin|dat|lock|log|ya?ml|xml|csv)\b/;
|
|
2261
2272
|
NOREPLY_RE = /^(?:no[._-]?reply|donot[._-]?reply|do[._-]?not[._-]?reply|noreply\+?.*|git|hg)$/i;
|
|
2262
2273
|
NAME = String.raw`(?=[A-Za-z_$])(?=[\w.$-]{0,64}?(?:key|token|secret|pass|pwd|credential|auth|cookie|session|creds))[A-Za-z_$][\w.$-]{0,127}`;
|
|
@@ -2610,7 +2621,7 @@ ${input.digestNote ? `${input.digestNote}
|
|
|
2610
2621
|
|
|
2611
2622
|
` : ""}${input.transcriptDigest}
|
|
2612
2623
|
` : ""}
|
|
2613
|
-
${input.focus ? `## What to capture
|
|
2624
|
+
${input.feedback ? feedbackSection(input.feedback) : ""}${input.focus ? `## What to capture
|
|
2614
2625
|
|
|
2615
2626
|
${input.focus}
|
|
2616
2627
|
` : ""}${guidelinesSection(input.guidelines, { forReflection: true })}
|
|
@@ -2690,6 +2701,34 @@ At most ${max} suggestions. Schema:
|
|
|
2690
2701
|
"rationale": string, "urgency": "normal"|"urgent",
|
|
2691
2702
|
"signal": "steering"|"environment"|"knowledge"|"capability"|"approach"}]}`;
|
|
2692
2703
|
}
|
|
2704
|
+
function feedbackSection(f) {
|
|
2705
|
+
return `
|
|
2706
|
+
## Feedback from the person (human steering)
|
|
2707
|
+
|
|
2708
|
+
${f.lesson ? "A person reviewed a lesson reflection took from this session" : "The person who worked in this session read what reflection took from it"} and wrote:
|
|
2709
|
+
|
|
2710
|
+
<feedback>
|
|
2711
|
+
${f.text.trim()}
|
|
2712
|
+
</feedback>
|
|
2713
|
+
${f.lesson ? `
|
|
2714
|
+
The lesson it is about:
|
|
2715
|
+
|
|
2716
|
+
<lesson>
|
|
2717
|
+
${f.lesson.trim()}
|
|
2718
|
+
</lesson>
|
|
2719
|
+
` : ""}
|
|
2720
|
+
Treat it as a correction from a person (signal "steering"): the strongest evidence there is. Act on it:
|
|
2721
|
+
- If it says the session taught something that was missed, capture that lesson in the right
|
|
2722
|
+
document, written as the guidelines below ask. ${f.transcript ? "Find it in the session below." : "The session's trace is no longer available: work from the feedback itself (the person's own account), the lessons already taken and the documents."}
|
|
2723
|
+
- If it corrects a lesson (too specific, wrong, in the wrong place), propose the corrected lesson
|
|
2724
|
+
(op "replace" if it already landed in a document, otherwise a new one) and name the lesson it
|
|
2725
|
+
corrects in the rationale. Lessons listed as already taken may be corrected this way.
|
|
2726
|
+
- If it says how lessons should be captured or written in general, propose a rule for the
|
|
2727
|
+
reflection guidelines, merged with the rules already there: never repeat one; refine an existing
|
|
2728
|
+
rule with op "replace" when the feedback sharpens it.
|
|
2729
|
+
Answer "nothing_new" only if everything it asks for is already in the library or proposed.
|
|
2730
|
+
`;
|
|
2731
|
+
}
|
|
2693
2732
|
function documentsSection(docs) {
|
|
2694
2733
|
if (!docs?.length) return "";
|
|
2695
2734
|
const body = docs.map((d) => `### ${d.id}: ${d.title} (${d.why})
|
|
@@ -2954,6 +2993,16 @@ var init_profile = __esm({
|
|
|
2954
2993
|
}
|
|
2955
2994
|
});
|
|
2956
2995
|
|
|
2996
|
+
// ../core/src/v2/sources.ts
|
|
2997
|
+
function sourceId(kind, name) {
|
|
2998
|
+
return `${kind}:${name.trim()}`;
|
|
2999
|
+
}
|
|
3000
|
+
var init_sources = __esm({
|
|
3001
|
+
"../core/src/v2/sources.ts"() {
|
|
3002
|
+
"use strict";
|
|
3003
|
+
}
|
|
3004
|
+
});
|
|
3005
|
+
|
|
2957
3006
|
// ../core/src/v2/index.ts
|
|
2958
3007
|
var init_v2 = __esm({
|
|
2959
3008
|
"../core/src/v2/index.ts"() {
|
|
@@ -2975,6 +3024,7 @@ var init_v2 = __esm({
|
|
|
2975
3024
|
init_research();
|
|
2976
3025
|
init_profile();
|
|
2977
3026
|
init_guidelines();
|
|
3027
|
+
init_sources();
|
|
2978
3028
|
}
|
|
2979
3029
|
});
|
|
2980
3030
|
|
|
@@ -3595,7 +3645,7 @@ var init_src = __esm({
|
|
|
3595
3645
|
// src/v2/store.ts
|
|
3596
3646
|
import fs6 from "node:fs";
|
|
3597
3647
|
import path5 from "node:path";
|
|
3598
|
-
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
3648
|
+
import { createHash as createHash2, randomBytes as randomBytes3 } from "node:crypto";
|
|
3599
3649
|
function hexId(prefix) {
|
|
3600
3650
|
return `${prefix}_${randomBytes3(6).toString("hex")}`;
|
|
3601
3651
|
}
|
|
@@ -3617,7 +3667,7 @@ function normalizeSession(id, raw) {
|
|
|
3617
3667
|
hinted: list2(raw.hinted)
|
|
3618
3668
|
};
|
|
3619
3669
|
}
|
|
3620
|
-
var SAFE_ID, fileSafe, V2Store, addUnique;
|
|
3670
|
+
var SAFE_ID, fileSafe, V2Store, rootKey, addUnique;
|
|
3621
3671
|
var init_store2 = __esm({
|
|
3622
3672
|
"src/v2/store.ts"() {
|
|
3623
3673
|
"use strict";
|
|
@@ -3752,11 +3802,12 @@ var init_store2 = __esm({
|
|
|
3752
3802
|
return { status: raw.status, source: raw.source || "robot", ...raw.metrics ? { metrics: raw.metrics } : {}, at };
|
|
3753
3803
|
}
|
|
3754
3804
|
// ── repo index / sync state ──
|
|
3755
|
-
|
|
3756
|
-
|
|
3805
|
+
/** One index per working tree: a store covering a folder of repositories keeps each one's. */
|
|
3806
|
+
readRepoIndex(root) {
|
|
3807
|
+
return readJson(this.p("repo-index", `${rootKey(root)}.json`), null);
|
|
3757
3808
|
}
|
|
3758
|
-
writeRepoIndex(index) {
|
|
3759
|
-
writeJsonAtomic(this.p("repo-index.json
|
|
3809
|
+
writeRepoIndex(root, index) {
|
|
3810
|
+
writeJsonAtomic(this.p("repo-index", `${rootKey(root)}.json`), index);
|
|
3760
3811
|
}
|
|
3761
3812
|
readSyncState() {
|
|
3762
3813
|
return readJson(this.p("sync.json"), {});
|
|
@@ -3824,6 +3875,7 @@ var init_store2 = __esm({
|
|
|
3824
3875
|
return out;
|
|
3825
3876
|
}
|
|
3826
3877
|
};
|
|
3878
|
+
rootKey = (root) => createHash2("sha256").update(path5.resolve(root)).digest("hex").slice(0, 16);
|
|
3827
3879
|
addUnique = (list2, ...ids) => {
|
|
3828
3880
|
for (const id of ids) if (id && !list2.includes(id)) list2.push(id);
|
|
3829
3881
|
};
|
|
@@ -3831,7 +3883,8 @@ var init_store2 = __esm({
|
|
|
3831
3883
|
});
|
|
3832
3884
|
|
|
3833
3885
|
// src/v2/deploy.ts
|
|
3834
|
-
import {
|
|
3886
|
+
import { execFileSync } from "node:child_process";
|
|
3887
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
3835
3888
|
import fs7 from "node:fs";
|
|
3836
3889
|
import os2 from "node:os";
|
|
3837
3890
|
import path6 from "node:path";
|
|
@@ -3844,46 +3897,31 @@ function deployWorkspace(env = process.env) {
|
|
|
3844
3897
|
return null;
|
|
3845
3898
|
}
|
|
3846
3899
|
}
|
|
3847
|
-
function deployPaths(env = process.env) {
|
|
3900
|
+
function deployPaths(env = process.env, orgId) {
|
|
3848
3901
|
const home = env.BUNSHIN_DEPLOY_HOME || os2.homedir();
|
|
3849
3902
|
const ws = deployWorkspace(env);
|
|
3903
|
+
const state = path6.join(home, ".bunshin", "deploy");
|
|
3904
|
+
const shared = { claude: path6.join(ws ?? home, ".claude", "skills"), codex: path6.join(ws ?? home, ".codex", "skills") };
|
|
3905
|
+
const org = orgId ? path6.join(state, orgKey(orgId)) : null;
|
|
3850
3906
|
return {
|
|
3851
3907
|
home,
|
|
3852
|
-
claudeSkills: path6.join(
|
|
3853
|
-
codexSkills: path6.join(
|
|
3908
|
+
claudeSkills: org ? path6.join(org, "claude-skills") : shared.claude,
|
|
3909
|
+
codexSkills: org ? path6.join(org, "codex-skills") : shared.codex,
|
|
3910
|
+
...org ? { legacy: [.../* @__PURE__ */ new Set([shared.claude, shared.codex, path6.join(home, ".claude", "skills"), path6.join(home, ".codex", "skills"), path6.join(home, ".bunshin", "library")])] } : {},
|
|
3854
3911
|
claudeMd: ws ? path6.join(ws, "CLAUDE.md") : path6.join(home, ".claude", "CLAUDE.md"),
|
|
3855
3912
|
agentsMd: ws ? path6.join(ws, "AGENTS.md") : path6.join(home, ".codex", "AGENTS.md"),
|
|
3856
|
-
library: path6.join(home, ".bunshin", "library"),
|
|
3913
|
+
library: org ? path6.join(org, "library") : path6.join(home, ".bunshin", "library"),
|
|
3914
|
+
// Pack executables are linked into one folder on PATH (shared by name; their files are per organization).
|
|
3857
3915
|
bin: path6.join(home, ".bunshin", "bin"),
|
|
3858
|
-
state
|
|
3916
|
+
state
|
|
3859
3917
|
};
|
|
3860
3918
|
}
|
|
3861
|
-
function setDeployWorkspace(dir, env = process.env) {
|
|
3862
|
-
const home = env.BUNSHIN_DEPLOY_HOME || os2.homedir();
|
|
3863
|
-
const ws = dir ? path6.resolve(dir.replace(/^~(?=$|\/)/, home)) : null;
|
|
3864
|
-
if (ws && !fs7.statSync(ws, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`${ws} is not a folder`);
|
|
3865
|
-
const stateDir = path6.join(home, ".bunshin", "deploy");
|
|
3866
|
-
const deployed = fs7.existsSync(stateDir) && fs7.readdirSync(stateDir).some((f) => {
|
|
3867
|
-
if (f === "settings.json" || !f.endsWith(".json")) return false;
|
|
3868
|
-
try {
|
|
3869
|
-
return Object.keys(JSON.parse(fs7.readFileSync(path6.join(stateDir, f), "utf8")).docs ?? {}).length > 0;
|
|
3870
|
-
} catch {
|
|
3871
|
-
return false;
|
|
3872
|
-
}
|
|
3873
|
-
});
|
|
3874
|
-
if (deployed && ws !== deployWorkspace({ ...env, BUNSHIN_DEPLOY_WORKSPACE: "" })) {
|
|
3875
|
-
throw new Error("The library is already deployed to the current location. Set the workspace before the first sync on this machine.");
|
|
3876
|
-
}
|
|
3877
|
-
fs7.mkdirSync(path6.dirname(settingsFile(home)), { recursive: true });
|
|
3878
|
-
fs7.writeFileSync(settingsFile(home), JSON.stringify(ws ? { workspace: ws } : {}, null, 2) + "\n");
|
|
3879
|
-
return ws;
|
|
3880
|
-
}
|
|
3881
3919
|
function statePath(p, orgId) {
|
|
3882
|
-
return path6.join(p.state, `${orgId
|
|
3920
|
+
return path6.join(p.state, `${orgKey(orgId)}.json`);
|
|
3883
3921
|
}
|
|
3884
3922
|
function readState(p, orgId) {
|
|
3885
3923
|
const s = readJson(statePath(p, orgId), null);
|
|
3886
|
-
return { orgId, since: s?.since ?? (/* @__PURE__ */ new Date()).toISOString(), docs: s?.docs ?? {}, created: s?.created ?? {}, links: s?.links ?? [], ...s?.libraryVersion ? { libraryVersion: s.libraryVersion } : {} };
|
|
3924
|
+
return { orgId, since: s?.since ?? (/* @__PURE__ */ new Date()).toISOString(), docs: s?.docs ?? {}, created: s?.created ?? {}, links: s?.links ?? [], ...s?.linked?.length ? { linked: s.linked } : {}, ...s?.libraryVersion ? { libraryVersion: s.libraryVersion } : {} };
|
|
3887
3925
|
}
|
|
3888
3926
|
function readText(file) {
|
|
3889
3927
|
try {
|
|
@@ -4205,7 +4243,7 @@ function captureInto(v2, config, ctx, state, p, harnesses, now, incoming, captur
|
|
|
4205
4243
|
return captured.map((s) => ({ suggestionId: s.id, op: s.op.op, ...s.target.sectionId ? { sectionId: s.target.sectionId } : {}, name: s.summary ?? s.op.op }));
|
|
4206
4244
|
}
|
|
4207
4245
|
function captureEdits(v2, config, ctx, opts = {}) {
|
|
4208
|
-
const p = opts.paths ?? deployPaths();
|
|
4246
|
+
const p = opts.paths ?? deployPaths(process.env, config.orgId);
|
|
4209
4247
|
const harnesses = new Set(opts.harnesses ?? config.deploy?.harnesses ?? ["claude-code", "codex"]);
|
|
4210
4248
|
const release = lockDeploy(p);
|
|
4211
4249
|
if (!release) return [];
|
|
@@ -4245,11 +4283,14 @@ function lockDeploy(p) {
|
|
|
4245
4283
|
}
|
|
4246
4284
|
return null;
|
|
4247
4285
|
}
|
|
4286
|
+
function linkHarnesses(config) {
|
|
4287
|
+
return [.../* @__PURE__ */ new Set([config.harness, ...config.deploy?.harnesses ?? []])].filter((h) => h === "claude-code" || h === "codex");
|
|
4288
|
+
}
|
|
4248
4289
|
function deployEnabled(config, ctx) {
|
|
4249
4290
|
return ctx.kind === "person" && config.deploy?.enabled !== false;
|
|
4250
4291
|
}
|
|
4251
4292
|
function deployLibrary(v2, config, ctx, bundle, opts = {}) {
|
|
4252
|
-
const p = opts.paths ?? deployPaths();
|
|
4293
|
+
const p = opts.paths ?? deployPaths(process.env, config.orgId);
|
|
4253
4294
|
const harnesses = new Set(opts.harnesses ?? config.deploy?.harnesses ?? ["claude-code", "codex"]);
|
|
4254
4295
|
const now = opts.now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
4255
4296
|
const release = lockDeploy(p);
|
|
@@ -4266,6 +4307,7 @@ function deployLocked(v2, config, ctx, bundle, opts, p, harnesses, now) {
|
|
|
4266
4307
|
const plan = planDeploy(bundle, p, harnesses);
|
|
4267
4308
|
report.warnings.push(...plan.warnings);
|
|
4268
4309
|
const owned = (abs) => [p.claudeSkills, p.codexSkills, p.library].some((r) => insideRoot(r, abs));
|
|
4310
|
+
const removable = (abs) => owned(abs) || (p.legacy ?? []).some((r) => insideRoot(r, abs));
|
|
4269
4311
|
if (opts.capture !== false) report.captured = captureInto(v2, config, ctx, state, p, harnesses, now, plan.skills.map((x) => x.skill.name), opts.captureNewSkills);
|
|
4270
4312
|
const decided = new Map(bundle.decisions.map((d) => [d.suggestionId, d.decision.status]));
|
|
4271
4313
|
const localStatus = (id) => decided.get(id) ?? v2.readSuggestion(id)?.status;
|
|
@@ -4323,7 +4365,7 @@ function deployLocked(v2, config, ctx, bundle, opts, p, harnesses, now) {
|
|
|
4323
4365
|
prevByAbs.delete(`${t.abs}#${t.role}`);
|
|
4324
4366
|
}
|
|
4325
4367
|
for (const old of prevByAbs.values()) {
|
|
4326
|
-
if (old.role === "block" || d.contents.has(old.abs) || !
|
|
4368
|
+
if (old.role === "block" || d.contents.has(old.abs) || !removable(old.abs)) continue;
|
|
4327
4369
|
const cur = readText(old.abs);
|
|
4328
4370
|
if (cur !== null && sha(cur) === old.sha) {
|
|
4329
4371
|
removeFile(old.abs);
|
|
@@ -4332,6 +4374,11 @@ function deployLocked(v2, config, ctx, bundle, opts, p, harnesses, now) {
|
|
|
4332
4374
|
}
|
|
4333
4375
|
}
|
|
4334
4376
|
if (d.kind === "skill") {
|
|
4377
|
+
for (const dir of prev?.dirs ?? []) {
|
|
4378
|
+
if ((d.dirs ?? []).includes(dir) || !removable(dir)) continue;
|
|
4379
|
+
removeFile(path6.join(dir, MANIFEST));
|
|
4380
|
+
pruneDirs(dir, path6.dirname(dir));
|
|
4381
|
+
}
|
|
4335
4382
|
for (const dir of d.dirs ?? []) {
|
|
4336
4383
|
const manifest = safeJoin(dir, MANIFEST);
|
|
4337
4384
|
if (foreign.has(dir) || !manifest || !fs7.existsSync(path6.join(dir, "SKILL.md"))) continue;
|
|
@@ -4350,7 +4397,7 @@ function deployLocked(v2, config, ctx, bundle, opts, p, harnesses, now) {
|
|
|
4350
4397
|
continue;
|
|
4351
4398
|
}
|
|
4352
4399
|
for (const t of doc.targets) {
|
|
4353
|
-
if (t.role === "block" || !
|
|
4400
|
+
if (t.role === "block" || !removable(t.abs)) continue;
|
|
4354
4401
|
const cur = readText(t.abs);
|
|
4355
4402
|
if (cur !== null && sha(cur) === t.sha) {
|
|
4356
4403
|
removeFile(t.abs);
|
|
@@ -4358,7 +4405,7 @@ function deployLocked(v2, config, ctx, bundle, opts, p, harnesses, now) {
|
|
|
4358
4405
|
}
|
|
4359
4406
|
}
|
|
4360
4407
|
for (const dir of doc.dirs ?? []) {
|
|
4361
|
-
if (!
|
|
4408
|
+
if (!removable(dir)) continue;
|
|
4362
4409
|
removeFile(path6.join(dir, MANIFEST));
|
|
4363
4410
|
pruneDirs(dir, path6.dirname(dir));
|
|
4364
4411
|
if (fs7.existsSync(dir)) {
|
|
@@ -4405,6 +4452,10 @@ function deployLocked(v2, config, ctx, bundle, opts, p, harnesses, now) {
|
|
|
4405
4452
|
const link = path6.join(dir, ".claude", "skills");
|
|
4406
4453
|
try {
|
|
4407
4454
|
fs7.lstatSync(link);
|
|
4455
|
+
if (fs7.readlinkSync(link) !== p.claudeSkills) {
|
|
4456
|
+
fs7.unlinkSync(link);
|
|
4457
|
+
fs7.symlinkSync(p.claudeSkills, link);
|
|
4458
|
+
}
|
|
4408
4459
|
} catch {
|
|
4409
4460
|
ensureDir(path6.dirname(link));
|
|
4410
4461
|
ensureDir(p.claudeSkills);
|
|
@@ -4423,14 +4474,26 @@ function deployLocked(v2, config, ctx, bundle, opts, p, harnesses, now) {
|
|
|
4423
4474
|
} catch {
|
|
4424
4475
|
}
|
|
4425
4476
|
}
|
|
4426
|
-
|
|
4477
|
+
let linked = state.linked ?? [];
|
|
4478
|
+
if (p.legacy) {
|
|
4479
|
+
const names = Object.values(next).filter((d) => d.kind === "skill" && (d.dirs ?? []).length).map((d) => d.name);
|
|
4480
|
+
const roots = [.../* @__PURE__ */ new Set([...linked, ...opts.linkRoot ? [path6.resolve(opts.linkRoot)] : []])];
|
|
4481
|
+
linked = [];
|
|
4482
|
+
for (const root of roots) {
|
|
4483
|
+
const res = linkSkills(p, root, opts.linkHarnesses ?? [...harnesses], names);
|
|
4484
|
+
report.warnings.push(...res.warnings);
|
|
4485
|
+
if (res.kept) linked.push(root);
|
|
4486
|
+
}
|
|
4487
|
+
}
|
|
4488
|
+
const saved = { ...state, docs: next, links: [...links], ...linked.length ? { linked } : {}, ...bundle.libraryVersion ? { libraryVersion: bundle.libraryVersion } : {} };
|
|
4489
|
+
if (!linked.length) delete saved.linked;
|
|
4427
4490
|
writeJsonAtomic(statePath(p, config.orgId), saved);
|
|
4428
4491
|
report.libraryVersion = bundle.libraryVersion;
|
|
4429
4492
|
return report;
|
|
4430
4493
|
}
|
|
4431
|
-
function deployedState(config, paths = deployPaths()) {
|
|
4494
|
+
function deployedState(config, paths = deployPaths(process.env, config.orgId)) {
|
|
4432
4495
|
const s = readState(paths, config.orgId);
|
|
4433
|
-
return { ...s.libraryVersion ? { libraryVersion: s.libraryVersion } : {}, docs: Object.values(s.docs) };
|
|
4496
|
+
return { ...s.libraryVersion ? { libraryVersion: s.libraryVersion } : {}, linked: s.linked ?? [], docs: Object.values(s.docs) };
|
|
4434
4497
|
}
|
|
4435
4498
|
function materialize(sections, dir) {
|
|
4436
4499
|
let files = 0;
|
|
@@ -4453,7 +4516,89 @@ function materialize(sections, dir) {
|
|
|
4453
4516
|
}
|
|
4454
4517
|
return { files };
|
|
4455
4518
|
}
|
|
4456
|
-
|
|
4519
|
+
function linkSkills(p, root, harnesses, names) {
|
|
4520
|
+
const out = { linked: 0, removed: 0, kept: false, warnings: [] };
|
|
4521
|
+
const abs = path6.resolve(root);
|
|
4522
|
+
if (abs === path6.resolve(p.home) || abs === path6.resolve(os2.homedir()) || !fs7.existsSync(abs)) return out;
|
|
4523
|
+
const managed = (target) => target.startsWith(path6.resolve(p.state) + path6.sep);
|
|
4524
|
+
const excluded = [];
|
|
4525
|
+
for (const h of harnesses) {
|
|
4526
|
+
const spec = LINK_DIRS[h];
|
|
4527
|
+
if (!spec) continue;
|
|
4528
|
+
const dir = path6.join(abs, spec.rel);
|
|
4529
|
+
const canonical = path6.resolve(p[spec.key]);
|
|
4530
|
+
const want = new Set(names.filter((n) => SAFE_NAME.test(n) && fs7.existsSync(path6.join(canonical, n, "SKILL.md"))));
|
|
4531
|
+
let entries = [];
|
|
4532
|
+
try {
|
|
4533
|
+
entries = fs7.readdirSync(dir, { withFileTypes: true });
|
|
4534
|
+
} catch {
|
|
4535
|
+
}
|
|
4536
|
+
for (const e of entries) {
|
|
4537
|
+
if (!e.isSymbolicLink()) continue;
|
|
4538
|
+
const link = path6.join(dir, e.name);
|
|
4539
|
+
let target = "";
|
|
4540
|
+
try {
|
|
4541
|
+
target = path6.resolve(dir, fs7.readlinkSync(link));
|
|
4542
|
+
} catch {
|
|
4543
|
+
continue;
|
|
4544
|
+
}
|
|
4545
|
+
if (!managed(target)) continue;
|
|
4546
|
+
if (want.has(e.name) && target === path6.join(canonical, e.name)) {
|
|
4547
|
+
want.delete(e.name);
|
|
4548
|
+
excluded.push(path6.join(spec.rel, e.name));
|
|
4549
|
+
continue;
|
|
4550
|
+
}
|
|
4551
|
+
removeFile(link);
|
|
4552
|
+
out.removed++;
|
|
4553
|
+
}
|
|
4554
|
+
for (const name of want) {
|
|
4555
|
+
const link = path6.join(dir, name);
|
|
4556
|
+
try {
|
|
4557
|
+
fs7.lstatSync(link);
|
|
4558
|
+
out.warnings.push(`${link} is the project's own; the library's ${name} skill was not linked there`);
|
|
4559
|
+
continue;
|
|
4560
|
+
} catch {
|
|
4561
|
+
}
|
|
4562
|
+
try {
|
|
4563
|
+
ensureDir(dir);
|
|
4564
|
+
fs7.symlinkSync(path6.join(canonical, name), link, "dir");
|
|
4565
|
+
excluded.push(path6.join(spec.rel, name));
|
|
4566
|
+
out.linked++;
|
|
4567
|
+
} catch (err) {
|
|
4568
|
+
out.warnings.push(`${link} not linked: ${err instanceof Error ? err.message : String(err)}`);
|
|
4569
|
+
}
|
|
4570
|
+
}
|
|
4571
|
+
pruneDirs(dir, abs);
|
|
4572
|
+
}
|
|
4573
|
+
out.kept = excluded.length > 0;
|
|
4574
|
+
gitExclude(abs, excluded);
|
|
4575
|
+
return out;
|
|
4576
|
+
}
|
|
4577
|
+
function gitExclude(root, rels) {
|
|
4578
|
+
if (!fs7.existsSync(path6.join(root, ".git"))) return;
|
|
4579
|
+
let file;
|
|
4580
|
+
try {
|
|
4581
|
+
file = path6.resolve(root, execFileSync("git", ["-C", root, "rev-parse", "--git-path", "info/exclude"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 2e3 }).trim());
|
|
4582
|
+
} catch {
|
|
4583
|
+
return;
|
|
4584
|
+
}
|
|
4585
|
+
const cur = readText(file) ?? "";
|
|
4586
|
+
const start = cur.indexOf(EXCLUDE_BEGIN);
|
|
4587
|
+
const end = start >= 0 ? cur.indexOf(EXCLUDE_END, start) : -1;
|
|
4588
|
+
const rest = start >= 0 && end >= 0 ? cur.slice(0, start) + cur.slice(end + EXCLUDE_END.length).replace(/^\n/, "") : cur;
|
|
4589
|
+
const block2 = rels.length ? `${EXCLUDE_BEGIN}
|
|
4590
|
+
${[...new Set(rels)].sort().map((r) => `/${r.split(path6.sep).join("/")}`).join("\n")}
|
|
4591
|
+
${EXCLUDE_END}
|
|
4592
|
+
` : "";
|
|
4593
|
+
const next = block2 ? `${rest}${rest && !rest.endsWith("\n") ? "\n" : ""}${block2}` : rest;
|
|
4594
|
+
if (next === cur) return;
|
|
4595
|
+
try {
|
|
4596
|
+
ensureDir(path6.dirname(file));
|
|
4597
|
+
fs7.writeFileSync(file, next);
|
|
4598
|
+
} catch {
|
|
4599
|
+
}
|
|
4600
|
+
}
|
|
4601
|
+
var settingsFile, orgKey, sha, BLOCK_BEGIN, BLOCK_END, MANIFEST, MAX_FILE, isExecutable, safeJoin, SAFE_NAME, neutralizeMarkers, LAYER_RANK, docMarker, BORN_SLACK_MS, LINK_DIRS, EXCLUDE_BEGIN, EXCLUDE_END;
|
|
4457
4602
|
var init_deploy = __esm({
|
|
4458
4603
|
"src/v2/deploy.ts"() {
|
|
4459
4604
|
"use strict";
|
|
@@ -4461,7 +4606,8 @@ var init_deploy = __esm({
|
|
|
4461
4606
|
init_util();
|
|
4462
4607
|
init_store2();
|
|
4463
4608
|
settingsFile = (home) => path6.join(home, ".bunshin", "deploy", "settings.json");
|
|
4464
|
-
|
|
4609
|
+
orgKey = (orgId) => orgId.replace(/[^A-Za-z0-9._-]/g, "_") || "org";
|
|
4610
|
+
sha = (text2) => createHash3("sha256").update(text2).digest("hex");
|
|
4465
4611
|
BLOCK_BEGIN = "<!-- bunshin:begin -->";
|
|
4466
4612
|
BLOCK_END = "<!-- bunshin:end -->";
|
|
4467
4613
|
MANIFEST = ".bunshin.json";
|
|
@@ -4479,6 +4625,12 @@ var init_deploy = __esm({
|
|
|
4479
4625
|
LAYER_RANK = { org: 0, dept: 1, team: 2, user: 3, fleet: 0, arm: 1, mount: 2, robot: 3 };
|
|
4480
4626
|
docMarker = (id, version2) => `<!-- bunshin:doc id=${id} v=${version2} -->`;
|
|
4481
4627
|
BORN_SLACK_MS = 2e3;
|
|
4628
|
+
LINK_DIRS = {
|
|
4629
|
+
"claude-code": { rel: path6.join(".claude", "skills"), key: "claudeSkills" },
|
|
4630
|
+
codex: { rel: path6.join(".codex", "skills"), key: "codexSkills" }
|
|
4631
|
+
};
|
|
4632
|
+
EXCLUDE_BEGIN = "# >>> bunshin: library skills linked here (managed by bunshin sync)";
|
|
4633
|
+
EXCLUDE_END = "# <<< bunshin";
|
|
4482
4634
|
}
|
|
4483
4635
|
});
|
|
4484
4636
|
|
|
@@ -4810,10 +4962,10 @@ var init_transcript = __esm({
|
|
|
4810
4962
|
// src/v2/repo.ts
|
|
4811
4963
|
import fs9 from "node:fs";
|
|
4812
4964
|
import path7 from "node:path";
|
|
4813
|
-
import { execFileSync } from "node:child_process";
|
|
4965
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
4814
4966
|
function git(cwd, args, timeout = 1500) {
|
|
4815
4967
|
try {
|
|
4816
|
-
return
|
|
4968
|
+
return execFileSync2("git", ["-C", cwd, ...args], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout, maxBuffer: 64 * 1024 * 1024 });
|
|
4817
4969
|
} catch {
|
|
4818
4970
|
return null;
|
|
4819
4971
|
}
|
|
@@ -4883,7 +5035,7 @@ function listDocs(root) {
|
|
|
4883
5035
|
function indexWorkingTree(v2, config, cwd, opts = {}) {
|
|
4884
5036
|
const root = repoRoot(cwd, v2.store.root);
|
|
4885
5037
|
const now = opts.now ?? Date.now();
|
|
4886
|
-
const cached = v2.readRepoIndex();
|
|
5038
|
+
const cached = v2.readRepoIndex(root);
|
|
4887
5039
|
const ownerTeam = config.ownerTeam || config.orgUnit || "unknown";
|
|
4888
5040
|
const sameRepo = cached && cached.ownerTeam === ownerTeam;
|
|
4889
5041
|
const repo = sameRepo && cached.repo && !opts.force ? cached.repo : repoName(root);
|
|
@@ -4921,7 +5073,7 @@ function indexWorkingTree(v2, config, cwd, opts = {}) {
|
|
|
4921
5073
|
};
|
|
4922
5074
|
if (changed) {
|
|
4923
5075
|
try {
|
|
4924
|
-
v2.writeRepoIndex(index);
|
|
5076
|
+
v2.writeRepoIndex(root, index);
|
|
4925
5077
|
} catch {
|
|
4926
5078
|
}
|
|
4927
5079
|
}
|
|
@@ -5506,6 +5658,110 @@ var init_ui = __esm({
|
|
|
5506
5658
|
}
|
|
5507
5659
|
});
|
|
5508
5660
|
|
|
5661
|
+
// src/v2/source.ts
|
|
5662
|
+
var source_exports = {};
|
|
5663
|
+
__export(source_exports, {
|
|
5664
|
+
cwdInPool: () => cwdInPool,
|
|
5665
|
+
inPool: () => inPool,
|
|
5666
|
+
listFolders: () => listFolders,
|
|
5667
|
+
projectRootFor: () => projectRootFor,
|
|
5668
|
+
select: () => select,
|
|
5669
|
+
sourceFor: () => sourceFor,
|
|
5670
|
+
storeHome: () => storeHome,
|
|
5671
|
+
topFolder: () => topFolder
|
|
5672
|
+
});
|
|
5673
|
+
import fs13 from "node:fs";
|
|
5674
|
+
import path9 from "node:path";
|
|
5675
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
5676
|
+
function storeHome(storeRoot) {
|
|
5677
|
+
return path9.dirname(path9.resolve(storeRoot));
|
|
5678
|
+
}
|
|
5679
|
+
function topFolder(cwd, storeRoot) {
|
|
5680
|
+
const rel = path9.relative(storeHome(storeRoot), path9.resolve(cwd));
|
|
5681
|
+
if (!rel || rel.startsWith("..") || path9.isAbsolute(rel)) return "";
|
|
5682
|
+
return rel.split(path9.sep)[0] ?? "";
|
|
5683
|
+
}
|
|
5684
|
+
function inPool(selection, folder) {
|
|
5685
|
+
if (!folder || !selection) return true;
|
|
5686
|
+
if (selection.exclude?.includes(folder)) return false;
|
|
5687
|
+
return selection.only ? selection.only.includes(folder) : true;
|
|
5688
|
+
}
|
|
5689
|
+
function cwdInPool(config, cwd, storeRoot) {
|
|
5690
|
+
return inPool(config.sources, topFolder(cwd, storeRoot));
|
|
5691
|
+
}
|
|
5692
|
+
function git2(cwd, args) {
|
|
5693
|
+
try {
|
|
5694
|
+
return execFileSync3("git", ["-C", cwd, ...args], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 1500 }).trim() || null;
|
|
5695
|
+
} catch {
|
|
5696
|
+
return null;
|
|
5697
|
+
}
|
|
5698
|
+
}
|
|
5699
|
+
function sourceFor(cwd, storeRoot, ctx) {
|
|
5700
|
+
if (ctx?.kind === "robot") return sourceId("robot", ctx.id);
|
|
5701
|
+
const home = storeHome(storeRoot);
|
|
5702
|
+
const top = git2(cwd, ["rev-parse", "--show-toplevel"]);
|
|
5703
|
+
if (top) {
|
|
5704
|
+
const remote = git2(top, ["remote", "get-url", "origin"]);
|
|
5705
|
+
const named = remote ? parseRemote(remote) : null;
|
|
5706
|
+
if (named) return sourceId("repo", named);
|
|
5707
|
+
const rel = path9.relative(home, top);
|
|
5708
|
+
return sourceId("folder", rel && !rel.startsWith("..") && !path9.isAbsolute(rel) ? rel.split(path9.sep).join("/") : path9.basename(top));
|
|
5709
|
+
}
|
|
5710
|
+
const folder = topFolder(cwd, storeRoot);
|
|
5711
|
+
return folder ? sourceId("folder", folder) : void 0;
|
|
5712
|
+
}
|
|
5713
|
+
function projectRootFor(cwd, storeRoot) {
|
|
5714
|
+
const home = storeHome(storeRoot);
|
|
5715
|
+
const top = git2(cwd, ["rev-parse", "--show-toplevel"]);
|
|
5716
|
+
if (top) {
|
|
5717
|
+
const rel = path9.relative(home, top);
|
|
5718
|
+
if (!rel.startsWith("..") && !path9.isAbsolute(rel)) return top;
|
|
5719
|
+
}
|
|
5720
|
+
const folder = topFolder(cwd, storeRoot);
|
|
5721
|
+
return folder ? path9.join(home, folder) : home;
|
|
5722
|
+
}
|
|
5723
|
+
function listFolders(storeRoot, selection, opts = {}) {
|
|
5724
|
+
const home = storeHome(storeRoot);
|
|
5725
|
+
let names = [];
|
|
5726
|
+
try {
|
|
5727
|
+
names = fs13.readdirSync(home, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort((a, b) => a.localeCompare(b));
|
|
5728
|
+
} catch {
|
|
5729
|
+
}
|
|
5730
|
+
return names.map((folder) => ({
|
|
5731
|
+
folder,
|
|
5732
|
+
included: inPool(selection, folder),
|
|
5733
|
+
...opts.sources ? { source: sourceFor(path9.join(home, folder), storeRoot) } : {}
|
|
5734
|
+
}));
|
|
5735
|
+
}
|
|
5736
|
+
function select(current, action, folders) {
|
|
5737
|
+
const uniq = (xs) => [...new Set(xs)].sort((a, b) => a.localeCompare(b));
|
|
5738
|
+
const only = current?.only;
|
|
5739
|
+
const exclude = current?.exclude ?? [];
|
|
5740
|
+
let next;
|
|
5741
|
+
switch (action) {
|
|
5742
|
+
case "all":
|
|
5743
|
+
return void 0;
|
|
5744
|
+
case "only":
|
|
5745
|
+
next = { only: uniq(folders) };
|
|
5746
|
+
break;
|
|
5747
|
+
case "include":
|
|
5748
|
+
next = { ...only ? { only: uniq([...only, ...folders]) } : {}, exclude: exclude.filter((f) => !folders.includes(f)) };
|
|
5749
|
+
break;
|
|
5750
|
+
case "exclude":
|
|
5751
|
+
next = { ...only ? { only: only.filter((f) => !folders.includes(f)) } : {}, exclude: uniq([...exclude, ...folders]) };
|
|
5752
|
+
break;
|
|
5753
|
+
}
|
|
5754
|
+
if (!next.exclude?.length) delete next.exclude;
|
|
5755
|
+
return next.only || next.exclude ? next : void 0;
|
|
5756
|
+
}
|
|
5757
|
+
var init_source = __esm({
|
|
5758
|
+
"src/v2/source.ts"() {
|
|
5759
|
+
"use strict";
|
|
5760
|
+
init_v22();
|
|
5761
|
+
init_repo();
|
|
5762
|
+
}
|
|
5763
|
+
});
|
|
5764
|
+
|
|
5509
5765
|
// src/v2/sync.ts
|
|
5510
5766
|
var sync_exports = {};
|
|
5511
5767
|
__export(sync_exports, {
|
|
@@ -5515,8 +5771,8 @@ __export(sync_exports, {
|
|
|
5515
5771
|
syncV2: () => syncV2,
|
|
5516
5772
|
uploadIndex: () => uploadIndex
|
|
5517
5773
|
});
|
|
5518
|
-
import
|
|
5519
|
-
import
|
|
5774
|
+
import fs14 from "node:fs";
|
|
5775
|
+
import path10 from "node:path";
|
|
5520
5776
|
function episodePayload(e) {
|
|
5521
5777
|
const { orgId: _o, uploaded: _u, servedReported: _s, reportedServed: _rs, reportedLoaded: _rl, transcriptPath: _tp, ...rest } = e;
|
|
5522
5778
|
void _o, _u, _s, _rs, _rl, _tp;
|
|
@@ -5550,7 +5806,7 @@ function markForRetrace(v2, { days = 30, now = Date.now() } = {}) {
|
|
|
5550
5806
|
if (e.upload === "header-only" || e.cliVersion || !e.transcript) continue;
|
|
5551
5807
|
if (now - Date.parse(e.endedAt || e.startedAt) > days * 864e5) continue;
|
|
5552
5808
|
const file = e.transcriptPath ?? v2.readSession(e.sessionId)?.transcriptPath;
|
|
5553
|
-
if (!file || !
|
|
5809
|
+
if (!file || !fs14.existsSync(file)) {
|
|
5554
5810
|
missing++;
|
|
5555
5811
|
continue;
|
|
5556
5812
|
}
|
|
@@ -5565,7 +5821,7 @@ function markForRetrace(v2, { days = 30, now = Date.now() } = {}) {
|
|
|
5565
5821
|
async function syncV2(store, opts = {}) {
|
|
5566
5822
|
const v2 = new V2Store(store);
|
|
5567
5823
|
const config = store.config();
|
|
5568
|
-
const cwd = opts.cwd ??
|
|
5824
|
+
const cwd = opts.cwd ?? path10.dirname(store.root);
|
|
5569
5825
|
const report = { offline: !canCall(config), uploadedEpisodes: 0, deferredTraces: 0, uploadedSuggestions: 0, trialUpdates: 0, bundle: v2.readBundle() ? "cached" : "none", settled: [], errors: [] };
|
|
5570
5826
|
const deadline = Date.now() + (opts.budgetMs ?? 24e4);
|
|
5571
5827
|
if (report.offline) {
|
|
@@ -5681,7 +5937,8 @@ async function syncV2(store, opts = {}) {
|
|
|
5681
5937
|
const bundle = v2.readBundle();
|
|
5682
5938
|
if (bundle) {
|
|
5683
5939
|
try {
|
|
5684
|
-
|
|
5940
|
+
const linkRoot = cwd && cwdInPool(config, cwd, store.root) ? projectRootFor(cwd, store.root) : void 0;
|
|
5941
|
+
report.deploy = deployLibrary(v2, config, ctx, bundle, { capture: false, ...linkRoot ? { linkRoot } : {}, linkHarnesses: linkHarnesses(config) });
|
|
5685
5942
|
} catch (err) {
|
|
5686
5943
|
report.errors.push(`deploy: ${err instanceof Error ? err.message : String(err)}`);
|
|
5687
5944
|
}
|
|
@@ -5711,7 +5968,7 @@ async function uploadIndex(store, cwd, deadline = Date.now() + 3e4) {
|
|
|
5711
5968
|
const { root, index } = indexWorkingTree(v2, config, cwd, { force: true });
|
|
5712
5969
|
const docs = (index.paths ?? Object.keys(index.files)).flatMap((rel) => {
|
|
5713
5970
|
try {
|
|
5714
|
-
return [{ path: rel, text:
|
|
5971
|
+
return [{ path: rel, text: fs14.readFileSync(path10.join(root, rel), "utf8") }];
|
|
5715
5972
|
} catch {
|
|
5716
5973
|
return [];
|
|
5717
5974
|
}
|
|
@@ -5729,6 +5986,7 @@ var init_sync = __esm({
|
|
|
5729
5986
|
init_api();
|
|
5730
5987
|
init_apply();
|
|
5731
5988
|
init_deploy();
|
|
5989
|
+
init_source();
|
|
5732
5990
|
init_repo();
|
|
5733
5991
|
init_store2();
|
|
5734
5992
|
init_view();
|
|
@@ -5739,10 +5997,10 @@ var init_sync = __esm({
|
|
|
5739
5997
|
|
|
5740
5998
|
// src/v2/reflect.ts
|
|
5741
5999
|
import { spawn as spawn3 } from "node:child_process";
|
|
5742
|
-
import { createHash as
|
|
5743
|
-
import
|
|
6000
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
6001
|
+
import fs15 from "node:fs";
|
|
5744
6002
|
function episodeIdFor(contextId, sessionId) {
|
|
5745
|
-
return `e_${
|
|
6003
|
+
return `e_${createHash4("sha256").update(`${contextId}
|
|
5746
6004
|
${sessionId}`).digest("hex").slice(0, 16)}`;
|
|
5747
6005
|
}
|
|
5748
6006
|
function runClaudeFork(sessionId, prompt, { cwd, model, timeoutMs = FORK_TIMEOUT_MS } = {}) {
|
|
@@ -5775,7 +6033,7 @@ function runClaudeFork(sessionId, prompt, { cwd, model, timeoutMs = FORK_TIMEOUT
|
|
|
5775
6033
|
};
|
|
5776
6034
|
let child;
|
|
5777
6035
|
try {
|
|
5778
|
-
child = spawn3(bin, args, { cwd: cwd &&
|
|
6036
|
+
child = spawn3(bin, args, { cwd: cwd && fs15.existsSync(cwd) ? cwd : void 0, env: modelEnv("claude-cli"), stdio: ["ignore", "pipe", "pipe"] });
|
|
5779
6037
|
} catch (e) {
|
|
5780
6038
|
resolve({ ok: false, error: e instanceof Error ? e.message : String(e) });
|
|
5781
6039
|
return;
|
|
@@ -5820,7 +6078,7 @@ function reflectionIndex(view, ids, task, focus, humanTurns = []) {
|
|
|
5820
6078
|
return [...chosen.values()].slice(0, 30).map(({ id, kind, layer, title, when, outline }) => ({ id, kind, layer, title, when, ...outline?.length ? { outline } : {} }));
|
|
5821
6079
|
}
|
|
5822
6080
|
function humanTurnsOf(transcriptPath) {
|
|
5823
|
-
if (!transcriptPath || !
|
|
6081
|
+
if (!transcriptPath || !fs15.existsSync(transcriptPath)) return [];
|
|
5824
6082
|
try {
|
|
5825
6083
|
return parseTranscript(transcriptPath).turns.filter((t) => t.role === "user" && t.text.trim()).map((t) => t.text.slice(0, 1e3));
|
|
5826
6084
|
} catch {
|
|
@@ -5828,7 +6086,7 @@ function humanTurnsOf(transcriptPath) {
|
|
|
5828
6086
|
}
|
|
5829
6087
|
}
|
|
5830
6088
|
function readFacts(transcriptPath) {
|
|
5831
|
-
if (!transcriptPath || !
|
|
6089
|
+
if (!transcriptPath || !fs15.existsSync(transcriptPath)) return null;
|
|
5832
6090
|
try {
|
|
5833
6091
|
return parseTranscript(transcriptPath);
|
|
5834
6092
|
} catch {
|
|
@@ -5891,7 +6149,7 @@ async function runReflectionV2(store, opts) {
|
|
|
5891
6149
|
result.method = "fork";
|
|
5892
6150
|
} else result.error = fork.error;
|
|
5893
6151
|
}
|
|
5894
|
-
if (result.method === "none" && session.transcriptPath &&
|
|
6152
|
+
if (result.method === "none" && session.transcriptPath && fs15.existsSync(session.transcriptPath)) {
|
|
5895
6153
|
const transcriptDigest = digest(facts ?? parseTranscript(session.transcriptPath), { from, maxChars: 4e4 });
|
|
5896
6154
|
const res = await askJson(config, {
|
|
5897
6155
|
system: "You are Bunshin's reflection pass. Follow the instructions in the user message and reply with one JSON object only.",
|
|
@@ -5912,8 +6170,10 @@ async function runReflectionV2(store, opts) {
|
|
|
5912
6170
|
result.nothingNew = result.method !== "none" && parsed.length === 0;
|
|
5913
6171
|
const apply = opts.forceApply || config.reflect.autoApply === "all" || config.reflect.autoApply === "robot" && view.ctx.kind === "robot";
|
|
5914
6172
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6173
|
+
const source = session.source !== void 0 ? session.source || void 0 : sourceFor(session.cwd || opts.cwd || process.cwd(), store.root, view.ctx);
|
|
5915
6174
|
for (const p of parsed) {
|
|
5916
6175
|
const s = toSuggestion(p, { id: hexId("g"), orgId: config.orgId, contextId: view.ctx.id, authorUserId: config.userId, origin, createdAt, episodeId });
|
|
6176
|
+
if (source) s.source = source;
|
|
5917
6177
|
s.summary = summarize(s.op);
|
|
5918
6178
|
if (apply) {
|
|
5919
6179
|
const outcome = applyLocally(v2, view, s);
|
|
@@ -5997,6 +6257,7 @@ var init_reflect3 = __esm({
|
|
|
5997
6257
|
init_signals();
|
|
5998
6258
|
init_ui();
|
|
5999
6259
|
init_view();
|
|
6260
|
+
init_source();
|
|
6000
6261
|
FORK_TIMEOUT_MS = 12e4;
|
|
6001
6262
|
FORK_DENIED_TOOLS = "Bash,Edit,Write,MultiEdit,NotebookEdit,WebFetch,WebSearch,Task,Agent,Skill,SlashCommand";
|
|
6002
6263
|
}
|
|
@@ -6014,51 +6275,51 @@ __export(uicmd_exports, {
|
|
|
6014
6275
|
modSource: () => modSource
|
|
6015
6276
|
});
|
|
6016
6277
|
import { spawnSync } from "node:child_process";
|
|
6017
|
-
import
|
|
6278
|
+
import fs16 from "node:fs";
|
|
6018
6279
|
import os3 from "node:os";
|
|
6019
|
-
import
|
|
6280
|
+
import path11 from "node:path";
|
|
6020
6281
|
import { fileURLToPath } from "node:url";
|
|
6021
6282
|
function modHome(home = process.env.BUNSHIN_DEPLOY_HOME || os3.homedir()) {
|
|
6022
|
-
return
|
|
6283
|
+
return path11.join(home, ".bunshin", "claude-code-ui");
|
|
6023
6284
|
}
|
|
6024
6285
|
function modSource() {
|
|
6025
|
-
const here =
|
|
6026
|
-
for (const dir of [
|
|
6027
|
-
if (
|
|
6286
|
+
const here = path11.dirname(fileURLToPath(import.meta.url));
|
|
6287
|
+
for (const dir of [path11.join(here, "claude-code-ui"), path11.join(here, "..", "claude-code-ui"), path11.resolve(here, "../../../../adapters/claude-code-ui")]) {
|
|
6288
|
+
if (fs16.existsSync(path11.join(dir, ".claude-plugin", "plugin.json"))) return dir;
|
|
6028
6289
|
}
|
|
6029
6290
|
return null;
|
|
6030
6291
|
}
|
|
6031
6292
|
function installedModVersion(home) {
|
|
6032
6293
|
try {
|
|
6033
|
-
return JSON.parse(
|
|
6294
|
+
return JSON.parse(fs16.readFileSync(path11.join(modHome(home), MOD_PLUGIN, ".claude-plugin", "plugin.json"), "utf8")).version ?? null;
|
|
6034
6295
|
} catch {
|
|
6035
6296
|
return null;
|
|
6036
6297
|
}
|
|
6037
6298
|
}
|
|
6038
6299
|
function copyDir(from, to) {
|
|
6039
|
-
|
|
6040
|
-
for (const e of
|
|
6300
|
+
fs16.mkdirSync(to, { recursive: true });
|
|
6301
|
+
for (const e of fs16.readdirSync(from, { withFileTypes: true })) {
|
|
6041
6302
|
if (e.name === "types" && from.endsWith(".claude-plugin")) continue;
|
|
6042
6303
|
if (e.name === "node_modules") continue;
|
|
6043
|
-
const a =
|
|
6044
|
-
const b =
|
|
6304
|
+
const a = path11.join(from, e.name);
|
|
6305
|
+
const b = path11.join(to, e.name);
|
|
6045
6306
|
if (e.isDirectory()) copyDir(a, b);
|
|
6046
|
-
else
|
|
6307
|
+
else fs16.copyFileSync(a, b);
|
|
6047
6308
|
}
|
|
6048
6309
|
}
|
|
6049
6310
|
function installMod(version2, opts = {}) {
|
|
6050
6311
|
const src = modSource();
|
|
6051
6312
|
if (!src) throw new Error("the bunshin-ui mod is not in this build of the CLI");
|
|
6052
6313
|
const home = modHome(opts.home);
|
|
6053
|
-
const dir =
|
|
6054
|
-
|
|
6314
|
+
const dir = path11.join(home, MOD_PLUGIN);
|
|
6315
|
+
fs16.rmSync(dir, { recursive: true, force: true });
|
|
6055
6316
|
copyDir(src, dir);
|
|
6056
|
-
const manifest =
|
|
6057
|
-
const m = JSON.parse(
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6317
|
+
const manifest = path11.join(dir, ".claude-plugin", "plugin.json");
|
|
6318
|
+
const m = JSON.parse(fs16.readFileSync(manifest, "utf8"));
|
|
6319
|
+
fs16.writeFileSync(manifest, JSON.stringify({ ...m, version: version2 }, null, 2) + "\n");
|
|
6320
|
+
fs16.mkdirSync(path11.join(home, ".claude-plugin"), { recursive: true });
|
|
6321
|
+
fs16.writeFileSync(
|
|
6322
|
+
path11.join(home, ".claude-plugin", "marketplace.json"),
|
|
6062
6323
|
JSON.stringify({ name: MOD_MARKETPLACE, owner: { name: "Bunshin" }, plugins: [{ name: MOD_PLUGIN, source: `./${MOD_PLUGIN}`, description: m.description }] }, null, 2) + "\n"
|
|
6063
6324
|
);
|
|
6064
6325
|
const run2 = opts.run ?? spawnSync;
|
|
@@ -6093,7 +6354,7 @@ async function cmdUi(store, args, out, err, version2) {
|
|
|
6093
6354
|
`);
|
|
6094
6355
|
for (const s of res.steps) out(` ${s.ok ? "\u2713" : "\xB7"} ${s.command}${s.ok ? "" : `: ${s.output.split("\n")[0] ?? ""}`}
|
|
6095
6356
|
`);
|
|
6096
|
-
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 " +
|
|
6357
|
+
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 " + path11.dirname(res.dir) + "\n claude plugin install bunshin-ui@bunshin-local --scope user\n");
|
|
6097
6358
|
}
|
|
6098
6359
|
return installed || !failed.length ? 0 : 1;
|
|
6099
6360
|
}
|
|
@@ -6174,19 +6435,19 @@ __export(update_exports, {
|
|
|
6174
6435
|
updateCheckDue: () => updateCheckDue,
|
|
6175
6436
|
updateNotice: () => updateNotice
|
|
6176
6437
|
});
|
|
6177
|
-
import
|
|
6178
|
-
import
|
|
6438
|
+
import fs17 from "node:fs";
|
|
6439
|
+
import path12 from "node:path";
|
|
6179
6440
|
function readState2() {
|
|
6180
6441
|
try {
|
|
6181
|
-
return JSON.parse(
|
|
6442
|
+
return JSON.parse(fs17.readFileSync(stateFile(), "utf8"));
|
|
6182
6443
|
} catch {
|
|
6183
6444
|
return {};
|
|
6184
6445
|
}
|
|
6185
6446
|
}
|
|
6186
6447
|
function writeState(s) {
|
|
6187
6448
|
try {
|
|
6188
|
-
|
|
6189
|
-
|
|
6449
|
+
fs17.mkdirSync(path12.dirname(stateFile()), { recursive: true });
|
|
6450
|
+
fs17.writeFileSync(stateFile(), JSON.stringify(s, null, 2) + "\n");
|
|
6190
6451
|
} catch {
|
|
6191
6452
|
}
|
|
6192
6453
|
}
|
|
@@ -6236,14 +6497,14 @@ var init_update = __esm({
|
|
|
6236
6497
|
PKG = "@bunshin-cc/bunshin";
|
|
6237
6498
|
LATEST_URL = `https://registry.npmjs.org/${PKG.replace("/", "%2f")}/latest`;
|
|
6238
6499
|
DAY = 864e5;
|
|
6239
|
-
stateFile = () =>
|
|
6500
|
+
stateFile = () => path12.join(homeDir(), ".bunshin", "update.json");
|
|
6240
6501
|
disabled = () => process.env.BUNSHIN_NO_UPDATE_CHECK === "1" || process.env.BUNSHIN_NO_UPDATE_CHECK === "true";
|
|
6241
6502
|
}
|
|
6242
6503
|
});
|
|
6243
6504
|
|
|
6244
6505
|
// src/v2/hooks.ts
|
|
6245
|
-
import
|
|
6246
|
-
import
|
|
6506
|
+
import fs18 from "node:fs";
|
|
6507
|
+
import path13 from "node:path";
|
|
6247
6508
|
function inContext(s) {
|
|
6248
6509
|
return s.inContext ?? [...s.loaded, ...s.pushed];
|
|
6249
6510
|
}
|
|
@@ -6258,6 +6519,12 @@ function touch(v2, ctx, patch = () => {
|
|
|
6258
6519
|
const s = v2.updateSession(id, ctx.cwd, (s2) => {
|
|
6259
6520
|
s2.cwd = ctx.cwd || s2.cwd;
|
|
6260
6521
|
s2.harness = ctx.store.config().harness;
|
|
6522
|
+
if (s2.source === void 0) {
|
|
6523
|
+
try {
|
|
6524
|
+
s2.source = sourceFor(s2.cwd, ctx.store.root, contextFor(v2, ctx.store.config())) ?? "";
|
|
6525
|
+
} catch {
|
|
6526
|
+
}
|
|
6527
|
+
}
|
|
6261
6528
|
if (ctx.input.transcript_path) s2.transcriptPath = ctx.input.transcript_path;
|
|
6262
6529
|
patch(s2);
|
|
6263
6530
|
});
|
|
@@ -6304,10 +6571,10 @@ async function keepModCurrent(ctx, config) {
|
|
|
6304
6571
|
const { version: version2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
|
|
6305
6572
|
const current = version2();
|
|
6306
6573
|
if (!modSource2() || installedModVersion2() === current) return;
|
|
6307
|
-
const marker =
|
|
6308
|
-
if (
|
|
6309
|
-
|
|
6310
|
-
|
|
6574
|
+
const marker = path13.join(modHome2(), `.attempted-${current}`);
|
|
6575
|
+
if (fs18.existsSync(marker)) return;
|
|
6576
|
+
fs18.mkdirSync(modHome2(), { recursive: true });
|
|
6577
|
+
fs18.writeFileSync(marker, (/* @__PURE__ */ new Date()).toISOString());
|
|
6311
6578
|
ctx.detach(["--root", ctx.store.root, "ui", "install", "--quiet"], { cwd: ctx.cwd });
|
|
6312
6579
|
} catch {
|
|
6313
6580
|
}
|
|
@@ -6411,7 +6678,7 @@ function liveUpdate(v2, ctx, session) {
|
|
|
6411
6678
|
}
|
|
6412
6679
|
const config = ctx.store.config();
|
|
6413
6680
|
const deployed = deployEnabled(config, localView(v2, config, ctx.cwd).ctx);
|
|
6414
|
-
const paths = deployed ? deployPaths() : null;
|
|
6681
|
+
const paths = deployed ? deployPaths(process.env, config.orgId) : null;
|
|
6415
6682
|
const root = paths ? config.harness === "codex" ? paths.codexSkills : paths.claudeSkills : null;
|
|
6416
6683
|
const skillPath = (name) => root ? `${root}/${name}/SKILL.md` : null;
|
|
6417
6684
|
const { text: text2, next } = changesSince(session.live, bundle, skillPath);
|
|
@@ -6436,13 +6703,13 @@ function skillHint(sec, others) {
|
|
|
6436
6703
|
const also = others.length ? ` Also possibly relevant: ${others.map((t) => `"${t}"`).join(", ")}.` : "";
|
|
6437
6704
|
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}`;
|
|
6438
6705
|
}
|
|
6439
|
-
async function remoteAuto(ctx, prompt, exclude) {
|
|
6706
|
+
async function remoteAuto(ctx, prompt, exclude, source) {
|
|
6440
6707
|
const config = ctx.store.config();
|
|
6441
6708
|
if (!canCall(config) || !config.recall.useMothership) return null;
|
|
6442
6709
|
const budget = Math.min(2500, ctx.deadline - Date.now() - 600);
|
|
6443
6710
|
if (budget < 300) return null;
|
|
6444
6711
|
try {
|
|
6445
|
-
const res = await v2api(config, "POST", "/recall/auto", { prompt: redact2(prompt).slice(0, 8e3), exclude }, { timeoutMs: budget });
|
|
6712
|
+
const res = await v2api(config, "POST", "/recall/auto", { prompt: redact2(prompt).slice(0, 8e3), exclude, ...source ? { source } : {} }, { timeoutMs: budget });
|
|
6446
6713
|
return res.available ? res : null;
|
|
6447
6714
|
} catch {
|
|
6448
6715
|
return null;
|
|
@@ -6497,7 +6764,7 @@ async function onPrompt(ctx) {
|
|
|
6497
6764
|
return withLive(deployed || !fresh2 ? {} : additional("UserPromptSubmit", renderSection(named, why, { deployed: false })));
|
|
6498
6765
|
}
|
|
6499
6766
|
const res = auto(view.sections, view.reader, prompt, { exclude: injected });
|
|
6500
|
-
const remote = await remoteAuto(ctx, prompt, injected);
|
|
6767
|
+
const remote = await remoteAuto(ctx, prompt, injected, session.source);
|
|
6501
6768
|
if (remote) {
|
|
6502
6769
|
const local = (id) => view.sections.find((s) => s.id === id);
|
|
6503
6770
|
if (remote.section) {
|
|
@@ -6649,6 +6916,7 @@ function buildEpisode(store, session, transcriptPath, now = /* @__PURE__ */ new
|
|
|
6649
6916
|
const v2 = new V2Store(store);
|
|
6650
6917
|
const config = store.config();
|
|
6651
6918
|
const view = localView(v2, config, session.cwd, { index: false });
|
|
6919
|
+
const source = sessionSource(store, session, view.ctx);
|
|
6652
6920
|
const scan = opts.scan ?? scanTranscript(transcriptPath ?? session.transcriptPath);
|
|
6653
6921
|
const outcome = v2.readOutcome(session.sessionId);
|
|
6654
6922
|
const turns = scan.facts.turns.length;
|
|
@@ -6688,6 +6956,7 @@ function buildEpisode(store, session, transcriptPath, now = /* @__PURE__ */ new
|
|
|
6688
6956
|
...outcome ? { outcome } : {},
|
|
6689
6957
|
...view.bundle?.release?.version ? { harnessVersion: view.bundle.release.version } : {},
|
|
6690
6958
|
cliVersion: version(),
|
|
6959
|
+
...source ? { source } : {},
|
|
6691
6960
|
uploaded: false,
|
|
6692
6961
|
...previous?.reportedServed ? { reportedServed: previous.reportedServed } : {},
|
|
6693
6962
|
...previous?.reportedLoaded ? { reportedLoaded: previous.reportedLoaded } : {},
|
|
@@ -6695,6 +6964,14 @@ function buildEpisode(store, session, transcriptPath, now = /* @__PURE__ */ new
|
|
|
6695
6964
|
};
|
|
6696
6965
|
return { episode, gate, scan };
|
|
6697
6966
|
}
|
|
6967
|
+
function sessionSource(store, session, ctx) {
|
|
6968
|
+
if (session.source !== void 0) return session.source || void 0;
|
|
6969
|
+
try {
|
|
6970
|
+
return sourceFor(session.cwd, store.root, ctx);
|
|
6971
|
+
} catch {
|
|
6972
|
+
return void 0;
|
|
6973
|
+
}
|
|
6974
|
+
}
|
|
6698
6975
|
function checkpointDue(session, scan, now) {
|
|
6699
6976
|
const since = Date.parse(session.checkpointAt ?? session.startedAt);
|
|
6700
6977
|
if (!Number.isFinite(since) || now.getTime() - since < CHECKPOINT_MS) return false;
|
|
@@ -6809,6 +7086,7 @@ var init_hooks = __esm({
|
|
|
6809
7086
|
init_signals();
|
|
6810
7087
|
init_store2();
|
|
6811
7088
|
init_ui();
|
|
7089
|
+
init_source();
|
|
6812
7090
|
init_view();
|
|
6813
7091
|
NUDGE_TEXT = "This looks worth capturing. Consider /learn.";
|
|
6814
7092
|
NUDGE_INTERVAL_MS = 60 * 6e4;
|
|
@@ -6823,6 +7101,8 @@ var init_hooks = __esm({
|
|
|
6823
7101
|
});
|
|
6824
7102
|
|
|
6825
7103
|
// src/hooks.ts
|
|
7104
|
+
import fs19 from "node:fs";
|
|
7105
|
+
import path14 from "node:path";
|
|
6826
7106
|
function normalizeEvent(raw) {
|
|
6827
7107
|
return EVENT_NAMES[String(raw ?? "").trim().toLowerCase().replace(/_/g, "-")] ?? null;
|
|
6828
7108
|
}
|
|
@@ -6866,6 +7146,8 @@ async function handleHook(rawEvent, input, opts = {}) {
|
|
|
6866
7146
|
const store = opts.store !== void 0 ? opts.store : Store.discover(cwd);
|
|
6867
7147
|
if (!store) return {};
|
|
6868
7148
|
const config = store.config();
|
|
7149
|
+
if (!cwdInPool(config, cwd, store.root)) return {};
|
|
7150
|
+
if (deferToProject(cwd)) return {};
|
|
6869
7151
|
const budgetMs = Math.min(opts.budgetMs ?? config.recall.hookBudgetMs ?? HARD_BUDGET_MS, HARD_BUDGET_MS);
|
|
6870
7152
|
const deadline = Date.now() + budgetMs;
|
|
6871
7153
|
try {
|
|
@@ -6886,6 +7168,24 @@ async function handleHook(rawEvent, input, opts = {}) {
|
|
|
6886
7168
|
return {};
|
|
6887
7169
|
}
|
|
6888
7170
|
}
|
|
7171
|
+
function deferToProject(cwd, env = process.env) {
|
|
7172
|
+
if (env.BUNSHIN_HOOK_SCOPE !== "user") return false;
|
|
7173
|
+
let dir = path14.resolve(cwd);
|
|
7174
|
+
for (let i = 0; i < 40; i++) {
|
|
7175
|
+
for (const f of [".claude/settings.local.json", ".claude/settings.json", ".codex/hooks.json"]) {
|
|
7176
|
+
try {
|
|
7177
|
+
const text2 = fs19.readFileSync(path14.join(dir, f), "utf8");
|
|
7178
|
+
if (/\bbunshin\b[^"]*\bhook\b/.test(text2) && !/BUNSHIN_HOOK_SCOPE=user/.test(text2)) return true;
|
|
7179
|
+
} catch {
|
|
7180
|
+
}
|
|
7181
|
+
}
|
|
7182
|
+
if (fs19.existsSync(path14.join(dir, ".git"))) return false;
|
|
7183
|
+
const up = path14.dirname(dir);
|
|
7184
|
+
if (up === dir) return false;
|
|
7185
|
+
dir = up;
|
|
7186
|
+
}
|
|
7187
|
+
return false;
|
|
7188
|
+
}
|
|
6889
7189
|
async function hookCommand(rawEvent, opts = {}) {
|
|
6890
7190
|
const write = opts.write ?? ((s) => process.stdout.write(s));
|
|
6891
7191
|
let output = {};
|
|
@@ -6908,6 +7208,7 @@ var init_hooks2 = __esm({
|
|
|
6908
7208
|
init_detach();
|
|
6909
7209
|
init_store();
|
|
6910
7210
|
init_hooks();
|
|
7211
|
+
init_source();
|
|
6911
7212
|
HARD_BUDGET_MS = 4e3;
|
|
6912
7213
|
EVENT_NAMES = {
|
|
6913
7214
|
"session-start": "session-start",
|
|
@@ -6927,32 +7228,32 @@ var init_hooks2 = __esm({
|
|
|
6927
7228
|
});
|
|
6928
7229
|
|
|
6929
7230
|
// src/project-hooks.ts
|
|
6930
|
-
import
|
|
6931
|
-
import
|
|
6932
|
-
import { execFileSync as
|
|
7231
|
+
import fs20 from "node:fs";
|
|
7232
|
+
import path15 from "node:path";
|
|
7233
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
6933
7234
|
function codexProjectHooks(cwd) {
|
|
6934
|
-
const legacyFile =
|
|
6935
|
-
const
|
|
7235
|
+
const legacyFile = path15.join(cwd, ".codex", "hooks.json");
|
|
7236
|
+
const git5 = (...args) => execFileSync4("git", ["-C", cwd, "rev-parse", ...args], {
|
|
6936
7237
|
encoding: "utf8",
|
|
6937
7238
|
stdio: ["ignore", "pipe", "ignore"],
|
|
6938
7239
|
timeout: 2e3
|
|
6939
7240
|
}).trim();
|
|
6940
7241
|
let root, common, directory;
|
|
6941
7242
|
try {
|
|
6942
|
-
root =
|
|
6943
|
-
common =
|
|
6944
|
-
directory =
|
|
7243
|
+
root = git5("--show-toplevel");
|
|
7244
|
+
common = git5("--path-format=absolute", "--git-common-dir");
|
|
7245
|
+
directory = git5("--absolute-git-dir");
|
|
6945
7246
|
} catch {
|
|
6946
7247
|
return { file: legacyFile, shared: false, legacyFile };
|
|
6947
7248
|
}
|
|
6948
|
-
const linked =
|
|
7249
|
+
const linked = fs20.realpathSync(common) !== fs20.realpathSync(directory);
|
|
6949
7250
|
if (linked) {
|
|
6950
|
-
if (
|
|
7251
|
+
if (path15.basename(common) !== ".git" || !fs20.statSync(common).isDirectory()) {
|
|
6951
7252
|
throw new Error("Codex hooks in a worktree of a bare repository are not supported yet. Use a full checkout for Bunshin setup.");
|
|
6952
7253
|
}
|
|
6953
|
-
root =
|
|
7254
|
+
root = path15.dirname(common);
|
|
6954
7255
|
}
|
|
6955
|
-
const file =
|
|
7256
|
+
const file = fs20.realpathSync(root) === fs20.realpathSync(cwd) ? legacyFile : path15.join(root, ".codex", "hooks.json");
|
|
6956
7257
|
return { file, shared: linked, legacyFile };
|
|
6957
7258
|
}
|
|
6958
7259
|
var init_project_hooks = __esm({
|
|
@@ -6975,6 +7276,7 @@ __export(init_exports, {
|
|
|
6975
7276
|
installLearnCommand: () => installLearnCommand,
|
|
6976
7277
|
installMcpConfig: () => installMcpConfig,
|
|
6977
7278
|
installPiExtension: () => installPiExtension,
|
|
7279
|
+
installUserMcp: () => installUserMcp,
|
|
6978
7280
|
isBunshinEntry: () => isBunshinEntry,
|
|
6979
7281
|
mcpLauncher: () => mcpLauncher,
|
|
6980
7282
|
mergeClaudeSettings: () => mergeClaudeSettings,
|
|
@@ -6982,8 +7284,9 @@ __export(init_exports, {
|
|
|
6982
7284
|
removeBunshinHooks: () => removeBunshinHooks,
|
|
6983
7285
|
runInit: () => runInit
|
|
6984
7286
|
});
|
|
6985
|
-
import
|
|
6986
|
-
import
|
|
7287
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
7288
|
+
import fs21 from "node:fs";
|
|
7289
|
+
import path16 from "node:path";
|
|
6987
7290
|
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "node:url";
|
|
6988
7291
|
function isBunshinEntry(entry) {
|
|
6989
7292
|
return entry?._bunshin === true || typeof entry?.command === "string" && /\bbunshin\b.*\bhook\b/.test(entry.command);
|
|
@@ -7008,13 +7311,13 @@ function removeBunshinHooks(settings) {
|
|
|
7008
7311
|
else delete out.hooks;
|
|
7009
7312
|
return out;
|
|
7010
7313
|
}
|
|
7011
|
-
function mergeClaudeSettings(settings, { bin = "bunshin", harness = "claude-code" } = {}) {
|
|
7314
|
+
function mergeClaudeSettings(settings, { bin = "bunshin", harness = "claude-code", user = false } = {}) {
|
|
7012
7315
|
const base = removeBunshinHooks(settings);
|
|
7013
7316
|
const hooks = { ...base.hooks ?? {} };
|
|
7014
7317
|
for (const { event, arg, timeout, matcher } of harness === "codex" ? CODEX_HOOK_EVENTS : HOOK_EVENTS) {
|
|
7015
7318
|
const groups = Array.isArray(hooks[event]) ? [...hooks[event]] : [];
|
|
7016
7319
|
const hook = bunshinHookEntry(arg, timeout, bin);
|
|
7017
|
-
hook.command =
|
|
7320
|
+
hook.command = `${user ? "BUNSHIN_HOOK_SCOPE=user " : ""}BUNSHIN_HARNESS=${harness} ${hook.command}`;
|
|
7018
7321
|
if (harness === "codex") delete hook._bunshin;
|
|
7019
7322
|
groups.push(matcher ? { matcher, hooks: [hook] } : { hooks: [hook] });
|
|
7020
7323
|
hooks[event] = groups;
|
|
@@ -7028,16 +7331,16 @@ function countBunshinHooks(settings) {
|
|
|
7028
7331
|
);
|
|
7029
7332
|
}
|
|
7030
7333
|
function claudeSettingsPath() {
|
|
7031
|
-
return
|
|
7334
|
+
return path16.join(homeDir(), ".claude", "settings.json");
|
|
7032
7335
|
}
|
|
7033
7336
|
function installPiExtension(cwd, remove = false, scope = "repo") {
|
|
7034
|
-
const file = scope === "global" ?
|
|
7337
|
+
const file = scope === "global" ? path16.join(homeDir(), ".pi/agent/extensions/bunshin.js") : path16.join(cwd, ".pi/extensions/bunshin.js");
|
|
7035
7338
|
const marker = "// Managed by Bunshin setup.";
|
|
7036
7339
|
try {
|
|
7037
|
-
const previous = fileExists(file) ?
|
|
7340
|
+
const previous = fileExists(file) ? fs21.readFileSync(file, "utf8") : "";
|
|
7038
7341
|
if (previous && !previous.startsWith(marker)) throw new Error("An existing Bunshin extension file is not managed by setup; preserve or move it before continuing.");
|
|
7039
7342
|
if (remove) {
|
|
7040
|
-
if (previous)
|
|
7343
|
+
if (previous) fs21.unlinkSync(file);
|
|
7041
7344
|
return { file, installed: 0, changed: !!previous };
|
|
7042
7345
|
}
|
|
7043
7346
|
const extension = fileURLToPath2(new URL("./pi-extension.js", import.meta.url));
|
|
@@ -7046,24 +7349,24 @@ function installPiExtension(cwd, remove = false, scope = "repo") {
|
|
|
7046
7349
|
export { default } from ${JSON.stringify(pathToFileURL(extension).href)};
|
|
7047
7350
|
`;
|
|
7048
7351
|
if (text2 !== previous) {
|
|
7049
|
-
ensureDir(
|
|
7050
|
-
|
|
7352
|
+
ensureDir(path16.dirname(file));
|
|
7353
|
+
fs21.writeFileSync(file, text2);
|
|
7051
7354
|
}
|
|
7052
7355
|
return { file, installed: 1, changed: text2 !== previous };
|
|
7053
7356
|
} catch (error) {
|
|
7054
7357
|
return { file, installed: 0, changed: false, error: error instanceof Error ? error.message : String(error) };
|
|
7055
7358
|
}
|
|
7056
7359
|
}
|
|
7057
|
-
function installClaudeHooks({ file = claudeSettingsPath(), bin = "bunshin", remove = false, harness = "claude-code" } = {}) {
|
|
7360
|
+
function installClaudeHooks({ file = claudeSettingsPath(), bin = "bunshin", remove = false, harness = "claude-code", user = false } = {}) {
|
|
7058
7361
|
try {
|
|
7059
|
-
const before = fileExists(file) ? JSON.parse(
|
|
7060
|
-
const after = remove ? removeBunshinHooks(before) : mergeClaudeSettings(before, { bin, harness });
|
|
7362
|
+
const before = fileExists(file) ? JSON.parse(fs21.readFileSync(file, "utf8")) : {};
|
|
7363
|
+
const after = remove ? removeBunshinHooks(before) : mergeClaudeSettings(before, { bin, harness, user });
|
|
7061
7364
|
const changed = JSON.stringify(before) !== JSON.stringify(after);
|
|
7062
7365
|
if (changed) {
|
|
7063
|
-
ensureDir(
|
|
7366
|
+
ensureDir(path16.dirname(file));
|
|
7064
7367
|
if (fileExists(file)) {
|
|
7065
7368
|
try {
|
|
7066
|
-
|
|
7369
|
+
fs21.copyFileSync(file, `${file}.bunshin-backup`);
|
|
7067
7370
|
} catch {
|
|
7068
7371
|
}
|
|
7069
7372
|
}
|
|
@@ -7078,23 +7381,23 @@ function mcpLauncher(bin) {
|
|
|
7078
7381
|
if (!bin || /^[\w./@+-]+$/.test(bin)) return { command: bin || "bunshin", args: ["mcp"] };
|
|
7079
7382
|
let script = process.argv[1] ?? "";
|
|
7080
7383
|
try {
|
|
7081
|
-
script =
|
|
7384
|
+
script = fs21.realpathSync(script);
|
|
7082
7385
|
} catch {
|
|
7083
7386
|
}
|
|
7084
7387
|
return { command: process.execPath, args: [script, "mcp"] };
|
|
7085
7388
|
}
|
|
7086
7389
|
function installLearnCommand({ file, remove = false }) {
|
|
7087
7390
|
try {
|
|
7088
|
-
const current = fileExists(file) ?
|
|
7391
|
+
const current = fileExists(file) ? fs21.readFileSync(file, "utf8") : null;
|
|
7089
7392
|
const ours = current === null || current === LEARN_COMMAND;
|
|
7090
7393
|
if (remove) {
|
|
7091
|
-
if (current !== null && ours)
|
|
7394
|
+
if (current !== null && ours) fs21.unlinkSync(file);
|
|
7092
7395
|
return { file, installed: 0, changed: current !== null && ours };
|
|
7093
7396
|
}
|
|
7094
7397
|
if (!ours) return { file, installed: 0, changed: false, error: "a different learn.md is already there; left as it is" };
|
|
7095
7398
|
if (current === null) {
|
|
7096
|
-
|
|
7097
|
-
|
|
7399
|
+
fs21.mkdirSync(path16.dirname(file), { recursive: true });
|
|
7400
|
+
fs21.writeFileSync(file, LEARN_COMMAND);
|
|
7098
7401
|
}
|
|
7099
7402
|
return { file, installed: 1, changed: current === null };
|
|
7100
7403
|
} catch (err) {
|
|
@@ -7103,7 +7406,7 @@ function installLearnCommand({ file, remove = false }) {
|
|
|
7103
7406
|
}
|
|
7104
7407
|
function installMcpConfig({ file, launcher, remove = false }) {
|
|
7105
7408
|
try {
|
|
7106
|
-
const before = fileExists(file) ? JSON.parse(
|
|
7409
|
+
const before = fileExists(file) ? JSON.parse(fs21.readFileSync(file, "utf8")) : {};
|
|
7107
7410
|
const servers = { ...before.mcpServers ?? {} };
|
|
7108
7411
|
if (remove) delete servers.bunshin;
|
|
7109
7412
|
else servers.bunshin = { type: "stdio", command: launcher.command, args: launcher.args, env: { BUNSHIN_HARNESS: "claude-code" } };
|
|
@@ -7111,7 +7414,7 @@ function installMcpConfig({ file, launcher, remove = false }) {
|
|
|
7111
7414
|
if (!Object.keys(servers).length) delete after.mcpServers;
|
|
7112
7415
|
const changed = JSON.stringify(before) !== JSON.stringify(after);
|
|
7113
7416
|
if (changed) {
|
|
7114
|
-
if (!Object.keys(after).length && fileExists(file))
|
|
7417
|
+
if (!Object.keys(after).length && fileExists(file)) fs21.unlinkSync(file);
|
|
7115
7418
|
else if (Object.keys(after).length) writeJsonAtomic(file, after);
|
|
7116
7419
|
}
|
|
7117
7420
|
return { file, installed: servers.bunshin ? 1 : 0, changed };
|
|
@@ -7119,25 +7422,49 @@ function installMcpConfig({ file, launcher, remove = false }) {
|
|
|
7119
7422
|
return { file, installed: 0, changed: false, error: err instanceof Error ? err.message : String(err) };
|
|
7120
7423
|
}
|
|
7121
7424
|
}
|
|
7425
|
+
function installUserMcp({ launcher, remove = false, claude = "claude" }) {
|
|
7426
|
+
const file = path16.join(homeDir(), ".claude.json");
|
|
7427
|
+
const run2 = (args) => execFileSync5(claude, args, { stdio: ["ignore", "pipe", "pipe"], timeout: 2e4, env: { ...process.env, BUNSHIN_CHILD: "1" } }).toString();
|
|
7428
|
+
try {
|
|
7429
|
+
let had = false;
|
|
7430
|
+
try {
|
|
7431
|
+
run2(["mcp", "get", "bunshin"]);
|
|
7432
|
+
had = true;
|
|
7433
|
+
} catch {
|
|
7434
|
+
}
|
|
7435
|
+
if (had) {
|
|
7436
|
+
try {
|
|
7437
|
+
run2(["mcp", "remove", "bunshin", "-s", "user"]);
|
|
7438
|
+
} catch {
|
|
7439
|
+
}
|
|
7440
|
+
}
|
|
7441
|
+
if (remove) return { file, installed: 0, changed: had };
|
|
7442
|
+
run2(["mcp", "add-json", "bunshin", JSON.stringify({ type: "stdio", command: launcher.command, args: launcher.args, env: { BUNSHIN_HARNESS: "claude-code" } }), "-s", "user"]);
|
|
7443
|
+
return { file, installed: 1, changed: true };
|
|
7444
|
+
} catch (err) {
|
|
7445
|
+
return { file, installed: 0, changed: false, error: err instanceof Error ? err.message.split("\n")[0] : String(err) };
|
|
7446
|
+
}
|
|
7447
|
+
}
|
|
7122
7448
|
function runInit(opts = {}) {
|
|
7123
7449
|
const cwd = opts.cwd ?? process.cwd();
|
|
7124
7450
|
const warnings = [];
|
|
7125
7451
|
const scope = opts.scope ?? "repo";
|
|
7126
|
-
const root = scope === "global" ?
|
|
7127
|
-
if (
|
|
7452
|
+
const root = scope === "global" ? path16.join(homeDir(), ".bunshin") : path16.join(cwd, ".bunshin");
|
|
7453
|
+
if (fs21.existsSync(root) && !new Store(root).trusted && fs21.readdirSync(root).length) {
|
|
7128
7454
|
const aside = `${root}.untrusted-${Date.now()}`;
|
|
7129
|
-
|
|
7455
|
+
fs21.renameSync(root, aside);
|
|
7130
7456
|
warnings.push(`moved ${root} aside to ${aside}: this user did not create it`);
|
|
7131
7457
|
}
|
|
7132
|
-
const existed = fileExists(
|
|
7458
|
+
const existed = fileExists(path16.join(root, "config.json"));
|
|
7133
7459
|
const store = new Store(root);
|
|
7134
7460
|
store.ensureLayout();
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
|
|
7138
|
-
const
|
|
7461
|
+
fs21.chmodSync(root, 448);
|
|
7462
|
+
const hookScope = opts.hooks ?? (scope === "global" ? "user" : (existed && store.trusted ? store.config().hooks : void 0) ?? "project");
|
|
7463
|
+
if (scope === "repo" && (hookScope === "project" || fileExists(path16.join(cwd, ".git")))) {
|
|
7464
|
+
const ignore = path16.join(cwd, ".gitignore");
|
|
7465
|
+
const current = fileExists(ignore) ? fs21.readFileSync(ignore, "utf8") : "";
|
|
7139
7466
|
if (!current.split(/\r?\n/).some((line) => /^\/?\.bunshin\/?$/.test(line.trim()))) {
|
|
7140
|
-
|
|
7467
|
+
fs21.appendFileSync(ignore, `${current && !current.endsWith("\n") ? "\n" : ""}.bunshin/
|
|
7141
7468
|
`);
|
|
7142
7469
|
}
|
|
7143
7470
|
}
|
|
@@ -7145,6 +7472,7 @@ function runInit(opts = {}) {
|
|
|
7145
7472
|
const config = {
|
|
7146
7473
|
...previous,
|
|
7147
7474
|
scope,
|
|
7475
|
+
hooks: hookScope,
|
|
7148
7476
|
orgId: opts.orgId ?? previous.orgId,
|
|
7149
7477
|
userId: opts.userId ?? previous.userId,
|
|
7150
7478
|
role: opts.role ?? previous.role,
|
|
@@ -7161,18 +7489,22 @@ function runInit(opts = {}) {
|
|
|
7161
7489
|
if (!fileExists(store.indexFile)) store.rebuildIndex();
|
|
7162
7490
|
let hooks;
|
|
7163
7491
|
if ((opts.removeHooks || !opts.noHooks) && config.harness === "pi") {
|
|
7164
|
-
hooks = installPiExtension(cwd, opts.removeHooks,
|
|
7492
|
+
hooks = installPiExtension(cwd, opts.removeHooks, hookScope === "user" ? "global" : "repo");
|
|
7165
7493
|
if (hooks.error) warnings.push(hooks.error);
|
|
7166
7494
|
}
|
|
7167
7495
|
if ((opts.removeHooks || !opts.noHooks) && (config.harness === "codex" || config.harness === "claude-code")) {
|
|
7168
|
-
const projectHooks = config.harness === "codex" &&
|
|
7169
|
-
const file = opts.settingsFile ?? (config.harness === "codex" ? projectHooks?.file ??
|
|
7170
|
-
hooks = installClaudeHooks({ file, bin: opts.bin, remove: opts.removeHooks, harness: config.harness });
|
|
7496
|
+
const projectHooks = config.harness === "codex" && hookScope === "project" && !opts.settingsFile ? codexProjectHooks(cwd) : void 0;
|
|
7497
|
+
const file = opts.settingsFile ?? (config.harness === "codex" ? projectHooks?.file ?? path16.join(process.env.CODEX_HOME ?? path16.join(homeDir(), ".codex"), "hooks.json") : hookScope === "project" ? path16.join(cwd, ".claude", "settings.local.json") : claudeSettingsPath());
|
|
7498
|
+
hooks = installClaudeHooks({ file, bin: opts.bin, remove: opts.removeHooks, harness: config.harness, user: hookScope === "user" && !opts.settingsFile });
|
|
7499
|
+
if (!opts.removeHooks && !opts.settingsFile && hookScope === "user" && config.harness === "claude-code") {
|
|
7500
|
+
const local = path16.join(cwd, ".claude", "settings.local.json");
|
|
7501
|
+
if (fileExists(local) && path16.resolve(local) !== path16.resolve(file)) installClaudeHooks({ file: local, remove: true });
|
|
7502
|
+
}
|
|
7171
7503
|
if (projectHooks?.shared) {
|
|
7172
7504
|
hooks.sharedWithWorktrees = true;
|
|
7173
|
-
if (!hooks.error && !opts.removeHooks) ensureDir(
|
|
7505
|
+
if (!hooks.error && !opts.removeHooks) ensureDir(path16.dirname(projectHooks.legacyFile));
|
|
7174
7506
|
}
|
|
7175
|
-
if (!hooks.error && projectHooks && projectHooks.file !== projectHooks.legacyFile && fileExists(projectHooks.legacyFile) &&
|
|
7507
|
+
if (!hooks.error && projectHooks && projectHooks.file !== projectHooks.legacyFile && fileExists(projectHooks.legacyFile) && fs21.realpathSync(projectHooks.file) !== fs21.realpathSync(projectHooks.legacyFile)) {
|
|
7176
7508
|
const old = installClaudeHooks({ file: projectHooks.legacyFile, remove: true, harness: "codex" });
|
|
7177
7509
|
if (old.error) hooks.error = `Could not remove old Bunshin hooks from ${old.file}: ${old.error}`;
|
|
7178
7510
|
}
|
|
@@ -7180,10 +7512,11 @@ function runInit(opts = {}) {
|
|
|
7180
7512
|
}
|
|
7181
7513
|
let mcp;
|
|
7182
7514
|
let learnCommand;
|
|
7183
|
-
if ((opts.removeHooks || !opts.noHooks) && config.harness === "claude-code"
|
|
7184
|
-
|
|
7515
|
+
if ((opts.removeHooks || !opts.noHooks) && config.harness === "claude-code") {
|
|
7516
|
+
const user = hookScope === "user";
|
|
7517
|
+
mcp = user ? installUserMcp({ launcher: opts.mcp ?? mcpLauncher(opts.bin), remove: opts.removeHooks }) : installMcpConfig({ file: path16.join(cwd, ".mcp.json"), launcher: opts.mcp ?? mcpLauncher(opts.bin), remove: opts.removeHooks });
|
|
7185
7518
|
if (mcp.error) warnings.push(`could not update ${mcp.file}: ${mcp.error}`);
|
|
7186
|
-
learnCommand = installLearnCommand({ file:
|
|
7519
|
+
learnCommand = installLearnCommand({ file: path16.join(user ? path16.join(homeDir(), ".claude") : path16.join(cwd, ".claude"), "commands", "learn.md"), remove: opts.removeHooks });
|
|
7187
7520
|
if (learnCommand.error) warnings.push(`/learn not installed at ${learnCommand.file}: ${learnCommand.error}`);
|
|
7188
7521
|
}
|
|
7189
7522
|
if (!resolveBackend(config)) {
|
|
@@ -7318,6 +7651,7 @@ __export(commands_exports, {
|
|
|
7318
7651
|
cmdReflectFork: () => cmdReflectFork,
|
|
7319
7652
|
cmdReview: () => cmdReview,
|
|
7320
7653
|
cmdReviewDecide: () => cmdReviewDecide,
|
|
7654
|
+
cmdSources: () => cmdSources,
|
|
7321
7655
|
cmdSuggest: () => cmdSuggest,
|
|
7322
7656
|
cmdSync: () => cmdSync,
|
|
7323
7657
|
robotAttrsFromFlags: () => robotAttrsFromFlags,
|
|
@@ -7325,9 +7659,9 @@ __export(commands_exports, {
|
|
|
7325
7659
|
setupRobot: () => setupRobot,
|
|
7326
7660
|
v2Status: () => v2Status
|
|
7327
7661
|
});
|
|
7328
|
-
import
|
|
7329
|
-
import
|
|
7330
|
-
import { createHash as
|
|
7662
|
+
import fs22 from "node:fs";
|
|
7663
|
+
import path17 from "node:path";
|
|
7664
|
+
import { createHash as createHash5, randomBytes as randomBytes4 } from "node:crypto";
|
|
7331
7665
|
function cmdReview(store, args, cwd, out, err) {
|
|
7332
7666
|
const v2 = new V2Store(store);
|
|
7333
7667
|
const sub = args._[1] ?? "list";
|
|
@@ -7496,11 +7830,11 @@ function cmdSuggest(store, args, cwd, out, err) {
|
|
|
7496
7830
|
err('bunshin suggest: usage: bunshin suggest --code <harness-relative path> [--file <local file>] [--rationale "<text>"]\n');
|
|
7497
7831
|
return 1;
|
|
7498
7832
|
}
|
|
7499
|
-
const harnessDir =
|
|
7500
|
-
const file = str3(args.flags.file) ?
|
|
7833
|
+
const harnessDir = path17.resolve(cwd, str3(args.flags["harness-dir"]) ?? ".");
|
|
7834
|
+
const file = str3(args.flags.file) ? path17.resolve(cwd, str3(args.flags.file)) : path17.join(harnessDir, rel);
|
|
7501
7835
|
let content;
|
|
7502
7836
|
try {
|
|
7503
|
-
content =
|
|
7837
|
+
content = fs22.readFileSync(file, "utf8");
|
|
7504
7838
|
} catch {
|
|
7505
7839
|
err(`bunshin suggest: cannot read ${file}
|
|
7506
7840
|
`);
|
|
@@ -7621,15 +7955,15 @@ async function cmdQuestions(store, args, out, err) {
|
|
|
7621
7955
|
return 0;
|
|
7622
7956
|
}
|
|
7623
7957
|
function saveCredential(storeRoot, apiUrl, apiKey, id) {
|
|
7624
|
-
const dir =
|
|
7625
|
-
|
|
7626
|
-
|
|
7627
|
-
const file =
|
|
7958
|
+
const dir = path17.join(homeDir(), ".config/bunshin/credentials");
|
|
7959
|
+
fs22.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
7960
|
+
fs22.chmodSync(dir, 448);
|
|
7961
|
+
const file = path17.join(dir, `${createHash5("sha256").update(`${storeRoot}
|
|
7628
7962
|
${apiUrl}
|
|
7629
7963
|
${id}`).digest("hex")}.json`);
|
|
7630
7964
|
const tmp = `${file}.${randomBytes4(4).toString("hex")}.tmp`;
|
|
7631
|
-
|
|
7632
|
-
|
|
7965
|
+
fs22.writeFileSync(tmp, JSON.stringify({ apiUrl, apiKey }) + "\n", { mode: 384, flag: "wx" });
|
|
7966
|
+
fs22.renameSync(tmp, file);
|
|
7633
7967
|
return file;
|
|
7634
7968
|
}
|
|
7635
7969
|
async function setupRobot(opts) {
|
|
@@ -7637,10 +7971,10 @@ async function setupRobot(opts) {
|
|
|
7637
7971
|
const existing = Store.discover(opts.cwd);
|
|
7638
7972
|
let store;
|
|
7639
7973
|
const harness = opts.harness ?? "other";
|
|
7640
|
-
if (existing &&
|
|
7974
|
+
if (existing && path17.resolve(path17.dirname(existing.root)) === path17.resolve(opts.cwd) && harness === "other") store = existing;
|
|
7641
7975
|
else {
|
|
7642
7976
|
runInit({ cwd: opts.cwd, harness, noHooks: harness === "other", ...opts.apiUrl ? { apiUrl: opts.apiUrl } : {} });
|
|
7643
|
-
store = new Store(
|
|
7977
|
+
store = new Store(path17.join(opts.cwd, ".bunshin"));
|
|
7644
7978
|
}
|
|
7645
7979
|
let config = store.config();
|
|
7646
7980
|
const apiUrl = (opts.apiUrl ?? config.apiUrl ?? "").replace(/\/$/, "");
|
|
@@ -7703,8 +8037,10 @@ function v2Status(store, cwd) {
|
|
|
7703
8037
|
}
|
|
7704
8038
|
const view = localView(v2, config, cwd, { index: false });
|
|
7705
8039
|
const sync = v2.readSyncState();
|
|
8040
|
+
const pooled = cwdInPool(config, cwd, store.root);
|
|
7706
8041
|
return {
|
|
7707
8042
|
context: { id: view.ctx.id, kind: view.ctx.kind, layers: view.layers },
|
|
8043
|
+
source: { id: pooled ? sourceFor(cwd, store.root, view.ctx) ?? null : null, inPool: pooled, folder: topFolder(cwd, store.root) || null },
|
|
7708
8044
|
bundle: bundle ? { sections: bundle.sections.length, generatedAt: bundle.generatedAt, release: bundle.release?.version ?? null } : null,
|
|
7709
8045
|
repo: { name: repo, sections: repoSections },
|
|
7710
8046
|
overlays: v2.readOverlays().length,
|
|
@@ -7715,6 +8051,46 @@ function v2Status(store, cwd) {
|
|
|
7715
8051
|
session: v2.readCurrent()?.sessionId ?? null
|
|
7716
8052
|
};
|
|
7717
8053
|
}
|
|
8054
|
+
function cmdSources(store, args, cwd, out, err) {
|
|
8055
|
+
const config = store.config();
|
|
8056
|
+
const action = args._[1];
|
|
8057
|
+
const names = args._.slice(2).flatMap((x) => x.split(",")).map((x) => x.trim().replace(/\/+$/, "")).filter(Boolean);
|
|
8058
|
+
if (action) {
|
|
8059
|
+
if (action !== "all" && action !== "only" && action !== "include" && action !== "exclude") {
|
|
8060
|
+
err("usage: bunshin sources [all | only <folders\u2026> | include <folders\u2026> | exclude <folders\u2026>]\n");
|
|
8061
|
+
return 1;
|
|
8062
|
+
}
|
|
8063
|
+
if (action !== "all" && !names.length) {
|
|
8064
|
+
err(`bunshin sources ${action}: name at least one folder
|
|
8065
|
+
`);
|
|
8066
|
+
return 1;
|
|
8067
|
+
}
|
|
8068
|
+
const known = new Set(listFolders(store.root, void 0).map((f) => f.folder));
|
|
8069
|
+
const unknown = names.filter((n) => !known.has(n));
|
|
8070
|
+
if (unknown.length) {
|
|
8071
|
+
err(`bunshin sources: not a folder in ${storeHome(store.root)}: ${unknown.join(", ")}
|
|
8072
|
+
`);
|
|
8073
|
+
return 1;
|
|
8074
|
+
}
|
|
8075
|
+
const next = select(config.sources, action, names);
|
|
8076
|
+
const { sources: _drop, ...rest } = config;
|
|
8077
|
+
void _drop;
|
|
8078
|
+
store.saveConfig(next ? { ...rest, sources: next } : rest);
|
|
8079
|
+
}
|
|
8080
|
+
const selection = store.config().sources;
|
|
8081
|
+
const folders = listFolders(store.root, selection, { sources: true });
|
|
8082
|
+
if (bool2(args.flags.json)) {
|
|
8083
|
+
out(JSON.stringify({ home: storeHome(store.root), selection: selection ?? null, folders }, null, 2) + "\n");
|
|
8084
|
+
return 0;
|
|
8085
|
+
}
|
|
8086
|
+
const here = topFolder(cwd, store.root);
|
|
8087
|
+
out(`Learning pool of ${storeHome(store.root)}: ${!selection ? "every folder (new ones join)" : selection.only ? "only the folders marked in (new ones stay out)" : "every folder but those marked out (new ones join)"}
|
|
8088
|
+
`);
|
|
8089
|
+
if (!folders.length) out(" (no folders here yet)\n");
|
|
8090
|
+
for (const f of folders) out(` ${f.included ? "in " : "out"} ${f.folder}${f.source ? ` (${f.source})` : ""}${f.folder === here ? " \u2190 here" : ""}
|
|
8091
|
+
`);
|
|
8092
|
+
return 0;
|
|
8093
|
+
}
|
|
7718
8094
|
var str3, bool2, list;
|
|
7719
8095
|
var init_commands = __esm({
|
|
7720
8096
|
"src/v2/commands.ts"() {
|
|
@@ -7731,6 +8107,7 @@ var init_commands = __esm({
|
|
|
7731
8107
|
init_repo();
|
|
7732
8108
|
init_store2();
|
|
7733
8109
|
init_sync();
|
|
8110
|
+
init_source();
|
|
7734
8111
|
init_view();
|
|
7735
8112
|
str3 = (v) => typeof v === "string" ? v : void 0;
|
|
7736
8113
|
bool2 = (v) => v === true || v === "true";
|
|
@@ -7743,6 +8120,7 @@ var setup_exports = {};
|
|
|
7743
8120
|
__export(setup_exports, {
|
|
7744
8121
|
HOSTED_API: () => HOSTED_API,
|
|
7745
8122
|
apiAddress: () => apiAddress,
|
|
8123
|
+
askSources: () => askSources,
|
|
7746
8124
|
connectionProfile: () => connectionProfile,
|
|
7747
8125
|
executable: () => executable,
|
|
7748
8126
|
hookBinary: () => hookBinary,
|
|
@@ -7750,16 +8128,16 @@ __export(setup_exports, {
|
|
|
7750
8128
|
setupProject: () => setupProject,
|
|
7751
8129
|
shellQuote: () => shellQuote
|
|
7752
8130
|
});
|
|
7753
|
-
import
|
|
7754
|
-
import
|
|
7755
|
-
import { createHash as
|
|
8131
|
+
import fs23 from "node:fs";
|
|
8132
|
+
import path18 from "node:path";
|
|
8133
|
+
import { createHash as createHash6, randomBytes as randomBytes5 } from "node:crypto";
|
|
7756
8134
|
function executable(name) {
|
|
7757
|
-
const directories = [...(process.env.PATH ?? "").split(
|
|
8135
|
+
const directories = [...(process.env.PATH ?? "").split(path18.delimiter), path18.join(homeDir(), ".local/bin")];
|
|
7758
8136
|
for (const directory of directories) {
|
|
7759
|
-
const file =
|
|
8137
|
+
const file = path18.resolve(directory, name);
|
|
7760
8138
|
try {
|
|
7761
|
-
|
|
7762
|
-
if (
|
|
8139
|
+
fs23.accessSync(file, fs23.constants.X_OK);
|
|
8140
|
+
if (fs23.statSync(file).isFile()) return file;
|
|
7763
8141
|
} catch {
|
|
7764
8142
|
}
|
|
7765
8143
|
}
|
|
@@ -7767,8 +8145,8 @@ function executable(name) {
|
|
|
7767
8145
|
}
|
|
7768
8146
|
function hookBinary(harness) {
|
|
7769
8147
|
const agent = executable(harness === "claude-code" ? "claude" : harness);
|
|
7770
|
-
const directories = [.../* @__PURE__ */ new Set([
|
|
7771
|
-
return `PATH=${shellQuote(directories.join(
|
|
8148
|
+
const directories = [.../* @__PURE__ */ new Set([path18.dirname(process.execPath), path18.join(homeDir(), ".local/bin"), ...agent ? [path18.dirname(agent)] : []])];
|
|
8149
|
+
return `PATH=${shellQuote(directories.join(path18.delimiter))}:"$PATH" ${shellQuote(process.execPath)} ${shellQuote(fs23.realpathSync(process.argv[1]))}`;
|
|
7772
8150
|
}
|
|
7773
8151
|
function apiAddress(raw) {
|
|
7774
8152
|
const url = new URL(raw);
|
|
@@ -7837,33 +8215,37 @@ async function setupProject(opts) {
|
|
|
7837
8215
|
const apiKey = opts.apiKey.trim();
|
|
7838
8216
|
if (!apiKey || /\s/.test(apiKey)) throw new Error("Paste the complete agent key from your Bunshin workspace.");
|
|
7839
8217
|
const profile = await connectionProfile(apiUrl, apiKey);
|
|
7840
|
-
const root =
|
|
8218
|
+
const root = path18.join(opts.cwd, ".bunshin");
|
|
7841
8219
|
const store = new Store(root);
|
|
7842
8220
|
const previous = store.config();
|
|
7843
8221
|
if ((previous.orgId !== profile.orgId || previous.userId !== profile.userId) && (store.listLessons().length || store.allTraces().length || store.listRecallLessons().length)) {
|
|
7844
8222
|
throw new Error("This project has lessons or activity from another profile. Use a fresh project to connect this workspace.");
|
|
7845
8223
|
}
|
|
7846
|
-
const credentialDirectory =
|
|
7847
|
-
|
|
7848
|
-
|
|
7849
|
-
const name =
|
|
8224
|
+
const credentialDirectory = path18.join(homeDir(), ".config/bunshin/credentials");
|
|
8225
|
+
fs23.mkdirSync(credentialDirectory, { recursive: true, mode: 448 });
|
|
8226
|
+
fs23.chmodSync(credentialDirectory, 448);
|
|
8227
|
+
const name = createHash6("sha256").update(`${root}
|
|
7850
8228
|
${apiUrl}
|
|
7851
8229
|
${profile.orgId}
|
|
7852
8230
|
${profile.userId}`).digest("hex");
|
|
7853
|
-
const credentialFile =
|
|
8231
|
+
const credentialFile = path18.join(credentialDirectory, `${name}.json`);
|
|
7854
8232
|
const temporary = `${credentialFile}.${randomBytes5(4).toString("hex")}.tmp`;
|
|
7855
|
-
|
|
7856
|
-
|
|
8233
|
+
fs23.writeFileSync(temporary, JSON.stringify({ apiUrl, apiKey }) + "\n", { mode: 384, flag: "wx" });
|
|
8234
|
+
fs23.renameSync(temporary, credentialFile);
|
|
7857
8235
|
const result = runInit({
|
|
7858
8236
|
cwd: opts.cwd,
|
|
7859
8237
|
harness: opts.harness,
|
|
7860
8238
|
apiUrl,
|
|
7861
8239
|
...profile,
|
|
7862
8240
|
noHooks: opts.noHooks,
|
|
7863
|
-
bin: opts.bin ?? hookBinary(opts.harness)
|
|
8241
|
+
bin: opts.bin ?? hookBinary(opts.harness),
|
|
8242
|
+
...opts.hooks ? { hooks: opts.hooks } : {}
|
|
7864
8243
|
});
|
|
8244
|
+
const { sources: kept, ...base } = result.config;
|
|
8245
|
+
const sources = opts.sources === void 0 ? kept : opts.sources ?? void 0;
|
|
7865
8246
|
store.saveConfig({
|
|
7866
|
-
...
|
|
8247
|
+
...base,
|
|
8248
|
+
...sources ? { sources } : {},
|
|
7867
8249
|
...profile,
|
|
7868
8250
|
apiKey: void 0,
|
|
7869
8251
|
credentialFile,
|
|
@@ -7874,6 +8256,39 @@ ${profile.userId}`).digest("hex");
|
|
|
7874
8256
|
trustStore(root);
|
|
7875
8257
|
return { ...result, profile, credentialFile };
|
|
7876
8258
|
}
|
|
8259
|
+
async function askSources(store, out) {
|
|
8260
|
+
const { listFolders: listFolders2, select: select2 } = await Promise.resolve().then(() => (init_source(), source_exports));
|
|
8261
|
+
const config = store.config();
|
|
8262
|
+
const folders = listFolders2(store.root, void 0);
|
|
8263
|
+
if (folders.length < 2) return;
|
|
8264
|
+
const readline2 = await import("node:readline/promises");
|
|
8265
|
+
const rl = readline2.createInterface({ input: process.stdin, output: process.stderr });
|
|
8266
|
+
try {
|
|
8267
|
+
out(`This folder holds ${folders.length} folders:
|
|
8268
|
+
`);
|
|
8269
|
+
folders.forEach((f, i) => out(` ${String(i + 1).padStart(2)}. ${f.folder}
|
|
8270
|
+
`));
|
|
8271
|
+
const answer = (await rl.question("Learn from [a]ll of them, all [e]xcept some, or [o]nly some? [a] ")).trim().toLowerCase();
|
|
8272
|
+
const mode = answer.startsWith("e") || answer.startsWith("x") ? "exclude" : answer.startsWith("o") || answer.startsWith("p") ? "only" : "all";
|
|
8273
|
+
const { sources: _drop, ...rest } = config;
|
|
8274
|
+
void _drop;
|
|
8275
|
+
if (mode === "all") {
|
|
8276
|
+
store.saveConfig(rest);
|
|
8277
|
+
out("Learning from every folder here; new folders join automatically.\n");
|
|
8278
|
+
return;
|
|
8279
|
+
}
|
|
8280
|
+
const raw = await rl.question(mode === "exclude" ? "Folders to leave out (numbers or names, comma-separated): " : "Folders to learn from (numbers or names, comma-separated): ");
|
|
8281
|
+
const picked = [...new Set(raw.split(/[,\s]+/).map((x) => x.trim()).filter(Boolean).map((x) => /^\d+$/.test(x) ? folders[Number(x) - 1]?.folder : folders.find((f) => f.folder === x)?.folder).filter((x) => !!x))];
|
|
8282
|
+
const next = select2(void 0, mode, picked);
|
|
8283
|
+
store.saveConfig(next ? { ...rest, sources: next } : rest);
|
|
8284
|
+
if (mode === "exclude") out(picked.length ? `Learning from every folder but ${picked.join(", ")}; new folders join automatically.
|
|
8285
|
+
` : "Learning from every folder here; new folders join automatically.\n");
|
|
8286
|
+
else out(picked.length ? `Learning from ${picked.join(", ")} only; new folders stay out until you add them (bunshin sources include <folder>).
|
|
8287
|
+
` : "Nothing here takes part until you add a folder (bunshin sources include <folder>).\n");
|
|
8288
|
+
} finally {
|
|
8289
|
+
rl.close();
|
|
8290
|
+
}
|
|
8291
|
+
}
|
|
7877
8292
|
var shellQuote;
|
|
7878
8293
|
var init_setup = __esm({
|
|
7879
8294
|
"src/setup.ts"() {
|
|
@@ -7924,6 +8339,7 @@ async function handleMcp(msg, opts) {
|
|
|
7924
8339
|
const cwd = opts.cwd ?? process.cwd();
|
|
7925
8340
|
const store = opts.store !== void 0 ? opts.store : Store.discover(cwd);
|
|
7926
8341
|
if (!store) return reply(text("Bunshin is not set up in this project (run `bunshin setup`). Proceed with general knowledge.", true));
|
|
8342
|
+
if (!cwdInPool(store.config(), cwd, store.root)) return reply(text("This folder is not part of Bunshin's learning pool (`bunshin sources`). Proceed with general knowledge.", true));
|
|
7927
8343
|
try {
|
|
7928
8344
|
if (name === "bunshin_recall") {
|
|
7929
8345
|
const taskAbstract = typeof args.task_abstract === "string" ? args.task_abstract : "";
|
|
@@ -7987,6 +8403,7 @@ var init_mcp = __esm({
|
|
|
7987
8403
|
"src/v2/mcp.ts"() {
|
|
7988
8404
|
"use strict";
|
|
7989
8405
|
init_v22();
|
|
8406
|
+
init_source();
|
|
7990
8407
|
init_store();
|
|
7991
8408
|
init_library();
|
|
7992
8409
|
DEFAULT_PROTOCOL = "2025-06-18";
|
|
@@ -8164,9 +8581,9 @@ __export(sync_exports2, {
|
|
|
8164
8581
|
resolveConflict: () => resolveConflict,
|
|
8165
8582
|
runSync: () => runSync
|
|
8166
8583
|
});
|
|
8167
|
-
import { createHash as
|
|
8168
|
-
import
|
|
8169
|
-
import
|
|
8584
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
8585
|
+
import path19 from "node:path";
|
|
8586
|
+
import fs24 from "node:fs";
|
|
8170
8587
|
async function api(config, method, route, body, timeoutMs = 1e4) {
|
|
8171
8588
|
const ctrl = new AbortController();
|
|
8172
8589
|
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
@@ -8204,22 +8621,22 @@ async function mothershipLookup(config, ids, timeoutMs = 8e3) {
|
|
|
8204
8621
|
}
|
|
8205
8622
|
}
|
|
8206
8623
|
async function runSync(store) {
|
|
8207
|
-
const lock =
|
|
8624
|
+
const lock = path19.join(store.root, "sync.lock");
|
|
8208
8625
|
const deadline = Date.now() + 12e4;
|
|
8209
8626
|
for (; ; ) {
|
|
8210
8627
|
try {
|
|
8211
|
-
|
|
8628
|
+
fs24.writeFileSync(lock, String(process.pid), { flag: "wx", mode: 384 });
|
|
8212
8629
|
break;
|
|
8213
8630
|
} catch (error) {
|
|
8214
8631
|
if (error.code !== "EEXIST") throw error;
|
|
8215
8632
|
try {
|
|
8216
|
-
const owner = Number(
|
|
8633
|
+
const owner = Number(fs24.readFileSync(lock, "utf8"));
|
|
8217
8634
|
if (Number.isInteger(owner) && owner > 0) {
|
|
8218
8635
|
try {
|
|
8219
8636
|
process.kill(owner, 0);
|
|
8220
8637
|
} catch (error2) {
|
|
8221
8638
|
if (error2.code === "ESRCH") {
|
|
8222
|
-
|
|
8639
|
+
fs24.unlinkSync(lock);
|
|
8223
8640
|
continue;
|
|
8224
8641
|
}
|
|
8225
8642
|
}
|
|
@@ -8238,7 +8655,7 @@ async function runSync(store) {
|
|
|
8238
8655
|
return await syncStore(store);
|
|
8239
8656
|
} finally {
|
|
8240
8657
|
try {
|
|
8241
|
-
|
|
8658
|
+
fs24.unlinkSync(lock);
|
|
8242
8659
|
} catch {
|
|
8243
8660
|
}
|
|
8244
8661
|
}
|
|
@@ -8266,9 +8683,9 @@ async function syncStore(store) {
|
|
|
8266
8683
|
result.errors.push(String(e instanceof Error ? e.message : e));
|
|
8267
8684
|
}
|
|
8268
8685
|
}
|
|
8269
|
-
const acknowledgementsFile =
|
|
8686
|
+
const acknowledgementsFile = path19.join(store.root, "synced-traces.json");
|
|
8270
8687
|
const acknowledgements = readJson(acknowledgementsFile, {});
|
|
8271
|
-
const fingerprint2 = (trace) =>
|
|
8688
|
+
const fingerprint2 = (trace) => createHash7("sha256").update(JSON.stringify(trace)).digest("hex");
|
|
8272
8689
|
const traces = store.allTraces().filter((t) => acknowledgements[t.id] !== fingerprint2(t)).slice(0, 50);
|
|
8273
8690
|
for (const t of traces) {
|
|
8274
8691
|
try {
|
|
@@ -8521,11 +8938,11 @@ __export(demo_exports, {
|
|
|
8521
8938
|
runDemo: () => runDemo
|
|
8522
8939
|
});
|
|
8523
8940
|
import assert from "node:assert/strict";
|
|
8524
|
-
import
|
|
8941
|
+
import fs25 from "node:fs";
|
|
8525
8942
|
import os4 from "node:os";
|
|
8526
|
-
import
|
|
8943
|
+
import path20 from "node:path";
|
|
8527
8944
|
async function runDemo(write) {
|
|
8528
|
-
const root =
|
|
8945
|
+
const root = fs25.mkdtempSync(path20.join(os4.tmpdir(), "bunshin-demo-"));
|
|
8529
8946
|
try {
|
|
8530
8947
|
const store = Store.init(root, { ...APR_ENGINEER, apiUrl: "" });
|
|
8531
8948
|
const { lessons, constraint, workflow } = aprScenario();
|
|
@@ -8542,7 +8959,7 @@ Task: ${APR_TASK}
|
|
|
8542
8959
|
write(result.text);
|
|
8543
8960
|
write("\nPASS: Collections constraint and workflow recalled; C-suite, other-company, and superseded lessons excluded.\n");
|
|
8544
8961
|
} finally {
|
|
8545
|
-
|
|
8962
|
+
fs25.rmSync(root, { recursive: true, force: true });
|
|
8546
8963
|
}
|
|
8547
8964
|
}
|
|
8548
8965
|
var init_demo = __esm({
|
|
@@ -8555,21 +8972,21 @@ var init_demo = __esm({
|
|
|
8555
8972
|
});
|
|
8556
8973
|
|
|
8557
8974
|
// src/v2/gitremote.ts
|
|
8558
|
-
import
|
|
8559
|
-
import
|
|
8560
|
-
import { createHash as
|
|
8975
|
+
import fs26 from "node:fs";
|
|
8976
|
+
import path21 from "node:path";
|
|
8977
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
8561
8978
|
function saveGitCredential(apiUrl, apiKey) {
|
|
8562
8979
|
const file = gitCredentialFile();
|
|
8563
|
-
|
|
8980
|
+
fs26.mkdirSync(path21.dirname(file), { recursive: true, mode: 448 });
|
|
8564
8981
|
let all = {};
|
|
8565
8982
|
try {
|
|
8566
|
-
all = JSON.parse(
|
|
8983
|
+
all = JSON.parse(fs26.readFileSync(file, "utf8"));
|
|
8567
8984
|
} catch {
|
|
8568
8985
|
}
|
|
8569
8986
|
all[apiUrl.replace(/\/$/, "")] = apiKey;
|
|
8570
8987
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
8571
|
-
|
|
8572
|
-
|
|
8988
|
+
fs26.writeFileSync(tmp, JSON.stringify(all, null, 2) + "\n", { mode: 384 });
|
|
8989
|
+
fs26.renameSync(tmp, file);
|
|
8573
8990
|
}
|
|
8574
8991
|
function resolveGitKey(apiUrl) {
|
|
8575
8992
|
const base = apiUrl.replace(/\/$/, "");
|
|
@@ -8577,20 +8994,20 @@ function resolveGitKey(apiUrl) {
|
|
|
8577
8994
|
const origin = urlOrigin(base);
|
|
8578
8995
|
if (process.env.BUNSHIN_API_KEY && origin && trustedOrigins().has(origin)) return process.env.BUNSHIN_API_KEY;
|
|
8579
8996
|
try {
|
|
8580
|
-
const all = JSON.parse(
|
|
8997
|
+
const all = JSON.parse(fs26.readFileSync(gitCredentialFile(), "utf8"));
|
|
8581
8998
|
if (all[base]) return all[base];
|
|
8582
8999
|
} catch {
|
|
8583
9000
|
}
|
|
8584
|
-
const dir =
|
|
9001
|
+
const dir = path21.join(homeDir(), ".config/bunshin/credentials");
|
|
8585
9002
|
let files = [];
|
|
8586
9003
|
try {
|
|
8587
|
-
files =
|
|
9004
|
+
files = fs26.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
8588
9005
|
} catch {
|
|
8589
9006
|
return void 0;
|
|
8590
9007
|
}
|
|
8591
9008
|
for (const f of files.sort()) {
|
|
8592
9009
|
try {
|
|
8593
|
-
const c = JSON.parse(
|
|
9010
|
+
const c = JSON.parse(fs26.readFileSync(path21.join(dir, f), "utf8"));
|
|
8594
9011
|
if (c.apiKey && c.apiUrl?.replace(/\/$/, "") === base) return c.apiKey;
|
|
8595
9012
|
} catch {
|
|
8596
9013
|
}
|
|
@@ -8598,7 +9015,7 @@ function resolveGitKey(apiUrl) {
|
|
|
8598
9015
|
return void 0;
|
|
8599
9016
|
}
|
|
8600
9017
|
function refsDigest(refs) {
|
|
8601
|
-
return
|
|
9018
|
+
return createHash8("sha256").update(
|
|
8602
9019
|
[...refs].sort((a, b) => a.ref.localeCompare(b.ref)).map((r) => `${r.sha} ${r.ref}`).join("\n")
|
|
8603
9020
|
).digest("hex").slice(0, 12);
|
|
8604
9021
|
}
|
|
@@ -8611,7 +9028,7 @@ var init_gitremote = __esm({
|
|
|
8611
9028
|
ZERO_SHA = "0".repeat(40);
|
|
8612
9029
|
REMOTE_PATH = "/v1/git/repos/";
|
|
8613
9030
|
remoteUrl = (apiUrl, repo) => `bunshin::${apiUrl.replace(/\/$/, "")}${REMOTE_PATH}${repo}`;
|
|
8614
|
-
gitCredentialFile = () =>
|
|
9031
|
+
gitCredentialFile = () => path21.join(homeDir(), ".config/bunshin/git-credentials.json");
|
|
8615
9032
|
GitApiError = class extends Error {
|
|
8616
9033
|
constructor(status, message, body = null) {
|
|
8617
9034
|
super(message);
|
|
@@ -8703,19 +9120,19 @@ __export(gitrepo_exports, {
|
|
|
8703
9120
|
githubReferences: () => githubReferences,
|
|
8704
9121
|
repoFromUrl: () => repoFromUrl
|
|
8705
9122
|
});
|
|
8706
|
-
import
|
|
8707
|
-
import
|
|
9123
|
+
import fs27 from "node:fs";
|
|
9124
|
+
import path22 from "node:path";
|
|
8708
9125
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
8709
|
-
function
|
|
9126
|
+
function git3(cwd, args, opts = {}) {
|
|
8710
9127
|
const r = spawnSync2("git", ["-C", cwd, ...args], { encoding: "utf8", stdio: opts.inherit ? ["ignore", "inherit", "inherit"] : ["ignore", "pipe", "pipe"], maxBuffer: 256 * 1024 * 1024 });
|
|
8711
9128
|
const res = { ok: r.status === 0, out: r.stdout ?? "", err: r.stderr ?? "" };
|
|
8712
9129
|
if (!res.ok && !opts.allowFail) throw new Error(`git ${args.join(" ")} failed: ${(res.err || res.out).trim()}`);
|
|
8713
9130
|
return res;
|
|
8714
9131
|
}
|
|
8715
9132
|
function onPath(name) {
|
|
8716
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
9133
|
+
for (const dir of (process.env.PATH ?? "").split(path22.delimiter)) {
|
|
8717
9134
|
try {
|
|
8718
|
-
|
|
9135
|
+
fs27.accessSync(path22.join(dir, name), fs27.constants.X_OK);
|
|
8719
9136
|
return true;
|
|
8720
9137
|
} catch {
|
|
8721
9138
|
}
|
|
@@ -8767,7 +9184,7 @@ async function connect(args, cwd, prompt = true, reuseKey = true) {
|
|
|
8767
9184
|
return client;
|
|
8768
9185
|
}
|
|
8769
9186
|
function toplevel(dir) {
|
|
8770
|
-
const r =
|
|
9187
|
+
const r = git3(dir, ["rev-parse", "--show-toplevel"], { allowFail: true });
|
|
8771
9188
|
if (!r.ok) throw new Error(`${dir} is not inside a git repository`);
|
|
8772
9189
|
return r.out.trim();
|
|
8773
9190
|
}
|
|
@@ -8775,7 +9192,7 @@ function githubReferences(root, repo) {
|
|
|
8775
9192
|
const [owner, name] = repo.split("/");
|
|
8776
9193
|
const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8777
9194
|
const pattern = `github\\.com[:/]${esc(owner)}/${esc(name)}|--repo[ =]${esc(owner)}/${esc(name)}|gh repo clone ${esc(owner)}/${esc(name)}`;
|
|
8778
|
-
const r =
|
|
9195
|
+
const r = git3(root, ["grep", "-n", "-I", "-E", pattern, "HEAD", "--"], { allowFail: true });
|
|
8779
9196
|
return r.out.split("\n").filter(Boolean).map((l) => l.replace(/^HEAD:/, ""));
|
|
8780
9197
|
}
|
|
8781
9198
|
function switchOrigin(root, url, out) {
|
|
@@ -8784,41 +9201,41 @@ function switchOrigin(root, url, out) {
|
|
|
8784
9201
|
let keptAs = null;
|
|
8785
9202
|
if (old && old !== url) {
|
|
8786
9203
|
keptAs = rs.has("github") ? `github-${Date.now()}` : "github";
|
|
8787
|
-
|
|
9204
|
+
git3(root, ["remote", "rename", "origin", keptAs]);
|
|
8788
9205
|
}
|
|
8789
|
-
if (rs.has("bunshin") && rs.get("bunshin") === url)
|
|
8790
|
-
else if (old !== url)
|
|
8791
|
-
|
|
8792
|
-
|
|
9206
|
+
if (rs.has("bunshin") && rs.get("bunshin") === url) git3(root, ["remote", "rename", "bunshin", "origin"]);
|
|
9207
|
+
else if (old !== url) git3(root, ["remote", "add", "origin", url]);
|
|
9208
|
+
git3(root, ["fetch", "--quiet", "--tags", "origin"]);
|
|
9209
|
+
git3(root, ["remote", "set-head", "origin", "--auto"], { allowFail: true });
|
|
8793
9210
|
const originRefs = refMap(root, "refs/remotes/origin");
|
|
8794
|
-
for (const b of
|
|
8795
|
-
if (originRefs.has(`refs/remotes/origin/${b}`))
|
|
9211
|
+
for (const b of git3(root, ["for-each-ref", "--format=%(refname:short)", "refs/heads"]).out.split("\n").filter(Boolean)) {
|
|
9212
|
+
if (originRefs.has(`refs/remotes/origin/${b}`)) git3(root, ["branch", `--set-upstream-to=origin/${b}`, b], { allowFail: true });
|
|
8796
9213
|
}
|
|
8797
9214
|
out(`origin \u2192 ${url}${keptAs ? ` (the old origin is now the remote "${keptAs}")` : ""}
|
|
8798
9215
|
`);
|
|
8799
9216
|
}
|
|
8800
9217
|
async function cmdImport(args, cwd, out, err) {
|
|
8801
9218
|
requireHelper();
|
|
8802
|
-
const root = toplevel(
|
|
8803
|
-
if (
|
|
9219
|
+
const root = toplevel(path22.resolve(str4(args.flags.path) ?? args._[1] ?? cwd));
|
|
9220
|
+
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");
|
|
8804
9221
|
const rs = remotes(root);
|
|
8805
9222
|
const originUrl = rs.get("origin");
|
|
8806
|
-
const name = str4(args.flags.name) ?? (originUrl && !originUrl.startsWith("bunshin::") ? repoFromUrl(originUrl) : null) ?? `${
|
|
9223
|
+
const name = str4(args.flags.name) ?? (originUrl && !originUrl.startsWith("bunshin::") ? repoFromUrl(originUrl) : null) ?? `${path22.basename(path22.dirname(root))}/${path22.basename(root)}`;
|
|
8807
9224
|
const client = await connect(args, cwd);
|
|
8808
9225
|
const url = remoteUrl(client.apiUrl, name);
|
|
8809
9226
|
const fromOrigin = !!originUrl && !originUrl.startsWith("bunshin::") && !bool3(args.flags.local);
|
|
8810
9227
|
if (fromOrigin) {
|
|
8811
9228
|
out(`Fetching ${originUrl} \u2026
|
|
8812
9229
|
`);
|
|
8813
|
-
const f =
|
|
9230
|
+
const f = git3(root, ["fetch", "--quiet", "--prune", "--tags", "origin"], { allowFail: true });
|
|
8814
9231
|
if (!f.ok) err(`! could not fetch origin (${f.err.trim().split("\n")[0]}); importing what this clone already has from it
|
|
8815
9232
|
`);
|
|
8816
9233
|
}
|
|
8817
9234
|
const branches = fromOrigin ? [...refMap(root, "refs/remotes/origin").entries()].filter(([ref]) => ref !== "refs/remotes/origin/HEAD").map(([ref, sha2]) => ({ src: ref, dst: ref.replace("refs/remotes/origin/", "refs/heads/"), sha: sha2 })) : [...refMap(root, "refs/heads").entries()].map(([ref, sha2]) => ({ src: ref, dst: ref, sha: sha2 }));
|
|
8818
9235
|
const tags = [...refMap(root, "refs/tags").entries()].map(([ref, sha2]) => ({ src: ref, dst: ref, sha: sha2 }));
|
|
8819
9236
|
if (!branches.length) throw new Error("nothing to import: no branches found");
|
|
8820
|
-
const originHead =
|
|
8821
|
-
const current =
|
|
9237
|
+
const originHead = git3(root, ["symbolic-ref", "--quiet", "refs/remotes/origin/HEAD"], { allowFail: true }).out.trim().replace("refs/remotes/origin/", "");
|
|
9238
|
+
const current = git3(root, ["symbolic-ref", "--quiet", "--short", "HEAD"], { allowFail: true }).out.trim();
|
|
8822
9239
|
const defaultBranch = str4(args.flags["default-branch"]) ?? (fromOrigin && originHead ? originHead : current || "main");
|
|
8823
9240
|
if (!branches.some((b) => b.dst === `refs/heads/${defaultBranch}`)) throw new Error(`default branch ${defaultBranch} is not among the imported branches`);
|
|
8824
9241
|
if (fromOrigin) {
|
|
@@ -8828,7 +9245,7 @@ async function cmdImport(args, cwd, out, err) {
|
|
|
8828
9245
|
if (!remote) err(`! local branch ${b} is not on the origin and was not imported (push it to Bunshin afterwards if you need it)
|
|
8829
9246
|
`);
|
|
8830
9247
|
else if (remote !== sha2) {
|
|
8831
|
-
const ahead =
|
|
9248
|
+
const ahead = git3(root, ["rev-list", "--count", `${remote}..${sha2}`], { allowFail: true }).out.trim();
|
|
8832
9249
|
if (ahead && ahead !== "0") err(`! ${b} has ${ahead} local commit${ahead === "1" ? "" : "s"} not on the origin; after the switch, \`git push\` sends them to Bunshin
|
|
8833
9250
|
`);
|
|
8834
9251
|
}
|
|
@@ -8851,13 +9268,13 @@ async function cmdImport(args, cwd, out, err) {
|
|
|
8851
9268
|
out(`${name} exists and is empty; importing into it
|
|
8852
9269
|
`);
|
|
8853
9270
|
}
|
|
8854
|
-
if (rs.has("bunshin"))
|
|
8855
|
-
else
|
|
9271
|
+
if (rs.has("bunshin")) git3(root, ["remote", "set-url", "bunshin", url]);
|
|
9272
|
+
else git3(root, ["remote", "add", "bunshin", url]);
|
|
8856
9273
|
const specs = [...branches, ...tags].map((r) => `${r.src}:${r.dst}`);
|
|
8857
|
-
const commits =
|
|
9274
|
+
const commits = git3(root, ["rev-list", "--count", "--all"]).out.trim();
|
|
8858
9275
|
out(`Pushing ${branches.length} branch${branches.length === 1 ? "" : "es"}, ${tags.length} tag${tags.length === 1 ? "" : "s"} (${commits} commits) \u2026
|
|
8859
9276
|
`);
|
|
8860
|
-
const push2 =
|
|
9277
|
+
const push2 = git3(root, ["push", "--quiet", "bunshin", ...specs], { allowFail: true });
|
|
8861
9278
|
if (!push2.ok) throw new Error(`push to Bunshin failed: ${push2.err.trim()}`);
|
|
8862
9279
|
const info = await client.info(name);
|
|
8863
9280
|
const want = [...branches, ...tags].map((r) => ({ ref: r.dst, sha: r.sha }));
|
|
@@ -8879,7 +9296,7 @@ ${refsToGithub.length} line${refsToGithub.length === 1 ? "" : "s"} in the reposi
|
|
|
8879
9296
|
if (refsToGithub.length > 40) out(` \u2026 and ${refsToGithub.length - 40} more (git grep for ${name})
|
|
8880
9297
|
`);
|
|
8881
9298
|
}
|
|
8882
|
-
if (
|
|
9299
|
+
if (fs27.existsSync(path22.join(root, ".github"))) out(`
|
|
8883
9300
|
Note: .github/ (Actions, CODEOWNERS, templates) only works on GitHub; Bunshin does not run it.
|
|
8884
9301
|
`);
|
|
8885
9302
|
out(`
|
|
@@ -8890,7 +9307,7 @@ New machines clone with: bunshin repo clone ${name}
|
|
|
8890
9307
|
}
|
|
8891
9308
|
async function cmdAttach(args, cwd, out, err) {
|
|
8892
9309
|
requireHelper();
|
|
8893
|
-
const root = toplevel(
|
|
9310
|
+
const root = toplevel(path22.resolve(str4(args.flags.path) ?? cwd));
|
|
8894
9311
|
const rs = remotes(root);
|
|
8895
9312
|
const originUrl = rs.get("origin");
|
|
8896
9313
|
const name = str4(args.flags.name) ?? args._[1] ?? (originUrl ? repoFromUrl(originUrl) : null);
|
|
@@ -8904,9 +9321,9 @@ async function cmdAttach(args, cwd, out, err) {
|
|
|
8904
9321
|
}
|
|
8905
9322
|
const info = await client.info(name);
|
|
8906
9323
|
if (!info.refs.length) throw new Error(`${name} in Bunshin is empty; import it first (bunshin repo import)`);
|
|
8907
|
-
if (rs.has("bunshin"))
|
|
8908
|
-
else
|
|
8909
|
-
|
|
9324
|
+
if (rs.has("bunshin")) git3(root, ["remote", "set-url", "bunshin", url]);
|
|
9325
|
+
else git3(root, ["remote", "add", "bunshin", url]);
|
|
9326
|
+
git3(root, ["fetch", "--quiet", "--tags", "bunshin"]);
|
|
8910
9327
|
if (originUrl) {
|
|
8911
9328
|
const behind = [];
|
|
8912
9329
|
for (const [ref, sha2] of refMap(root, "refs/remotes/origin")) {
|
|
@@ -8914,7 +9331,7 @@ async function cmdAttach(args, cwd, out, err) {
|
|
|
8914
9331
|
const b = ref.replace("refs/remotes/origin/", "");
|
|
8915
9332
|
const theirs = info.refs.find((r) => r.ref === `refs/heads/${b}`)?.sha;
|
|
8916
9333
|
if (!theirs) behind.push(`${b} (missing in Bunshin)`);
|
|
8917
|
-
else if (theirs !== sha2 && !
|
|
9334
|
+
else if (theirs !== sha2 && !git3(root, ["merge-base", "--is-ancestor", sha2, theirs], { allowFail: true }).ok) behind.push(`${b} (the old origin has commits Bunshin lacks)`);
|
|
8918
9335
|
}
|
|
8919
9336
|
if (behind.length && !bool3(args.flags.force)) {
|
|
8920
9337
|
err(`The old origin has work that is not in Bunshin:
|
|
@@ -8925,7 +9342,7 @@ Push it to Bunshin first (git push bunshin <branch>), or re-run with --force.
|
|
|
8925
9342
|
}
|
|
8926
9343
|
}
|
|
8927
9344
|
switchOrigin(root, url, out);
|
|
8928
|
-
const status =
|
|
9345
|
+
const status = git3(root, ["status", "-sb"]).out.split("\n")[0];
|
|
8929
9346
|
out(`${status}
|
|
8930
9347
|
`);
|
|
8931
9348
|
return 0;
|
|
@@ -8938,12 +9355,12 @@ async function cmdClone(args, cwd, out) {
|
|
|
8938
9355
|
const dir = args._[2] ?? name.split("/")[1];
|
|
8939
9356
|
const r = spawnSync2("git", ["clone", remoteUrl(client.apiUrl, name), dir], { cwd, stdio: "inherit" });
|
|
8940
9357
|
if (r.status !== 0) return r.status ?? 1;
|
|
8941
|
-
out(`Cloned ${name} into ${
|
|
9358
|
+
out(`Cloned ${name} into ${path22.resolve(cwd, dir)}
|
|
8942
9359
|
`);
|
|
8943
9360
|
return 0;
|
|
8944
9361
|
}
|
|
8945
9362
|
async function cmdVerify(args, cwd, out, err) {
|
|
8946
|
-
const root = toplevel(
|
|
9363
|
+
const root = toplevel(path22.resolve(str4(args.flags.path) ?? cwd));
|
|
8947
9364
|
const rs = remotes(root);
|
|
8948
9365
|
const originUrl = rs.get("origin") ?? "";
|
|
8949
9366
|
if (!originUrl.startsWith("bunshin::")) throw new Error("origin is not a Bunshin repository (run bunshin repo attach)");
|
|
@@ -8964,7 +9381,7 @@ async function cmdVerify(args, cwd, out, err) {
|
|
|
8964
9381
|
`);
|
|
8965
9382
|
}
|
|
8966
9383
|
}
|
|
8967
|
-
const fsck =
|
|
9384
|
+
const fsck = git3(root, ["fsck", "--connectivity-only", "--no-dangling"], { allowFail: true });
|
|
8968
9385
|
if (!fsck.ok) {
|
|
8969
9386
|
problems++;
|
|
8970
9387
|
err(`! git fsck: ${fsck.err.trim().split("\n")[0]}
|
|
@@ -9054,12 +9471,12 @@ var init_gitrepo = __esm({
|
|
|
9054
9471
|
bool3 = (v) => v === true || v === "true";
|
|
9055
9472
|
remotes = (root) => {
|
|
9056
9473
|
const m = /* @__PURE__ */ new Map();
|
|
9057
|
-
for (const name of
|
|
9474
|
+
for (const name of git3(root, ["remote"]).out.split("\n").filter(Boolean)) m.set(name, git3(root, ["remote", "get-url", name]).out.trim());
|
|
9058
9475
|
return m;
|
|
9059
9476
|
};
|
|
9060
9477
|
refMap = (root, prefix) => {
|
|
9061
9478
|
const m = /* @__PURE__ */ new Map();
|
|
9062
|
-
for (const line of
|
|
9479
|
+
for (const line of git3(root, ["for-each-ref", "--format=%(objectname) %(refname)", prefix]).out.split("\n").filter(Boolean)) {
|
|
9063
9480
|
const [sha2, ref] = line.split(" ");
|
|
9064
9481
|
m.set(ref, sha2);
|
|
9065
9482
|
}
|
|
@@ -9106,22 +9523,22 @@ __export(runner_exports, {
|
|
|
9106
9523
|
setupText: () => setupText
|
|
9107
9524
|
});
|
|
9108
9525
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
9109
|
-
import
|
|
9526
|
+
import fs28 from "node:fs";
|
|
9110
9527
|
import os5 from "node:os";
|
|
9111
|
-
import
|
|
9528
|
+
import path23 from "node:path";
|
|
9112
9529
|
function runnerHome(home = process.env.BUNSHIN_RUNNER_HOME || os5.homedir()) {
|
|
9113
|
-
return
|
|
9530
|
+
return path23.join(home, ".bunshin", "runner");
|
|
9114
9531
|
}
|
|
9115
9532
|
function readInstalled(home) {
|
|
9116
9533
|
try {
|
|
9117
|
-
return JSON.parse(
|
|
9534
|
+
return JSON.parse(fs28.readFileSync(path23.join(runnerHome(home), "config.json"), "utf8"));
|
|
9118
9535
|
} catch {
|
|
9119
9536
|
return null;
|
|
9120
9537
|
}
|
|
9121
9538
|
}
|
|
9122
9539
|
function connection(args, cwd) {
|
|
9123
9540
|
const saved = readInstalled();
|
|
9124
|
-
const store = saved &&
|
|
9541
|
+
const store = saved && path23.resolve(cwd).startsWith(runnerHome()) ? null : Store.discover(cwd);
|
|
9125
9542
|
const config = store?.config();
|
|
9126
9543
|
const key = str5(args.flags.key) || process.env.BUNSHIN_RUNNER_KEY || process.env.BUNSHIN_API_KEY || config?.apiKey || saved?.key || "";
|
|
9127
9544
|
const apiUrl = (str5(args.flags["api-url"]) || process.env.BUNSHIN_API_URL || config?.apiUrl || saved?.apiUrl || DEFAULT_API).replace(/\/$/, "");
|
|
@@ -9181,9 +9598,9 @@ async function runOnce(c, args, out, err) {
|
|
|
9181
9598
|
const [job] = await lease(c, args);
|
|
9182
9599
|
if (!job) break;
|
|
9183
9600
|
const started = Date.now();
|
|
9184
|
-
const scratch =
|
|
9601
|
+
const scratch = fs28.mkdtempSync(path23.join(os5.tmpdir(), "bunshin-job-"));
|
|
9185
9602
|
const res = await askJson(config, { system: job.system, user: job.prompt, schema: job.schema, enforceSchema: false, timeoutMs: 15 * 6e4, maxTokens: 16e3, cwd: scratch }).finally(
|
|
9186
|
-
() =>
|
|
9603
|
+
() => fs28.rmSync(scratch, { recursive: true, force: true })
|
|
9187
9604
|
);
|
|
9188
9605
|
const model = config.backend === "codex-cli" ? config.codexModel ?? "codex default" : config.backend === "pi-cli" ? config.piModel ?? "pi default" : config.model;
|
|
9189
9606
|
if (!res.ok) {
|
|
@@ -9273,24 +9690,24 @@ function renderCheck(id, c) {
|
|
|
9273
9690
|
}
|
|
9274
9691
|
function logAnswer(entry) {
|
|
9275
9692
|
try {
|
|
9276
|
-
|
|
9277
|
-
|
|
9693
|
+
fs28.mkdirSync(runnerHome(), { recursive: true });
|
|
9694
|
+
fs28.appendFileSync(path23.join(runnerHome(), "log.jsonl"), `${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), ...entry })}
|
|
9278
9695
|
`);
|
|
9279
9696
|
} catch {
|
|
9280
9697
|
}
|
|
9281
9698
|
}
|
|
9282
9699
|
function which(name) {
|
|
9283
|
-
for (const d of [...(process.env.PATH || "").split(
|
|
9284
|
-
if (d &&
|
|
9700
|
+
for (const d of [...(process.env.PATH || "").split(path23.delimiter), path23.join(os5.homedir(), ".local", "bin")]) {
|
|
9701
|
+
if (d && fs28.existsSync(path23.join(d, name))) return path23.join(d, name);
|
|
9285
9702
|
}
|
|
9286
9703
|
return null;
|
|
9287
9704
|
}
|
|
9288
9705
|
function scheduledCommand(backend) {
|
|
9289
|
-
const node =
|
|
9290
|
-
const npx =
|
|
9706
|
+
const node = path23.dirname(process.execPath);
|
|
9707
|
+
const npx = fs28.existsSync(path23.join(node, "npx")) ? path23.join(node, "npx") : which("npx") ?? "npx";
|
|
9291
9708
|
const bin = BIN_OF[backend] ? which(BIN_OF[backend]) : null;
|
|
9292
|
-
const dirs = [.../* @__PURE__ */ new Set([node, ...bin ? [
|
|
9293
|
-
return { argv: [npx, "-y", PKG2, "runner", "--once"], path: dirs.join(
|
|
9709
|
+
const dirs = [.../* @__PURE__ */ new Set([node, ...bin ? [path23.dirname(bin)] : [], path23.join(os5.homedir(), ".local", "bin"), "/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin", "/usr/sbin", "/sbin"])];
|
|
9710
|
+
return { argv: [npx, "-y", PKG2, "runner", "--once"], path: dirs.join(path23.delimiter) };
|
|
9294
9711
|
}
|
|
9295
9712
|
function launchdPlist(backend, everyMinutes, home = runnerHome()) {
|
|
9296
9713
|
const cmd = scheduledCommand(backend);
|
|
@@ -9305,8 +9722,8 @@ function launchdPlist(backend, everyMinutes, home = runnerHome()) {
|
|
|
9305
9722
|
<dict><key>PATH</key><string>${xml(cmd.path)}</string></dict>
|
|
9306
9723
|
<key>WorkingDirectory</key><string>${xml(home)}</string>
|
|
9307
9724
|
<key>StartInterval</key><integer>${everyMinutes * 60}</integer>
|
|
9308
|
-
<key>StandardOutPath</key><string>${xml(
|
|
9309
|
-
<key>StandardErrorPath</key><string>${xml(
|
|
9725
|
+
<key>StandardOutPath</key><string>${xml(path23.join(home, "runner.log"))}</string>
|
|
9726
|
+
<key>StandardErrorPath</key><string>${xml(path23.join(home, "runner.log"))}</string>
|
|
9310
9727
|
<key>ProcessType</key><string>Background</string>
|
|
9311
9728
|
<key>LowPriorityIO</key><true/>
|
|
9312
9729
|
</dict>
|
|
@@ -9317,19 +9734,19 @@ function cronLine(backend, everyMinutes, home = runnerHome()) {
|
|
|
9317
9734
|
const cmd = scheduledCommand(backend);
|
|
9318
9735
|
const when = everyMinutes >= 60 ? `17 */${Math.max(1, Math.round(everyMinutes / 60))} * * *` : `*/${everyMinutes} * * * *`;
|
|
9319
9736
|
const q = (v) => `'${v.replace(/'/g, `'\\''`)}'`;
|
|
9320
|
-
return `${when} cd ${q(home)} && PATH=${q(cmd.path)} ${cmd.argv.map(q).join(" ")} >> ${q(
|
|
9737
|
+
return `${when} cd ${q(home)} && PATH=${q(cmd.path)} ${cmd.argv.map(q).join(" ")} >> ${q(path23.join(home, "runner.log"))} 2>&1 ${CRON_MARK}`;
|
|
9321
9738
|
}
|
|
9322
9739
|
function launchAgentsDir(opts) {
|
|
9323
|
-
return opts.launchAgents ??
|
|
9740
|
+
return opts.launchAgents ?? path23.join(process.env.BUNSHIN_RUNNER_HOME || os5.homedir(), "Library", "LaunchAgents");
|
|
9324
9741
|
}
|
|
9325
9742
|
function installSchedule(backend, everyMinutes, opts = {}) {
|
|
9326
9743
|
const platform = opts.platform ?? process.platform;
|
|
9327
9744
|
const run2 = opts.run ?? realRun;
|
|
9328
9745
|
if (platform === "darwin") {
|
|
9329
9746
|
const dir = launchAgentsDir(opts);
|
|
9330
|
-
const plist =
|
|
9331
|
-
|
|
9332
|
-
|
|
9747
|
+
const plist = path23.join(dir, `${LAUNCHD_LABEL}.plist`);
|
|
9748
|
+
fs28.mkdirSync(dir, { recursive: true });
|
|
9749
|
+
fs28.writeFileSync(plist, launchdPlist(backend, everyMinutes));
|
|
9333
9750
|
const domain = `gui/${process.getuid?.() ?? 501}`;
|
|
9334
9751
|
run2("launchctl", ["bootout", `${domain}/${LAUNCHD_LABEL}`]);
|
|
9335
9752
|
const r = run2("launchctl", ["bootstrap", domain, plist]);
|
|
@@ -9349,10 +9766,10 @@ function removeSchedule(opts = {}) {
|
|
|
9349
9766
|
const platform = opts.platform ?? process.platform;
|
|
9350
9767
|
const run2 = opts.run ?? realRun;
|
|
9351
9768
|
if (platform === "darwin") {
|
|
9352
|
-
const plist =
|
|
9769
|
+
const plist = path23.join(launchAgentsDir(opts), `${LAUNCHD_LABEL}.plist`);
|
|
9353
9770
|
run2("launchctl", ["bootout", `gui/${process.getuid?.() ?? 501}/${LAUNCHD_LABEL}`]);
|
|
9354
|
-
if (!
|
|
9355
|
-
|
|
9771
|
+
if (!fs28.existsSync(plist)) return "no schedule";
|
|
9772
|
+
fs28.rmSync(plist, { force: true });
|
|
9356
9773
|
return `removed ${plist}`;
|
|
9357
9774
|
}
|
|
9358
9775
|
const current = run2("crontab", ["-l"]);
|
|
@@ -9379,10 +9796,10 @@ async function install(args, cwd, out, err, opts = {}) {
|
|
|
9379
9796
|
}
|
|
9380
9797
|
const every = Math.max(15, Number(str5(args.flags.every) ?? 60) || 60);
|
|
9381
9798
|
const home = runnerHome();
|
|
9382
|
-
|
|
9799
|
+
fs28.mkdirSync(home, { recursive: true, mode: 448 });
|
|
9383
9800
|
const saved = { apiUrl: c.apiUrl, key: c.key, backend, ...config.model && str5(args.flags.model) ? { model: config.model } : {}, name: c.name, every };
|
|
9384
|
-
|
|
9385
|
-
|
|
9801
|
+
fs28.writeFileSync(path23.join(home, "config.json"), JSON.stringify(saved, null, 2) + "\n", { mode: 384 });
|
|
9802
|
+
fs28.chmodSync(path23.join(home, "config.json"), 384);
|
|
9386
9803
|
let schedule;
|
|
9387
9804
|
try {
|
|
9388
9805
|
schedule = installSchedule(backend, every, opts);
|
|
@@ -9395,7 +9812,7 @@ Run it yourself: npx -y ${PKG2} runner --once
|
|
|
9395
9812
|
out(`\u2713 Runner "${c.name}" answers ${c.apiUrl}'s model jobs with ${BACKEND_NAME[backend] ?? backend} on this computer.
|
|
9396
9813
|
`);
|
|
9397
9814
|
out(schedule.kind === "none" ? ` No scheduler here: run \`npx -y ${PKG2} runner --once\` on a schedule yourself.
|
|
9398
|
-
` : ` Every ${every} minutes while this computer is on (${schedule.kind}). Log: ${
|
|
9815
|
+
` : ` Every ${every} minutes while this computer is on (${schedule.kind}). Log: ${path23.join(home, "runner.log")}
|
|
9399
9816
|
`);
|
|
9400
9817
|
out(` Stop: npx -y ${PKG2} runner uninstall
|
|
9401
9818
|
`);
|
|
@@ -9412,9 +9829,9 @@ Answering what waits now\u2026
|
|
|
9412
9829
|
function uninstall(out, opts = {}) {
|
|
9413
9830
|
out(`${removeSchedule(opts)}
|
|
9414
9831
|
`);
|
|
9415
|
-
const file =
|
|
9416
|
-
if (
|
|
9417
|
-
|
|
9832
|
+
const file = path23.join(runnerHome(), "config.json");
|
|
9833
|
+
if (fs28.existsSync(file)) {
|
|
9834
|
+
fs28.rmSync(file, { force: true });
|
|
9418
9835
|
out(`removed ${file} (revoke the key in the console: Agent keys)
|
|
9419
9836
|
`);
|
|
9420
9837
|
}
|
|
@@ -9594,7 +10011,7 @@ async function cmdRunner(args, cwd, out, err, opts = {}) {
|
|
|
9594
10011
|
out(text2);
|
|
9595
10012
|
return 0;
|
|
9596
10013
|
}
|
|
9597
|
-
|
|
10014
|
+
fs28.writeFileSync(file, text2, { mode: 384 });
|
|
9598
10015
|
const left = Math.round((Date.parse(job.leaseUntil) - Date.now()) / 6e4);
|
|
9599
10016
|
out(
|
|
9600
10017
|
[
|
|
@@ -9612,7 +10029,7 @@ async function cmdRunner(args, cwd, out, err, opts = {}) {
|
|
|
9612
10029
|
const id = args._[2];
|
|
9613
10030
|
const file = args._[3] ?? "-";
|
|
9614
10031
|
if (!id) throw new Error("usage: bunshin runner validate <job id> <file|->");
|
|
9615
|
-
const text2 = file === "-" ?
|
|
10032
|
+
const text2 = file === "-" ? fs28.readFileSync(0, "utf8") : fs28.readFileSync(file, "utf8");
|
|
9616
10033
|
const r = await call(c, "POST", `/jobs/${id}/check`, { answer: text2, runner: c.name }, 12e4);
|
|
9617
10034
|
out(renderCheck(id, r.body));
|
|
9618
10035
|
return r.body.ok ? 0 : 1;
|
|
@@ -9621,7 +10038,7 @@ async function cmdRunner(args, cwd, out, err, opts = {}) {
|
|
|
9621
10038
|
const id = args._[2];
|
|
9622
10039
|
const file = args._[3] ?? "-";
|
|
9623
10040
|
if (!id) throw new Error("usage: bunshin runner submit <job id> <file|-> [--force]");
|
|
9624
|
-
const text2 = file === "-" ?
|
|
10041
|
+
const text2 = file === "-" ? fs28.readFileSync(0, "utf8") : fs28.readFileSync(file, "utf8");
|
|
9625
10042
|
const check = await call(c, "POST", `/jobs/${id}/check`, { answer: text2, runner: c.name }, 12e4).catch(() => null);
|
|
9626
10043
|
if (check?.body && (check.status < 400 || check.body.problems)) {
|
|
9627
10044
|
out(renderCheck(id, check.body));
|
|
@@ -9645,7 +10062,7 @@ async function cmdRunner(args, cwd, out, err, opts = {}) {
|
|
|
9645
10062
|
const since = Date.parse(str5(args.flags.since) ?? "") || Date.now() - 12 * 36e5;
|
|
9646
10063
|
const entries = (() => {
|
|
9647
10064
|
try {
|
|
9648
|
-
return
|
|
10065
|
+
return fs28.readFileSync(path23.join(runnerHome(), "log.jsonl"), "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l)).filter((e) => Date.parse(e.at) >= since);
|
|
9649
10066
|
} catch {
|
|
9650
10067
|
return [];
|
|
9651
10068
|
}
|
|
@@ -9714,9 +10131,9 @@ var init_runner = __esm({
|
|
|
9714
10131
|
});
|
|
9715
10132
|
|
|
9716
10133
|
// src/v2/importer.ts
|
|
9717
|
-
import { execFileSync as
|
|
9718
|
-
import
|
|
9719
|
-
import
|
|
10134
|
+
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
10135
|
+
import fs29 from "node:fs";
|
|
10136
|
+
import path24 from "node:path";
|
|
9720
10137
|
function rewriteRepoPaths(text2, repoName2, pack) {
|
|
9721
10138
|
const re = new RegExp(`(?<=^|[\\s\`'"(=:])(?:~|\\$\\{?[A-Za-z_][A-Za-z0-9_]*\\}?)?(?:/[\\w.@+-]+)*/${escapeRe(repoName2)}(?=[/\\s\`'")\\]]|[.,;:](?:\\s|$)|$)`, "gm");
|
|
9722
10139
|
return text2.replace(re, `~/.bunshin/library/${pack}`);
|
|
@@ -9755,7 +10172,7 @@ function buildDocs(files, opts) {
|
|
|
9755
10172
|
const main2 = `${dir}/SKILL.md`;
|
|
9756
10173
|
const text2 = rw(files.get(main2).content);
|
|
9757
10174
|
const fm = frontmatterDescription(text2);
|
|
9758
|
-
const name =
|
|
10175
|
+
const name = path24.posix.basename(dir);
|
|
9759
10176
|
const bundle = {};
|
|
9760
10177
|
for (const p of all.filter((x) => x.startsWith(`${dir}/`))) {
|
|
9761
10178
|
claimed.add(p);
|
|
@@ -9837,45 +10254,45 @@ ${Object.keys(loose).map((p) => `- \`${p}\``).join("\n")}
|
|
|
9837
10254
|
}
|
|
9838
10255
|
return docs.sort((a, b) => a.path.localeCompare(b.path));
|
|
9839
10256
|
}
|
|
9840
|
-
function
|
|
9841
|
-
return
|
|
10257
|
+
function git4(root, args) {
|
|
10258
|
+
return execFileSync6("git", ["-C", root, ...args], { encoding: "utf8", maxBuffer: 256 * 1024 * 1024 });
|
|
9842
10259
|
}
|
|
9843
10260
|
function gitBuf(root, args) {
|
|
9844
|
-
return
|
|
10261
|
+
return execFileSync6("git", ["-C", root, ...args], { maxBuffer: 256 * 1024 * 1024 });
|
|
9845
10262
|
}
|
|
9846
10263
|
function isGitRepo(root) {
|
|
9847
10264
|
try {
|
|
9848
|
-
return
|
|
10265
|
+
return git4(root, ["rev-parse", "--is-inside-work-tree"]).trim() === "true";
|
|
9849
10266
|
} catch {
|
|
9850
10267
|
return false;
|
|
9851
10268
|
}
|
|
9852
10269
|
}
|
|
9853
10270
|
function workingTreeFiles(root) {
|
|
9854
10271
|
const out = /* @__PURE__ */ new Map();
|
|
9855
|
-
const list2 = isGitRepo(root) ?
|
|
9856
|
-
return
|
|
10272
|
+
const list2 = isGitRepo(root) ? git4(root, ["ls-files", "-z"]).split("\0").filter(Boolean) : (function walk(dir, base = "") {
|
|
10273
|
+
return fs29.readdirSync(dir, { withFileTypes: true }).flatMap((e) => {
|
|
9857
10274
|
if (e.name === ".git" || e.name === "node_modules") return [];
|
|
9858
10275
|
const rel = base ? `${base}/${e.name}` : e.name;
|
|
9859
|
-
return e.isDirectory() ? walk(
|
|
10276
|
+
return e.isDirectory() ? walk(path24.join(dir, e.name), rel) : e.isFile() ? [rel] : [];
|
|
9860
10277
|
});
|
|
9861
10278
|
})(root);
|
|
9862
10279
|
for (const rel of list2) {
|
|
9863
|
-
const abs =
|
|
10280
|
+
const abs = path24.join(root, rel);
|
|
9864
10281
|
let buf;
|
|
9865
10282
|
try {
|
|
9866
|
-
buf =
|
|
10283
|
+
buf = fs29.readFileSync(abs);
|
|
9867
10284
|
} catch {
|
|
9868
10285
|
continue;
|
|
9869
10286
|
}
|
|
9870
10287
|
if (!isText(buf)) continue;
|
|
9871
|
-
const exec = (
|
|
10288
|
+
const exec = (fs29.statSync(abs).mode & 73) !== 0;
|
|
9872
10289
|
out.set(rel, { content: buf.toString("utf8"), ...exec ? { mode: "x" } : {} });
|
|
9873
10290
|
}
|
|
9874
10291
|
return out;
|
|
9875
10292
|
}
|
|
9876
10293
|
function commitsOf(root, since) {
|
|
9877
10294
|
const range = since ? [`${since}..HEAD`] : ["HEAD"];
|
|
9878
|
-
return
|
|
10295
|
+
return git4(root, ["log", "--reverse", "--topo-order", "--format=%H%x1f%an%x1f%aI%x1f%s", ...range]).split("\n").filter(Boolean).map((l) => {
|
|
9879
10296
|
const [sha2, author, at, subject] = l.split("");
|
|
9880
10297
|
return { sha: sha2, author, at, subject: subject ?? "" };
|
|
9881
10298
|
});
|
|
@@ -9888,7 +10305,7 @@ function readAt(root, sha2, rel, mode) {
|
|
|
9888
10305
|
}
|
|
9889
10306
|
function treeAt(root, sha2) {
|
|
9890
10307
|
const out = /* @__PURE__ */ new Map();
|
|
9891
|
-
for (const line of
|
|
10308
|
+
for (const line of git4(root, ["ls-tree", "-r", "-z", sha2]).split("\0").filter(Boolean)) {
|
|
9892
10309
|
const m = /^(\d+) blob [0-9a-f]+\t(.+)$/.exec(line);
|
|
9893
10310
|
if (!m) continue;
|
|
9894
10311
|
const f = readAt(root, sha2, m[2], m[1]);
|
|
@@ -9902,7 +10319,7 @@ function diffDocs(prev, next) {
|
|
|
9902
10319
|
return { docs: next.filter((d) => before.get(d.path) !== fingerprint(d)), removed: [...before.keys()].filter((p) => !after.has(p)) };
|
|
9903
10320
|
}
|
|
9904
10321
|
function readSkillSystem(root, opts) {
|
|
9905
|
-
const repoName2 =
|
|
10322
|
+
const repoName2 = path24.basename(path24.resolve(root));
|
|
9906
10323
|
const build = (files2) => buildDocs(files2, { pack: opts.pack, repoName: repoName2, ...opts.rewrite === false ? { rewrite: false } : {} });
|
|
9907
10324
|
const tree = build(workingTreeFiles(root));
|
|
9908
10325
|
if (!opts.history || !isGitRepo(root)) return { versions: [{ summary: `import ${opts.pack}`, docs: tree, removed: [], complete: true }], docs: tree };
|
|
@@ -9914,7 +10331,7 @@ function readSkillSystem(root, opts) {
|
|
|
9914
10331
|
commits.forEach((c, i) => {
|
|
9915
10332
|
if (!prevSha) files = treeAt(root, c.sha);
|
|
9916
10333
|
else {
|
|
9917
|
-
const changes =
|
|
10334
|
+
const changes = git4(root, ["diff-tree", "-r", "-z", "--no-renames", "--no-commit-id", prevSha, c.sha]).split("\0").filter(Boolean);
|
|
9918
10335
|
for (let k = 0; k + 1 < changes.length; k += 2) {
|
|
9919
10336
|
const m = /^:\d+ (\d+) [0-9a-f]+ [0-9a-f]+ ([A-Z])/.exec(changes[k]);
|
|
9920
10337
|
const rel = changes[k + 1];
|
|
@@ -9979,7 +10396,7 @@ var init_importer = __esm({
|
|
|
9979
10396
|
escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
9980
10397
|
looksGenerated = (text2) => /generated by|do not edit( by hand)?/i.test(text2.slice(0, 600));
|
|
9981
10398
|
isStopHook = (text2) => /"decision"\s*:\s*"block"|decision.{0,4}block/.test(text2) && /REASON=/.test(text2);
|
|
9982
|
-
isUpdateHook = (p, text2) => /auto-?update/i.test(
|
|
10399
|
+
isUpdateHook = (p, text2) => /auto-?update/i.test(path24.basename(p)) || /git (pull|fetch)/.test(text2) && /SessionStart|session start/i.test(text2);
|
|
9983
10400
|
fingerprint = (d) => JSON.stringify(d);
|
|
9984
10401
|
REWRITE_SYSTEM = `You adapt a company's skill documents after its skill system moved into Bunshin.
|
|
9985
10402
|
|
|
@@ -10015,8 +10432,8 @@ __export(skills_exports, {
|
|
|
10015
10432
|
cmdSkill: () => cmdSkill,
|
|
10016
10433
|
parsePin: () => parsePin
|
|
10017
10434
|
});
|
|
10018
|
-
import
|
|
10019
|
-
import
|
|
10435
|
+
import fs30 from "node:fs";
|
|
10436
|
+
import path25 from "node:path";
|
|
10020
10437
|
function requireOnline(store, what) {
|
|
10021
10438
|
if (!canCall(store.config())) throw new Error(`${what} needs the hosted library: run \`bunshin setup\` first`);
|
|
10022
10439
|
}
|
|
@@ -10025,9 +10442,9 @@ async function cmdImport2(store, args, cwd, out, err) {
|
|
|
10025
10442
|
err("usage: bunshin import skills <dir> [--pack <name>] [--layer org|team:<unit>] [--owner-team <unit>] [--no-history] [--dry-run] [--propose-rewrites]\n");
|
|
10026
10443
|
return 1;
|
|
10027
10444
|
}
|
|
10028
|
-
const root =
|
|
10029
|
-
if (!
|
|
10030
|
-
const pack = str6(args.flags.pack) ??
|
|
10445
|
+
const root = path25.resolve(cwd, args._[2]);
|
|
10446
|
+
if (!fs30.existsSync(root)) throw new Error(`${root} does not exist`);
|
|
10447
|
+
const pack = str6(args.flags.pack) ?? path25.basename(root);
|
|
10031
10448
|
const history = args.flags.history !== false && !bool4(args.flags["no-history"]);
|
|
10032
10449
|
const read = readSkillSystem(root, { pack, history, ...str6(args.flags.since) ? { since: str6(args.flags.since) } : {} });
|
|
10033
10450
|
const kinds = /* @__PURE__ */ new Map();
|
|
@@ -10062,7 +10479,7 @@ async function cmdImport2(store, args, cwd, out, err) {
|
|
|
10062
10479
|
const propose = args.flags["propose-rewrites"];
|
|
10063
10480
|
if (propose) {
|
|
10064
10481
|
const only = typeof propose === "string" && propose !== "true" ? propose.split(",").map((x) => x.trim()).filter(Boolean) : [];
|
|
10065
|
-
await proposeRewrites(store, read.docs.filter((d) => !only.length || only.some((o) => d.path.includes(o))), pack,
|
|
10482
|
+
await proposeRewrites(store, read.docs.filter((d) => !only.length || only.some((o) => d.path.includes(o))), pack, path25.basename(root), out, err);
|
|
10066
10483
|
}
|
|
10067
10484
|
out("Run `bunshin sync` on each machine (it runs at session start) to install the skills, instructions and files.\n");
|
|
10068
10485
|
return 0;
|
|
@@ -10202,7 +10619,7 @@ ${report.skills} skills, ${report.references} references, ${report.errors.length
|
|
|
10202
10619
|
}
|
|
10203
10620
|
const graph = str6(args.flags.graph) ?? (bool4(args.flags.graph) ? "SKILLS-GRAPH.md" : void 0);
|
|
10204
10621
|
if (graph) {
|
|
10205
|
-
|
|
10622
|
+
fs30.writeFileSync(path25.resolve(cwd, graph), renderSkillGraph(report));
|
|
10206
10623
|
out(`graph written to ${graph}
|
|
10207
10624
|
`);
|
|
10208
10625
|
}
|
|
@@ -10269,12 +10686,12 @@ async function cmdLibrary(store, args, cwd, out, err) {
|
|
|
10269
10686
|
}
|
|
10270
10687
|
case "status": {
|
|
10271
10688
|
const d = deployedState(config);
|
|
10272
|
-
const p = deployPaths();
|
|
10689
|
+
const p = deployPaths(process.env, config.orgId);
|
|
10273
10690
|
const pending = d.docs.filter((x) => x.pending);
|
|
10274
10691
|
out(`deployed: ${d.libraryVersion ? `lib@${d.libraryVersion}` : "nothing yet"}; ${d.docs.filter((x) => x.kind === "skill").length} skills, ${d.docs.filter((x) => x.kind === "instructions").length} instruction documents, ${d.docs.filter((x) => x.kind === "pack").length} pack documents
|
|
10275
10692
|
`);
|
|
10276
|
-
out(` skills: ${p.claudeSkills}, ${p.codexSkills}
|
|
10277
|
-
|
|
10693
|
+
out(` skills: ${p.claudeSkills}, ${p.codexSkills} (this workspace's own copy)
|
|
10694
|
+
linked into: ${d.linked.length ? d.linked.join(", ") : "no project yet (each project gets links when a session runs there)"}
|
|
10278
10695
|
files: ${p.library}
|
|
10279
10696
|
`);
|
|
10280
10697
|
for (const x of pending) out(` edited here, awaiting the nightly: ${x.name}
|
|
@@ -10282,16 +10699,7 @@ async function cmdLibrary(store, args, cwd, out, err) {
|
|
|
10282
10699
|
return 0;
|
|
10283
10700
|
}
|
|
10284
10701
|
case "workspace": {
|
|
10285
|
-
|
|
10286
|
-
if (bool4(args.flags.clear)) setDeployWorkspace(null);
|
|
10287
|
-
else if (target) setDeployWorkspace(target);
|
|
10288
|
-
const ws = deployWorkspace();
|
|
10289
|
-
const p = deployPaths();
|
|
10290
|
-
out(ws ? `workspace: ${ws}
|
|
10291
|
-
` : "workspace: none (skills and instructions are user-wide)\n");
|
|
10292
|
-
out(` skills: ${p.claudeSkills}, ${p.codexSkills}
|
|
10293
|
-
instructions: ${p.claudeMd}, ${p.agentsMd}
|
|
10294
|
-
`);
|
|
10702
|
+
out("Skills no longer go to one shared folder: each workspace keeps its own copy and links it into the projects its sessions run in, so they never reach another workspace's projects. See `bunshin library status`.\n");
|
|
10295
10703
|
return 0;
|
|
10296
10704
|
}
|
|
10297
10705
|
case "pin": {
|
|
@@ -10301,9 +10709,9 @@ async function cmdLibrary(store, args, cwd, out, err) {
|
|
|
10301
10709
|
`;
|
|
10302
10710
|
const file = str6(args.flags.write) ?? (bool4(args.flags.write) ? ".claude/skills-version" : void 0);
|
|
10303
10711
|
if (file) {
|
|
10304
|
-
const abs =
|
|
10305
|
-
|
|
10306
|
-
|
|
10712
|
+
const abs = path25.resolve(cwd, file);
|
|
10713
|
+
fs30.mkdirSync(path25.dirname(abs), { recursive: true });
|
|
10714
|
+
fs30.writeFileSync(abs, line);
|
|
10307
10715
|
out(`${file}: ${line}`);
|
|
10308
10716
|
} else out(line);
|
|
10309
10717
|
return 0;
|
|
@@ -10313,12 +10721,12 @@ async function cmdLibrary(store, args, cwd, out, err) {
|
|
|
10313
10721
|
const to = str6(args.flags.to);
|
|
10314
10722
|
if (!to) throw new Error("--to <dir> is required");
|
|
10315
10723
|
const pinFile = str6(args.flags.pin);
|
|
10316
|
-
const pin = str6(args.flags.git) ? { git: str6(args.flags.git) } : pinFile ? parsePin(
|
|
10724
|
+
const pin = str6(args.flags.git) ? { git: str6(args.flags.git) } : pinFile ? parsePin(fs30.readFileSync(path25.resolve(cwd, pinFile), "utf8")) : parsePin(args._[2] ?? "");
|
|
10317
10725
|
const q = pin.git ? `git=${pin.git}` : pin.version ? `version=${pin.version}` : "";
|
|
10318
10726
|
if (!q) throw new Error("name a version: lib@N, --git <sha>, or --pin <file>");
|
|
10319
10727
|
const snap = await v2api(config, "GET", `/library/snapshot?${q}`, void 0, { timeoutMs: 12e4 });
|
|
10320
|
-
const dir =
|
|
10321
|
-
if (
|
|
10728
|
+
const dir = path25.resolve(cwd, to);
|
|
10729
|
+
if (fs30.existsSync(dir) && fs30.readdirSync(dir).length && !bool4(args.flags.force)) throw new Error(`${dir} is not empty (use --force)`);
|
|
10322
10730
|
const pack = str6(args.flags.pack);
|
|
10323
10731
|
const docs = snap.sections.filter((s) => !pack || s.pack === pack);
|
|
10324
10732
|
const res = materialize(docs, dir);
|
|
@@ -10357,27 +10765,27 @@ __export(reflect_exports, {
|
|
|
10357
10765
|
buildLesson: () => buildLesson,
|
|
10358
10766
|
runReflection: () => runReflection
|
|
10359
10767
|
});
|
|
10360
|
-
import
|
|
10361
|
-
import
|
|
10362
|
-
import { createHash as
|
|
10768
|
+
import fs31 from "node:fs";
|
|
10769
|
+
import path26 from "node:path";
|
|
10770
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
10363
10771
|
async function runReflection(store, opts) {
|
|
10364
10772
|
const empty = { lessonIds: [], used: [], missed: [], attribution: [] };
|
|
10365
|
-
const lock =
|
|
10773
|
+
const lock = path26.join(store.root, `reflect-${createHash9("sha256").update(opts.sessionId).digest("hex")}.lock`);
|
|
10366
10774
|
const deadline = Date.now() + 66e4;
|
|
10367
10775
|
for (; ; ) {
|
|
10368
10776
|
try {
|
|
10369
|
-
|
|
10777
|
+
fs31.writeFileSync(lock, String(process.pid), { flag: "wx", mode: 384 });
|
|
10370
10778
|
break;
|
|
10371
10779
|
} catch (error) {
|
|
10372
10780
|
if (error.code !== "EEXIST") throw error;
|
|
10373
10781
|
try {
|
|
10374
|
-
const owner = Number(
|
|
10782
|
+
const owner = Number(fs31.readFileSync(lock, "utf8"));
|
|
10375
10783
|
if (Number.isInteger(owner) && owner > 0) {
|
|
10376
10784
|
try {
|
|
10377
10785
|
process.kill(owner, 0);
|
|
10378
10786
|
} catch (error2) {
|
|
10379
10787
|
if (error2.code === "ESRCH") {
|
|
10380
|
-
|
|
10788
|
+
fs31.unlinkSync(lock);
|
|
10381
10789
|
continue;
|
|
10382
10790
|
}
|
|
10383
10791
|
}
|
|
@@ -10392,7 +10800,7 @@ async function runReflection(store, opts) {
|
|
|
10392
10800
|
return await reflectSession(store, opts);
|
|
10393
10801
|
} finally {
|
|
10394
10802
|
try {
|
|
10395
|
-
|
|
10803
|
+
fs31.unlinkSync(lock);
|
|
10396
10804
|
} catch {
|
|
10397
10805
|
}
|
|
10398
10806
|
}
|
|
@@ -10734,8 +11142,8 @@ __export(cli_exports, {
|
|
|
10734
11142
|
parseArgs: () => parseArgs,
|
|
10735
11143
|
version: () => version
|
|
10736
11144
|
});
|
|
10737
|
-
import
|
|
10738
|
-
import
|
|
11145
|
+
import fs32 from "node:fs";
|
|
11146
|
+
import path27 from "node:path";
|
|
10739
11147
|
function parseArgs(argv) {
|
|
10740
11148
|
const out = { _: [], flags: {} };
|
|
10741
11149
|
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"]);
|
|
@@ -10835,7 +11243,7 @@ async function main(argv, opts = {}) {
|
|
|
10835
11243
|
if (harness !== "codex" && harness !== "claude-code" && harness !== "pi") throw new Error("Choose your agent: bunshin setup --harness codex, claude-code, or pi.");
|
|
10836
11244
|
const agent = harness === "claude-code" ? "claude" : harness;
|
|
10837
11245
|
if (!executable2(agent)) throw new Error(`${agent} was not found. Install and sign in to your agent, then run setup again.`);
|
|
10838
|
-
const prior = new Store(
|
|
11246
|
+
const prior = new Store(path27.join(cwd, ".bunshin"));
|
|
10839
11247
|
const existing = prior.trusted ? prior.config() : { apiUrl: "", apiKey: void 0 };
|
|
10840
11248
|
const apiUrl = str7(args.flags["api-url"]);
|
|
10841
11249
|
const key = bool5(args.flags["key-stdin"]) ? await new Promise((resolve, reject) => {
|
|
@@ -10847,11 +11255,30 @@ async function main(argv, opts = {}) {
|
|
|
10847
11255
|
process.stdin.on("end", () => resolve(value.trim()));
|
|
10848
11256
|
process.stdin.on("error", reject);
|
|
10849
11257
|
}) : process.env.BUNSHIN_API_KEY || (!apiUrl || apiUrl === existing.apiUrl) && existing.apiKey || await readKey2();
|
|
11258
|
+
const hooksFlag = str7(args.flags.hooks);
|
|
11259
|
+
if (hooksFlag && hooksFlag !== "user" && hooksFlag !== "project") throw new Error("--hooks must be user (every session under this folder) or project (this folder only)");
|
|
11260
|
+
const only = str7(args.flags.only);
|
|
11261
|
+
const exclude = str7(args.flags.exclude);
|
|
11262
|
+
const listed = (v) => v.split(",").map((x) => x.trim().replace(/\/+$/, "")).filter(Boolean);
|
|
10850
11263
|
out("Connecting to Bunshin\u2026\n");
|
|
10851
|
-
const result = await setupProject2({
|
|
11264
|
+
const result = await setupProject2({
|
|
11265
|
+
cwd,
|
|
11266
|
+
harness,
|
|
11267
|
+
apiKey: key,
|
|
11268
|
+
apiUrl,
|
|
11269
|
+
noHooks: bool5(args.flags["no-hooks"]),
|
|
11270
|
+
...hooksFlag ? { hooks: hooksFlag } : {},
|
|
11271
|
+
...bool5(args.flags.all) ? { sources: null } : only || exclude ? { sources: { ...only ? { only: listed(only) } : {}, ...exclude ? { exclude: listed(exclude) } : {} } } : {}
|
|
11272
|
+
});
|
|
11273
|
+
if (result.config.hooks === "user" && !only && !exclude && !bool5(args.flags.all) && process.stdin.isTTY && !bool5(args.flags["key-stdin"])) {
|
|
11274
|
+
const { askSources: askSources2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
|
|
11275
|
+
await askSources2(new Store(path27.join(cwd, ".bunshin")), out);
|
|
11276
|
+
}
|
|
10852
11277
|
out(`Connected as ${result.profile.role}${result.profile.orgUnit ? ` in ${result.profile.orgUnit}` : " (company-wide)"}.
|
|
10853
11278
|
`);
|
|
10854
|
-
out(`
|
|
11279
|
+
out(result.config.hooks === "user" ? `Folder: ${cwd} (every session in it or below it uses this workspace)
|
|
11280
|
+
Key saved privately outside the folder.
|
|
11281
|
+
` : `Project: ${cwd}
|
|
10855
11282
|
Key saved privately outside the project.
|
|
10856
11283
|
`);
|
|
10857
11284
|
if (result.hooks?.error) throw new Error(`Connected, but hooks could not be configured: ${result.hooks.error}. Fix the file and run setup again.`);
|
|
@@ -10864,6 +11291,7 @@ Key saved privately outside the project.
|
|
|
10864
11291
|
if (result.hooks?.sharedWithWorktrees) out("Codex shares these hooks with the repository\u2019s linked worktrees. Each connected worktree keeps its own Bunshin store.\n");
|
|
10865
11292
|
if (harness === "codex" && result.hooks) out("Run codex interactively in this project. Review and accept project trust, then run /hooks and review and trust the five Bunshin hooks.\n");
|
|
10866
11293
|
else if (harness === "pi" && result.hooks) out("Open Pi in this project and approve its project extension. Check it with /bunshin status.\n");
|
|
11294
|
+
else if (result.hooks && result.config.hooks === "user") out("Open a new Claude Code session in any project under this folder to load the hooks. Sessions elsewhere are untouched.\nChange which folders take part with: bunshin sources\n");
|
|
10867
11295
|
else if (result.hooks) out(`Open a new Claude Code session in this project to load the hooks${result.mcp?.installed ? " and approve the bunshin MCP server (.mcp.json)" : ""}.
|
|
10868
11296
|
`);
|
|
10869
11297
|
else out("Hooks are disabled. Run setup again without --no-hooks to configure them.\n");
|
|
@@ -10879,7 +11307,7 @@ Key saved privately outside the project.
|
|
|
10879
11307
|
if (cmd === "mcp") {
|
|
10880
11308
|
const { runMcpServer: runMcpServer2 } = await Promise.resolve().then(() => (init_mcp(), mcp_exports));
|
|
10881
11309
|
const root = str7(args.flags.root);
|
|
10882
|
-
await runMcpServer2({ cwd, ...root ? { store: new Store(
|
|
11310
|
+
await runMcpServer2({ cwd, ...root ? { store: new Store(path27.resolve(root)) } : {}, version: version() });
|
|
10883
11311
|
return 0;
|
|
10884
11312
|
}
|
|
10885
11313
|
if (cmd === "demo") {
|
|
@@ -10906,6 +11334,14 @@ Key saved privately outside the project.
|
|
|
10906
11334
|
}
|
|
10907
11335
|
if (skipped.length && cmd === "status") err(`! ${untrustedNote(skipped[0])}
|
|
10908
11336
|
`);
|
|
11337
|
+
if (["learn", "reflect", "recall", "load", "suggest"].includes(cmd) && !str7(args.flags.root)) {
|
|
11338
|
+
const { cwdInPool: cwdInPool2, topFolder: topFolder2 } = await Promise.resolve().then(() => (init_source(), source_exports));
|
|
11339
|
+
if (!cwdInPool2(store.config(), cwd, store.root)) {
|
|
11340
|
+
err(`bunshin: ${topFolder2(cwd, store.root)} is not part of this store's learning pool. Add it with \`bunshin sources include ${topFolder2(cwd, store.root)}\`.
|
|
11341
|
+
`);
|
|
11342
|
+
return 1;
|
|
11343
|
+
}
|
|
11344
|
+
}
|
|
10909
11345
|
try {
|
|
10910
11346
|
switch (cmd) {
|
|
10911
11347
|
case "status":
|
|
@@ -10957,6 +11393,9 @@ Key saved privately outside the project.
|
|
|
10957
11393
|
return await (await Promise.resolve().then(() => (init_skills(), skills_exports))).cmdLibrary(store, args, cwd, out, err);
|
|
10958
11394
|
case "ui":
|
|
10959
11395
|
return await (await Promise.resolve().then(() => (init_uicmd(), uicmd_exports))).cmdUi(store, args, out, err, version());
|
|
11396
|
+
case "sources":
|
|
11397
|
+
case "source":
|
|
11398
|
+
return (await Promise.resolve().then(() => (init_commands(), commands_exports))).cmdSources(store, args, cwd, out, err);
|
|
10960
11399
|
default:
|
|
10961
11400
|
err(`bunshin: unknown command "${cmd}"
|
|
10962
11401
|
|
|
@@ -10972,7 +11411,7 @@ ${HELP2}`);
|
|
|
10972
11411
|
function openStore(args, cwd) {
|
|
10973
11412
|
const root = str7(args.flags.root);
|
|
10974
11413
|
if (root) {
|
|
10975
|
-
const store = new Store(
|
|
11414
|
+
const store = new Store(path27.resolve(root));
|
|
10976
11415
|
store.ensureLayout();
|
|
10977
11416
|
return store;
|
|
10978
11417
|
}
|
|
@@ -10988,7 +11427,7 @@ async function cmdInit(args, cwd, out, err) {
|
|
|
10988
11427
|
orgId: str7(args.flags["org-id"]),
|
|
10989
11428
|
userId: str7(args.flags["user-id"]),
|
|
10990
11429
|
apiUrl: str7(args.flags["api-url"]),
|
|
10991
|
-
apiKey: bool5(args.flags["key-stdin"]) ?
|
|
11430
|
+
apiKey: bool5(args.flags["key-stdin"]) ? fs32.readFileSync(0, "utf8").trim() || void 0 : str7(args.flags["api-key"]),
|
|
10992
11431
|
scope: bool5(args.flags.global) ? "global" : str7(args.flags.scope),
|
|
10993
11432
|
harness,
|
|
10994
11433
|
noHooks: args.flags.hooks === false || bool5(args.flags["no-hooks"]),
|
|
@@ -11031,10 +11470,10 @@ function cmdStatus(store, args, out, v2) {
|
|
|
11031
11470
|
const conflicts = store.listConflicts().filter((c) => c.status === "open");
|
|
11032
11471
|
const metrics = store.readMetrics(void 0);
|
|
11033
11472
|
const last50 = metrics?.windows?.find((w) => w.name === "last50");
|
|
11034
|
-
const hookFiles = config.harness === "codex" ? [.../* @__PURE__ */ new Set([
|
|
11473
|
+
const hookFiles = config.harness === "codex" ? [.../* @__PURE__ */ new Set([path27.join(process.env.CODEX_HOME ?? path27.join(homeDir(), ".codex"), "hooks.json"), codexProjectHooks(path27.dirname(store.root)).file])] : config.harness === "claude-code" ? [path27.join(homeDir(), ".claude", "settings.json"), path27.join(path27.dirname(store.root), ".claude", "settings.local.json")] : config.harness === "pi" ? [path27.join(homeDir(), ".pi/agent/extensions/bunshin.js"), path27.join(path27.dirname(store.root), ".pi/extensions/bunshin.js")] : [];
|
|
11035
11474
|
const hooks = config.harness === "pi" ? hookFiles.filter((file) => {
|
|
11036
11475
|
try {
|
|
11037
|
-
return
|
|
11476
|
+
return fs32.readFileSync(file, "utf8").startsWith("// Managed by Bunshin setup.");
|
|
11038
11477
|
} catch {
|
|
11039
11478
|
return false;
|
|
11040
11479
|
}
|
|
@@ -11052,7 +11491,7 @@ function cmdStatus(store, args, out, v2) {
|
|
|
11052
11491
|
mothership: config.apiUrl ? { apiUrl: config.apiUrl, key: config.apiKey ? "set" : "missing", lastSyncAt: config.lastSyncAt ?? null } : null,
|
|
11053
11492
|
llmBackend: resolveBackend(config) ?? "none (set ANTHROPIC_API_KEY or install the claude/codex CLI)",
|
|
11054
11493
|
hooks: { [config.harness]: hooks },
|
|
11055
|
-
hookFiles: hookFiles.filter((file) =>
|
|
11494
|
+
hookFiles: hookFiles.filter((file) => fs32.existsSync(file)),
|
|
11056
11495
|
recallQuality: last50 ? { precision: last50.precision, recall: last50.recall, f1: last50.f1, traces: last50.traces } : null,
|
|
11057
11496
|
v2
|
|
11058
11497
|
};
|
|
@@ -11068,6 +11507,8 @@ function cmdStatus(store, args, out, v2) {
|
|
|
11068
11507
|
out(`user ${config.userId} \u2014 ${config.role} @ ${config.orgUnit} (org ${config.orgId})
|
|
11069
11508
|
`);
|
|
11070
11509
|
out(`context ${v2.context.id} (${v2.context.kind}; layers ${v2.context.layers.join(", ")})
|
|
11510
|
+
`);
|
|
11511
|
+
if (v2.source) out(`source ${!v2.source.inPool ? `${v2.source.folder} is outside the learning pool (\`bunshin sources\`)` : v2.source.id ?? "none (the store's own folder)"}
|
|
11071
11512
|
`);
|
|
11072
11513
|
out(`library ${v2.bundle ? `${v2.bundle.sections} bundle section(s), generated ${v2.bundle.generatedAt}` : "no bundle yet"}; ${v2.repo.sections} working-tree section(s)${v2.repo.name ? ` in ${v2.repo.name}` : ""}
|
|
11073
11514
|
`);
|
|
@@ -11093,7 +11534,7 @@ function cmdStatus(store, args, out, v2) {
|
|
|
11093
11534
|
}
|
|
11094
11535
|
function hookCount(file) {
|
|
11095
11536
|
try {
|
|
11096
|
-
const raw = JSON.parse(
|
|
11537
|
+
const raw = JSON.parse(fs32.readFileSync(file, "utf8"));
|
|
11097
11538
|
let n = 0;
|
|
11098
11539
|
for (const groups of Object.values(raw.hooks ?? {})) {
|
|
11099
11540
|
for (const g of groups ?? []) {
|
|
@@ -11213,7 +11654,7 @@ function cmdLessonAdd(store, args, out, err) {
|
|
|
11213
11654
|
let content = str7(args.flags.content) ?? "";
|
|
11214
11655
|
if (!content || content === "-" || args._.includes("-")) {
|
|
11215
11656
|
try {
|
|
11216
|
-
content =
|
|
11657
|
+
content = fs32.readFileSync(0, "utf8");
|
|
11217
11658
|
} catch {
|
|
11218
11659
|
}
|
|
11219
11660
|
}
|
|
@@ -11279,7 +11720,7 @@ async function cmdReflect(store, args, cwd, out, err) {
|
|
|
11279
11720
|
}
|
|
11280
11721
|
const { runReflection: runReflection2 } = await Promise.resolve().then(() => (init_reflect4(), reflect_exports));
|
|
11281
11722
|
const result = await runReflection2(store, {
|
|
11282
|
-
sessionId: str7(args.flags.session) ??
|
|
11723
|
+
sessionId: str7(args.flags.session) ?? path27.basename(transcript).replace(/\.jsonl?$/, ""),
|
|
11283
11724
|
transcriptPath: transcript,
|
|
11284
11725
|
cwd,
|
|
11285
11726
|
force: bool5(args.flags.force)
|
|
@@ -11404,6 +11845,10 @@ Usage: bunshin <command> [options]
|
|
|
11404
11845
|
setup --harness codex|claude-code|pi connect this project to your hosted workspace
|
|
11405
11846
|
--api-url <url> --no-hooks paste the key at the hidden prompt; profile is read from Bunshin
|
|
11406
11847
|
--key-stdin read the key from stdin (for automated setup)
|
|
11848
|
+
--hooks user a folder of projects: hooks in your user settings, so every
|
|
11849
|
+
session under this folder uses its workspace (default: project)
|
|
11850
|
+
--only a,b | --exclude c | --all
|
|
11851
|
+
which of the folder's projects take part (asked when omitted)
|
|
11407
11852
|
setup --robot --robot-id <id> --arm so101 --gripper <g> --mount <m> --cameras scene,wrist
|
|
11408
11853
|
--domain <d> --site <s> --cell <c> [--depth] [--api-url <url>] [--harness claude-code]
|
|
11409
11854
|
register this machine as a robot context (x-bunshin-context)
|
|
@@ -11413,6 +11858,8 @@ Usage: bunshin <command> [options]
|
|
|
11413
11858
|
--harness claude-code|codex|pi|other --no-hooks --remove-hooks
|
|
11414
11859
|
|
|
11415
11860
|
status [--json] store, context, library, outbox, hooks and sync health
|
|
11861
|
+
sources [all | only <folders\u2026> | include <folders\u2026> | exclude <folders\u2026>] [--json]
|
|
11862
|
+
which folders under this store's folder take part (learning pool)
|
|
11416
11863
|
recall "<task, abstractly>" [--hypothetical "<lesson>"] [--id a,b] [--limit n] [--json]
|
|
11417
11864
|
pull candidates from the library (offline: local view)
|
|
11418
11865
|
load <id> [--anchor <heading>] [--json]
|
|
@@ -11475,7 +11922,7 @@ Global: --root <dir> (use this .bunshin store) --version --help
|
|
|
11475
11922
|
Env: BUNSHIN_API_KEY, BUNSHIN_HARNESS, BUNSHIN_DISABLE, BUNSHIN_CHILD, BUNSHIN_CLAUDE_BIN,
|
|
11476
11923
|
CLAUDE_CODE_SESSION_ID (learn / mcp session), ANTHROPIC_API_KEY (non-fork reflection)
|
|
11477
11924
|
`;
|
|
11478
|
-
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 ${
|
|
11925
|
+
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 ${path27.dirname(root)} to trust it.`;
|
|
11479
11926
|
}
|
|
11480
11927
|
});
|
|
11481
11928
|
|