@bunshin-cc/bunshin 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +862 -659
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -1074,6 +1074,112 @@ var init_permissions = __esm({
|
|
|
1074
1074
|
}
|
|
1075
1075
|
});
|
|
1076
1076
|
|
|
1077
|
+
// ../core/src/v2/sources.ts
|
|
1078
|
+
function sourceId(kind, name) {
|
|
1079
|
+
return `${kind}:${name.trim()}`;
|
|
1080
|
+
}
|
|
1081
|
+
function parseSource(id) {
|
|
1082
|
+
if (!id) return null;
|
|
1083
|
+
const i = id.indexOf(":");
|
|
1084
|
+
if (i < 0) return null;
|
|
1085
|
+
const kind = id.slice(0, i);
|
|
1086
|
+
const name = id.slice(i + 1);
|
|
1087
|
+
if (!SOURCE_KINDS.includes(kind) || !NAME.test(name) || name.trim() !== name) return null;
|
|
1088
|
+
return { kind, name };
|
|
1089
|
+
}
|
|
1090
|
+
function sourceName(id) {
|
|
1091
|
+
return parseSource(id)?.name ?? id;
|
|
1092
|
+
}
|
|
1093
|
+
var SOURCE_KINDS, NAME;
|
|
1094
|
+
var init_sources = __esm({
|
|
1095
|
+
"../core/src/v2/sources.ts"() {
|
|
1096
|
+
"use strict";
|
|
1097
|
+
SOURCE_KINDS = ["repo", "folder", "robot"];
|
|
1098
|
+
NAME = /^[^\0-\x1f\x7f]{1,280}$/;
|
|
1099
|
+
}
|
|
1100
|
+
});
|
|
1101
|
+
|
|
1102
|
+
// ../core/src/v2/scopes.ts
|
|
1103
|
+
function emptyTree() {
|
|
1104
|
+
return { version: 0, groups: [], sources: {} };
|
|
1105
|
+
}
|
|
1106
|
+
function isScopeLayer(layer) {
|
|
1107
|
+
const k = layerKind(layer);
|
|
1108
|
+
return k === "group" || k === "source";
|
|
1109
|
+
}
|
|
1110
|
+
function groupChain(tree, groupId) {
|
|
1111
|
+
const byId = new Map(tree.groups.map((g) => [g.id, g]));
|
|
1112
|
+
const out = [];
|
|
1113
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1114
|
+
let cur = groupId ? byId.get(groupId) : void 0;
|
|
1115
|
+
while (cur && !seen.has(cur.id)) {
|
|
1116
|
+
seen.add(cur.id);
|
|
1117
|
+
out.unshift(cur.id);
|
|
1118
|
+
cur = cur.parent ? byId.get(cur.parent) : void 0;
|
|
1119
|
+
}
|
|
1120
|
+
return out;
|
|
1121
|
+
}
|
|
1122
|
+
function scopePath(tree, source) {
|
|
1123
|
+
if (!source || !parseSource(source)) return [];
|
|
1124
|
+
const placed = tree?.sources[source]?.group ?? null;
|
|
1125
|
+
return [...(tree ? groupChain(tree, placed) : []).map(groupLayer), sourceLayer(source)];
|
|
1126
|
+
}
|
|
1127
|
+
function inBoundary(section, reader) {
|
|
1128
|
+
return !isScopeLayer(section.layer) || reader.layers.includes(section.layer);
|
|
1129
|
+
}
|
|
1130
|
+
function parentScope(tree, layer) {
|
|
1131
|
+
const k = layerKind(layer);
|
|
1132
|
+
if (k === "source") {
|
|
1133
|
+
const g = tree.sources[layerValue(layer)]?.group ?? null;
|
|
1134
|
+
return g && tree.groups.some((x) => x.id === g) ? groupLayer(g) : "org";
|
|
1135
|
+
}
|
|
1136
|
+
if (k === "group") {
|
|
1137
|
+
const g = tree.groups.find((x) => x.id === layerValue(layer));
|
|
1138
|
+
return g?.parent && tree.groups.some((x) => x.id === g.parent) ? groupLayer(g.parent) : "org";
|
|
1139
|
+
}
|
|
1140
|
+
return null;
|
|
1141
|
+
}
|
|
1142
|
+
function ancestorScopes(tree, layer) {
|
|
1143
|
+
const out = [];
|
|
1144
|
+
for (let cur = parentScope(tree, layer); cur && !out.includes(cur); cur = parentScope(tree, cur)) out.push(cur);
|
|
1145
|
+
return out;
|
|
1146
|
+
}
|
|
1147
|
+
function sourcesIn(tree, layer) {
|
|
1148
|
+
if (layer === "org") return Object.keys(tree.sources);
|
|
1149
|
+
if (layerKind(layer) === "source") return [layerValue(layer)];
|
|
1150
|
+
if (layerKind(layer) !== "group") return [];
|
|
1151
|
+
const gid = layerValue(layer);
|
|
1152
|
+
return Object.keys(tree.sources).filter((sid) => groupChain(tree, tree.sources[sid].group).includes(gid));
|
|
1153
|
+
}
|
|
1154
|
+
function scopeLabel(layer, tree) {
|
|
1155
|
+
const k = layerKind(layer);
|
|
1156
|
+
if (layer === "org") return "everywhere";
|
|
1157
|
+
if (k === "source") {
|
|
1158
|
+
const sid = layerValue(layer);
|
|
1159
|
+
return `${parseSource(sid)?.kind === "robot" ? "robot" : "project"} ${sourceName(sid)}`;
|
|
1160
|
+
}
|
|
1161
|
+
if (k === "group") return `group ${tree?.groups.find((g) => g.id === layerValue(layer))?.name ?? layerValue(layer)}`;
|
|
1162
|
+
if (k === "user") return "personal";
|
|
1163
|
+
return layer;
|
|
1164
|
+
}
|
|
1165
|
+
function scopeChoices(tree, source) {
|
|
1166
|
+
const t = tree ?? emptyTree();
|
|
1167
|
+
return scopePath(t, source).map((layer) => {
|
|
1168
|
+
const n = sourcesIn(t, layer).length;
|
|
1169
|
+
return { layer, label: layerKind(layer) === "source" ? `this ${scopeLabel(layer, t)} only` : `${scopeLabel(layer, t)} (${n} project${n === 1 ? "" : "s"})` };
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
var groupLayer, sourceLayer;
|
|
1173
|
+
var init_scopes = __esm({
|
|
1174
|
+
"../core/src/v2/scopes.ts"() {
|
|
1175
|
+
"use strict";
|
|
1176
|
+
init_layers();
|
|
1177
|
+
init_sources();
|
|
1178
|
+
groupLayer = (id) => `group:${id}`;
|
|
1179
|
+
sourceLayer = (id) => `source:${id}`;
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
|
|
1077
1183
|
// ../core/src/v2/layers.ts
|
|
1078
1184
|
function layerKind(layer) {
|
|
1079
1185
|
const kind = layer.split(":")[0];
|
|
@@ -1083,19 +1189,29 @@ function layerValue(layer) {
|
|
|
1083
1189
|
const i = layer.indexOf(":");
|
|
1084
1190
|
return i < 0 ? "" : layer.slice(i + 1);
|
|
1085
1191
|
}
|
|
1192
|
+
function privateLayer(ctx) {
|
|
1193
|
+
return ctx.kind === "robot" ? `robot:${ctx.id}` : `user:${ctx.userId}`;
|
|
1194
|
+
}
|
|
1086
1195
|
function layersFor(ctx) {
|
|
1087
|
-
|
|
1088
|
-
return ["fleet", `arm:${ctx.robot.armModel}`, `mount:${ctx.robot.mount}`, `robot:${ctx.id}`];
|
|
1089
|
-
}
|
|
1090
|
-
const team = ctx.person?.team ?? "";
|
|
1091
|
-
const dept = department(team);
|
|
1092
|
-
return ["org", ...dept ? [`dept:${dept}`] : [], ...team ? [`team:${team}`] : [], `user:${ctx.userId}`];
|
|
1196
|
+
return ["org", privateLayer(ctx)];
|
|
1093
1197
|
}
|
|
1094
|
-
function
|
|
1198
|
+
function isLocalLayer(layer) {
|
|
1199
|
+
const kind = layerKind(layer);
|
|
1200
|
+
return kind === "user" || kind === "robot";
|
|
1201
|
+
}
|
|
1202
|
+
function withScope(layers, scope) {
|
|
1203
|
+
const path28 = scopePath(scope?.tree, scope?.source);
|
|
1204
|
+
if (!path28.length) return layers;
|
|
1205
|
+
const i = layers.findIndex((l) => isLocalLayer(l));
|
|
1206
|
+
return i < 0 ? [...layers, ...path28] : [...layers.slice(0, i), ...path28, ...layers.slice(i)];
|
|
1207
|
+
}
|
|
1208
|
+
function readerFor(ctx, ownerClearance, scope) {
|
|
1209
|
+
if (ctx.kind === "robot" && !scope?.source) scope = { ...scope, source: `robot:${ctx.id}` };
|
|
1210
|
+
const source = scope?.source ? { source: scope.source } : {};
|
|
1095
1211
|
if (ctx.kind === "robot") {
|
|
1096
1212
|
const rank = (c) => c === "private" ? 0 : CLEARANCE_RANK[c];
|
|
1097
1213
|
const clearance = ownerClearance && rank(ownerClearance) < CLEARANCE_RANK.org ? ownerClearance : "org";
|
|
1098
|
-
return { userId: ctx.userId, orgId: ctx.orgId, role: "robot", team: `fleet/${ctx.robot?.site ?? "site"}`, clearance, contextId: ctx.id, layers: layersFor(ctx), ...ctx.robot?.domain ? { domain: ctx.robot.domain } : {} };
|
|
1214
|
+
return { userId: ctx.userId, orgId: ctx.orgId, role: "robot", team: `fleet/${ctx.robot?.site ?? "site"}`, clearance, contextId: ctx.id, layers: withScope(layersFor(ctx), scope), ...ctx.robot?.domain ? { domain: ctx.robot.domain } : {}, ...source };
|
|
1099
1215
|
}
|
|
1100
1216
|
return {
|
|
1101
1217
|
userId: ctx.userId,
|
|
@@ -1104,7 +1220,8 @@ function readerFor(ctx, ownerClearance) {
|
|
|
1104
1220
|
team: ctx.person?.team ?? "",
|
|
1105
1221
|
clearance: ctx.person?.clearance ?? "team",
|
|
1106
1222
|
contextId: ctx.id,
|
|
1107
|
-
layers: layersFor(ctx)
|
|
1223
|
+
layers: withScope(layersFor(ctx), scope),
|
|
1224
|
+
...source
|
|
1108
1225
|
};
|
|
1109
1226
|
}
|
|
1110
1227
|
function explainSectionAccess(section, reader) {
|
|
@@ -1112,9 +1229,6 @@ function explainSectionAccess(section, reader) {
|
|
|
1112
1229
|
const kind = layerKind(section.layer);
|
|
1113
1230
|
if (kind === "user" && section.layer !== `user:${reader.userId}`) return { allowed: false, reason: "other-user" };
|
|
1114
1231
|
if (kind === "robot" && section.layer !== `robot:${reader.contextId}`) return { allowed: false, reason: "other-robot" };
|
|
1115
|
-
if ((kind === "arm" || kind === "mount") && reader.role === "robot" && !reader.layers.includes(section.layer)) {
|
|
1116
|
-
return { allowed: false, reason: "other-hardware" };
|
|
1117
|
-
}
|
|
1118
1232
|
const access = explainAccess(
|
|
1119
1233
|
{
|
|
1120
1234
|
clearance: section.clearance,
|
|
@@ -1134,33 +1248,26 @@ function scopeAffinity(section, reader) {
|
|
|
1134
1248
|
}
|
|
1135
1249
|
function baseAffinity(section, reader) {
|
|
1136
1250
|
const kind = layerKind(section.layer);
|
|
1137
|
-
if (
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
return 0.05;
|
|
1251
|
+
if (kind === "group" || kind === "source") {
|
|
1252
|
+
if (!reader.layers.includes(section.layer)) return 0.05;
|
|
1253
|
+
const path28 = reader.layers.filter((l) => {
|
|
1254
|
+
const k = layerKind(l);
|
|
1255
|
+
return k === "group" || k === "source";
|
|
1256
|
+
});
|
|
1257
|
+
return 0.6 + 0.4 * ((path28.indexOf(section.layer) + 1) / path28.length);
|
|
1145
1258
|
}
|
|
1146
|
-
if (
|
|
1147
|
-
return 0.
|
|
1259
|
+
if (isLocalLayer(section.layer)) return reader.layers.includes(section.layer) ? 1 : 0;
|
|
1260
|
+
return 0.5;
|
|
1148
1261
|
}
|
|
1149
1262
|
function reachesReader(section, reader) {
|
|
1150
|
-
|
|
1151
|
-
const kind = layerKind(section.layer);
|
|
1152
|
-
if (kind === "org" || kind === "dept" || FLEET_LAYER_ORDER.includes(kind)) return canRead(section, reader);
|
|
1153
|
-
if (kind !== "team") return false;
|
|
1154
|
-
return [layerValue(section.layer), ...section.allowedTeams ?? []].some((t) => !!t && orgUnitUnder(reader.team, t));
|
|
1263
|
+
return reader.layers.includes(section.layer) && canRead(section, reader);
|
|
1155
1264
|
}
|
|
1156
|
-
var COMPANY_LAYER_ORDER, FLEET_LAYER_ORDER;
|
|
1157
1265
|
var init_layers = __esm({
|
|
1158
1266
|
"../core/src/v2/layers.ts"() {
|
|
1159
1267
|
"use strict";
|
|
1160
1268
|
init_permissions();
|
|
1161
1269
|
init_text();
|
|
1162
|
-
|
|
1163
|
-
FLEET_LAYER_ORDER = ["fleet", "arm", "mount", "robot"];
|
|
1270
|
+
init_scopes();
|
|
1164
1271
|
}
|
|
1165
1272
|
});
|
|
1166
1273
|
|
|
@@ -1344,6 +1451,9 @@ var init_document = __esm({
|
|
|
1344
1451
|
});
|
|
1345
1452
|
|
|
1346
1453
|
// ../core/src/v2/sections/markdown.ts
|
|
1454
|
+
function repoLayer(repo) {
|
|
1455
|
+
return `source:${repo.includes("/") ? "repo" : "folder"}:${repo}`;
|
|
1456
|
+
}
|
|
1347
1457
|
function repoSectionId(repo, path28, anchor = "") {
|
|
1348
1458
|
return anchor ? `repo:${repo}/${path28}#${anchor}` : `repo:${repo}/${path28}`;
|
|
1349
1459
|
}
|
|
@@ -1376,7 +1486,7 @@ function parseRepoDoc(doc, opts) {
|
|
|
1376
1486
|
id: meta.id ? repoSectionId(doc.repo, doc.path, meta.id) : repoSectionId(doc.repo, doc.path),
|
|
1377
1487
|
orgId: opts.orgId,
|
|
1378
1488
|
kind,
|
|
1379
|
-
layer:
|
|
1489
|
+
layer: opts.layer ?? repoLayer(doc.repo),
|
|
1380
1490
|
title,
|
|
1381
1491
|
body: doc.text,
|
|
1382
1492
|
when,
|
|
@@ -1748,8 +1858,8 @@ function servable(sections, reader) {
|
|
|
1748
1858
|
}
|
|
1749
1859
|
return [...out.values()];
|
|
1750
1860
|
}
|
|
1751
|
-
function recallable(sections, reader) {
|
|
1752
|
-
return servable(sections, reader).filter((s) => s.delivery !== "always" && s.delivery !== "stop" && s.delivery !== "reflect");
|
|
1861
|
+
function recallable(sections, reader, opts = {}) {
|
|
1862
|
+
return servable(sections, reader).filter((s) => s.delivery !== "always" && s.delivery !== "stop" && s.delivery !== "reflect" && (opts.across || inBoundary(s, reader)));
|
|
1753
1863
|
}
|
|
1754
1864
|
function passagesOfSection(s) {
|
|
1755
1865
|
const hit = passageCache.get(s);
|
|
@@ -1797,6 +1907,7 @@ function passageDense(embedder, pool, q) {
|
|
|
1797
1907
|
function sourceLabel(s) {
|
|
1798
1908
|
if (s.storage.type === "repo") return `repo:${s.storage.repo}/${s.storage.path}`;
|
|
1799
1909
|
if (s.storage.type === "harness") return `harness:${s.storage.path}`;
|
|
1910
|
+
if (isScopeLayer(s.layer)) return s.layer;
|
|
1800
1911
|
return s.owner.team || s.layer;
|
|
1801
1912
|
}
|
|
1802
1913
|
function push(sections, reader, query) {
|
|
@@ -1837,7 +1948,7 @@ function maxSim(embedder, q, lists) {
|
|
|
1837
1948
|
function pull(sections, reader, query, opts = {}) {
|
|
1838
1949
|
const embedders = opts.embedders ?? DEFAULT_EMBEDDERS;
|
|
1839
1950
|
const exclude = new Set(opts.exclude ?? []);
|
|
1840
|
-
const pool = recallable(sections, reader).filter((s) => !exclude.has(s.id));
|
|
1951
|
+
const pool = recallable(sections, reader, opts).filter((s) => !exclude.has(s.id));
|
|
1841
1952
|
if (!pool.length) return [];
|
|
1842
1953
|
const limit = Math.max(1, Math.min(20, query.limit ?? 10));
|
|
1843
1954
|
const signals = [];
|
|
@@ -1878,6 +1989,7 @@ function pull(sections, reader, query, opts = {}) {
|
|
|
1878
1989
|
kind: section.kind,
|
|
1879
1990
|
layer: section.layer,
|
|
1880
1991
|
source: sourceLabel(section),
|
|
1992
|
+
...isScopeLayer(section.layer) && !reader.layers.includes(section.layer) ? { outside: true } : {},
|
|
1881
1993
|
score: Math.round(score2 * 1e3) / 1e3,
|
|
1882
1994
|
reasons,
|
|
1883
1995
|
...part.get(section.id) ? { part: part.get(section.id) } : {},
|
|
@@ -1946,9 +2058,9 @@ function renderSection(s, trigger, opts = {}) {
|
|
|
1946
2058
|
`_Load the whole document, or one part, with bunshin_load {"section_id": "${s.id}"${part ? `, "anchor": "${opts.anchor}"` : ""}} (or \`bunshin load ${s.id}${part ? ` --anchor ${opts.anchor}` : ""}\`)._`
|
|
1947
2059
|
].join("\n");
|
|
1948
2060
|
}
|
|
1949
|
-
function renderCandidates(c) {
|
|
2061
|
+
function renderCandidates(c, tree) {
|
|
1950
2062
|
if (!c.length) return "No sections matched. Proceed with general knowledge; nothing here is required.";
|
|
1951
|
-
return c.map((x, i) => `${i + 1}. [${x.source}] ${x.title} (id: ${x.id}${x.part ? `, best part: \xA7${x.part}` : ""}${x.size ? `, ${x.size} words` : ""})
|
|
2063
|
+
return c.map((x, i) => `${i + 1}. [${isScopeLayer(x.source) ? scopeLabel(x.source, tree) : x.source}] ${x.title} (id: ${x.id}${x.part ? `, best part: \xA7${x.part}` : ""}${x.size ? `, ${x.size} words` : ""}${x.outside ? "; borrowed from outside this project's scope" : ""})
|
|
1952
2064
|
when: ${x.when}`).join("\n");
|
|
1953
2065
|
}
|
|
1954
2066
|
var PASSAGE_CHARS, passageCache, words;
|
|
@@ -1957,6 +2069,7 @@ var init_engine = __esm({
|
|
|
1957
2069
|
"use strict";
|
|
1958
2070
|
init_text();
|
|
1959
2071
|
init_layers();
|
|
2072
|
+
init_scopes();
|
|
1960
2073
|
init_document();
|
|
1961
2074
|
init_text_index();
|
|
1962
2075
|
PASSAGE_CHARS = 4e3;
|
|
@@ -1967,8 +2080,9 @@ var init_engine = __esm({
|
|
|
1967
2080
|
|
|
1968
2081
|
// ../core/src/v2/guidelines.ts
|
|
1969
2082
|
function reflectionGuidelines(sections, reader, opts = {}) {
|
|
2083
|
+
const covering = opts.layer ? /* @__PURE__ */ new Set(["org", opts.layer, ...opts.tree ? ancestorScopes(opts.tree, opts.layer) : []]) : void 0;
|
|
1970
2084
|
const pool = reader ? servable(sections, reader) : sections;
|
|
1971
|
-
return pool.filter((s) => s.delivery === "reflect" && s.status === "active").filter((s) => !
|
|
2085
|
+
return pool.filter((s) => s.delivery === "reflect" && s.status === "active" && (!reader || inBoundary(s, reader))).filter((s) => !covering || covering.has(s.layer)).sort((a, b) => generality(a.layer) - generality(b.layer) || a.id.localeCompare(b.id)).map((s) => ({ id: s.id, title: s.title, layer: s.layer, body: s.body }));
|
|
1972
2086
|
}
|
|
1973
2087
|
function guidelineRules(body) {
|
|
1974
2088
|
const at = body.search(/^## /m);
|
|
@@ -2004,8 +2118,9 @@ var init_guidelines = __esm({
|
|
|
2004
2118
|
init_types();
|
|
2005
2119
|
init_layers();
|
|
2006
2120
|
init_engine();
|
|
2121
|
+
init_scopes();
|
|
2007
2122
|
GUIDELINES_ANCHOR = "rules";
|
|
2008
|
-
generality = (l) => ({ org: 0,
|
|
2123
|
+
generality = (l) => ({ org: 0, group: 1, source: 2, robot: 3, user: 3 })[layerKind(l)] ?? 4;
|
|
2009
2124
|
}
|
|
2010
2125
|
});
|
|
2011
2126
|
|
|
@@ -2191,7 +2306,7 @@ function redactDeep(value, opts = {}) {
|
|
|
2191
2306
|
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, redactDeep(v, opts)]));
|
|
2192
2307
|
return value;
|
|
2193
2308
|
}
|
|
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,
|
|
2309
|
+
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, NAME2, Q, VALUE, ARGS, STRONG_LAST, KEY_FILE, plainValue, PATTERNS;
|
|
2195
2310
|
var init_redact = __esm({
|
|
2196
2311
|
"../core/src/redact.ts"() {
|
|
2197
2312
|
"use strict";
|
|
@@ -2270,7 +2385,7 @@ var init_redact = __esm({
|
|
|
2270
2385
|
CARD_DIGITS = new Uint8Array(64);
|
|
2271
2386
|
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/;
|
|
2272
2387
|
NOREPLY_RE = /^(?:no[._-]?reply|donot[._-]?reply|do[._-]?not[._-]?reply|noreply\+?.*|git|hg)$/i;
|
|
2273
|
-
|
|
2388
|
+
NAME2 = String.raw`(?=[A-Za-z_$])(?=[\w.$-]{0,64}?(?:key|token|secret|pass|pwd|credential|auth|cookie|session|creds))[A-Za-z_$][\w.$-]{0,127}`;
|
|
2274
2389
|
Q = String.raw`(?:\\\\){0,8}\\?["'\x60]`;
|
|
2275
2390
|
VALUE = String.raw`(?:(${Q})(?!\s)(.{0,4096}?)\4|(${Q})?([^\s"'\x60\\,]{1,4096}))`;
|
|
2276
2391
|
ARGS = String.raw`[^\n|;&]{0,256}?`;
|
|
@@ -2356,7 +2471,7 @@ var init_redact = __esm({
|
|
|
2356
2471
|
},
|
|
2357
2472
|
{
|
|
2358
2473
|
kind: "assigned-secret",
|
|
2359
|
-
re: new RegExp(String.raw`(?:(?<![\w.$-])|(?=["'\x60\\]))((?:\\\\){0,8}\\?["'\x60]?)(${
|
|
2474
|
+
re: new RegExp(String.raw`(?:(?<![\w.$-])|(?=["'\x60\\]))((?:\\\\){0,8}\\?["'\x60]?)(${NAME2})\1(\??\s{0,64}(?::=|=>|==?|:(?=[\s"'\x60\\]|$))[ \t]{0,64})${VALUE}`, "gim"),
|
|
2360
2475
|
retryFrom: (m) => m[1].length * 2 + m[2].length + m[3].length,
|
|
2361
2476
|
replace: (m, R, at, all) => {
|
|
2362
2477
|
const [whole, , name, , q1, inQuotes, q2, bare] = m;
|
|
@@ -2396,7 +2511,7 @@ var init_redact = __esm({
|
|
|
2396
2511
|
// YAML block scalar: `TOKEN: |` and the indented lines under it.
|
|
2397
2512
|
{
|
|
2398
2513
|
kind: "assigned-secret",
|
|
2399
|
-
re: new RegExp(String.raw`^([ \t]{0,64})((?:-[ \t]{1,16})?(["']?)(${
|
|
2514
|
+
re: new RegExp(String.raw`^([ \t]{0,64})((?:-[ \t]{1,16})?(["']?)(${NAME2})\3[ \t]{0,16}:[ \t]{0,16}[|>][+-]?\d?[ \t]{0,16})((?:\n(?:[ \t]{0,64}\n){0,8}\1[ \t]{1,64}\S[^\n]{0,4096}){1,500})`, "gim"),
|
|
2400
2515
|
replace: (m, R) => {
|
|
2401
2516
|
const [, indent, head, , name, body] = m;
|
|
2402
2517
|
if (!secretName(name)) return null;
|
|
@@ -2668,7 +2783,15 @@ ${input.pendingNew.map((p) => `- ${p}`).join("\n")}
|
|
|
2668
2783
|
Already captured this session (do not repeat): ${input.captured.length ? input.captured.join(", ") : "none"}
|
|
2669
2784
|
|
|
2670
2785
|
Layers you may target: ${input.layers.join(", ")}.
|
|
2671
|
-
${input.
|
|
2786
|
+
${input.scopes?.length ? `
|
|
2787
|
+
Where a lesson applies (put each at the narrowest scope it holds for; when unsure, the narrowest):
|
|
2788
|
+
${[...input.scopes].reverse().map((x) => `- ${x.layer}: ${x.label}`).join("\n")}
|
|
2789
|
+
- org: everywhere in this workspace
|
|
2790
|
+
A fact, command or habit of this project \u2192 this project. True of every project in a group alike \u2192
|
|
2791
|
+
that group. True anywhere this workspace works \u2192 org. Lessons that come up again in other scopes
|
|
2792
|
+
are widened by the nightly generalization pass, so a narrow scope is never a loss. Name it as
|
|
2793
|
+
"layer" in the target of a new document (an edit keeps the document's own scope).
|
|
2794
|
+
` : ""}${input.profileId ? `- How THIS person works, prefers or wants things done (a steering or approach lesson about them,
|
|
2672
2795
|
not about the task) \u2192 op "append" to their profile ${input.profileId}, anchor "how-i-work".
|
|
2673
2796
|
A lesson for anyone doing the task belongs in the task's document instead.
|
|
2674
2797
|
` : ""}- Knowledge about one repo that its team owns \u2192 that repo's section (repo:... ids).
|
|
@@ -2952,6 +3075,8 @@ var init_reconcile = __esm({
|
|
|
2952
3075
|
init_document();
|
|
2953
3076
|
init_markdown();
|
|
2954
3077
|
init_text_index();
|
|
3078
|
+
init_scopes();
|
|
3079
|
+
init_sources();
|
|
2955
3080
|
init_types();
|
|
2956
3081
|
}
|
|
2957
3082
|
});
|
|
@@ -2993,13 +3118,25 @@ var init_profile = __esm({
|
|
|
2993
3118
|
}
|
|
2994
3119
|
});
|
|
2995
3120
|
|
|
2996
|
-
// ../core/src/v2/
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3121
|
+
// ../core/src/v2/generalize.ts
|
|
3122
|
+
var init_generalize = __esm({
|
|
3123
|
+
"../core/src/v2/generalize.ts"() {
|
|
3124
|
+
"use strict";
|
|
3125
|
+
init_layers();
|
|
3126
|
+
init_guidelines();
|
|
3127
|
+
init_reflect();
|
|
3128
|
+
init_scopes();
|
|
3129
|
+
}
|
|
3130
|
+
});
|
|
3131
|
+
|
|
3132
|
+
// ../core/src/v2/adopt.ts
|
|
3133
|
+
var init_adopt = __esm({
|
|
3134
|
+
"../core/src/v2/adopt.ts"() {
|
|
3002
3135
|
"use strict";
|
|
3136
|
+
init_text();
|
|
3137
|
+
init_layers();
|
|
3138
|
+
init_scopes();
|
|
3139
|
+
init_sources();
|
|
3003
3140
|
}
|
|
3004
3141
|
});
|
|
3005
3142
|
|
|
@@ -3025,6 +3162,9 @@ var init_v2 = __esm({
|
|
|
3025
3162
|
init_profile();
|
|
3026
3163
|
init_guidelines();
|
|
3027
3164
|
init_sources();
|
|
3165
|
+
init_scopes();
|
|
3166
|
+
init_generalize();
|
|
3167
|
+
init_adopt();
|
|
3028
3168
|
}
|
|
3029
3169
|
});
|
|
3030
3170
|
|
|
@@ -3123,72 +3263,354 @@ var init_api = __esm({
|
|
|
3123
3263
|
}
|
|
3124
3264
|
});
|
|
3125
3265
|
|
|
3126
|
-
//
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3266
|
+
// src/v2/repo.ts
|
|
3267
|
+
import fs6 from "node:fs";
|
|
3268
|
+
import path5 from "node:path";
|
|
3269
|
+
import { execFileSync } from "node:child_process";
|
|
3270
|
+
function git(cwd, args, timeout = 1500) {
|
|
3271
|
+
try {
|
|
3272
|
+
return execFileSync("git", ["-C", cwd, ...args], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout, maxBuffer: 64 * 1024 * 1024 });
|
|
3273
|
+
} catch {
|
|
3274
|
+
return null;
|
|
3130
3275
|
}
|
|
3131
|
-
}
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
);
|
|
3276
|
+
}
|
|
3277
|
+
function repoRoot(cwd, storeRoot) {
|
|
3278
|
+
const key = `${cwd}
|
|
3279
|
+
${storeRoot ?? ""}`;
|
|
3280
|
+
const hit = rootCache.get(key);
|
|
3281
|
+
if (hit) return hit;
|
|
3282
|
+
const top = git(cwd, ["rev-parse", "--show-toplevel"])?.trim();
|
|
3283
|
+
let root = top || "";
|
|
3284
|
+
if (!root && storeRoot && path5.basename(storeRoot) === ".bunshin") {
|
|
3285
|
+
const parent = path5.dirname(storeRoot);
|
|
3286
|
+
if (path5.resolve(cwd).startsWith(path5.resolve(parent))) root = parent;
|
|
3143
3287
|
}
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3288
|
+
root = root || path5.resolve(cwd);
|
|
3289
|
+
rootCache.set(key, root);
|
|
3290
|
+
return root;
|
|
3291
|
+
}
|
|
3292
|
+
function parseRemote(url) {
|
|
3293
|
+
const clean2 = url.trim().replace(/\.git$/, "").replace(/\/+$/, "");
|
|
3294
|
+
if (!clean2) return null;
|
|
3295
|
+
const scp = clean2.match(/^[^@/\s]+@[^:/\s]+:(.+)$/);
|
|
3296
|
+
const pathPart = scp ? scp[1] : (() => {
|
|
3297
|
+
try {
|
|
3298
|
+
return new URL(clean2).pathname;
|
|
3299
|
+
} catch {
|
|
3300
|
+
return clean2;
|
|
3301
|
+
}
|
|
3302
|
+
})();
|
|
3303
|
+
const segs = pathPart.split("/").filter(Boolean);
|
|
3304
|
+
if (segs.length >= 2) return `${segs[segs.length - 2]}/${segs[segs.length - 1]}`;
|
|
3305
|
+
return segs[0] ?? null;
|
|
3306
|
+
}
|
|
3307
|
+
function repoName(root) {
|
|
3308
|
+
const url = git(root, ["remote", "get-url", "origin"]);
|
|
3309
|
+
return url && parseRemote(url) || path5.basename(root);
|
|
3310
|
+
}
|
|
3311
|
+
function repoCommit(root) {
|
|
3312
|
+
return git(root, ["rev-parse", "HEAD"])?.trim() || void 0;
|
|
3313
|
+
}
|
|
3314
|
+
function listDocs(root) {
|
|
3315
|
+
const listed = git(root, ["ls-files", "-co", "--exclude-standard", "-z"], 3e3);
|
|
3316
|
+
if (listed !== null) return [...new Set(listed.split("\0").filter(Boolean).filter(isDoc))].filter((rel) => fs6.existsSync(path5.join(root, rel)) && !deployedSkill(root, rel)).sort();
|
|
3317
|
+
const out = [];
|
|
3318
|
+
let visited = 0;
|
|
3319
|
+
const walk = (dir, rel, depth) => {
|
|
3320
|
+
if (depth > 8 || visited++ > 2e4) return;
|
|
3321
|
+
let entries;
|
|
3322
|
+
try {
|
|
3323
|
+
entries = fs6.readdirSync(dir, { withFileTypes: true });
|
|
3324
|
+
} catch {
|
|
3325
|
+
return;
|
|
3326
|
+
}
|
|
3327
|
+
for (const e of entries) {
|
|
3328
|
+
if (e.isDirectory()) {
|
|
3329
|
+
if (!SKIP_DIRS.has(e.name) && !fs6.existsSync(path5.join(dir, e.name, ".git"))) walk(path5.join(dir, e.name), rel ? `${rel}/${e.name}` : e.name, depth + 1);
|
|
3330
|
+
} else if (e.isFile()) {
|
|
3331
|
+
const r = rel ? `${rel}/${e.name}` : e.name;
|
|
3332
|
+
if (isDoc(r) && !deployedSkill(root, r)) out.push(r);
|
|
3333
|
+
}
|
|
3334
|
+
}
|
|
3335
|
+
};
|
|
3336
|
+
walk(root, "", 0);
|
|
3337
|
+
return out.sort();
|
|
3338
|
+
}
|
|
3339
|
+
function indexWorkingTree(v2, config, cwd, opts = {}) {
|
|
3340
|
+
const root = repoRoot(cwd, v2.store.root);
|
|
3341
|
+
const now = opts.now ?? Date.now();
|
|
3342
|
+
const cached = v2.readRepoIndex(root);
|
|
3343
|
+
const ownerTeam = config.ownerTeam || config.orgUnit || "unknown";
|
|
3344
|
+
const source = sourceFor(root, v2.store.root);
|
|
3345
|
+
const sameRepo = cached && cached.ownerTeam === ownerTeam && cached.format === INDEX_FORMAT;
|
|
3346
|
+
const repo = sameRepo && cached.repo && !opts.force ? cached.repo : repoName(root);
|
|
3347
|
+
const fresh = sameRepo && cached.repo === repo && cached.paths && cached.listedAt && now - Date.parse(cached.listedAt) < LIST_TTL_MS && !opts.force;
|
|
3348
|
+
const paths = fresh ? cached.paths : listDocs(root);
|
|
3349
|
+
let changed = !fresh || !sameRepo || cached.repo !== repo;
|
|
3350
|
+
const files = {};
|
|
3351
|
+
for (const rel of paths) {
|
|
3352
|
+
const abs = path5.join(root, rel);
|
|
3353
|
+
let stat;
|
|
3354
|
+
try {
|
|
3355
|
+
stat = fs6.statSync(abs);
|
|
3356
|
+
} catch {
|
|
3357
|
+
changed = true;
|
|
3358
|
+
continue;
|
|
3359
|
+
}
|
|
3360
|
+
const prev = sameRepo && cached.repo === repo ? cached.files[rel] : void 0;
|
|
3361
|
+
if (prev && prev.mtimeMs === stat.mtimeMs && prev.size === stat.size) {
|
|
3362
|
+
files[rel] = prev;
|
|
3363
|
+
continue;
|
|
3364
|
+
}
|
|
3365
|
+
changed = true;
|
|
3366
|
+
try {
|
|
3367
|
+
const text2 = fs6.readFileSync(abs, "utf8");
|
|
3368
|
+
files[rel] = { mtimeMs: stat.mtimeMs, size: stat.size, sections: parseRepoDoc({ repo, path: rel, text: text2 }, { orgId: config.orgId, ownerTeam, layer: source ? sourceLayer(source) : "org" }) };
|
|
3369
|
+
} catch {
|
|
3370
|
+
}
|
|
3150
3371
|
}
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3372
|
+
const index = {
|
|
3373
|
+
format: INDEX_FORMAT,
|
|
3374
|
+
repo,
|
|
3375
|
+
ownerTeam,
|
|
3376
|
+
listedAt: fresh ? cached.listedAt : new Date(now).toISOString(),
|
|
3377
|
+
paths,
|
|
3378
|
+
files
|
|
3379
|
+
};
|
|
3380
|
+
if (changed) {
|
|
3381
|
+
try {
|
|
3382
|
+
v2.writeRepoIndex(root, index);
|
|
3383
|
+
} catch {
|
|
3384
|
+
}
|
|
3157
3385
|
}
|
|
3158
|
-
}
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3386
|
+
return { root, index, sections: Object.values(files).flatMap((f) => f.sections) };
|
|
3387
|
+
}
|
|
3388
|
+
function relativeToRoot(root, file, cwd = root) {
|
|
3389
|
+
if (!file) return null;
|
|
3390
|
+
const abs = path5.isAbsolute(file) ? file : path5.resolve(cwd, file);
|
|
3391
|
+
let real = abs;
|
|
3392
|
+
let realRoot = root;
|
|
3393
|
+
try {
|
|
3394
|
+
realRoot = fs6.realpathSync(root);
|
|
3395
|
+
} catch {
|
|
3164
3396
|
}
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3397
|
+
try {
|
|
3398
|
+
real = fs6.realpathSync(abs);
|
|
3399
|
+
} catch {
|
|
3400
|
+
try {
|
|
3401
|
+
real = path5.join(fs6.realpathSync(path5.dirname(abs)), path5.basename(abs));
|
|
3402
|
+
} catch {
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3405
|
+
for (const [r, f] of [
|
|
3406
|
+
[realRoot, real],
|
|
3407
|
+
[root, abs]
|
|
3408
|
+
]) {
|
|
3409
|
+
const rel = path5.relative(r, f);
|
|
3410
|
+
if (rel && !rel.startsWith("..") && !path5.isAbsolute(rel)) return rel.split(path5.sep).join("/");
|
|
3411
|
+
}
|
|
3412
|
+
return null;
|
|
3413
|
+
}
|
|
3414
|
+
var DOC_NAMES, SKIP_DIRS, LIST_TTL_MS, rootCache, isDoc, deployedSkill, INDEX_FORMAT;
|
|
3415
|
+
var init_repo = __esm({
|
|
3416
|
+
"src/v2/repo.ts"() {
|
|
3170
3417
|
"use strict";
|
|
3418
|
+
init_v22();
|
|
3419
|
+
init_source();
|
|
3420
|
+
DOC_NAMES = /* @__PURE__ */ new Set(["spec.md", "testing.md", "skill.md"]);
|
|
3421
|
+
SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".bunshin"]);
|
|
3422
|
+
LIST_TTL_MS = 6e4;
|
|
3423
|
+
rootCache = /* @__PURE__ */ new Map();
|
|
3424
|
+
isDoc = (rel) => {
|
|
3425
|
+
const parts = rel.split("/");
|
|
3426
|
+
if (parts.some((p) => SKIP_DIRS.has(p))) return false;
|
|
3427
|
+
return DOC_NAMES.has(parts[parts.length - 1].toLowerCase());
|
|
3428
|
+
};
|
|
3429
|
+
deployedSkill = (root, rel) => /(^|\/)SKILL\.md$/i.test(rel) && fs6.existsSync(path5.join(root, path5.posix.dirname(rel), ".bunshin.json"));
|
|
3430
|
+
INDEX_FORMAT = 2;
|
|
3171
3431
|
}
|
|
3172
3432
|
});
|
|
3173
3433
|
|
|
3174
|
-
//
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3434
|
+
// src/v2/source.ts
|
|
3435
|
+
var source_exports = {};
|
|
3436
|
+
__export(source_exports, {
|
|
3437
|
+
cwdInPool: () => cwdInPool,
|
|
3438
|
+
inPool: () => inPool,
|
|
3439
|
+
listFolders: () => listFolders,
|
|
3440
|
+
projectRootFor: () => projectRootFor,
|
|
3441
|
+
select: () => select,
|
|
3442
|
+
sourceFor: () => sourceFor,
|
|
3443
|
+
storeHome: () => storeHome,
|
|
3444
|
+
topFolder: () => topFolder
|
|
3445
|
+
});
|
|
3446
|
+
import fs7 from "node:fs";
|
|
3447
|
+
import path6 from "node:path";
|
|
3448
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
3449
|
+
function storeHome(storeRoot) {
|
|
3450
|
+
return path6.dirname(path6.resolve(storeRoot));
|
|
3451
|
+
}
|
|
3452
|
+
function topFolder(cwd, storeRoot) {
|
|
3453
|
+
const rel = path6.relative(storeHome(storeRoot), path6.resolve(cwd));
|
|
3454
|
+
if (!rel || rel.startsWith("..") || path6.isAbsolute(rel)) return "";
|
|
3455
|
+
return rel.split(path6.sep)[0] ?? "";
|
|
3456
|
+
}
|
|
3457
|
+
function inPool(selection, folder) {
|
|
3458
|
+
if (!folder || !selection) return true;
|
|
3459
|
+
if (selection.exclude?.includes(folder)) return false;
|
|
3460
|
+
return selection.only ? selection.only.includes(folder) : true;
|
|
3461
|
+
}
|
|
3462
|
+
function cwdInPool(config, cwd, storeRoot) {
|
|
3463
|
+
return inPool(config.sources, topFolder(cwd, storeRoot));
|
|
3464
|
+
}
|
|
3465
|
+
function git2(cwd, args) {
|
|
3466
|
+
try {
|
|
3467
|
+
return execFileSync2("git", ["-C", cwd, ...args], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 1500 }).trim() || null;
|
|
3468
|
+
} catch {
|
|
3469
|
+
return null;
|
|
3470
|
+
}
|
|
3471
|
+
}
|
|
3472
|
+
function sourceFor(cwd, storeRoot, ctx) {
|
|
3473
|
+
if (ctx?.kind === "robot") return sourceId("robot", ctx.id);
|
|
3474
|
+
const key = `${path6.resolve(cwd)}
|
|
3475
|
+
${storeRoot}`;
|
|
3476
|
+
if (sourceCache.has(key)) return sourceCache.get(key);
|
|
3477
|
+
const found = findSource(cwd, storeRoot);
|
|
3478
|
+
sourceCache.set(key, found);
|
|
3479
|
+
return found;
|
|
3480
|
+
}
|
|
3481
|
+
function findSource(cwd, storeRoot) {
|
|
3482
|
+
const home = storeHome(storeRoot);
|
|
3483
|
+
const top = git2(cwd, ["rev-parse", "--show-toplevel"]);
|
|
3484
|
+
if (top) {
|
|
3485
|
+
const remote = git2(top, ["remote", "get-url", "origin"]);
|
|
3486
|
+
const named = remote ? parseRemote(remote) : null;
|
|
3487
|
+
if (named) return sourceId("repo", named);
|
|
3488
|
+
const rel = path6.relative(home, top);
|
|
3489
|
+
return sourceId("folder", rel && !rel.startsWith("..") && !path6.isAbsolute(rel) ? rel.split(path6.sep).join("/") : path6.basename(top));
|
|
3490
|
+
}
|
|
3491
|
+
const folder = topFolder(cwd, storeRoot);
|
|
3492
|
+
return folder ? sourceId("folder", folder) : void 0;
|
|
3493
|
+
}
|
|
3494
|
+
function projectRootFor(cwd, storeRoot) {
|
|
3495
|
+
const home = storeHome(storeRoot);
|
|
3496
|
+
const top = git2(cwd, ["rev-parse", "--show-toplevel"]);
|
|
3497
|
+
if (top) {
|
|
3498
|
+
const rel = path6.relative(home, top);
|
|
3499
|
+
if (!rel.startsWith("..") && !path6.isAbsolute(rel)) return top;
|
|
3500
|
+
}
|
|
3501
|
+
const folder = topFolder(cwd, storeRoot);
|
|
3502
|
+
return folder ? path6.join(home, folder) : home;
|
|
3503
|
+
}
|
|
3504
|
+
function listFolders(storeRoot, selection, opts = {}) {
|
|
3505
|
+
const home = storeHome(storeRoot);
|
|
3506
|
+
let names = [];
|
|
3507
|
+
try {
|
|
3508
|
+
names = fs7.readdirSync(home, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort((a, b) => a.localeCompare(b));
|
|
3509
|
+
} catch {
|
|
3510
|
+
}
|
|
3511
|
+
return names.map((folder) => ({
|
|
3512
|
+
folder,
|
|
3513
|
+
included: inPool(selection, folder),
|
|
3514
|
+
...opts.sources ? { source: sourceFor(path6.join(home, folder), storeRoot) } : {}
|
|
3515
|
+
}));
|
|
3516
|
+
}
|
|
3517
|
+
function select(current, action, folders) {
|
|
3518
|
+
const uniq = (xs) => [...new Set(xs)].sort((a, b) => a.localeCompare(b));
|
|
3519
|
+
const only = current?.only;
|
|
3520
|
+
const exclude = current?.exclude ?? [];
|
|
3521
|
+
let next;
|
|
3522
|
+
switch (action) {
|
|
3523
|
+
case "all":
|
|
3524
|
+
return void 0;
|
|
3525
|
+
case "only":
|
|
3526
|
+
next = { only: uniq(folders) };
|
|
3527
|
+
break;
|
|
3528
|
+
case "include":
|
|
3529
|
+
next = { ...only ? { only: uniq([...only, ...folders]) } : {}, exclude: exclude.filter((f) => !folders.includes(f)) };
|
|
3530
|
+
break;
|
|
3531
|
+
case "exclude":
|
|
3532
|
+
next = { ...only ? { only: only.filter((f) => !folders.includes(f)) } : {}, exclude: uniq([...exclude, ...folders]) };
|
|
3533
|
+
break;
|
|
3534
|
+
}
|
|
3535
|
+
if (!next.exclude?.length) delete next.exclude;
|
|
3536
|
+
return next.only || next.exclude ? next : void 0;
|
|
3537
|
+
}
|
|
3538
|
+
var sourceCache;
|
|
3539
|
+
var init_source = __esm({
|
|
3540
|
+
"src/v2/source.ts"() {
|
|
3541
|
+
"use strict";
|
|
3542
|
+
init_v22();
|
|
3543
|
+
init_repo();
|
|
3544
|
+
sourceCache = /* @__PURE__ */ new Map();
|
|
3545
|
+
}
|
|
3546
|
+
});
|
|
3547
|
+
|
|
3548
|
+
// ../core/src/types.ts
|
|
3549
|
+
var init_types2 = __esm({
|
|
3550
|
+
"../core/src/types.ts"() {
|
|
3551
|
+
"use strict";
|
|
3552
|
+
}
|
|
3553
|
+
});
|
|
3554
|
+
|
|
3555
|
+
// ../core/src/gate.ts
|
|
3556
|
+
var CURRENT_CHAT_PREFIX, ANAPHORIC_QUESTION;
|
|
3557
|
+
var init_gate = __esm({
|
|
3558
|
+
"../core/src/gate.ts"() {
|
|
3559
|
+
"use strict";
|
|
3560
|
+
CURRENT_CHAT_PREFIX = "(?:(?:so\\s+)?(?:in|within)\\s+(?:this|the)\\s+(?:chat|conversation|session)\\s*,?\\s*)?";
|
|
3561
|
+
ANAPHORIC_QUESTION = new RegExp(
|
|
3562
|
+
"^" + CURRENT_CHAT_PREFIX + "(?:(?:and\\s+)?(?:does|do|did|is|are|was|were|can|could|would|will|has|have|had)\\s+.*\\b(?:this|that|it|these|those|they)\\b.*|(?:and\\s+)?(?:does|do|did|is|are|was|were|can|could|would|will|has|have|had)\\b.*\\b(?:also|too|as\\s+well)\\b.*|(?:what|why|how|where|when)\\s+(?:does|do|did|is|are|was|were)\\s+(?:this|that|it|these|those|they)\\b.*|(?:can|could|would)\\s+you\\s+(?:explain|clarify|show)\\s+(?:this|that|it)\\b.*)$",
|
|
3563
|
+
"i"
|
|
3564
|
+
);
|
|
3565
|
+
}
|
|
3566
|
+
});
|
|
3567
|
+
|
|
3568
|
+
// ../core/src/metrics.ts
|
|
3569
|
+
var init_metrics = __esm({
|
|
3570
|
+
"../core/src/metrics.ts"() {
|
|
3571
|
+
"use strict";
|
|
3572
|
+
}
|
|
3573
|
+
});
|
|
3574
|
+
|
|
3575
|
+
// ../core/src/lesson/id.ts
|
|
3576
|
+
var init_id = __esm({
|
|
3577
|
+
"../core/src/lesson/id.ts"() {
|
|
3578
|
+
"use strict";
|
|
3579
|
+
}
|
|
3580
|
+
});
|
|
3581
|
+
|
|
3582
|
+
// ../core/src/lesson/markdown.ts
|
|
3583
|
+
var init_markdown2 = __esm({
|
|
3584
|
+
"../core/src/lesson/markdown.ts"() {
|
|
3585
|
+
"use strict";
|
|
3586
|
+
}
|
|
3587
|
+
});
|
|
3588
|
+
|
|
3589
|
+
// ../core/src/lesson/index.ts
|
|
3590
|
+
var init_lesson = __esm({
|
|
3591
|
+
"../core/src/lesson/index.ts"() {
|
|
3592
|
+
"use strict";
|
|
3593
|
+
}
|
|
3594
|
+
});
|
|
3595
|
+
|
|
3596
|
+
// ../core/src/serve/score.ts
|
|
3597
|
+
function classifyTaskMode(task) {
|
|
3598
|
+
const t = (task || "").trim();
|
|
3599
|
+
if (INVESTIGATE_RE.test(t) && !BUILD_RE.test(t)) return "investigate";
|
|
3600
|
+
if (BUILD_RE.test(t)) return "build";
|
|
3601
|
+
if (CONVERSE_RE.test(t) || t.endsWith("?")) return "converse";
|
|
3602
|
+
return "build";
|
|
3603
|
+
}
|
|
3604
|
+
function lessonText(lesson) {
|
|
3605
|
+
const extra = lesson.kind === "workflow" ? [...lesson.preconditions, ...lesson.steps.map((s) => `${s.action} ${s.detail ?? ""} ${s.verify ?? ""}`), lesson.outcome].join(" ") : "";
|
|
3606
|
+
return `${lesson.title}
|
|
3607
|
+
${lesson.gist}
|
|
3608
|
+
${lesson.tags.join(" ")}
|
|
3609
|
+
${lesson.content}
|
|
3610
|
+
${extra}`;
|
|
3611
|
+
}
|
|
3612
|
+
function score(lesson, request, profile, textRelevance, opts = {}) {
|
|
3613
|
+
const w = { ...DEFAULT_WEIGHTS, ...opts.weights ?? {} };
|
|
3192
3614
|
const now = opts.now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
3193
3615
|
const reasons = [];
|
|
3194
3616
|
const mode = classifyTaskMode(request.task);
|
|
@@ -3643,8 +4065,8 @@ var init_src = __esm({
|
|
|
3643
4065
|
});
|
|
3644
4066
|
|
|
3645
4067
|
// src/v2/store.ts
|
|
3646
|
-
import
|
|
3647
|
-
import
|
|
4068
|
+
import fs8 from "node:fs";
|
|
4069
|
+
import path7 from "node:path";
|
|
3648
4070
|
import { createHash as createHash2, randomBytes as randomBytes3 } from "node:crypto";
|
|
3649
4071
|
function hexId(prefix) {
|
|
3650
4072
|
return `${prefix}_${randomBytes3(6).toString("hex")}`;
|
|
@@ -3678,12 +4100,12 @@ var init_store2 = __esm({
|
|
|
3678
4100
|
V2Store = class {
|
|
3679
4101
|
constructor(store) {
|
|
3680
4102
|
this.store = store;
|
|
3681
|
-
this.dir =
|
|
4103
|
+
this.dir = path7.join(store.root, "v2");
|
|
3682
4104
|
}
|
|
3683
4105
|
store;
|
|
3684
4106
|
dir;
|
|
3685
4107
|
p(...parts) {
|
|
3686
|
-
return
|
|
4108
|
+
return path7.join(this.dir, ...parts);
|
|
3687
4109
|
}
|
|
3688
4110
|
// ── context / bundle / params ──
|
|
3689
4111
|
readContext() {
|
|
@@ -3764,9 +4186,9 @@ var init_store2 = __esm({
|
|
|
3764
4186
|
const dir = this.p("sessions");
|
|
3765
4187
|
if (!fileExists(dir)) return [];
|
|
3766
4188
|
const out = [];
|
|
3767
|
-
for (const f of
|
|
4189
|
+
for (const f of fs8.readdirSync(dir)) {
|
|
3768
4190
|
if (!f.endsWith(".json") || f === "current.json") continue;
|
|
3769
|
-
const raw = readJson(
|
|
4191
|
+
const raw = readJson(path7.join(dir, f), null);
|
|
3770
4192
|
if (raw?.sessionId) out.push(normalizeSession(raw.sessionId, raw));
|
|
3771
4193
|
}
|
|
3772
4194
|
return out;
|
|
@@ -3794,7 +4216,7 @@ var init_store2 = __esm({
|
|
|
3794
4216
|
let at = raw.at;
|
|
3795
4217
|
if (!at) {
|
|
3796
4218
|
try {
|
|
3797
|
-
at =
|
|
4219
|
+
at = fs8.statSync(this.p("outcome", `${fileSafe(sessionId)}.json`)).mtime.toISOString();
|
|
3798
4220
|
} catch {
|
|
3799
4221
|
at = (/* @__PURE__ */ new Date()).toISOString();
|
|
3800
4222
|
}
|
|
@@ -3821,13 +4243,13 @@ var init_store2 = __esm({
|
|
|
3821
4243
|
/** Exclusive lock file; returns a release function, or null when another process holds it. */
|
|
3822
4244
|
tryLock(name, staleMs = 10 * 6e4) {
|
|
3823
4245
|
const file = this.p("locks", `${fileSafe(name)}.lock`);
|
|
3824
|
-
ensureDir(
|
|
4246
|
+
ensureDir(path7.dirname(file));
|
|
3825
4247
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
3826
4248
|
try {
|
|
3827
|
-
|
|
4249
|
+
fs8.writeFileSync(file, String(process.pid), { flag: "wx" });
|
|
3828
4250
|
return () => {
|
|
3829
4251
|
try {
|
|
3830
|
-
|
|
4252
|
+
fs8.unlinkSync(file);
|
|
3831
4253
|
} catch {
|
|
3832
4254
|
}
|
|
3833
4255
|
};
|
|
@@ -3835,8 +4257,8 @@ var init_store2 = __esm({
|
|
|
3835
4257
|
if (err.code !== "EEXIST") return null;
|
|
3836
4258
|
let stale = false;
|
|
3837
4259
|
try {
|
|
3838
|
-
const owner = Number(
|
|
3839
|
-
const age = Date.now() -
|
|
4260
|
+
const owner = Number(fs8.readFileSync(file, "utf8"));
|
|
4261
|
+
const age = Date.now() - fs8.statSync(file).mtimeMs;
|
|
3840
4262
|
if (age > staleMs) stale = true;
|
|
3841
4263
|
else if (Number.isInteger(owner) && owner > 0) {
|
|
3842
4264
|
try {
|
|
@@ -3850,7 +4272,7 @@ var init_store2 = __esm({
|
|
|
3850
4272
|
}
|
|
3851
4273
|
if (!stale) return null;
|
|
3852
4274
|
try {
|
|
3853
|
-
|
|
4275
|
+
fs8.unlinkSync(file);
|
|
3854
4276
|
} catch {
|
|
3855
4277
|
}
|
|
3856
4278
|
}
|
|
@@ -3867,15 +4289,15 @@ var init_store2 = __esm({
|
|
|
3867
4289
|
const dir = this.p(name);
|
|
3868
4290
|
if (!fileExists(dir)) return [];
|
|
3869
4291
|
const out = [];
|
|
3870
|
-
for (const f of
|
|
4292
|
+
for (const f of fs8.readdirSync(dir)) {
|
|
3871
4293
|
if (!f.endsWith(".json")) continue;
|
|
3872
|
-
const v = readJson(
|
|
4294
|
+
const v = readJson(path7.join(dir, f), null);
|
|
3873
4295
|
if (v) out.push(v);
|
|
3874
4296
|
}
|
|
3875
4297
|
return out;
|
|
3876
4298
|
}
|
|
3877
4299
|
};
|
|
3878
|
-
rootKey = (root) => createHash2("sha256").update(
|
|
4300
|
+
rootKey = (root) => createHash2("sha256").update(path7.resolve(root)).digest("hex").slice(0, 16);
|
|
3879
4301
|
addUnique = (list2, ...ids) => {
|
|
3880
4302
|
for (const id of ids) if (id && !list2.includes(id)) list2.push(id);
|
|
3881
4303
|
};
|
|
@@ -3883,15 +4305,15 @@ var init_store2 = __esm({
|
|
|
3883
4305
|
});
|
|
3884
4306
|
|
|
3885
4307
|
// src/v2/deploy.ts
|
|
3886
|
-
import { execFileSync } from "node:child_process";
|
|
4308
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
3887
4309
|
import { createHash as createHash3 } from "node:crypto";
|
|
3888
|
-
import
|
|
4310
|
+
import fs9 from "node:fs";
|
|
3889
4311
|
import os2 from "node:os";
|
|
3890
|
-
import
|
|
4312
|
+
import path8 from "node:path";
|
|
3891
4313
|
function deployWorkspace(env = process.env) {
|
|
3892
|
-
if (env.BUNSHIN_DEPLOY_WORKSPACE) return
|
|
4314
|
+
if (env.BUNSHIN_DEPLOY_WORKSPACE) return path8.resolve(env.BUNSHIN_DEPLOY_WORKSPACE);
|
|
3893
4315
|
try {
|
|
3894
|
-
const ws = JSON.parse(
|
|
4316
|
+
const ws = JSON.parse(fs9.readFileSync(settingsFile(env.BUNSHIN_DEPLOY_HOME || os2.homedir()), "utf8")).workspace;
|
|
3895
4317
|
return typeof ws === "string" && ws ? ws : null;
|
|
3896
4318
|
} catch {
|
|
3897
4319
|
return null;
|
|
@@ -3900,24 +4322,24 @@ function deployWorkspace(env = process.env) {
|
|
|
3900
4322
|
function deployPaths(env = process.env, orgId) {
|
|
3901
4323
|
const home = env.BUNSHIN_DEPLOY_HOME || os2.homedir();
|
|
3902
4324
|
const ws = deployWorkspace(env);
|
|
3903
|
-
const state =
|
|
3904
|
-
const shared = { claude:
|
|
3905
|
-
const org = orgId ?
|
|
4325
|
+
const state = path8.join(home, ".bunshin", "deploy");
|
|
4326
|
+
const shared = { claude: path8.join(ws ?? home, ".claude", "skills"), codex: path8.join(ws ?? home, ".codex", "skills") };
|
|
4327
|
+
const org = orgId ? path8.join(state, orgKey(orgId)) : null;
|
|
3906
4328
|
return {
|
|
3907
4329
|
home,
|
|
3908
|
-
claudeSkills: org ?
|
|
3909
|
-
codexSkills: org ?
|
|
3910
|
-
...org ? { legacy: [.../* @__PURE__ */ new Set([shared.claude, shared.codex,
|
|
3911
|
-
claudeMd: ws ?
|
|
3912
|
-
agentsMd: ws ?
|
|
3913
|
-
library: org ?
|
|
4330
|
+
claudeSkills: org ? path8.join(org, "claude-skills") : shared.claude,
|
|
4331
|
+
codexSkills: org ? path8.join(org, "codex-skills") : shared.codex,
|
|
4332
|
+
...org ? { legacy: [.../* @__PURE__ */ new Set([shared.claude, shared.codex, path8.join(home, ".claude", "skills"), path8.join(home, ".codex", "skills"), path8.join(home, ".bunshin", "library")])] } : {},
|
|
4333
|
+
claudeMd: ws ? path8.join(ws, "CLAUDE.md") : path8.join(home, ".claude", "CLAUDE.md"),
|
|
4334
|
+
agentsMd: ws ? path8.join(ws, "AGENTS.md") : path8.join(home, ".codex", "AGENTS.md"),
|
|
4335
|
+
library: org ? path8.join(org, "library") : path8.join(home, ".bunshin", "library"),
|
|
3914
4336
|
// Pack executables are linked into one folder on PATH (shared by name; their files are per organization).
|
|
3915
|
-
bin:
|
|
4337
|
+
bin: path8.join(home, ".bunshin", "bin"),
|
|
3916
4338
|
state
|
|
3917
4339
|
};
|
|
3918
4340
|
}
|
|
3919
4341
|
function statePath(p, orgId) {
|
|
3920
|
-
return
|
|
4342
|
+
return path8.join(p.state, `${orgKey(orgId)}.json`);
|
|
3921
4343
|
}
|
|
3922
4344
|
function readState(p, orgId) {
|
|
3923
4345
|
const s = readJson(statePath(p, orgId), null);
|
|
@@ -3925,7 +4347,7 @@ function readState(p, orgId) {
|
|
|
3925
4347
|
}
|
|
3926
4348
|
function readText(file) {
|
|
3927
4349
|
try {
|
|
3928
|
-
return
|
|
4350
|
+
return fs9.readFileSync(file, "utf8");
|
|
3929
4351
|
} catch {
|
|
3930
4352
|
return null;
|
|
3931
4353
|
}
|
|
@@ -3933,13 +4355,13 @@ function readText(file) {
|
|
|
3933
4355
|
function writeFile(file, content, mode) {
|
|
3934
4356
|
writeTextAtomic(file, content);
|
|
3935
4357
|
try {
|
|
3936
|
-
|
|
4358
|
+
fs9.chmodSync(file, mode === "x" ? 493 : 420);
|
|
3937
4359
|
} catch {
|
|
3938
4360
|
}
|
|
3939
4361
|
}
|
|
3940
4362
|
function removeFile(file) {
|
|
3941
4363
|
try {
|
|
3942
|
-
|
|
4364
|
+
fs9.unlinkSync(file);
|
|
3943
4365
|
} catch {
|
|
3944
4366
|
}
|
|
3945
4367
|
}
|
|
@@ -3947,33 +4369,38 @@ function pruneDirs(dir, stop) {
|
|
|
3947
4369
|
let d = dir;
|
|
3948
4370
|
while (d.startsWith(stop) && d !== stop) {
|
|
3949
4371
|
try {
|
|
3950
|
-
if (
|
|
3951
|
-
|
|
4372
|
+
if (fs9.readdirSync(d).length) return;
|
|
4373
|
+
fs9.rmdirSync(d);
|
|
3952
4374
|
} catch {
|
|
3953
4375
|
return;
|
|
3954
4376
|
}
|
|
3955
|
-
d =
|
|
4377
|
+
d = path8.dirname(d);
|
|
3956
4378
|
}
|
|
3957
4379
|
}
|
|
3958
4380
|
function listFiles(dir, base = dir) {
|
|
3959
4381
|
let entries;
|
|
3960
4382
|
try {
|
|
3961
|
-
entries =
|
|
4383
|
+
entries = fs9.readdirSync(dir, { withFileTypes: true });
|
|
3962
4384
|
} catch {
|
|
3963
4385
|
return [];
|
|
3964
4386
|
}
|
|
3965
4387
|
const out = [];
|
|
3966
4388
|
for (const e of entries) {
|
|
3967
|
-
const abs =
|
|
4389
|
+
const abs = path8.join(dir, e.name);
|
|
3968
4390
|
if (e.isDirectory()) out.push(...listFiles(abs, base));
|
|
3969
|
-
else if (e.isFile()) out.push(
|
|
4391
|
+
else if (e.isFile()) out.push(path8.relative(base, abs).split(path8.sep).join("/"));
|
|
3970
4392
|
}
|
|
3971
4393
|
return out.sort();
|
|
3972
4394
|
}
|
|
3973
4395
|
function childDir(root, name) {
|
|
3974
4396
|
if (!name || !SAFE_NAME.test(name)) return null;
|
|
3975
|
-
const dir =
|
|
3976
|
-
return
|
|
4397
|
+
const dir = path8.join(root, name);
|
|
4398
|
+
return path8.dirname(path8.resolve(dir)) === path8.resolve(root) && insideRoot(root, dir) ? dir : null;
|
|
4399
|
+
}
|
|
4400
|
+
function layerRank(layer, tree) {
|
|
4401
|
+
const kind = layerKind(layer);
|
|
4402
|
+
if (kind === "group") return 1 + (tree ? groupChain(tree, layerValue(layer)).length : 0);
|
|
4403
|
+
return kind === "source" ? 1e3 : kind === "user" || kind === "robot" ? 2e3 : 0;
|
|
3977
4404
|
}
|
|
3978
4405
|
function planDeploy(bundle, p, harnesses) {
|
|
3979
4406
|
const active = bundle.sections.filter((s) => s.status === "active" || s.status === "stale");
|
|
@@ -3985,7 +4412,7 @@ function planDeploy(bundle, p, harnesses) {
|
|
|
3985
4412
|
continue;
|
|
3986
4413
|
}
|
|
3987
4414
|
const prev = byName.get(s.skill.name);
|
|
3988
|
-
if (!prev || (
|
|
4415
|
+
if (!prev || layerRank(s.layer, bundle.scopes) > layerRank(prev.layer, bundle.scopes)) byName.set(s.skill.name, s);
|
|
3989
4416
|
}
|
|
3990
4417
|
const skills = [...byName.values()].sort((a, b) => a.skill.name.localeCompare(b.skill.name));
|
|
3991
4418
|
const instructions = active.filter((s) => s.delivery === "always" && !s.skill && !s.profileOf).sort((a, b) => (a.packPath ?? a.title).localeCompare(b.packPath ?? b.title));
|
|
@@ -4080,7 +4507,7 @@ function removeBlock(file) {
|
|
|
4080
4507
|
if (start < 0 || end < start) return false;
|
|
4081
4508
|
const rest = (text2.slice(0, start).replace(/\s*$/, "") + "\n" + text2.slice(end + BLOCK_END.length).replace(/^\s*/, "")).trim();
|
|
4082
4509
|
if (rest) writeTextAtomic(file, rest + "\n");
|
|
4083
|
-
else
|
|
4510
|
+
else fs9.unlinkSync(file);
|
|
4084
4511
|
return true;
|
|
4085
4512
|
}
|
|
4086
4513
|
function writeBlock(file, block2) {
|
|
@@ -4101,7 +4528,7 @@ function skillIndex(skills, p) {
|
|
|
4101
4528
|
"",
|
|
4102
4529
|
"When a task matches a skill below, read its SKILL.md and follow it before doing the work by hand.",
|
|
4103
4530
|
"",
|
|
4104
|
-
...skills.map((s) => `- **${s.skill.name}** (\`${
|
|
4531
|
+
...skills.map((s) => `- **${s.skill.name}** (\`${path8.join(p.codexSkills, s.skill.name, "SKILL.md")}\`): ${neutralizeMarkers(s.skill.description).replace(/\s+/g, " ").trim()}`)
|
|
4105
4532
|
];
|
|
4106
4533
|
return lines.join("\n");
|
|
4107
4534
|
}
|
|
@@ -4150,7 +4577,7 @@ function captureDoc(c, doc, out) {
|
|
|
4150
4577
|
for (const dir of doc.dirs ?? []) {
|
|
4151
4578
|
for (const rel of listFiles(dir)) {
|
|
4152
4579
|
if (rel === "SKILL.md" || rel === MANIFEST || seen.has(`file:${rel}`)) continue;
|
|
4153
|
-
const abs =
|
|
4580
|
+
const abs = path8.join(dir, rel);
|
|
4154
4581
|
const content = readText(abs);
|
|
4155
4582
|
if (content === null || Buffer.byteLength(content) > MAX_FILE) continue;
|
|
4156
4583
|
byRel.set(`file:${rel}`, { target: { abs, rel, role: "file", sha: "", ...isExecutable(abs) ? { mode: "x" } : {} }, content });
|
|
@@ -4189,18 +4616,18 @@ function captureNewSkills(c, state, roots, owned, incoming, out, layer, only) {
|
|
|
4189
4616
|
for (const root of roots) {
|
|
4190
4617
|
let names = [];
|
|
4191
4618
|
try {
|
|
4192
|
-
names =
|
|
4619
|
+
names = fs9.readdirSync(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
4193
4620
|
} catch {
|
|
4194
4621
|
continue;
|
|
4195
4622
|
}
|
|
4196
4623
|
for (const name of names) {
|
|
4197
|
-
const dir =
|
|
4624
|
+
const dir = path8.join(root, name);
|
|
4198
4625
|
if (owned.has(dir) || incoming.has(name) || only && name !== only) continue;
|
|
4199
|
-
const skillMd = readText(
|
|
4626
|
+
const skillMd = readText(path8.join(dir, "SKILL.md"));
|
|
4200
4627
|
if (skillMd === null) continue;
|
|
4201
4628
|
let born = 0;
|
|
4202
4629
|
try {
|
|
4203
|
-
const st =
|
|
4630
|
+
const st = fs9.statSync(path8.join(dir, "SKILL.md"));
|
|
4204
4631
|
born = Math.min(st.birthtimeMs || st.mtimeMs, st.mtimeMs);
|
|
4205
4632
|
} catch {
|
|
4206
4633
|
continue;
|
|
@@ -4209,8 +4636,8 @@ function captureNewSkills(c, state, roots, owned, incoming, out, layer, only) {
|
|
|
4209
4636
|
const files = {};
|
|
4210
4637
|
for (const rel of listFiles(dir).filter((r) => !r.split("/").some((seg) => seg.startsWith(".")))) {
|
|
4211
4638
|
if (rel === "SKILL.md" || rel === MANIFEST) continue;
|
|
4212
|
-
const content = readText(
|
|
4213
|
-
if (content !== null && Buffer.byteLength(content) <= MAX_FILE) files[rel] = { content, ...isExecutable(
|
|
4639
|
+
const content = readText(path8.join(dir, rel));
|
|
4640
|
+
if (content !== null && Buffer.byteLength(content) <= MAX_FILE) files[rel] = { content, ...isExecutable(path8.join(dir, rel)) ? { mode: "x" } : {} };
|
|
4214
4641
|
}
|
|
4215
4642
|
const hash = sha(JSON.stringify([skillMd, files]));
|
|
4216
4643
|
if (state.created[name]?.hash === hash) continue;
|
|
@@ -4258,14 +4685,14 @@ function captureEdits(v2, config, ctx, opts = {}) {
|
|
|
4258
4685
|
}
|
|
4259
4686
|
function lockDeploy(p) {
|
|
4260
4687
|
ensureDir(p.state);
|
|
4261
|
-
const file =
|
|
4688
|
+
const file = path8.join(p.state, ".lock");
|
|
4262
4689
|
for (let i = 0; i < 2; i++) {
|
|
4263
4690
|
try {
|
|
4264
|
-
|
|
4691
|
+
fs9.writeFileSync(file, String(process.pid), { flag: "wx" });
|
|
4265
4692
|
return () => removeFile(file);
|
|
4266
4693
|
} catch {
|
|
4267
4694
|
try {
|
|
4268
|
-
const age = Date.now() -
|
|
4695
|
+
const age = Date.now() - fs9.statSync(file).mtimeMs;
|
|
4269
4696
|
const pid = Number(readText(file));
|
|
4270
4697
|
let alive = age < 12e4;
|
|
4271
4698
|
if (alive && pid > 0) {
|
|
@@ -4332,7 +4759,7 @@ function deployLocked(v2, config, ctx, bundle, opts, p, harnesses, now) {
|
|
|
4332
4759
|
const foreign = /* @__PURE__ */ new Set();
|
|
4333
4760
|
if (d.kind === "skill" && !prev) {
|
|
4334
4761
|
for (const dir of d.dirs ?? []) {
|
|
4335
|
-
const cur = readText(
|
|
4762
|
+
const cur = readText(path8.join(dir, "SKILL.md"));
|
|
4336
4763
|
if (cur === null || cur === d.section.body || state.created[d.name]) continue;
|
|
4337
4764
|
foreign.add(dir);
|
|
4338
4765
|
report.warnings.push(`${dir} is not managed by Bunshin; the library's ${d.name} skill was not installed there`);
|
|
@@ -4344,7 +4771,7 @@ function deployLocked(v2, config, ctx, bundle, opts, p, harnesses, now) {
|
|
|
4344
4771
|
targets.push({ ...t, sha: sha(neutralizeMarkers(d.section.body).replace(/\n*$/, "\n")) });
|
|
4345
4772
|
continue;
|
|
4346
4773
|
}
|
|
4347
|
-
if ([...foreign].some((dir) => t.abs.startsWith(dir +
|
|
4774
|
+
if ([...foreign].some((dir) => t.abs.startsWith(dir + path8.sep))) continue;
|
|
4348
4775
|
if (!(d.dirs ?? (d.root ? [d.root] : [])).some((r) => insideRoot(r, t.abs))) {
|
|
4349
4776
|
report.warnings.push(`${t.abs} not written: it leads outside its folder`);
|
|
4350
4777
|
continue;
|
|
@@ -4353,10 +4780,10 @@ function deployLocked(v2, config, ctx, bundle, opts, p, harnesses, now) {
|
|
|
4353
4780
|
const existing = readText(t.abs);
|
|
4354
4781
|
try {
|
|
4355
4782
|
if (existing !== content) {
|
|
4356
|
-
ensureDir(
|
|
4783
|
+
ensureDir(path8.dirname(t.abs));
|
|
4357
4784
|
writeFile(t.abs, content, t.mode);
|
|
4358
4785
|
report.written++;
|
|
4359
|
-
} else if (t.mode === "x" && !isExecutable(t.abs))
|
|
4786
|
+
} else if (t.mode === "x" && !isExecutable(t.abs)) fs9.chmodSync(t.abs, 493);
|
|
4360
4787
|
} catch (err) {
|
|
4361
4788
|
report.warnings.push(`${t.abs} not written: ${err instanceof Error ? err.message : String(err)}`);
|
|
4362
4789
|
continue;
|
|
@@ -4369,19 +4796,19 @@ function deployLocked(v2, config, ctx, bundle, opts, p, harnesses, now) {
|
|
|
4369
4796
|
const cur = readText(old.abs);
|
|
4370
4797
|
if (cur !== null && sha(cur) === old.sha) {
|
|
4371
4798
|
removeFile(old.abs);
|
|
4372
|
-
pruneDirs(
|
|
4799
|
+
pruneDirs(path8.dirname(old.abs), d.kind === "skill" ? path8.dirname(path8.dirname(old.abs)) : p.library);
|
|
4373
4800
|
report.removed++;
|
|
4374
4801
|
}
|
|
4375
4802
|
}
|
|
4376
4803
|
if (d.kind === "skill") {
|
|
4377
4804
|
for (const dir of prev?.dirs ?? []) {
|
|
4378
4805
|
if ((d.dirs ?? []).includes(dir) || !removable(dir)) continue;
|
|
4379
|
-
removeFile(
|
|
4380
|
-
pruneDirs(dir,
|
|
4806
|
+
removeFile(path8.join(dir, MANIFEST));
|
|
4807
|
+
pruneDirs(dir, path8.dirname(dir));
|
|
4381
4808
|
}
|
|
4382
4809
|
for (const dir of d.dirs ?? []) {
|
|
4383
4810
|
const manifest = safeJoin(dir, MANIFEST);
|
|
4384
|
-
if (foreign.has(dir) || !manifest || !
|
|
4811
|
+
if (foreign.has(dir) || !manifest || !fs9.existsSync(path8.join(dir, "SKILL.md"))) continue;
|
|
4385
4812
|
writeJsonAtomic(manifest, { sectionId: id, name: d.name, version: d.section.version, libraryVersion: bundle.libraryVersion ?? null, managedBy: "bunshin sync" });
|
|
4386
4813
|
}
|
|
4387
4814
|
report.skills++;
|
|
@@ -4406,12 +4833,12 @@ function deployLocked(v2, config, ctx, bundle, opts, p, harnesses, now) {
|
|
|
4406
4833
|
}
|
|
4407
4834
|
for (const dir of doc.dirs ?? []) {
|
|
4408
4835
|
if (!removable(dir)) continue;
|
|
4409
|
-
removeFile(
|
|
4410
|
-
pruneDirs(dir,
|
|
4411
|
-
if (
|
|
4836
|
+
removeFile(path8.join(dir, MANIFEST));
|
|
4837
|
+
pruneDirs(dir, path8.dirname(dir));
|
|
4838
|
+
if (fs9.existsSync(dir)) {
|
|
4412
4839
|
const left = listFiles(dir);
|
|
4413
4840
|
if (left.length) report.warnings.push(`${dir} kept: it has files Bunshin did not write`);
|
|
4414
|
-
else pruneDirs(dir,
|
|
4841
|
+
else pruneDirs(dir, path8.dirname(dir));
|
|
4415
4842
|
}
|
|
4416
4843
|
}
|
|
4417
4844
|
}
|
|
@@ -4427,17 +4854,17 @@ function deployLocked(v2, config, ctx, bundle, opts, p, harnesses, now) {
|
|
|
4427
4854
|
if (d.kind === "skill") continue;
|
|
4428
4855
|
for (const t of d.targets) {
|
|
4429
4856
|
if (t.role === "block" || t.mode !== "x" || !/(^|\/)bin\/[^/]+$/.test(t.rel) || !owned(t.abs)) continue;
|
|
4430
|
-
const link =
|
|
4857
|
+
const link = path8.join(p.bin, path8.basename(t.abs));
|
|
4431
4858
|
ensureDir(p.bin);
|
|
4432
4859
|
try {
|
|
4433
|
-
const cur =
|
|
4860
|
+
const cur = fs9.readlinkSync(link);
|
|
4434
4861
|
if (cur !== t.abs) {
|
|
4435
|
-
|
|
4436
|
-
|
|
4862
|
+
fs9.unlinkSync(link);
|
|
4863
|
+
fs9.symlinkSync(t.abs, link);
|
|
4437
4864
|
}
|
|
4438
4865
|
} catch {
|
|
4439
4866
|
try {
|
|
4440
|
-
if (!
|
|
4867
|
+
if (!fs9.existsSync(link)) fs9.symlinkSync(t.abs, link);
|
|
4441
4868
|
} catch {
|
|
4442
4869
|
}
|
|
4443
4870
|
}
|
|
@@ -4448,19 +4875,19 @@ function deployLocked(v2, config, ctx, bundle, opts, p, harnesses, now) {
|
|
|
4448
4875
|
if (harnesses.has("claude-code")) {
|
|
4449
4876
|
for (const pack of packs) {
|
|
4450
4877
|
const dir = childDir(p.library, pack);
|
|
4451
|
-
if (!dir || !insideRoot(dir,
|
|
4452
|
-
const link =
|
|
4878
|
+
if (!dir || !insideRoot(dir, path8.join(dir, ".claude", "x"))) continue;
|
|
4879
|
+
const link = path8.join(dir, ".claude", "skills");
|
|
4453
4880
|
try {
|
|
4454
|
-
|
|
4455
|
-
if (
|
|
4456
|
-
|
|
4457
|
-
|
|
4881
|
+
fs9.lstatSync(link);
|
|
4882
|
+
if (fs9.readlinkSync(link) !== p.claudeSkills) {
|
|
4883
|
+
fs9.unlinkSync(link);
|
|
4884
|
+
fs9.symlinkSync(p.claudeSkills, link);
|
|
4458
4885
|
}
|
|
4459
4886
|
} catch {
|
|
4460
|
-
ensureDir(
|
|
4887
|
+
ensureDir(path8.dirname(link));
|
|
4461
4888
|
ensureDir(p.claudeSkills);
|
|
4462
4889
|
try {
|
|
4463
|
-
|
|
4890
|
+
fs9.symlinkSync(p.claudeSkills, link);
|
|
4464
4891
|
} catch {
|
|
4465
4892
|
}
|
|
4466
4893
|
}
|
|
@@ -4469,17 +4896,26 @@ function deployLocked(v2, config, ctx, bundle, opts, p, harnesses, now) {
|
|
|
4469
4896
|
for (const old of state.links) {
|
|
4470
4897
|
if (links.has(old)) continue;
|
|
4471
4898
|
try {
|
|
4472
|
-
const target =
|
|
4473
|
-
if (!
|
|
4899
|
+
const target = fs9.readlinkSync(old);
|
|
4900
|
+
if (!fs9.existsSync(target)) fs9.unlinkSync(old);
|
|
4474
4901
|
} catch {
|
|
4475
4902
|
}
|
|
4476
4903
|
}
|
|
4477
4904
|
let linked = state.linked ?? [];
|
|
4478
4905
|
if (p.legacy) {
|
|
4479
|
-
const
|
|
4480
|
-
const
|
|
4906
|
+
const deployed = Object.values(next).filter((d) => d.kind === "skill" && (d.dirs ?? []).length);
|
|
4907
|
+
const layerOf = new Map(plan.skills.map((x) => [x.skill.name, x.layer]));
|
|
4908
|
+
const roots = [.../* @__PURE__ */ new Set([...linked, ...opts.linkRoot ? [path8.resolve(opts.linkRoot)] : []])];
|
|
4481
4909
|
linked = [];
|
|
4482
4910
|
for (const root of roots) {
|
|
4911
|
+
let source;
|
|
4912
|
+
try {
|
|
4913
|
+
source = sourceFor(root, v2.store.root, ctx);
|
|
4914
|
+
} catch {
|
|
4915
|
+
source = void 0;
|
|
4916
|
+
}
|
|
4917
|
+
const reader = readerFor(ctx, void 0, { ...source ? { source } : {}, ...bundle.scopes ? { tree: bundle.scopes } : {} });
|
|
4918
|
+
const names = deployed.filter((d) => inBoundary({ layer: layerOf.get(d.name) ?? "org" }, reader)).map((d) => d.name);
|
|
4483
4919
|
const res = linkSkills(p, root, opts.linkHarnesses ?? [...harnesses], names);
|
|
4484
4920
|
report.warnings.push(...res.warnings);
|
|
4485
4921
|
if (res.kept) linked.push(root);
|
|
@@ -4500,13 +4936,13 @@ function materialize(sections, dir) {
|
|
|
4500
4936
|
const put = (rel, content, mode) => {
|
|
4501
4937
|
const abs = safeJoin(dir, rel);
|
|
4502
4938
|
if (!abs) return;
|
|
4503
|
-
ensureDir(
|
|
4939
|
+
ensureDir(path8.dirname(abs));
|
|
4504
4940
|
writeFile(abs, content, mode);
|
|
4505
4941
|
files++;
|
|
4506
4942
|
};
|
|
4507
4943
|
for (const s of sections.filter((x) => x.status === "active" || x.status === "stale")) {
|
|
4508
4944
|
if (s.skill) {
|
|
4509
|
-
const base = s.packPath && /(^|\/)SKILL\.md$/.test(s.packPath) ?
|
|
4945
|
+
const base = s.packPath && /(^|\/)SKILL\.md$/.test(s.packPath) ? path8.posix.dirname(s.packPath) : `.claude/skills/${s.skill.name}`;
|
|
4510
4946
|
put(`${base}/SKILL.md`, s.body);
|
|
4511
4947
|
for (const [rel, f] of Object.entries(s.skill.files ?? {})) put(`${base}/${rel}`, f.content, f.mode);
|
|
4512
4948
|
continue;
|
|
@@ -4518,51 +4954,51 @@ function materialize(sections, dir) {
|
|
|
4518
4954
|
}
|
|
4519
4955
|
function linkSkills(p, root, harnesses, names) {
|
|
4520
4956
|
const out = { linked: 0, removed: 0, kept: false, warnings: [] };
|
|
4521
|
-
const abs =
|
|
4522
|
-
if (abs ===
|
|
4523
|
-
const managed = (target) => target.startsWith(
|
|
4957
|
+
const abs = path8.resolve(root);
|
|
4958
|
+
if (abs === path8.resolve(p.home) || abs === path8.resolve(os2.homedir()) || !fs9.existsSync(abs)) return out;
|
|
4959
|
+
const managed = (target) => target.startsWith(path8.resolve(p.state) + path8.sep);
|
|
4524
4960
|
const excluded = [];
|
|
4525
4961
|
for (const h of harnesses) {
|
|
4526
4962
|
const spec = LINK_DIRS[h];
|
|
4527
4963
|
if (!spec) continue;
|
|
4528
|
-
const dir =
|
|
4529
|
-
const canonical =
|
|
4530
|
-
const want = new Set(names.filter((n) => SAFE_NAME.test(n) &&
|
|
4964
|
+
const dir = path8.join(abs, spec.rel);
|
|
4965
|
+
const canonical = path8.resolve(p[spec.key]);
|
|
4966
|
+
const want = new Set(names.filter((n) => SAFE_NAME.test(n) && fs9.existsSync(path8.join(canonical, n, "SKILL.md"))));
|
|
4531
4967
|
let entries = [];
|
|
4532
4968
|
try {
|
|
4533
|
-
entries =
|
|
4969
|
+
entries = fs9.readdirSync(dir, { withFileTypes: true });
|
|
4534
4970
|
} catch {
|
|
4535
4971
|
}
|
|
4536
4972
|
for (const e of entries) {
|
|
4537
4973
|
if (!e.isSymbolicLink()) continue;
|
|
4538
|
-
const link =
|
|
4974
|
+
const link = path8.join(dir, e.name);
|
|
4539
4975
|
let target = "";
|
|
4540
4976
|
try {
|
|
4541
|
-
target =
|
|
4977
|
+
target = path8.resolve(dir, fs9.readlinkSync(link));
|
|
4542
4978
|
} catch {
|
|
4543
4979
|
continue;
|
|
4544
4980
|
}
|
|
4545
4981
|
if (!managed(target)) continue;
|
|
4546
|
-
if (want.has(e.name) && target ===
|
|
4982
|
+
if (want.has(e.name) && target === path8.join(canonical, e.name)) {
|
|
4547
4983
|
want.delete(e.name);
|
|
4548
|
-
excluded.push(
|
|
4984
|
+
excluded.push(path8.join(spec.rel, e.name));
|
|
4549
4985
|
continue;
|
|
4550
4986
|
}
|
|
4551
4987
|
removeFile(link);
|
|
4552
4988
|
out.removed++;
|
|
4553
4989
|
}
|
|
4554
4990
|
for (const name of want) {
|
|
4555
|
-
const link =
|
|
4991
|
+
const link = path8.join(dir, name);
|
|
4556
4992
|
try {
|
|
4557
|
-
|
|
4993
|
+
fs9.lstatSync(link);
|
|
4558
4994
|
out.warnings.push(`${link} is the project's own; the library's ${name} skill was not linked there`);
|
|
4559
4995
|
continue;
|
|
4560
4996
|
} catch {
|
|
4561
4997
|
}
|
|
4562
4998
|
try {
|
|
4563
4999
|
ensureDir(dir);
|
|
4564
|
-
|
|
4565
|
-
excluded.push(
|
|
5000
|
+
fs9.symlinkSync(path8.join(canonical, name), link, "dir");
|
|
5001
|
+
excluded.push(path8.join(spec.rel, name));
|
|
4566
5002
|
out.linked++;
|
|
4567
5003
|
} catch (err) {
|
|
4568
5004
|
out.warnings.push(`${link} not linked: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -4575,10 +5011,10 @@ function linkSkills(p, root, harnesses, names) {
|
|
|
4575
5011
|
return out;
|
|
4576
5012
|
}
|
|
4577
5013
|
function gitExclude(root, rels) {
|
|
4578
|
-
if (!
|
|
5014
|
+
if (!fs9.existsSync(path8.join(root, ".git"))) return;
|
|
4579
5015
|
let file;
|
|
4580
5016
|
try {
|
|
4581
|
-
file =
|
|
5017
|
+
file = path8.resolve(root, execFileSync3("git", ["-C", root, "rev-parse", "--git-path", "info/exclude"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 2e3 }).trim());
|
|
4582
5018
|
} catch {
|
|
4583
5019
|
return;
|
|
4584
5020
|
}
|
|
@@ -4587,25 +5023,26 @@ function gitExclude(root, rels) {
|
|
|
4587
5023
|
const end = start >= 0 ? cur.indexOf(EXCLUDE_END, start) : -1;
|
|
4588
5024
|
const rest = start >= 0 && end >= 0 ? cur.slice(0, start) + cur.slice(end + EXCLUDE_END.length).replace(/^\n/, "") : cur;
|
|
4589
5025
|
const block2 = rels.length ? `${EXCLUDE_BEGIN}
|
|
4590
|
-
${[...new Set(rels)].sort().map((r) => `/${r.split(
|
|
5026
|
+
${[...new Set(rels)].sort().map((r) => `/${r.split(path8.sep).join("/")}`).join("\n")}
|
|
4591
5027
|
${EXCLUDE_END}
|
|
4592
5028
|
` : "";
|
|
4593
5029
|
const next = block2 ? `${rest}${rest && !rest.endsWith("\n") ? "\n" : ""}${block2}` : rest;
|
|
4594
5030
|
if (next === cur) return;
|
|
4595
5031
|
try {
|
|
4596
|
-
ensureDir(
|
|
4597
|
-
|
|
5032
|
+
ensureDir(path8.dirname(file));
|
|
5033
|
+
fs9.writeFileSync(file, next);
|
|
4598
5034
|
} catch {
|
|
4599
5035
|
}
|
|
4600
5036
|
}
|
|
4601
|
-
var settingsFile, orgKey, sha, BLOCK_BEGIN, BLOCK_END, MANIFEST, MAX_FILE, isExecutable, safeJoin, SAFE_NAME, neutralizeMarkers,
|
|
5037
|
+
var settingsFile, orgKey, sha, BLOCK_BEGIN, BLOCK_END, MANIFEST, MAX_FILE, isExecutable, safeJoin, SAFE_NAME, neutralizeMarkers, docMarker, BORN_SLACK_MS, LINK_DIRS, EXCLUDE_BEGIN, EXCLUDE_END;
|
|
4602
5038
|
var init_deploy = __esm({
|
|
4603
5039
|
"src/v2/deploy.ts"() {
|
|
4604
5040
|
"use strict";
|
|
4605
5041
|
init_v22();
|
|
5042
|
+
init_source();
|
|
4606
5043
|
init_util();
|
|
4607
5044
|
init_store2();
|
|
4608
|
-
settingsFile = (home) =>
|
|
5045
|
+
settingsFile = (home) => path8.join(home, ".bunshin", "deploy", "settings.json");
|
|
4609
5046
|
orgKey = (orgId) => orgId.replace(/[^A-Za-z0-9._-]/g, "_") || "org";
|
|
4610
5047
|
sha = (text2) => createHash3("sha256").update(text2).digest("hex");
|
|
4611
5048
|
BLOCK_BEGIN = "<!-- bunshin:begin -->";
|
|
@@ -4614,7 +5051,7 @@ var init_deploy = __esm({
|
|
|
4614
5051
|
MAX_FILE = 2 * 1024 * 1024;
|
|
4615
5052
|
isExecutable = (file) => {
|
|
4616
5053
|
try {
|
|
4617
|
-
return (
|
|
5054
|
+
return (fs9.statSync(file).mode & 73) !== 0;
|
|
4618
5055
|
} catch {
|
|
4619
5056
|
return false;
|
|
4620
5057
|
}
|
|
@@ -4622,12 +5059,11 @@ var init_deploy = __esm({
|
|
|
4622
5059
|
safeJoin = containedPath;
|
|
4623
5060
|
SAFE_NAME = /^(?!\.\.?$)[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
4624
5061
|
neutralizeMarkers = (text2) => text2.replace(/<!--(?=\s*bunshin:)/gi, "<!-\u200B-");
|
|
4625
|
-
LAYER_RANK = { org: 0, dept: 1, team: 2, user: 3, fleet: 0, arm: 1, mount: 2, robot: 3 };
|
|
4626
5062
|
docMarker = (id, version2) => `<!-- bunshin:doc id=${id} v=${version2} -->`;
|
|
4627
5063
|
BORN_SLACK_MS = 2e3;
|
|
4628
5064
|
LINK_DIRS = {
|
|
4629
|
-
"claude-code": { rel:
|
|
4630
|
-
codex: { rel:
|
|
5065
|
+
"claude-code": { rel: path8.join(".claude", "skills"), key: "claudeSkills" },
|
|
5066
|
+
codex: { rel: path8.join(".codex", "skills"), key: "codexSkills" }
|
|
4631
5067
|
};
|
|
4632
5068
|
EXCLUDE_BEGIN = "# >>> bunshin: library skills linked here (managed by bunshin sync)";
|
|
4633
5069
|
EXCLUDE_END = "# <<< bunshin";
|
|
@@ -4739,11 +5175,11 @@ var init_live = __esm({
|
|
|
4739
5175
|
});
|
|
4740
5176
|
|
|
4741
5177
|
// src/transcript.ts
|
|
4742
|
-
import
|
|
5178
|
+
import fs10 from "node:fs";
|
|
4743
5179
|
function parseTranscript(file) {
|
|
4744
5180
|
let raw = "";
|
|
4745
5181
|
try {
|
|
4746
|
-
raw =
|
|
5182
|
+
raw = fs10.readFileSync(file, "utf8");
|
|
4747
5183
|
} catch {
|
|
4748
5184
|
return { turns: [], userMessages: 0, toolCalls: 0, toolFailures: 0 };
|
|
4749
5185
|
}
|
|
@@ -4834,292 +5270,128 @@ function isHumanText(text2) {
|
|
|
4834
5270
|
function blockText(content) {
|
|
4835
5271
|
if (typeof content === "string") return content;
|
|
4836
5272
|
if (Array.isArray(content)) {
|
|
4837
|
-
return content.map((c) => c && typeof c === "object" && "text" in c ? String(c.text) : "").filter(Boolean).join("\n");
|
|
4838
|
-
}
|
|
4839
|
-
return content == null ? "" : JSON.stringify(content);
|
|
4840
|
-
}
|
|
4841
|
-
function codexToolResults(output, depth = 0) {
|
|
4842
|
-
const raw = blockText(output);
|
|
4843
|
-
let envelope = output;
|
|
4844
|
-
if (typeof output === "string") {
|
|
4845
|
-
try {
|
|
4846
|
-
envelope = JSON.parse(output);
|
|
4847
|
-
} catch {
|
|
4848
|
-
}
|
|
4849
|
-
}
|
|
4850
|
-
if (depth > 6) return [{ text: raw, pending: true }];
|
|
4851
|
-
if (Array.isArray(envelope)) return envelope.flatMap((part) => codexToolResults(part, depth + 1));
|
|
4852
|
-
if (envelope && typeof envelope === "object" && !Array.isArray(envelope)) {
|
|
4853
|
-
const result = envelope;
|
|
4854
|
-
if (typeof result.output === "string" && (Number.isInteger(result.exit_code) || result.exit_code === null || Number.isInteger(result.session_id))) {
|
|
4855
|
-
const pending = !Number.isInteger(result.exit_code);
|
|
4856
|
-
return [{
|
|
4857
|
-
text: `${pending ? "Command still running" : `Process exited with code ${result.exit_code}`}
|
|
4858
|
-
${result.output}`,
|
|
4859
|
-
isError: !pending && result.exit_code !== 0,
|
|
4860
|
-
pending
|
|
4861
|
-
}];
|
|
4862
|
-
}
|
|
4863
|
-
if (result.status === "fulfilled" && "value" in result) return codexToolResults(result.value, depth + 1);
|
|
4864
|
-
if (result.status === "rejected") return [{ text: raw, isError: true }];
|
|
4865
|
-
}
|
|
4866
|
-
return [{
|
|
4867
|
-
text: raw,
|
|
4868
|
-
isError: /(?:Process exited with code|exit_code["']?\s*:)\s*[1-9]\d*\b/i.test(raw),
|
|
4869
|
-
pending: /(?:Process running with session ID|Script running with cell ID)/i.test(raw)
|
|
4870
|
-
}];
|
|
4871
|
-
}
|
|
4872
|
-
function summarizeInput(input) {
|
|
4873
|
-
if (input == null) return "";
|
|
4874
|
-
if (typeof input !== "object") return String(input);
|
|
4875
|
-
const obj = input;
|
|
4876
|
-
for (const key of ["command", "prompt", "pattern", "file_path", "query", "description"]) {
|
|
4877
|
-
if (typeof obj[key] === "string") return `${key}: ${obj[key]}`;
|
|
4878
|
-
}
|
|
4879
|
-
return JSON.stringify(obj);
|
|
4880
|
-
}
|
|
4881
|
-
function bounded(text2, maxChars) {
|
|
4882
|
-
if (text2.length <= maxChars) return text2;
|
|
4883
|
-
const marker = "\n[... middle omitted ...]\n";
|
|
4884
|
-
if (maxChars <= marker.length) return text2.slice(0, Math.max(0, maxChars));
|
|
4885
|
-
const head = Math.floor((maxChars - marker.length) / 2);
|
|
4886
|
-
return text2.slice(0, head) + marker + text2.slice(-(maxChars - marker.length - head));
|
|
4887
|
-
}
|
|
4888
|
-
function digest(facts, { from = 0, maxChars = 6e4, toolResultChars = 6e3 } = {}) {
|
|
4889
|
-
const parts = [];
|
|
4890
|
-
for (const t of facts.turns.slice(from)) {
|
|
4891
|
-
const text2 = redact2(t.text);
|
|
4892
|
-
if (t.role === "user") parts.push(`[USER]
|
|
4893
|
-
${bounded(text2, maxChars)}`);
|
|
4894
|
-
else if (t.role === "assistant") parts.push(`[ASSISTANT]
|
|
4895
|
-
${bounded(text2, maxChars)}`);
|
|
4896
|
-
else if (t.tool === "result") parts.push(`[TOOL RESULT${t.isError ? " \u2014 ERROR" : t.pending ? " \u2014 RUNNING" : ""}]
|
|
4897
|
-
${bounded(text2, toolResultChars)}`);
|
|
4898
|
-
else parts.push(`[TOOL ${t.tool}] ${truncate(text2, 400)}`);
|
|
4899
|
-
}
|
|
4900
|
-
return bounded(parts.join("\n\n"), maxChars);
|
|
4901
|
-
}
|
|
4902
|
-
function fullTrace(facts, { from = 0, to } = {}) {
|
|
4903
|
-
return facts.turns.slice(from, to).map((t) => {
|
|
4904
|
-
if (t.role === "user") return `[USER]
|
|
4905
|
-
${redact2(t.text)}`;
|
|
4906
|
-
if (t.role === "assistant") return `[ASSISTANT]
|
|
4907
|
-
${redact2(t.text)}`;
|
|
4908
|
-
if (t.tool === "result") return `[TOOL RESULT${t.isError ? " \u2014 ERROR" : t.pending ? " \u2014 RUNNING" : ""}]
|
|
4909
|
-
${redact2(t.text)}`;
|
|
4910
|
-
const input = t.input === void 0 ? t.text : typeof t.input === "string" ? t.input : JSON.stringify(t.input, null, 1);
|
|
4911
|
-
return `[TOOL ${t.tool}]
|
|
4912
|
-
${redact2(input)}`;
|
|
4913
|
-
});
|
|
4914
|
-
}
|
|
4915
|
-
function traceChunks(turns, maxBytes = 1e6) {
|
|
4916
|
-
const out = [];
|
|
4917
|
-
let cur = "";
|
|
4918
|
-
let size = 0;
|
|
4919
|
-
turns.forEach((turn, i) => {
|
|
4920
|
-
let piece = (i ? "\n\n" : "") + turn;
|
|
4921
|
-
let bytes = jsonBytes(piece);
|
|
4922
|
-
if (size + bytes <= maxBytes) {
|
|
4923
|
-
cur += piece;
|
|
4924
|
-
size += bytes;
|
|
4925
|
-
return;
|
|
4926
|
-
}
|
|
4927
|
-
if (cur) out.push(cur);
|
|
4928
|
-
while (bytes > maxBytes) {
|
|
4929
|
-
let n = Math.min(piece.length, maxBytes);
|
|
4930
|
-
for (let b = jsonBytes(piece.slice(0, n)); b > maxBytes; b = jsonBytes(piece.slice(0, n))) n = Math.max(1, Math.floor(n * maxBytes / b) - 1);
|
|
4931
|
-
const code2 = piece.charCodeAt(n - 1);
|
|
4932
|
-
if (n > 1 && code2 >= 55296 && code2 <= 56319) n--;
|
|
4933
|
-
const head = piece.slice(0, n);
|
|
4934
|
-
out.push(head);
|
|
4935
|
-
piece = piece.slice(n);
|
|
4936
|
-
bytes -= jsonBytes(head);
|
|
4937
|
-
}
|
|
4938
|
-
cur = piece;
|
|
4939
|
-
size = bytes;
|
|
4940
|
-
});
|
|
4941
|
-
if (cur) out.push(cur);
|
|
4942
|
-
return out;
|
|
4943
|
-
}
|
|
4944
|
-
function recentContext(file, { turns = 2, maxChars = 6e3 } = {}) {
|
|
4945
|
-
const facts = parseTranscript(file);
|
|
4946
|
-
const visible = facts.turns.filter((t) => t.role !== "tool");
|
|
4947
|
-
const userIdx = visible.map((t, i) => t.role === "user" ? i : -1).filter((i) => i >= 0);
|
|
4948
|
-
const start = userIdx.length > turns ? userIdx[userIdx.length - turns] : 0;
|
|
4949
|
-
return digest({ ...facts, turns: visible.slice(start) }, { maxChars });
|
|
4950
|
-
}
|
|
4951
|
-
var BANNER, jsonBytes;
|
|
4952
|
-
var init_transcript = __esm({
|
|
4953
|
-
"src/transcript.ts"() {
|
|
4954
|
-
"use strict";
|
|
4955
|
-
init_redact2();
|
|
4956
|
-
init_util();
|
|
4957
|
-
BANNER = "## Recalled lessons (Bunshin)";
|
|
4958
|
-
jsonBytes = (s) => Buffer.byteLength(JSON.stringify(s)) - 2;
|
|
4959
|
-
}
|
|
4960
|
-
});
|
|
4961
|
-
|
|
4962
|
-
// src/v2/repo.ts
|
|
4963
|
-
import fs9 from "node:fs";
|
|
4964
|
-
import path7 from "node:path";
|
|
4965
|
-
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
4966
|
-
function git(cwd, args, timeout = 1500) {
|
|
4967
|
-
try {
|
|
4968
|
-
return execFileSync2("git", ["-C", cwd, ...args], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout, maxBuffer: 64 * 1024 * 1024 });
|
|
4969
|
-
} catch {
|
|
4970
|
-
return null;
|
|
4971
|
-
}
|
|
4972
|
-
}
|
|
4973
|
-
function repoRoot(cwd, storeRoot) {
|
|
4974
|
-
const key = `${cwd}
|
|
4975
|
-
${storeRoot ?? ""}`;
|
|
4976
|
-
const hit = rootCache.get(key);
|
|
4977
|
-
if (hit) return hit;
|
|
4978
|
-
const top = git(cwd, ["rev-parse", "--show-toplevel"])?.trim();
|
|
4979
|
-
let root = top || "";
|
|
4980
|
-
if (!root && storeRoot && path7.basename(storeRoot) === ".bunshin") {
|
|
4981
|
-
const parent = path7.dirname(storeRoot);
|
|
4982
|
-
if (path7.resolve(cwd).startsWith(path7.resolve(parent))) root = parent;
|
|
4983
|
-
}
|
|
4984
|
-
root = root || path7.resolve(cwd);
|
|
4985
|
-
rootCache.set(key, root);
|
|
4986
|
-
return root;
|
|
4987
|
-
}
|
|
4988
|
-
function parseRemote(url) {
|
|
4989
|
-
const clean2 = url.trim().replace(/\.git$/, "").replace(/\/+$/, "");
|
|
4990
|
-
if (!clean2) return null;
|
|
4991
|
-
const scp = clean2.match(/^[^@/\s]+@[^:/\s]+:(.+)$/);
|
|
4992
|
-
const pathPart = scp ? scp[1] : (() => {
|
|
4993
|
-
try {
|
|
4994
|
-
return new URL(clean2).pathname;
|
|
4995
|
-
} catch {
|
|
4996
|
-
return clean2;
|
|
4997
|
-
}
|
|
4998
|
-
})();
|
|
4999
|
-
const segs = pathPart.split("/").filter(Boolean);
|
|
5000
|
-
if (segs.length >= 2) return `${segs[segs.length - 2]}/${segs[segs.length - 1]}`;
|
|
5001
|
-
return segs[0] ?? null;
|
|
5002
|
-
}
|
|
5003
|
-
function repoName(root) {
|
|
5004
|
-
const url = git(root, ["remote", "get-url", "origin"]);
|
|
5005
|
-
return url && parseRemote(url) || path7.basename(root);
|
|
5006
|
-
}
|
|
5007
|
-
function repoCommit(root) {
|
|
5008
|
-
return git(root, ["rev-parse", "HEAD"])?.trim() || void 0;
|
|
5009
|
-
}
|
|
5010
|
-
function listDocs(root) {
|
|
5011
|
-
const listed = git(root, ["ls-files", "-co", "--exclude-standard", "-z"], 3e3);
|
|
5012
|
-
if (listed !== null) return [...new Set(listed.split("\0").filter(Boolean).filter(isDoc))].filter((rel) => fs9.existsSync(path7.join(root, rel)) && !deployedSkill(root, rel)).sort();
|
|
5013
|
-
const out = [];
|
|
5014
|
-
let visited = 0;
|
|
5015
|
-
const walk = (dir, rel, depth) => {
|
|
5016
|
-
if (depth > 8 || visited++ > 2e4) return;
|
|
5017
|
-
let entries;
|
|
5018
|
-
try {
|
|
5019
|
-
entries = fs9.readdirSync(dir, { withFileTypes: true });
|
|
5020
|
-
} catch {
|
|
5021
|
-
return;
|
|
5022
|
-
}
|
|
5023
|
-
for (const e of entries) {
|
|
5024
|
-
if (e.isDirectory()) {
|
|
5025
|
-
if (!SKIP_DIRS.has(e.name) && !fs9.existsSync(path7.join(dir, e.name, ".git"))) walk(path7.join(dir, e.name), rel ? `${rel}/${e.name}` : e.name, depth + 1);
|
|
5026
|
-
} else if (e.isFile()) {
|
|
5027
|
-
const r = rel ? `${rel}/${e.name}` : e.name;
|
|
5028
|
-
if (isDoc(r) && !deployedSkill(root, r)) out.push(r);
|
|
5029
|
-
}
|
|
5030
|
-
}
|
|
5031
|
-
};
|
|
5032
|
-
walk(root, "", 0);
|
|
5033
|
-
return out.sort();
|
|
5034
|
-
}
|
|
5035
|
-
function indexWorkingTree(v2, config, cwd, opts = {}) {
|
|
5036
|
-
const root = repoRoot(cwd, v2.store.root);
|
|
5037
|
-
const now = opts.now ?? Date.now();
|
|
5038
|
-
const cached = v2.readRepoIndex(root);
|
|
5039
|
-
const ownerTeam = config.ownerTeam || config.orgUnit || "unknown";
|
|
5040
|
-
const sameRepo = cached && cached.ownerTeam === ownerTeam;
|
|
5041
|
-
const repo = sameRepo && cached.repo && !opts.force ? cached.repo : repoName(root);
|
|
5042
|
-
const fresh = sameRepo && cached.repo === repo && cached.paths && cached.listedAt && now - Date.parse(cached.listedAt) < LIST_TTL_MS && !opts.force;
|
|
5043
|
-
const paths = fresh ? cached.paths : listDocs(root);
|
|
5044
|
-
let changed = !fresh || !sameRepo || cached.repo !== repo;
|
|
5045
|
-
const files = {};
|
|
5046
|
-
for (const rel of paths) {
|
|
5047
|
-
const abs = path7.join(root, rel);
|
|
5048
|
-
let stat;
|
|
5049
|
-
try {
|
|
5050
|
-
stat = fs9.statSync(abs);
|
|
5051
|
-
} catch {
|
|
5052
|
-
changed = true;
|
|
5053
|
-
continue;
|
|
5054
|
-
}
|
|
5055
|
-
const prev = sameRepo && cached.repo === repo ? cached.files[rel] : void 0;
|
|
5056
|
-
if (prev && prev.mtimeMs === stat.mtimeMs && prev.size === stat.size) {
|
|
5057
|
-
files[rel] = prev;
|
|
5058
|
-
continue;
|
|
5059
|
-
}
|
|
5060
|
-
changed = true;
|
|
5061
|
-
try {
|
|
5062
|
-
const text2 = fs9.readFileSync(abs, "utf8");
|
|
5063
|
-
files[rel] = { mtimeMs: stat.mtimeMs, size: stat.size, sections: parseRepoDoc({ repo, path: rel, text: text2 }, { orgId: config.orgId, ownerTeam }) };
|
|
5064
|
-
} catch {
|
|
5065
|
-
}
|
|
5066
|
-
}
|
|
5067
|
-
const index = {
|
|
5068
|
-
repo,
|
|
5069
|
-
ownerTeam,
|
|
5070
|
-
listedAt: fresh ? cached.listedAt : new Date(now).toISOString(),
|
|
5071
|
-
paths,
|
|
5072
|
-
files
|
|
5073
|
-
};
|
|
5074
|
-
if (changed) {
|
|
5075
|
-
try {
|
|
5076
|
-
v2.writeRepoIndex(root, index);
|
|
5077
|
-
} catch {
|
|
5078
|
-
}
|
|
5273
|
+
return content.map((c) => c && typeof c === "object" && "text" in c ? String(c.text) : "").filter(Boolean).join("\n");
|
|
5079
5274
|
}
|
|
5080
|
-
return
|
|
5275
|
+
return content == null ? "" : JSON.stringify(content);
|
|
5081
5276
|
}
|
|
5082
|
-
function
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
let realRoot = root;
|
|
5087
|
-
try {
|
|
5088
|
-
realRoot = fs9.realpathSync(root);
|
|
5089
|
-
} catch {
|
|
5090
|
-
}
|
|
5091
|
-
try {
|
|
5092
|
-
real = fs9.realpathSync(abs);
|
|
5093
|
-
} catch {
|
|
5277
|
+
function codexToolResults(output, depth = 0) {
|
|
5278
|
+
const raw = blockText(output);
|
|
5279
|
+
let envelope = output;
|
|
5280
|
+
if (typeof output === "string") {
|
|
5094
5281
|
try {
|
|
5095
|
-
|
|
5282
|
+
envelope = JSON.parse(output);
|
|
5096
5283
|
} catch {
|
|
5097
5284
|
}
|
|
5098
5285
|
}
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5286
|
+
if (depth > 6) return [{ text: raw, pending: true }];
|
|
5287
|
+
if (Array.isArray(envelope)) return envelope.flatMap((part) => codexToolResults(part, depth + 1));
|
|
5288
|
+
if (envelope && typeof envelope === "object" && !Array.isArray(envelope)) {
|
|
5289
|
+
const result = envelope;
|
|
5290
|
+
if (typeof result.output === "string" && (Number.isInteger(result.exit_code) || result.exit_code === null || Number.isInteger(result.session_id))) {
|
|
5291
|
+
const pending = !Number.isInteger(result.exit_code);
|
|
5292
|
+
return [{
|
|
5293
|
+
text: `${pending ? "Command still running" : `Process exited with code ${result.exit_code}`}
|
|
5294
|
+
${result.output}`,
|
|
5295
|
+
isError: !pending && result.exit_code !== 0,
|
|
5296
|
+
pending
|
|
5297
|
+
}];
|
|
5298
|
+
}
|
|
5299
|
+
if (result.status === "fulfilled" && "value" in result) return codexToolResults(result.value, depth + 1);
|
|
5300
|
+
if (result.status === "rejected") return [{ text: raw, isError: true }];
|
|
5105
5301
|
}
|
|
5106
|
-
return
|
|
5302
|
+
return [{
|
|
5303
|
+
text: raw,
|
|
5304
|
+
isError: /(?:Process exited with code|exit_code["']?\s*:)\s*[1-9]\d*\b/i.test(raw),
|
|
5305
|
+
pending: /(?:Process running with session ID|Script running with cell ID)/i.test(raw)
|
|
5306
|
+
}];
|
|
5107
5307
|
}
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
"
|
|
5308
|
+
function summarizeInput(input) {
|
|
5309
|
+
if (input == null) return "";
|
|
5310
|
+
if (typeof input !== "object") return String(input);
|
|
5311
|
+
const obj = input;
|
|
5312
|
+
for (const key of ["command", "prompt", "pattern", "file_path", "query", "description"]) {
|
|
5313
|
+
if (typeof obj[key] === "string") return `${key}: ${obj[key]}`;
|
|
5314
|
+
}
|
|
5315
|
+
return JSON.stringify(obj);
|
|
5316
|
+
}
|
|
5317
|
+
function bounded(text2, maxChars) {
|
|
5318
|
+
if (text2.length <= maxChars) return text2;
|
|
5319
|
+
const marker = "\n[... middle omitted ...]\n";
|
|
5320
|
+
if (maxChars <= marker.length) return text2.slice(0, Math.max(0, maxChars));
|
|
5321
|
+
const head = Math.floor((maxChars - marker.length) / 2);
|
|
5322
|
+
return text2.slice(0, head) + marker + text2.slice(-(maxChars - marker.length - head));
|
|
5323
|
+
}
|
|
5324
|
+
function digest(facts, { from = 0, maxChars = 6e4, toolResultChars = 6e3 } = {}) {
|
|
5325
|
+
const parts = [];
|
|
5326
|
+
for (const t of facts.turns.slice(from)) {
|
|
5327
|
+
const text2 = redact2(t.text);
|
|
5328
|
+
if (t.role === "user") parts.push(`[USER]
|
|
5329
|
+
${bounded(text2, maxChars)}`);
|
|
5330
|
+
else if (t.role === "assistant") parts.push(`[ASSISTANT]
|
|
5331
|
+
${bounded(text2, maxChars)}`);
|
|
5332
|
+
else if (t.tool === "result") parts.push(`[TOOL RESULT${t.isError ? " \u2014 ERROR" : t.pending ? " \u2014 RUNNING" : ""}]
|
|
5333
|
+
${bounded(text2, toolResultChars)}`);
|
|
5334
|
+
else parts.push(`[TOOL ${t.tool}] ${truncate(text2, 400)}`);
|
|
5335
|
+
}
|
|
5336
|
+
return bounded(parts.join("\n\n"), maxChars);
|
|
5337
|
+
}
|
|
5338
|
+
function fullTrace(facts, { from = 0, to } = {}) {
|
|
5339
|
+
return facts.turns.slice(from, to).map((t) => {
|
|
5340
|
+
if (t.role === "user") return `[USER]
|
|
5341
|
+
${redact2(t.text)}`;
|
|
5342
|
+
if (t.role === "assistant") return `[ASSISTANT]
|
|
5343
|
+
${redact2(t.text)}`;
|
|
5344
|
+
if (t.tool === "result") return `[TOOL RESULT${t.isError ? " \u2014 ERROR" : t.pending ? " \u2014 RUNNING" : ""}]
|
|
5345
|
+
${redact2(t.text)}`;
|
|
5346
|
+
const input = t.input === void 0 ? t.text : typeof t.input === "string" ? t.input : JSON.stringify(t.input, null, 1);
|
|
5347
|
+
return `[TOOL ${t.tool}]
|
|
5348
|
+
${redact2(input)}`;
|
|
5349
|
+
});
|
|
5350
|
+
}
|
|
5351
|
+
function traceChunks(turns, maxBytes = 1e6) {
|
|
5352
|
+
const out = [];
|
|
5353
|
+
let cur = "";
|
|
5354
|
+
let size = 0;
|
|
5355
|
+
turns.forEach((turn, i) => {
|
|
5356
|
+
let piece = (i ? "\n\n" : "") + turn;
|
|
5357
|
+
let bytes = jsonBytes(piece);
|
|
5358
|
+
if (size + bytes <= maxBytes) {
|
|
5359
|
+
cur += piece;
|
|
5360
|
+
size += bytes;
|
|
5361
|
+
return;
|
|
5362
|
+
}
|
|
5363
|
+
if (cur) out.push(cur);
|
|
5364
|
+
while (bytes > maxBytes) {
|
|
5365
|
+
let n = Math.min(piece.length, maxBytes);
|
|
5366
|
+
for (let b = jsonBytes(piece.slice(0, n)); b > maxBytes; b = jsonBytes(piece.slice(0, n))) n = Math.max(1, Math.floor(n * maxBytes / b) - 1);
|
|
5367
|
+
const code2 = piece.charCodeAt(n - 1);
|
|
5368
|
+
if (n > 1 && code2 >= 55296 && code2 <= 56319) n--;
|
|
5369
|
+
const head = piece.slice(0, n);
|
|
5370
|
+
out.push(head);
|
|
5371
|
+
piece = piece.slice(n);
|
|
5372
|
+
bytes -= jsonBytes(head);
|
|
5373
|
+
}
|
|
5374
|
+
cur = piece;
|
|
5375
|
+
size = bytes;
|
|
5376
|
+
});
|
|
5377
|
+
if (cur) out.push(cur);
|
|
5378
|
+
return out;
|
|
5379
|
+
}
|
|
5380
|
+
function recentContext(file, { turns = 2, maxChars = 6e3 } = {}) {
|
|
5381
|
+
const facts = parseTranscript(file);
|
|
5382
|
+
const visible = facts.turns.filter((t) => t.role !== "tool");
|
|
5383
|
+
const userIdx = visible.map((t, i) => t.role === "user" ? i : -1).filter((i) => i >= 0);
|
|
5384
|
+
const start = userIdx.length > turns ? userIdx[userIdx.length - turns] : 0;
|
|
5385
|
+
return digest({ ...facts, turns: visible.slice(start) }, { maxChars });
|
|
5386
|
+
}
|
|
5387
|
+
var BANNER, jsonBytes;
|
|
5388
|
+
var init_transcript = __esm({
|
|
5389
|
+
"src/transcript.ts"() {
|
|
5111
5390
|
"use strict";
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
rootCache = /* @__PURE__ */ new Map();
|
|
5117
|
-
isDoc = (rel) => {
|
|
5118
|
-
const parts = rel.split("/");
|
|
5119
|
-
if (parts.some((p) => SKIP_DIRS.has(p))) return false;
|
|
5120
|
-
return DOC_NAMES.has(parts[parts.length - 1].toLowerCase());
|
|
5121
|
-
};
|
|
5122
|
-
deployedSkill = (root, rel) => /(^|\/)SKILL\.md$/i.test(rel) && fs9.existsSync(path7.join(root, path7.posix.dirname(rel), ".bunshin.json"));
|
|
5391
|
+
init_redact2();
|
|
5392
|
+
init_util();
|
|
5393
|
+
BANNER = "## Recalled lessons (Bunshin)";
|
|
5394
|
+
jsonBytes = (s) => Buffer.byteLength(JSON.stringify(s)) - 2;
|
|
5123
5395
|
}
|
|
5124
5396
|
});
|
|
5125
5397
|
|
|
@@ -5188,8 +5460,14 @@ function applyOverlays(sections, overlays, ctx) {
|
|
|
5188
5460
|
}
|
|
5189
5461
|
function localView(v2, config, cwd, opts = {}) {
|
|
5190
5462
|
const ctx = contextFor(v2, config);
|
|
5191
|
-
const reader = readerFor(ctx);
|
|
5192
5463
|
const bundle = v2.readBundle();
|
|
5464
|
+
let source;
|
|
5465
|
+
try {
|
|
5466
|
+
source = opts.source !== void 0 ? opts.source || void 0 : sourceFor(cwd, v2.store.root, ctx);
|
|
5467
|
+
} catch {
|
|
5468
|
+
source = void 0;
|
|
5469
|
+
}
|
|
5470
|
+
const reader = readerFor(ctx, void 0, { ...source ? { source } : {}, ...bundle?.scopes ? { tree: bundle.scopes } : {} });
|
|
5193
5471
|
const byId = /* @__PURE__ */ new Map();
|
|
5194
5472
|
for (const s of bundle?.sections ?? []) byId.set(s.id, s);
|
|
5195
5473
|
let root = cwd;
|
|
@@ -5207,7 +5485,7 @@ function localView(v2, config, cwd, opts = {}) {
|
|
|
5207
5485
|
}
|
|
5208
5486
|
}
|
|
5209
5487
|
applyOverlays(byId, v2.readOverlays(), ctx);
|
|
5210
|
-
return { ctx, reader, layers:
|
|
5488
|
+
return { ctx, reader, ...source ? { source } : {}, layers: reader.layers, bundle, sections: [...byId.values()], byId, root, repo };
|
|
5211
5489
|
}
|
|
5212
5490
|
var ACTIVE_OVERLAY;
|
|
5213
5491
|
var init_view = __esm({
|
|
@@ -5215,12 +5493,13 @@ var init_view = __esm({
|
|
|
5215
5493
|
"use strict";
|
|
5216
5494
|
init_v22();
|
|
5217
5495
|
init_repo();
|
|
5496
|
+
init_source();
|
|
5218
5497
|
ACTIVE_OVERLAY = /* @__PURE__ */ new Set(["proposed", "trial", "submitted", "kept-local", "needs-review"]);
|
|
5219
5498
|
}
|
|
5220
5499
|
});
|
|
5221
5500
|
|
|
5222
5501
|
// src/v2/apply.ts
|
|
5223
|
-
import
|
|
5502
|
+
import fs11 from "node:fs";
|
|
5224
5503
|
function appendedText(op) {
|
|
5225
5504
|
return op.condition ? `- **${op.condition}:** ${op.text.trim()}` : op.text.trim();
|
|
5226
5505
|
}
|
|
@@ -5275,12 +5554,12 @@ function applyLocally(v2, view, s) {
|
|
|
5275
5554
|
if (section.storage.type === "repo") {
|
|
5276
5555
|
const file = containedPath(view.root, section.storage.path);
|
|
5277
5556
|
const ours = !view.repo || section.storage.repo === view.repo;
|
|
5278
|
-
if (ours && file &&
|
|
5279
|
-
const before =
|
|
5557
|
+
if (ours && file && fs11.existsSync(file)) {
|
|
5558
|
+
const before = fs11.readFileSync(file, "utf8");
|
|
5280
5559
|
const res2 = applyOpToMarkdown(before, section.storage.anchor, op);
|
|
5281
5560
|
if (res2.ok) {
|
|
5282
5561
|
const changed = res2.text !== before;
|
|
5283
|
-
if (changed)
|
|
5562
|
+
if (changed) fs11.writeFileSync(file, res2.text);
|
|
5284
5563
|
const overlay2 = { ...base, kind: "working-tree", sectionId: section.id, file: section.storage.path, anchor: section.storage.anchor, changed };
|
|
5285
5564
|
upsertOverlay(v2, overlay2);
|
|
5286
5565
|
return { applied: true, target: describeTarget(s, view), overlay: overlay2 };
|
|
@@ -5301,7 +5580,7 @@ function revertWorkingTree(root, o) {
|
|
|
5301
5580
|
if (!file) return { reverted: false, reason: "path outside the repository" };
|
|
5302
5581
|
let text2;
|
|
5303
5582
|
try {
|
|
5304
|
-
text2 =
|
|
5583
|
+
text2 = fs11.readFileSync(file, "utf8");
|
|
5305
5584
|
} catch {
|
|
5306
5585
|
return { reverted: false, reason: "file missing" };
|
|
5307
5586
|
}
|
|
@@ -5321,7 +5600,7 @@ ${appendedText(o.op)}`;
|
|
|
5321
5600
|
if (once(text2, o.op.text) >= 0 && !text2.includes(o.op.find)) next = text2.replace(o.op.text, o.op.find);
|
|
5322
5601
|
}
|
|
5323
5602
|
if (next === null) return { reverted: false, reason: "file changed since the trial edit" };
|
|
5324
|
-
|
|
5603
|
+
fs11.writeFileSync(file, next);
|
|
5325
5604
|
return { reverted: true };
|
|
5326
5605
|
}
|
|
5327
5606
|
function revertParam(v2, o) {
|
|
@@ -5394,7 +5673,7 @@ var init_apply = __esm({
|
|
|
5394
5673
|
});
|
|
5395
5674
|
|
|
5396
5675
|
// src/v2/signals.ts
|
|
5397
|
-
import
|
|
5676
|
+
import fs12 from "node:fs";
|
|
5398
5677
|
function isCorrection(text2) {
|
|
5399
5678
|
const t = text2.trim().slice(0, 600);
|
|
5400
5679
|
return !!t && CORRECTION_RE.test(t);
|
|
@@ -5416,7 +5695,7 @@ function scanTranscript(file) {
|
|
|
5416
5695
|
if (!file) return empty;
|
|
5417
5696
|
let raw;
|
|
5418
5697
|
try {
|
|
5419
|
-
raw =
|
|
5698
|
+
raw = fs12.readFileSync(file, "utf8");
|
|
5420
5699
|
} catch {
|
|
5421
5700
|
return empty;
|
|
5422
5701
|
}
|
|
@@ -5500,8 +5779,8 @@ var init_signals = __esm({
|
|
|
5500
5779
|
});
|
|
5501
5780
|
|
|
5502
5781
|
// src/v2/ui.ts
|
|
5503
|
-
import
|
|
5504
|
-
import
|
|
5782
|
+
import fs13 from "node:fs";
|
|
5783
|
+
import path9 from "node:path";
|
|
5505
5784
|
function feedPath(v2, sessionId) {
|
|
5506
5785
|
return v2.p("ui", `${safe(sessionId)}.json`);
|
|
5507
5786
|
}
|
|
@@ -5517,7 +5796,7 @@ function emptyFeed(v2, sessionId, now) {
|
|
|
5517
5796
|
}
|
|
5518
5797
|
function cliArgv() {
|
|
5519
5798
|
const entry = process.argv[1];
|
|
5520
|
-
return entry ? [process.execPath,
|
|
5799
|
+
return entry ? [process.execPath, path9.resolve(entry)] : ["bunshin"];
|
|
5521
5800
|
}
|
|
5522
5801
|
function updateFeed(v2, sessionId, patch, now = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
5523
5802
|
if (!sessionId || sessionId === "no-session") return null;
|
|
@@ -5532,7 +5811,7 @@ function updateFeed(v2, sessionId, patch, now = (/* @__PURE__ */ new Date()).toI
|
|
|
5532
5811
|
f.cli = cliArgv();
|
|
5533
5812
|
f.served = f.served.slice(0, MAX_SERVED);
|
|
5534
5813
|
f.events = f.events.slice(-MAX_EVENTS);
|
|
5535
|
-
|
|
5814
|
+
fs13.mkdirSync(path9.dirname(feedPath(v2, sessionId)), { recursive: true });
|
|
5536
5815
|
writeJsonAtomic(feedPath(v2, sessionId), f);
|
|
5537
5816
|
return f;
|
|
5538
5817
|
} catch {
|
|
@@ -5633,16 +5912,16 @@ function latestFeed(v2) {
|
|
|
5633
5912
|
const dir = v2.p("ui");
|
|
5634
5913
|
let best = null;
|
|
5635
5914
|
try {
|
|
5636
|
-
for (const f2 of
|
|
5915
|
+
for (const f2 of fs13.readdirSync(dir)) {
|
|
5637
5916
|
if (!f2.endsWith(".json") || f2.endsWith(".mod.json")) continue;
|
|
5638
|
-
const m =
|
|
5917
|
+
const m = fs13.statSync(path9.join(dir, f2)).mtimeMs;
|
|
5639
5918
|
if (!best || m > best.m) best = { f: f2, m };
|
|
5640
5919
|
}
|
|
5641
5920
|
} catch {
|
|
5642
5921
|
return null;
|
|
5643
5922
|
}
|
|
5644
5923
|
if (!best) return null;
|
|
5645
|
-
const f = readJson(
|
|
5924
|
+
const f = readJson(path9.join(dir, best.f), null);
|
|
5646
5925
|
return f && f.v === UI_FEED_VERSION ? f : null;
|
|
5647
5926
|
}
|
|
5648
5927
|
var UI_FEED_VERSION, MAX_SERVED, MAX_EVENTS, MOD_FRESH_MS, DEFAULT_MODES;
|
|
@@ -5658,110 +5937,6 @@ var init_ui = __esm({
|
|
|
5658
5937
|
}
|
|
5659
5938
|
});
|
|
5660
5939
|
|
|
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
|
-
|
|
5765
5940
|
// src/v2/sync.ts
|
|
5766
5941
|
var sync_exports = {};
|
|
5767
5942
|
__export(sync_exports, {
|
|
@@ -5974,7 +6149,8 @@ async function uploadIndex(store, cwd, deadline = Date.now() + 3e4) {
|
|
|
5974
6149
|
}
|
|
5975
6150
|
});
|
|
5976
6151
|
const commit = repoCommit(root);
|
|
5977
|
-
const
|
|
6152
|
+
const source = sourceFor(root, store.root);
|
|
6153
|
+
const res = await v2api(config, "POST", "/sections/index", { repo: index.repo, ...commit ? { commit } : {}, ...source ? { source } : {}, ownerTeam: index.ownerTeam, docs }, { deadline, timeoutMs: 3e4 });
|
|
5978
6154
|
return typeof res.indexed === "number" ? res.indexed : docs.length;
|
|
5979
6155
|
}
|
|
5980
6156
|
var isLocalId, carriesTrace;
|
|
@@ -6113,7 +6289,7 @@ async function runReflectionV2(store, opts) {
|
|
|
6113
6289
|
if (!release) return { ...result, skipped: "a reflection of this session is already running" };
|
|
6114
6290
|
try {
|
|
6115
6291
|
const session = v2.session(opts.sessionId, opts.cwd ?? process.cwd());
|
|
6116
|
-
const view = localView(v2, config, session.cwd || opts.cwd || process.cwd());
|
|
6292
|
+
const view = localView(v2, config, session.cwd || opts.cwd || process.cwd(), session.source !== void 0 ? { source: session.source } : {});
|
|
6117
6293
|
const origin = opts.origin ?? (opts.focus !== void 0 ? "learn" : "session-end");
|
|
6118
6294
|
const episodeId = episodeIdFor(view.ctx.id, opts.sessionId);
|
|
6119
6295
|
const prior = session.suggestionIds.map((id) => v2.readSuggestion(id)).filter((s) => !!s);
|
|
@@ -6130,7 +6306,7 @@ async function runReflectionV2(store, opts) {
|
|
|
6130
6306
|
const lessons = [.../* @__PURE__ */ new Set([...prior.map(lessonLine), ...session.serverLessons ?? []])].slice(0, 40);
|
|
6131
6307
|
const earlier = from > 0 || lessons.length ? { ...from > 0 && facts ? { from: anchorAt(facts, from) } : {}, lessons } : void 0;
|
|
6132
6308
|
const guidelines = reflectionGuidelines(view.sections, view.reader);
|
|
6133
|
-
const promptBase = { contextKind: view.ctx.kind, layers: view.layers, index, captured, guidelines, ...opts.focus ? { focus: opts.focus } : {}, ...earlier ? { earlier } : {}, ...profile ? { profileId: profile.id } : {} };
|
|
6309
|
+
const promptBase = { contextKind: view.ctx.kind, layers: view.layers, index, captured, guidelines, scopes: scopeChoices(view.bundle?.scopes, view.source), ...opts.focus ? { focus: opts.focus } : {}, ...earlier ? { earlier } : {}, ...profile ? { profileId: profile.id } : {} };
|
|
6134
6310
|
let raw = null;
|
|
6135
6311
|
if (harness === "claude-code") {
|
|
6136
6312
|
const fork = await runClaudeFork(opts.sessionId, buildReflectPrompt(promptBase), { cwd: session.cwd, model: config.model });
|
|
@@ -6170,7 +6346,7 @@ async function runReflectionV2(store, opts) {
|
|
|
6170
6346
|
result.nothingNew = result.method !== "none" && parsed.length === 0;
|
|
6171
6347
|
const apply = opts.forceApply || config.reflect.autoApply === "all" || config.reflect.autoApply === "robot" && view.ctx.kind === "robot";
|
|
6172
6348
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6173
|
-
const source =
|
|
6349
|
+
const source = view.source;
|
|
6174
6350
|
for (const p of parsed) {
|
|
6175
6351
|
const s = toSuggestion(p, { id: hexId("g"), orgId: config.orgId, contextId: view.ctx.id, authorUserId: config.userId, origin, createdAt, episodeId });
|
|
6176
6352
|
if (source) s.source = source;
|
|
@@ -6257,7 +6433,6 @@ var init_reflect3 = __esm({
|
|
|
6257
6433
|
init_signals();
|
|
6258
6434
|
init_ui();
|
|
6259
6435
|
init_view();
|
|
6260
|
-
init_source();
|
|
6261
6436
|
FORK_TIMEOUT_MS = 12e4;
|
|
6262
6437
|
FORK_DENIED_TOOLS = "Bash,Edit,Write,MultiEdit,NotebookEdit,WebFetch,WebSearch,Task,Agent,Skill,SlashCommand";
|
|
6263
6438
|
}
|
|
@@ -6677,7 +6852,7 @@ function liveUpdate(v2, ctx, session) {
|
|
|
6677
6852
|
return "";
|
|
6678
6853
|
}
|
|
6679
6854
|
const config = ctx.store.config();
|
|
6680
|
-
const deployed = deployEnabled(config,
|
|
6855
|
+
const deployed = deployEnabled(config, contextFor(v2, config));
|
|
6681
6856
|
const paths = deployed ? deployPaths(process.env, config.orgId) : null;
|
|
6682
6857
|
const root = paths ? config.harness === "codex" ? paths.codexSkills : paths.claudeSkills : null;
|
|
6683
6858
|
const skillPath = (name) => root ? `${root}/${name}/SKILL.md` : null;
|
|
@@ -6744,7 +6919,7 @@ async function onPrompt(ctx) {
|
|
|
6744
6919
|
};
|
|
6745
6920
|
if (!prompt.trim()) return withLive({});
|
|
6746
6921
|
const config = ctx.store.config();
|
|
6747
|
-
const view = localView(v2, config, ctx.cwd);
|
|
6922
|
+
const view = localView(v2, config, ctx.cwd, session.source !== void 0 ? { source: session.source } : {});
|
|
6748
6923
|
const injected = inContext(session);
|
|
6749
6924
|
const named = invokedSkill(prompt, view.sections);
|
|
6750
6925
|
if (named) {
|
|
@@ -6832,7 +7007,7 @@ function onPreToolUse(ctx) {
|
|
|
6832
7007
|
const raw = toolPaths(ctx.input.tool_input);
|
|
6833
7008
|
if (!raw.length) return withLive({});
|
|
6834
7009
|
const config = ctx.store.config();
|
|
6835
|
-
const view = localView(v2, config, ctx.cwd);
|
|
7010
|
+
const view = localView(v2, config, ctx.cwd, session.source !== void 0 ? { source: session.source } : {});
|
|
6836
7011
|
const paths = raw.map((p) => relativeToRoot(view.root, p, ctx.cwd)).filter((p) => !!p);
|
|
6837
7012
|
const text2 = [session.task, session.lastPrompt].filter(Boolean).join("\n");
|
|
6838
7013
|
if (!paths.length && !text2) return withLive({});
|
|
@@ -6957,6 +7132,7 @@ function buildEpisode(store, session, transcriptPath, now = /* @__PURE__ */ new
|
|
|
6957
7132
|
...view.bundle?.release?.version ? { harnessVersion: view.bundle.release.version } : {},
|
|
6958
7133
|
cliVersion: version(),
|
|
6959
7134
|
...source ? { source } : {},
|
|
7135
|
+
...session.borrowed?.length ? { borrowed: session.borrowed } : {},
|
|
6960
7136
|
uploaded: false,
|
|
6961
7137
|
...previous?.reportedServed ? { reportedServed: previous.reportedServed } : {},
|
|
6962
7138
|
...previous?.reportedLoaded ? { reportedLoaded: previous.reportedLoaded } : {},
|
|
@@ -7265,6 +7441,7 @@ var init_project_hooks = __esm({
|
|
|
7265
7441
|
// src/init.ts
|
|
7266
7442
|
var init_exports = {};
|
|
7267
7443
|
__export(init_exports, {
|
|
7444
|
+
BORROW_COMMAND: () => BORROW_COMMAND,
|
|
7268
7445
|
CODEX_HOOK_EVENTS: () => CODEX_HOOK_EVENTS,
|
|
7269
7446
|
HOOK_EVENTS: () => HOOK_EVENTS,
|
|
7270
7447
|
LEARN_COMMAND: () => LEARN_COMMAND,
|
|
@@ -7386,18 +7563,18 @@ function mcpLauncher(bin) {
|
|
|
7386
7563
|
}
|
|
7387
7564
|
return { command: process.execPath, args: [script, "mcp"] };
|
|
7388
7565
|
}
|
|
7389
|
-
function installLearnCommand({ file, remove = false }) {
|
|
7566
|
+
function installLearnCommand({ file, remove = false, content = LEARN_COMMAND }) {
|
|
7390
7567
|
try {
|
|
7391
7568
|
const current = fileExists(file) ? fs21.readFileSync(file, "utf8") : null;
|
|
7392
|
-
const ours = current === null || current ===
|
|
7569
|
+
const ours = current === null || current === content;
|
|
7393
7570
|
if (remove) {
|
|
7394
7571
|
if (current !== null && ours) fs21.unlinkSync(file);
|
|
7395
7572
|
return { file, installed: 0, changed: current !== null && ours };
|
|
7396
7573
|
}
|
|
7397
|
-
if (!ours) return { file, installed: 0, changed: false, error:
|
|
7574
|
+
if (!ours) return { file, installed: 0, changed: false, error: `a different ${path16.basename(file)} is already there; left as it is` };
|
|
7398
7575
|
if (current === null) {
|
|
7399
7576
|
fs21.mkdirSync(path16.dirname(file), { recursive: true });
|
|
7400
|
-
fs21.writeFileSync(file,
|
|
7577
|
+
fs21.writeFileSync(file, content);
|
|
7401
7578
|
}
|
|
7402
7579
|
return { file, installed: 1, changed: current === null };
|
|
7403
7580
|
} catch (err) {
|
|
@@ -7519,11 +7696,20 @@ function runInit(opts = {}) {
|
|
|
7519
7696
|
learnCommand = installLearnCommand({ file: path16.join(user ? path16.join(homeDir(), ".claude") : path16.join(cwd, ".claude"), "commands", "learn.md"), remove: opts.removeHooks });
|
|
7520
7697
|
if (learnCommand.error) warnings.push(`/learn not installed at ${learnCommand.file}: ${learnCommand.error}`);
|
|
7521
7698
|
}
|
|
7699
|
+
let borrowCommand;
|
|
7700
|
+
if (opts.removeHooks || !opts.noHooks) {
|
|
7701
|
+
const user = hookScope === "user";
|
|
7702
|
+
const file = config.harness === "claude-code" ? path16.join(user ? path16.join(homeDir(), ".claude") : path16.join(cwd, ".claude"), "commands", "borrow.md") : config.harness === "codex" ? path16.join(process.env.CODEX_HOME ?? path16.join(homeDir(), ".codex"), "prompts", "borrow.md") : config.harness === "pi" ? user ? path16.join(homeDir(), ".pi", "agent", "prompts", "borrow.md") : path16.join(cwd, ".pi", "prompts", "borrow.md") : null;
|
|
7703
|
+
if (file) {
|
|
7704
|
+
borrowCommand = installLearnCommand({ file, remove: opts.removeHooks, content: BORROW_COMMAND });
|
|
7705
|
+
if (borrowCommand.error) warnings.push(`/borrow not installed at ${borrowCommand.file}: ${borrowCommand.error}`);
|
|
7706
|
+
}
|
|
7707
|
+
}
|
|
7522
7708
|
if (!resolveBackend(config)) {
|
|
7523
7709
|
warnings.push("No model backend found. Install and sign in to Codex or Claude Code before using automatic learning.");
|
|
7524
7710
|
}
|
|
7525
7711
|
store.log("init", { root, scope, role: config.role, orgUnit: config.orgUnit, hooks: hooks?.installed ?? 0 });
|
|
7526
|
-
return { root, config, created: !existed, hooks, ...mcp ? { mcp } : {}, ...learnCommand ? { learnCommand } : {}, warnings };
|
|
7712
|
+
return { root, config, created: !existed, hooks, ...mcp ? { mcp } : {}, ...learnCommand ? { learnCommand } : {}, ...borrowCommand ? { borrowCommand } : {}, warnings };
|
|
7527
7713
|
}
|
|
7528
7714
|
function otherHarnessInstructions() {
|
|
7529
7715
|
return [
|
|
@@ -7539,7 +7725,7 @@ function otherHarnessInstructions() {
|
|
|
7539
7725
|
" Claude Code plugin users need no settings.json wiring \u2014 the plugin ships hooks/hooks.json."
|
|
7540
7726
|
].join("\n");
|
|
7541
7727
|
}
|
|
7542
|
-
var PRE_TOOL_MATCHER, HOOK_EVENTS, CODEX_HOOK_EVENTS, LEARN_COMMAND;
|
|
7728
|
+
var PRE_TOOL_MATCHER, HOOK_EVENTS, CODEX_HOOK_EVENTS, LEARN_COMMAND, BORROW_COMMAND;
|
|
7543
7729
|
var init_init = __esm({
|
|
7544
7730
|
"src/init.ts"() {
|
|
7545
7731
|
"use strict";
|
|
@@ -7565,6 +7751,7 @@ var init_init = __esm({
|
|
|
7565
7751
|
{ event: "SessionEnd", arg: "session-end", timeout: 3 }
|
|
7566
7752
|
];
|
|
7567
7753
|
LEARN_COMMAND = '---\ndescription: Capture what this session learned into the Bunshin library now (applied locally as a trial, submitted for the nightly review)\nargument-hint: "[what to capture, e.g. \\"the ledger must be reconciled before closing\\"]"\nallowed-tools: Bash(bunshin learn:*)\n---\n\nCapture knowledge from this session into Bunshin.\n\nRun exactly this with the Bash tool (it takes up to two minutes; use a timeout of 180000 ms):\n\n```bash\nbunshin learn "$ARGUMENTS"\n```\n\n`bunshin learn` forks this conversation (`claude -p --resume <this session> --fork-session`),\nso the reflection sees everything you saw without re-sending the transcript. It applies what it\nfinds locally as a trial and queues it for the nightly review:\n\n- edits to this repo\'s SPEC.md / TESTING.md / SKILL.md land in the working tree (they show up\n in `git diff`);\n- knowledge with no repo home becomes a local overlay on the Bunshin library;\n- per-machine values stay private.\n\nRelay its output to the user as it is. Each line reads\n`captured: <summary> \u2192 <section or file#anchor>`. If it prints `nothing new to capture.`, say so\nin one line; that is the normal outcome. If it reports an error, show it and stop; do not retry\nin a loop, and do not edit the library files by hand.\n\nIf the user named nothing specific, run it without arguments. Do not continue with other work in\nthe same reply unless the user asked you to.\n';
|
|
7754
|
+
BORROW_COMMAND = '---\ndescription: Bring in a lesson or skill learned elsewhere in this workspace (another project or group) when you know it will help here\nargument-hint: "[what to look for, e.g. \\"the release checklist from the api repo\\"]"\nallowed-tools: Bash(bunshin recall:*), Bash(bunshin load:*)\n---\n\nThe person wants something learned outside this project\'s scope. Search the whole workspace for it\n(run with the Bash tool):\n\n```bash\nbunshin recall "$ARGUMENTS" --hypothetical "$ARGUMENTS" --across\n```\n\nShow the person the numbered results as printed (each says where it was learned). Load the one\nthey pick, or the one that plainly matches, with `bunshin load <id>`, then use it as prior\nknowledge for the task. Loading it records that it was borrowed into this project: the nightly\ngeneralization reads that as evidence the lesson applies beyond where it was learned.\n\nIf nothing matches, say so in one line and carry on.\n';
|
|
7568
7755
|
}
|
|
7569
7756
|
});
|
|
7570
7757
|
|
|
@@ -7586,19 +7773,19 @@ function mark(v2, sessionId, cwd, field, ids) {
|
|
|
7586
7773
|
} catch {
|
|
7587
7774
|
}
|
|
7588
7775
|
}
|
|
7589
|
-
async function recallPull(store, query, { cwd, sessionId, timeoutMs = 6e3 }) {
|
|
7776
|
+
async function recallPull(store, query, { cwd, sessionId, timeoutMs = 6e3, across = false }) {
|
|
7590
7777
|
const v2 = new V2Store(store);
|
|
7591
7778
|
const config = store.config();
|
|
7592
7779
|
const sid = sessionId ?? sessionIdFromEnv(v2);
|
|
7593
7780
|
const session = sid ? v2.readSession(sid) : null;
|
|
7594
7781
|
const exclude = session ? [...session.loaded] : [];
|
|
7595
7782
|
const limit = Math.max(1, Math.min(10, query.limit ?? 5));
|
|
7596
|
-
const view = localView(v2, config, cwd);
|
|
7597
|
-
const local = () => pull(view.sections, view.reader, { ...query, limit }, { exclude });
|
|
7783
|
+
const view = localView(v2, config, cwd, session?.source !== void 0 ? { source: session.source } : {});
|
|
7784
|
+
const local = () => pull(view.sections, view.reader, { ...query, limit }, { exclude, across });
|
|
7598
7785
|
let out;
|
|
7599
7786
|
if (canCall(config)) {
|
|
7600
7787
|
try {
|
|
7601
|
-
const res = await v2api(config, "POST", "/recall/pull", { ...redactDeep(query), limit, exclude }, { timeoutMs });
|
|
7788
|
+
const res = await v2api(config, "POST", "/recall/pull", { ...redactDeep(query), limit, exclude, ...view.source ? { source: view.source } : {}, ...across ? { across: true } : {} }, { timeoutMs });
|
|
7602
7789
|
const remote = Array.isArray(res.candidates) ? res.candidates : [];
|
|
7603
7790
|
const extra = local().filter((c) => c.id.startsWith("local:") && !remote.some((r) => r.id === c.id));
|
|
7604
7791
|
out = { candidates: [...remote, ...extra].slice(0, limit), source: "mothership" };
|
|
@@ -7613,7 +7800,8 @@ async function loadSection(store, id, { cwd, sessionId, anchor }) {
|
|
|
7613
7800
|
const v2 = new V2Store(store);
|
|
7614
7801
|
const config = store.config();
|
|
7615
7802
|
const sid = sessionId ?? sessionIdFromEnv(v2);
|
|
7616
|
-
const
|
|
7803
|
+
const session = sid ? v2.readSession(sid) : null;
|
|
7804
|
+
const view = localView(v2, config, cwd, session?.source !== void 0 ? { source: session.source } : {});
|
|
7617
7805
|
let section = view.byId.get(id);
|
|
7618
7806
|
let source = "local";
|
|
7619
7807
|
if (!section && canCall(config)) {
|
|
@@ -7626,8 +7814,17 @@ async function loadSection(store, id, { cwd, sessionId, anchor }) {
|
|
|
7626
7814
|
}
|
|
7627
7815
|
if (!section) return null;
|
|
7628
7816
|
mark(v2, sid, cwd, "loaded", [section.id]);
|
|
7817
|
+
const borrowed = !inBoundary(section, view.reader);
|
|
7818
|
+
if (borrowed && sid) {
|
|
7819
|
+
try {
|
|
7820
|
+
v2.updateSession(sid, cwd, (s) => {
|
|
7821
|
+
s.borrowed = [...(s.borrowed ?? []).filter((b) => b.sectionId !== section.id), { sectionId: section.id, layer: section.layer, at: (/* @__PURE__ */ new Date()).toISOString() }].slice(-50);
|
|
7822
|
+
});
|
|
7823
|
+
} catch {
|
|
7824
|
+
}
|
|
7825
|
+
}
|
|
7629
7826
|
const part = anchor?.replace(/^[§#]/, "");
|
|
7630
|
-
return { section, text: renderSection(section, void 0, part ? { anchor: part, maxChars: 0 } : { maxChars: Number.POSITIVE_INFINITY }), source };
|
|
7827
|
+
return { section, text: renderSection(section, void 0, part ? { anchor: part, maxChars: 0 } : { maxChars: Number.POSITIVE_INFINITY }), source, ...borrowed ? { borrowed: true } : {} };
|
|
7631
7828
|
}
|
|
7632
7829
|
var init_library = __esm({
|
|
7633
7830
|
"src/v2/library.ts"() {
|
|
@@ -7761,18 +7958,19 @@ async function cmdReviewDecide(store, args, out, err) {
|
|
|
7761
7958
|
async function cmdRecall(store, args, cwd, out, err) {
|
|
7762
7959
|
const abstract = args._.slice(1).join(" ").trim() || str3(args.flags.abstract) || "";
|
|
7763
7960
|
if (!abstract) {
|
|
7764
|
-
err('bunshin recall: usage: bunshin recall "<the task, abstractly>" --hypothetical "<the lesson you would want>" --id a,b [--json]\n');
|
|
7961
|
+
err('bunshin recall: usage: bunshin recall "<the task, abstractly>" --hypothetical "<the lesson you would want>" --id a,b [--across] [--json]\n');
|
|
7765
7962
|
return 1;
|
|
7766
7963
|
}
|
|
7767
7964
|
const limit = Number(str3(args.flags.limit)) || void 0;
|
|
7768
|
-
const
|
|
7965
|
+
const across = bool2(args.flags.across);
|
|
7966
|
+
const res = await recallPull(store, { taskAbstract: abstract, hypotheticalLesson: str3(args.flags.hypothetical) ?? abstract, identifiers: list(args.flags.id ?? args.flags.ids), ...limit ? { limit } : {} }, { cwd, sessionId: str3(args.flags.session), across });
|
|
7769
7967
|
if (bool2(args.flags.json)) {
|
|
7770
7968
|
out(JSON.stringify(res, null, 2) + "\n");
|
|
7771
7969
|
return 0;
|
|
7772
7970
|
}
|
|
7773
|
-
out(renderCandidates(res.candidates) + "\n");
|
|
7971
|
+
out(renderCandidates(res.candidates, new V2Store(store).readBundle()?.scopes) + "\n");
|
|
7774
7972
|
if (res.candidates.length) out(`
|
|
7775
|
-
(${res.source}) Load one with: bunshin load <id
|
|
7973
|
+
(${res.source}) Load one with: bunshin load <id>${across && res.candidates.some((c) => c.outside) ? " (a lesson from outside this project's scope is recorded as borrowed: evidence it applies here too)" : ""}
|
|
7776
7974
|
`);
|
|
7777
7975
|
return 0;
|
|
7778
7976
|
}
|
|
@@ -7789,7 +7987,7 @@ async function cmdLoad(store, args, cwd, out, err) {
|
|
|
7789
7987
|
`);
|
|
7790
7988
|
return 1;
|
|
7791
7989
|
}
|
|
7792
|
-
out(bool2(args.flags.json) ? JSON.stringify({ section: res.section, source: res.source }, null, 2) + "\n" : res.text + "\n");
|
|
7990
|
+
out(bool2(args.flags.json) ? JSON.stringify({ section: res.section, source: res.source, ...res.borrowed ? { borrowed: true } : {} }, null, 2) + "\n" : res.text + "\n");
|
|
7793
7991
|
return 0;
|
|
7794
7992
|
}
|
|
7795
7993
|
async function cmdLearn(store, args, cwd, out, err) {
|
|
@@ -7853,7 +8051,7 @@ function cmdSuggest(store, args, cwd, out, err) {
|
|
|
7853
8051
|
orgId: config.orgId,
|
|
7854
8052
|
contextId: view.ctx.id,
|
|
7855
8053
|
authorUserId: config.userId,
|
|
7856
|
-
target: known ? { sectionId: known } : {
|
|
8054
|
+
target: known ? { sectionId: known } : { storage: { type: "harness", path: rel, symbol: symbol ?? "" } },
|
|
7857
8055
|
op: { op: "append", text: rationale },
|
|
7858
8056
|
rationale,
|
|
7859
8057
|
evidenceEpisodeIds: sessionId ? [episodeIdFor(view.ctx.id, sessionId)] : [],
|
|
@@ -8347,8 +8545,8 @@ async function handleMcp(msg, opts) {
|
|
|
8347
8545
|
if (!taskAbstract.trim()) return reply(text("task_abstract is required: describe the task abstractly.", true));
|
|
8348
8546
|
const identifiers = Array.isArray(args.identifiers) ? args.identifiers.filter((x) => typeof x === "string") : [];
|
|
8349
8547
|
const limit = typeof args.limit === "number" ? args.limit : void 0;
|
|
8350
|
-
const res = await recallPull(store, { taskAbstract, hypotheticalLesson, identifiers, ...limit ? { limit } : {} }, { cwd });
|
|
8351
|
-
const body = renderCandidates(res.candidates);
|
|
8548
|
+
const res = await recallPull(store, { taskAbstract, hypotheticalLesson, identifiers, ...limit ? { limit } : {} }, { cwd, across: args.across === true });
|
|
8549
|
+
const body = renderCandidates(res.candidates, new V2Store(store).readBundle()?.scopes);
|
|
8352
8550
|
return reply(text(res.candidates.length ? `${body}
|
|
8353
8551
|
|
|
8354
8552
|
Load the ones that would change what you do with bunshin_load (0-3).` : body));
|
|
@@ -8405,6 +8603,7 @@ var init_mcp = __esm({
|
|
|
8405
8603
|
init_v22();
|
|
8406
8604
|
init_source();
|
|
8407
8605
|
init_store();
|
|
8606
|
+
init_store2();
|
|
8408
8607
|
init_library();
|
|
8409
8608
|
DEFAULT_PROTOCOL = "2025-06-18";
|
|
8410
8609
|
MCP_TOOLS = [
|
|
@@ -8418,7 +8617,8 @@ var init_mcp = __esm({
|
|
|
8418
8617
|
task_abstract: { type: "string", description: "The task in abstract, structural terms (what kind of problem this is), not a restatement of its topic." },
|
|
8419
8618
|
hypothetical_lesson: { type: "string", description: "The lesson you would want to find, written as if it existed." },
|
|
8420
8619
|
identifiers: { type: "array", items: { type: "string" }, description: "Concrete identifiers involved: function names, error codes, file paths, product or system names. May be empty." },
|
|
8421
|
-
limit: { type: "integer", minimum: 1, maximum: 10, description: "Maximum results (default 5)." }
|
|
8620
|
+
limit: { type: "integer", minimum: 1, maximum: 10, description: "Maximum results (default 5)." },
|
|
8621
|
+
across: { type: "boolean", description: "Only when the person asks for something learned elsewhere (another project or group of this workspace): search across the project's boundary. Default false." }
|
|
8422
8622
|
},
|
|
8423
8623
|
required: ["task_abstract", "hypothetical_lesson", "identifiers"],
|
|
8424
8624
|
additionalProperties: false
|
|
@@ -9627,7 +9827,7 @@ function effortOf(job) {
|
|
|
9627
9827
|
const tokens = Math.round(chars / 4);
|
|
9628
9828
|
const minutes = Math.max(2, Math.round(2 + tokens / 15e3));
|
|
9629
9829
|
const proposals = (job.prompt.match(/^### g_\w+/gm) ?? []).length;
|
|
9630
|
-
const what = job.kind === "reflect" ? "read every [USER] message and each failed or denied result first; most sessions teach nothing new, and at most 4 lessons are kept" : job.kind === "merge" ? `${proposals} proposal${proposals === 1 ? "" : "s"} against one document: decide each, edit only what the applied ones need` : `${proposals} proposed new document${proposals === 1 ? "" : "s"}: combine, fold into existing ones, or reject`;
|
|
9830
|
+
const what = job.kind === "reflect" ? "read every [USER] message and each failed or denied result first; most sessions teach nothing new, and at most 4 lessons are kept" : job.kind === "merge" ? `${proposals} proposal${proposals === 1 ? "" : "s"} against one document: decide each, edit only what the applied ones need` : job.kind === "generalize" ? `${(job.prompt.match(/^<document /gm) ?? []).length} documents from sibling scopes: write once at the parent what at least two of them share, and trim it out of them; "nothing_shared" is a normal answer` : `${proposals} proposed new document${proposals === 1 ? "" : "s"}: combine, fold into existing ones, or reject`;
|
|
9631
9831
|
return `${chars.toLocaleString("en")} chars (~${tokens.toLocaleString("en")} tokens); about ${minutes} min. ${what}.`;
|
|
9632
9832
|
}
|
|
9633
9833
|
function jobIndex(text2) {
|
|
@@ -10115,7 +10315,7 @@ var init_runner = __esm({
|
|
|
10115
10315
|
init_trust();
|
|
10116
10316
|
init_version();
|
|
10117
10317
|
DEFAULT_API = "https://api.bunshin.cc";
|
|
10118
|
-
KINDS2 = ["reflect", "merge", "create"];
|
|
10318
|
+
KINDS2 = ["reflect", "merge", "create", "generalize"];
|
|
10119
10319
|
str5 = (v) => typeof v === "string" && v.trim() ? v.trim() : void 0;
|
|
10120
10320
|
LAUNCHD_LABEL = "cc.bunshin.runner";
|
|
10121
10321
|
CRON_MARK = "# bunshin-runner";
|
|
@@ -11146,7 +11346,7 @@ import fs32 from "node:fs";
|
|
|
11146
11346
|
import path27 from "node:path";
|
|
11147
11347
|
function parseArgs(argv) {
|
|
11148
11348
|
const out = { _: [], flags: {} };
|
|
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"]);
|
|
11349
|
+
const valueless = /* @__PURE__ */ new Set(["json", "quiet", "force", "sync", "global", "help", "version", "dry-run", "offline", "apply", "no-hooks", "remove-hooks", "key-stdin", "debug", "verbose", "robot", "fork", "upload", "index", "all", "legacy", "depth", "local", "skip-github", "propose-rewrites", "clear", "watch", "once", "allow-codex-shell", "retrace", "across"]);
|
|
11150
11350
|
for (let i = 0; i < argv.length; i++) {
|
|
11151
11351
|
const a = argv[i];
|
|
11152
11352
|
if (a === "--") {
|
|
@@ -11285,6 +11485,8 @@ Key saved privately outside the project.
|
|
|
11285
11485
|
if (result.hooks) out(`${result.hooks.installed} ${harness === "pi" ? "extension" : "hooks"} configured in ${result.hooks.file}
|
|
11286
11486
|
`);
|
|
11287
11487
|
if (result.learnCommand?.installed) out(`/learn installed in ${result.learnCommand.file}
|
|
11488
|
+
`);
|
|
11489
|
+
if (result.borrowCommand?.installed) out(`/borrow installed in ${result.borrowCommand.file} (bring in a lesson learned in another project or group)
|
|
11288
11490
|
`);
|
|
11289
11491
|
else if (result.learnCommand?.error) out(`/learn not installed: ${result.learnCommand.error} (${result.learnCommand.file})
|
|
11290
11492
|
`);
|
|
@@ -11860,10 +12062,12 @@ Usage: bunshin <command> [options]
|
|
|
11860
12062
|
status [--json] store, context, library, outbox, hooks and sync health
|
|
11861
12063
|
sources [all | only <folders\u2026> | include <folders\u2026> | exclude <folders\u2026>] [--json]
|
|
11862
12064
|
which folders under this store's folder take part (learning pool)
|
|
11863
|
-
recall "<task, abstractly>" [--hypothetical "<lesson>"] [--id a,b] [--limit n] [--json]
|
|
11864
|
-
pull candidates from
|
|
12065
|
+
recall "<task, abstractly>" [--hypothetical "<lesson>"] [--id a,b] [--limit n] [--across] [--json]
|
|
12066
|
+
pull candidates from this project's scopes (offline: local view);
|
|
12067
|
+
--across borrows from anywhere in the workspace (/borrow)
|
|
11865
12068
|
load <id> [--anchor <heading>] [--json]
|
|
11866
|
-
print one document (or one part of it) and mark it loaded
|
|
12069
|
+
print one document (or one part of it) and mark it loaded; a
|
|
12070
|
+
lesson from outside this project's scopes is recorded as borrowed
|
|
11867
12071
|
learn [focus...] capture what this session learned now (fork reflection),
|
|
11868
12072
|
apply it locally as a trial and submit it
|
|
11869
12073
|
review [list [--all]|apply <id>|submit <id>|skip <id>|apply-all|approve <id>|reject <id> [--note <text>]]
|
|
@@ -11883,9 +12087,8 @@ Usage: bunshin <command> [options]
|
|
|
11883
12087
|
the deployed skills; push proposes local edits / a new skill now
|
|
11884
12088
|
library version|log|status|pin [--write [file]]|checkout <lib@N> --to <dir> [--git <sha>] [--pin <file>]
|
|
11885
12089
|
library versions (lib@N), history, and exact reproductions
|
|
11886
|
-
library workspace
|
|
11887
|
-
|
|
11888
|
-
~/Documents/dev) instead of ~/.claude and ~/.codex; set it before the first sync
|
|
12090
|
+
library workspace (retired: each workspace keeps its own skills and links them into
|
|
12091
|
+
the projects its sessions run in; see library status)
|
|
11889
12092
|
ui [--session <id>] [--json] [--watch] what Bunshin did this session, as the person sees it
|
|
11890
12093
|
ui status | install | uninstall the Claude Code mod (band above the prompt, /bunshin, toasts)
|
|
11891
12094
|
ui enable|disable --harness <h> [--mode auto|mod|widget|lines|off] how a harness shows it
|
|
@@ -11896,9 +12099,9 @@ Usage: bunshin <command> [options]
|
|
|
11896
12099
|
repo import|attach|clone|verify|list|url|login
|
|
11897
12100
|
host a git repository in Bunshin (full history; git push/pull
|
|
11898
12101
|
keep working through git-remote-bunshin). bunshin repo help
|
|
11899
|
-
runner [--once|--watch] [--backend auto|sdk|claude-cli|codex-cli|pi-cli] [--model <m>] [--kinds reflect,merge,create]
|
|
12102
|
+
runner [--once|--watch] [--backend auto|sdk|claude-cli|codex-cli|pi-cli] [--model <m>] [--kinds reflect,merge,create,generalize]
|
|
11900
12103
|
answer the company's queued model work (cloud reflection,
|
|
11901
|
-
reconciliation) with this machine's model. BUNSHIN_RUNNER_KEY
|
|
12104
|
+
reconciliation, generalization) with this machine's model. BUNSHIN_RUNNER_KEY
|
|
11902
12105
|
runner next | submit <job> <file|-> | fail <job> --error <why>
|
|
11903
12106
|
agent mode, for a Claude Code routine or Codex cloud task
|
|
11904
12107
|
runner install --key <runner key> [--every <minutes>] [--no-run] | uninstall
|