squadrant 0.27.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -466,7 +466,8 @@ function validateRuleFrontmatter(fm) {
466
466
  if (fm[k] !== void 0 && !isStrArr(fm[k]))
467
467
  p.push(`${k} must be a string array`);
468
468
  }
469
- if (!Array.isArray(fm.sources) || fm.sources.length === 0) {
469
+ const mayBeSourceless = fm.status === "stale" || fm.status === "retired";
470
+ if (!Array.isArray(fm.sources) || fm.sources.length === 0 && !mayBeSourceless) {
470
471
  p.push("sources must be a non-empty array");
471
472
  } else {
472
473
  fm.sources.forEach((s, i) => {
@@ -500,6 +501,26 @@ function validateSourceEntry(e) {
500
501
  p.push("domain must be a string");
501
502
  if (e.sensitivity !== void 0 && e.sensitivity !== "local-only")
502
503
  p.push("sensitivity must be 'local-only' when set");
504
+ if (e.review !== void 0 && e.review !== "required")
505
+ p.push("review must be 'required' when set");
506
+ return p;
507
+ }
508
+ function validateErratum(e) {
509
+ if (!isObj(e))
510
+ return ["erratum must be an object"];
511
+ const p = [];
512
+ if (typeof e.id !== "string" || !RULE_ID_RE.test(e.id))
513
+ p.push(`id must match ${RULE_ID_RE}`);
514
+ for (const k of ["ref", "quote", "reason"]) {
515
+ if (typeof e[k] !== "string" || e[k] === "")
516
+ p.push(`${k} must be a non-empty string`);
517
+ }
518
+ if (e.evidence !== void 0 && !isStrArr(e.evidence))
519
+ p.push("evidence must be a string array");
520
+ for (const k of ["checked", "by", "at", "decision"]) {
521
+ if (e[k] !== void 0 && typeof e[k] !== "string")
522
+ p.push(`${k} must be a string`);
523
+ }
503
524
  return p;
504
525
  }
505
526
  function kbConfigs(cfg) {
@@ -624,22 +645,22 @@ function defaultCmuxConfigPath() {
624
645
  return join(homedir(), ".config", "cmux", "cmux.json");
625
646
  }
626
647
  function ensureSocketAutomation(opts = {}) {
627
- const path59 = opts.path ?? defaultCmuxConfigPath();
628
- if (!existsSync(path59)) {
629
- mkdirSync(dirname(path59), { recursive: true });
630
- writeFileSync(path59, MINIMAL_TEMPLATE);
631
- return { path: path59, changed: true, alreadySet: false };
648
+ const path60 = opts.path ?? defaultCmuxConfigPath();
649
+ if (!existsSync(path60)) {
650
+ mkdirSync(dirname(path60), { recursive: true });
651
+ writeFileSync(path60, MINIMAL_TEMPLATE);
652
+ return { path: path60, changed: true, alreadySet: false };
632
653
  }
633
- const text = readFileSync(path59, "utf-8");
654
+ const text = readFileSync(path60, "utf-8");
634
655
  const current = parse(text)?.automation?.socketControlMode;
635
656
  if (current === AUTOMATION_MODE) {
636
- return { path: path59, changed: false, alreadySet: true };
657
+ return { path: path60, changed: false, alreadySet: true };
637
658
  }
638
659
  const edits = modify(text, [...SOCKET_CONTROL_MODE_PATH], AUTOMATION_MODE, {
639
660
  formattingOptions: { insertSpaces: true, tabSize: 2 }
640
661
  });
641
- writeFileSync(path59, applyEdits(text, edits));
642
- return { path: path59, changed: true, alreadySet: false };
662
+ writeFileSync(path60, applyEdits(text, edits));
663
+ return { path: path60, changed: true, alreadySet: false };
643
664
  }
644
665
  var SOCKET_CONTROL_MODE_PATH, AUTOMATION_MODE, MINIMAL_TEMPLATE;
645
666
  var init_cmux_config = __esm({
@@ -792,9 +813,9 @@ import { join as join4 } from "path";
792
813
  function defaultStatePath() {
793
814
  return join4(homedir3(), ".config", "squadrant", "state", "cmux-autoconfig.json");
794
815
  }
795
- function readState(path59) {
816
+ function readState(path60) {
796
817
  try {
797
- return JSON.parse(readConfigFileSync(path59));
818
+ return JSON.parse(readConfigFileSync(path60));
798
819
  } catch {
799
820
  return {};
800
821
  }
@@ -955,18 +976,18 @@ function detectDrift(user, def) {
955
976
  }
956
977
  }
957
978
  }
958
- const checkBackend = (path59, backend, agentName) => {
979
+ const checkBackend = (path60, backend, agentName) => {
959
980
  if (backend === void 0)
960
981
  return;
961
982
  if (!isBackendMode(backend)) {
962
- items.push({ path: path59, kind: "invalid", severity: "warn", current: backend, note: "unknown backend; expected native|direct|proxy" });
983
+ items.push({ path: path60, kind: "invalid", severity: "warn", current: backend, note: "unknown backend; expected native|direct|proxy" });
963
984
  return;
964
985
  }
965
986
  if (backend !== "native") {
966
987
  if (agentName !== "claude") {
967
- items.push({ path: path59, kind: "invalid", severity: "warn", current: backend, note: `backend '${backend}' is claude-only (agent '${agentName ?? "?"}')` });
988
+ items.push({ path: path60, kind: "invalid", severity: "warn", current: backend, note: `backend '${backend}' is claude-only (agent '${agentName ?? "?"}')` });
968
989
  } else if (!router) {
969
- items.push({ path: path59, kind: "invalid", severity: "warn", current: backend, note: `backend '${backend}' requires defaults.router` });
990
+ items.push({ path: path60, kind: "invalid", severity: "warn", current: backend, note: `backend '${backend}' requires defaults.router` });
970
991
  }
971
992
  }
972
993
  };
@@ -1859,6 +1880,7 @@ __export(dist_exports, {
1859
1880
  saveConfig: () => saveConfig,
1860
1881
  saveProjectOverride: () => saveProjectOverride,
1861
1882
  subscribedKbs: () => subscribedKbs,
1883
+ validateErratum: () => validateErratum,
1862
1884
  validateRuleFrontmatter: () => validateRuleFrontmatter,
1863
1885
  validateSourceEntry: () => validateSourceEntry,
1864
1886
  withStamp: () => withStamp,
@@ -3510,10 +3532,10 @@ function detectForeignInstall(parsed, thisEntry, registeredEntryExists) {
3510
3532
  isUpgrade: isVersionUpgrade(parsed.daemonEntry, thisEntry)
3511
3533
  };
3512
3534
  }
3513
- function sanitizePathForPlist(path59) {
3535
+ function sanitizePathForPlist(path60) {
3514
3536
  const seen = /* @__PURE__ */ new Set();
3515
3537
  const stable = [];
3516
- for (const p of path59.split(":")) {
3538
+ for (const p of path60.split(":")) {
3517
3539
  if (!p)
3518
3540
  continue;
3519
3541
  if (p.includes("/.claude/plugins/"))
@@ -6053,11 +6075,11 @@ var init_captain_record = __esm({
6053
6075
  // packages/core/dist/opencode-session.js
6054
6076
  import { execFileSync as execFileSync4 } from "child_process";
6055
6077
  async function listSessions(port, fetchImpl = fetch, timeoutMs = 5e3) {
6056
- for (const path59 of ["/session", "/api/session"]) {
6078
+ for (const path60 of ["/session", "/api/session"]) {
6057
6079
  const ac = new AbortController();
6058
6080
  const t = setTimeout(() => ac.abort(), timeoutMs);
6059
6081
  try {
6060
- const res = await fetchImpl(`http://127.0.0.1:${port}${path59}`, { signal: ac.signal });
6082
+ const res = await fetchImpl(`http://127.0.0.1:${port}${path60}`, { signal: ac.signal });
6061
6083
  if (!res.ok)
6062
6084
  continue;
6063
6085
  const rows = await res.json();
@@ -6750,10 +6772,10 @@ function distBuiltAt() {
6750
6772
  return 0;
6751
6773
  }
6752
6774
  }
6753
- function gatherLogStats(path59, now, windowMs) {
6775
+ function gatherLogStats(path60, now, windowMs) {
6754
6776
  let sizeBytes = 0;
6755
6777
  try {
6756
- sizeBytes = statSync3(path59).size;
6778
+ sizeBytes = statSync3(path60).size;
6757
6779
  } catch {
6758
6780
  return { errorCount: 0, sizeBytes: 0, windowMs };
6759
6781
  }
@@ -6764,7 +6786,7 @@ function gatherLogStats(path59, now, windowMs) {
6764
6786
  const len = sizeBytes - start;
6765
6787
  let text = "";
6766
6788
  try {
6767
- const fd = openSync2(path59, "r");
6789
+ const fd = openSync2(path60, "r");
6768
6790
  try {
6769
6791
  const buf = Buffer.alloc(len);
6770
6792
  readSync(fd, buf, 0, len, start);
@@ -9010,8 +9032,8 @@ var init_stream = __esm({
9010
9032
  // packages/core/dist/router/shim.js
9011
9033
  import { createServer as createServer3 } from "http";
9012
9034
  import { Readable } from "stream";
9013
- function joinUrl(base, path59) {
9014
- return base.replace(/\/+$/, "") + path59;
9035
+ function joinUrl(base, path60) {
9036
+ return base.replace(/\/+$/, "") + path60;
9015
9037
  }
9016
9038
  async function readBody(req) {
9017
9039
  const chunks = [];
@@ -9187,8 +9209,8 @@ function createRouterShim(opts) {
9187
9209
  writeJson(res, 200, { input_tokens: Math.max(1, Math.ceil(text.length / 4)) });
9188
9210
  }
9189
9211
  function handler(req, res) {
9190
- const path59 = pathnameOf(req.url);
9191
- if (req.method === "POST" && path59 === "/v1/messages") {
9212
+ const path60 = pathnameOf(req.url);
9213
+ if (req.method === "POST" && path60 === "/v1/messages") {
9192
9214
  void handleMessages(req, res).catch((err) => {
9193
9215
  log(`router internal error: ${err instanceof Error ? err.message : String(err)}`);
9194
9216
  if (!res.headersSent) {
@@ -9200,18 +9222,18 @@ function createRouterShim(opts) {
9200
9222
  });
9201
9223
  return;
9202
9224
  }
9203
- if (req.method === "POST" && path59 === "/v1/messages/count_tokens") {
9225
+ if (req.method === "POST" && path60 === "/v1/messages/count_tokens") {
9204
9226
  void handleCountTokens(req, res).catch(() => {
9205
9227
  if (!res.headersSent)
9206
9228
  writeJson(res, 502, anthropicError(502, "api_error", "router internal error").body);
9207
9229
  });
9208
9230
  return;
9209
9231
  }
9210
- if (req.method === "GET" && path59 === "/v1/models") {
9232
+ if (req.method === "GET" && path60 === "/v1/models") {
9211
9233
  writeJson(res, 200, modelsList([...advertisedModels]));
9212
9234
  return;
9213
9235
  }
9214
- if (req.method === "GET" && path59 === "/healthz") {
9236
+ if (req.method === "GET" && path60 === "/healthz") {
9215
9237
  writeJson(res, 200, { ready: server?.listening === true, upstreamReachable, lastError });
9216
9238
  return;
9217
9239
  }
@@ -9480,11 +9502,11 @@ async function probeCaptainChannel(project, opts = {}) {
9480
9502
  }
9481
9503
  }
9482
9504
  async function httpReachable(port, fetchImpl, timeoutMs) {
9483
- for (const path59 of ["/session", "/api/session"]) {
9505
+ for (const path60 of ["/session", "/api/session"]) {
9484
9506
  const ac = new AbortController();
9485
9507
  const t = setTimeout(() => ac.abort(), timeoutMs);
9486
9508
  try {
9487
- const res = await fetchImpl(`http://127.0.0.1:${port}${path59}`, { signal: ac.signal });
9509
+ const res = await fetchImpl(`http://127.0.0.1:${port}${path60}`, { signal: ac.signal });
9488
9510
  if (res.ok)
9489
9511
  return true;
9490
9512
  } catch {
@@ -10341,16 +10363,281 @@ var init_paths = __esm({
10341
10363
  }
10342
10364
  });
10343
10365
 
10344
- // packages/core/dist/knowledge/store.js
10366
+ // packages/core/dist/knowledge/ingest.js
10367
+ import crypto3 from "crypto";
10345
10368
  import fs15 from "fs";
10346
10369
  import os7 from "os";
10347
10370
  import path16 from "path";
10371
+ import { execFile as execFile2 } from "child_process";
10372
+ import { promisify as promisify2 } from "util";
10373
+ function refFor(kbRoot, file) {
10374
+ const rel = path16.relative(kbRoot, file);
10375
+ if (rel && !rel.startsWith("..") && !path16.isAbsolute(rel))
10376
+ return rel.split(path16.sep).join("/");
10377
+ const home = os7.homedir();
10378
+ return file.startsWith(home + path16.sep) ? `~/${path16.relative(home, file).split(path16.sep).join("/")}` : file;
10379
+ }
10380
+ function readState2(kbRoot, ref) {
10381
+ try {
10382
+ return JSON.parse(fs15.readFileSync(stampFile(kbRoot, ref), "utf8"));
10383
+ } catch {
10384
+ return null;
10385
+ }
10386
+ }
10387
+ function writeState(kbRoot, st) {
10388
+ fs15.mkdirSync(path16.join(kbRoot, CONVERTED_DIR), { recursive: true });
10389
+ fs15.writeFileSync(stampFile(kbRoot, st.ref), JSON.stringify(st) + "\n");
10390
+ }
10391
+ function globToRegExp(glob) {
10392
+ let re = "";
10393
+ for (let i = 0; i < glob.length; i++) {
10394
+ const c = glob[i];
10395
+ if (c === "*") {
10396
+ if (glob[i + 1] === "*") {
10397
+ re += glob[i + 2] === "/" ? "(?:.*/)?" : ".*";
10398
+ i += glob[i + 2] === "/" ? 2 : 1;
10399
+ } else
10400
+ re += "[^/]*";
10401
+ } else if (c === "?")
10402
+ re += "[^/]";
10403
+ else
10404
+ re += c.replace(/[.+^${}()|[\]\\]/g, "\\$&");
10405
+ }
10406
+ return new RegExp(`^${re}$`);
10407
+ }
10408
+ function walkFiles(dir, out = []) {
10409
+ if (!fs15.existsSync(dir))
10410
+ return out;
10411
+ for (const ent of fs15.readdirSync(dir, { withFileTypes: true })) {
10412
+ if (ent.name === ".git" || ent.name === CONVERTED_DIR || ent.name === "node_modules")
10413
+ continue;
10414
+ const full = path16.join(dir, ent.name);
10415
+ if (ent.isDirectory())
10416
+ walkFiles(full, out);
10417
+ else if (ent.isFile())
10418
+ out.push(full);
10419
+ }
10420
+ return out;
10421
+ }
10422
+ function expandSource(kbRoot, entry) {
10423
+ const abs = path16.resolve(kbRoot, expandHome(entry.path));
10424
+ if (!/[*?]/.test(entry.path))
10425
+ return fs15.existsSync(abs) && fs15.statSync(abs).isFile() ? [abs] : [];
10426
+ const base = abs.split(/[*?]/)[0];
10427
+ const root = base.endsWith(path16.sep) ? base : path16.dirname(base);
10428
+ const re = globToRegExp(abs.split(path16.sep).join("/"));
10429
+ return walkFiles(root).filter((f) => re.test(f.split(path16.sep).join("/"))).sort();
10430
+ }
10431
+ function sourceCovers(kbRoot, entry, ref) {
10432
+ const abs = path16.resolve(kbRoot, expandHome(entry.path));
10433
+ const file = fileForRef(kbRoot, ref);
10434
+ return /[*?]/.test(entry.path) ? globToRegExp(abs.split(path16.sep).join("/")).test(file.split(path16.sep).join("/")) : abs === file;
10435
+ }
10436
+ function detectChange(kbRoot, file) {
10437
+ const ref = refFor(kbRoot, file);
10438
+ const st = fs15.statSync(file);
10439
+ const prior = readState2(kbRoot, ref);
10440
+ if (prior && prior.size === st.size && prior.mtimeMs === st.mtimeMs) {
10441
+ return { ref, changed: false, stamp: { sha: prior.sha, size: st.size, mtimeMs: st.mtimeMs }, prior };
10442
+ }
10443
+ const sha = sha256(file);
10444
+ return { ref, changed: !prior || prior.sha !== sha, stamp: { sha, size: st.size, mtimeMs: st.mtimeMs }, prior };
10445
+ }
10446
+ function splitSections(text) {
10447
+ const cuts = [];
10448
+ const re = /^#{1,6}[ \t]+(.+)$/gm;
10449
+ for (let m = re.exec(text); m; m = re.exec(text))
10450
+ cuts.push({ at: m.index, heading: m[1].trim() });
10451
+ if (!cuts.length || cuts[0].at > 0)
10452
+ cuts.unshift({ at: 0 });
10453
+ const spans = [];
10454
+ cuts.forEach((c, i) => {
10455
+ const end = i + 1 < cuts.length ? cuts[i + 1].at : text.length;
10456
+ let start = c.at;
10457
+ while (end - start > SECTION_TARGET_CHARS * 1.5) {
10458
+ const brk = text.indexOf("\n\n", start + SECTION_TARGET_CHARS);
10459
+ if (brk < 0 || brk >= end)
10460
+ break;
10461
+ spans.push({ index: 0, start, end: brk, heading: c.heading });
10462
+ start = brk + 2;
10463
+ }
10464
+ if (text.slice(start, end).trim())
10465
+ spans.push({ index: 0, start, end, heading: c.heading });
10466
+ });
10467
+ return spans.map((s, i) => ({ ...s, index: i }));
10468
+ }
10469
+ async function planIngest(kbRoot, opts) {
10470
+ const converter = opts.converter ?? markitdownConverter;
10471
+ const cap = opts.maxSectionsPerPass ?? DEFAULT_MAX_SECTIONS_PER_PASS;
10472
+ const plan = { sources: [], skipped: [], failed: [], carriedSections: 0, sectionCount: 0, estimatedTokens: 0, existing: opts.existing ?? [] };
10473
+ let budget = cap;
10474
+ const seen = /* @__PURE__ */ new Set();
10475
+ for (const entry of opts.sources) {
10476
+ for (const file of expandSource(kbRoot, entry)) {
10477
+ const { ref, changed, stamp, prior } = detectChange(kbRoot, file);
10478
+ if (seen.has(ref))
10479
+ continue;
10480
+ seen.add(ref);
10481
+ const pending = !!prior && prior.cursor < prior.sections;
10482
+ if (!changed && !pending)
10483
+ continue;
10484
+ if (entry.sensitivity === "local-only" && !opts.localModel) {
10485
+ plan.skipped.push({ ref, reason: "sensitivity: local-only and no local model available" });
10486
+ continue;
10487
+ }
10488
+ if (budget <= 0) {
10489
+ plan.carriedSections += changed ? 1 : prior.sections - prior.cursor;
10490
+ continue;
10491
+ }
10492
+ let text;
10493
+ const conv = convertedFile(kbRoot, ref);
10494
+ try {
10495
+ if (!changed && fs15.existsSync(conv))
10496
+ text = fs15.readFileSync(conv, "utf8");
10497
+ else {
10498
+ text = PASS_THROUGH.has(path16.extname(file).toLowerCase()) ? fs15.readFileSync(file, "utf8") : await converter(file);
10499
+ if (!opts.dryRun) {
10500
+ fs15.mkdirSync(path16.dirname(conv), { recursive: true });
10501
+ fs15.writeFileSync(conv, text);
10502
+ }
10503
+ }
10504
+ } catch (e) {
10505
+ plan.failed.push({ ref, error: e.message.split("\n")[0] });
10506
+ continue;
10507
+ }
10508
+ const all = splitSections(text);
10509
+ const from = changed ? 0 : prior.cursor;
10510
+ const take = all.slice(from, from + budget);
10511
+ budget -= take.length;
10512
+ plan.carriedSections += all.length - from - take.length;
10513
+ plan.sectionCount += take.length;
10514
+ plan.estimatedTokens += Math.ceil(take.reduce((n, s) => n + (s.end - s.start), 0) / CHARS_PER_TOKEN);
10515
+ plan.sources.push({
10516
+ ref,
10517
+ file,
10518
+ converted: conv,
10519
+ sha: stamp.sha,
10520
+ priority: entry.priority,
10521
+ domain: entry.domain,
10522
+ sensitivity: entry.sensitivity,
10523
+ sections: take,
10524
+ totalSections: all.length
10525
+ });
10526
+ if (!opts.dryRun)
10527
+ writeState(kbRoot, { ref, ...stamp, sections: all.length, cursor: from + take.length, priority: entry.priority });
10528
+ }
10529
+ }
10530
+ return plan;
10531
+ }
10532
+ var execFileAsync, CONVERTED_DIR, DEFAULT_MAX_SECTIONS_PER_PASS, CHARS_PER_TOKEN, PASS_THROUGH, SECTION_TARGET_CHARS, markitdownConverter, expandHome, fileForRef, cacheKey, convertedFile, stampFile, isGatedRef, sha256;
10533
+ var init_ingest = __esm({
10534
+ "packages/core/dist/knowledge/ingest.js"() {
10535
+ execFileAsync = promisify2(execFile2);
10536
+ CONVERTED_DIR = ".converted";
10537
+ DEFAULT_MAX_SECTIONS_PER_PASS = 40;
10538
+ CHARS_PER_TOKEN = 4;
10539
+ PASS_THROUGH = /* @__PURE__ */ new Set([".md", ".markdown", ".txt"]);
10540
+ SECTION_TARGET_CHARS = 4e3;
10541
+ markitdownConverter = async (file) => {
10542
+ const { stdout } = await execFileAsync("markitdown", [file], { maxBuffer: 64 * 1024 * 1024 });
10543
+ return stdout;
10544
+ };
10545
+ expandHome = (p) => p === "~" || p.startsWith("~/") ? path16.join(os7.homedir(), p.slice(1)) : p;
10546
+ fileForRef = (kbRoot, ref) => path16.resolve(kbRoot, expandHome(ref));
10547
+ cacheKey = (ref) => `${ref.replace(/[^A-Za-z0-9._-]+/g, "_")}.${crypto3.createHash("sha1").update(ref).digest("hex").slice(0, 6)}`;
10548
+ convertedFile = (kbRoot, ref) => path16.join(kbRoot, CONVERTED_DIR, `${cacheKey(ref)}.md`);
10549
+ stampFile = (kbRoot, ref) => path16.join(kbRoot, CONVERTED_DIR, `${cacheKey(ref)}.sha`);
10550
+ isGatedRef = (kbRoot, sources, ref) => sources.some((e) => e.review === "required" && sourceCovers(kbRoot, e, ref));
10551
+ sha256 = (file) => crypto3.createHash("sha256").update(fs15.readFileSync(file)).digest("hex");
10552
+ }
10553
+ });
10554
+
10555
+ // packages/core/dist/knowledge/verify.js
10556
+ function normalizeWithMap(src, keepBreakHyphen = false) {
10557
+ const out = [];
10558
+ const map2 = [];
10559
+ const push = (s, at) => {
10560
+ for (const ch of s) {
10561
+ out.push(ch);
10562
+ map2.push(at);
10563
+ }
10564
+ };
10565
+ let i = 0;
10566
+ while (i < src.length) {
10567
+ const ch = src[i];
10568
+ if (ch === "-" && /^-[ \t]*\r?\n[ \t]*[a-z]/.test(src.slice(i, i + 40)) && i > 0 && /[A-Za-z]/.test(src[i - 1])) {
10569
+ if (keepBreakHyphen)
10570
+ push("-", i);
10571
+ i++;
10572
+ while (/\s/.test(src[i]))
10573
+ i++;
10574
+ continue;
10575
+ }
10576
+ if (/\s/.test(ch)) {
10577
+ const start = i;
10578
+ while (i < src.length && /\s/.test(src[i]))
10579
+ i++;
10580
+ if (out.length && out[out.length - 1] !== " ")
10581
+ push(" ", start);
10582
+ continue;
10583
+ }
10584
+ if (MARKUP.has(ch)) {
10585
+ i++;
10586
+ continue;
10587
+ }
10588
+ if (LIGATURES[ch])
10589
+ push(LIGATURES[ch], i);
10590
+ else if (QUOTES[ch])
10591
+ push(QUOTES[ch], i);
10592
+ else if (DASHES.has(ch))
10593
+ push("-", i);
10594
+ else
10595
+ push(ch, i);
10596
+ i++;
10597
+ }
10598
+ while (out.length && out[out.length - 1] === " ") {
10599
+ out.pop();
10600
+ map2.pop();
10601
+ }
10602
+ return { text: out.join(""), map: map2 };
10603
+ }
10604
+ function findQuote(source, quote) {
10605
+ const needle = normalizeText(quote);
10606
+ if (!needle)
10607
+ return null;
10608
+ const views = typeof source === "string" ? normalizedViews(source) : source;
10609
+ for (const hay of views) {
10610
+ const at = hay.text.indexOf(needle);
10611
+ if (at >= 0)
10612
+ return [hay.map[at], hay.map[at + needle.length - 1] + 1];
10613
+ }
10614
+ return null;
10615
+ }
10616
+ var LIGATURES, QUOTES, DASHES, MARKUP, normalizeText, normalizedViews, DOCLING_DROP_THRESHOLD, needsDocling, spansOverlap;
10617
+ var init_verify = __esm({
10618
+ "packages/core/dist/knowledge/verify.js"() {
10619
+ LIGATURES = { "\uFB01": "fi", "\uFB02": "fl", "\uFB00": "ff", "\uFB03": "ffi", "\uFB04": "ffl", "\uFB05": "st", "\uFB06": "st" };
10620
+ QUOTES = { "\u2018": "'", "\u2019": "'", "\u201A": "'", "\u201B": "'", "\u201C": '"', "\u201D": '"', "\u201E": '"', "\u201F": '"' };
10621
+ DASHES = /* @__PURE__ */ new Set(["\u2010", "\u2011", "\u2012", "\u2013", "\u2014", "\u2015", "\u2212"]);
10622
+ MARKUP = /* @__PURE__ */ new Set(["*", "_", "`", "\\"]);
10623
+ normalizeText = (s) => normalizeWithMap(s).text;
10624
+ normalizedViews = (src) => [normalizeWithMap(src), normalizeWithMap(src, true)];
10625
+ DOCLING_DROP_THRESHOLD = 0.2;
10626
+ needsDocling = (dropped, total) => total > 0 && dropped / total > DOCLING_DROP_THRESHOLD;
10627
+ spansOverlap = (a, b) => a[0] < b[1] && b[0] < a[1];
10628
+ }
10629
+ });
10630
+
10631
+ // packages/core/dist/knowledge/store.js
10632
+ import fs16 from "fs";
10633
+ import os8 from "os";
10634
+ import path17 from "path";
10348
10635
  import matter2 from "gray-matter";
10349
10636
  function duplicateIdProblem(id, ignored, used) {
10350
10637
  return `duplicate id ${id}: ${ignored} is ignored, ${used} is used (live files win over _proposed, then path order). Fix: rename the id in one file, or delete or move one of them.`;
10351
10638
  }
10352
10639
  function isPendingProposalDup(e, rulesDir) {
10353
- return e.problems.every((p) => p.startsWith("duplicate id")) && path16.relative(rulesDir, e.file).split(path16.sep).includes(PROPOSED_DIR);
10640
+ return e.problems.every((p) => p.startsWith("duplicate id")) && path17.relative(rulesDir, e.file).split(path17.sep).includes(PROPOSED_DIR);
10354
10641
  }
10355
10642
  function splitStatement(body) {
10356
10643
  const first = body.trim().split(/\n\s*\n/)[0] ?? "";
@@ -10359,8 +10646,8 @@ function splitStatement(body) {
10359
10646
  function listMarkdown(dir, includeProposed) {
10360
10647
  const out = [];
10361
10648
  const walk = (d) => {
10362
- for (const ent of fs15.readdirSync(d, { withFileTypes: true })) {
10363
- const full = path16.join(d, ent.name);
10649
+ for (const ent of fs16.readdirSync(d, { withFileTypes: true })) {
10650
+ const full = path17.join(d, ent.name);
10364
10651
  if (ent.isDirectory()) {
10365
10652
  if (ent.name === PROPOSED_DIR && !includeProposed)
10366
10653
  continue;
@@ -10371,11 +10658,11 @@ function listMarkdown(dir, includeProposed) {
10371
10658
  }
10372
10659
  };
10373
10660
  walk(dir);
10374
- const isProposed = (f) => path16.relative(dir, f).split(path16.sep).includes(PROPOSED_DIR);
10661
+ const isProposed = (f) => path17.relative(dir, f).split(path17.sep).includes(PROPOSED_DIR);
10375
10662
  return out.sort((a, b) => Number(isProposed(a)) - Number(isProposed(b)) || (a < b ? -1 : a > b ? 1 : 0));
10376
10663
  }
10377
10664
  function loadRulesDir(dir, layer, opts = {}) {
10378
- if (!fs15.existsSync(dir))
10665
+ if (!fs16.existsSync(dir))
10379
10666
  return { rules: [], errors: [] };
10380
10667
  const rules = [];
10381
10668
  const errors = [];
@@ -10383,7 +10670,7 @@ function loadRulesDir(dir, layer, opts = {}) {
10383
10670
  for (const file of listMarkdown(dir, opts.includeProposed ?? false)) {
10384
10671
  let parsed;
10385
10672
  try {
10386
- parsed = matter2(fs15.readFileSync(file, "utf8"), NO_JS_FRONTMATTER);
10673
+ parsed = matter2(fs16.readFileSync(file, "utf8"), NO_JS_FRONTMATTER);
10387
10674
  } catch (e) {
10388
10675
  errors.push({ file, problems: [`frontmatter parse error: ${e.message}`] });
10389
10676
  continue;
@@ -10407,14 +10694,14 @@ function loadRulesDir(dir, layer, opts = {}) {
10407
10694
  function resolveSourceRef(ref, kbRoot, sources) {
10408
10695
  const bases = /* @__PURE__ */ new Set([kbRoot]);
10409
10696
  for (const s of sources) {
10410
- let d = path16.resolve(kbRoot, expandHome(s.path.split(/[*?[{]/)[0]));
10411
- for (let i = 0; i < 6 && d !== path16.dirname(d); i++) {
10412
- d = path16.dirname(d);
10697
+ let d = path17.resolve(kbRoot, expandHome2(s.path.split(/[*?[{]/)[0]));
10698
+ for (let i = 0; i < 6 && d !== path17.dirname(d); i++) {
10699
+ d = path17.dirname(d);
10413
10700
  bases.add(d);
10414
10701
  }
10415
10702
  }
10416
- const cands = [path16.resolve(kbRoot, expandHome(ref)), ...[...bases].map((b) => path16.join(b, ref))];
10417
- return cands.find((c) => fs15.existsSync(c) && fs15.statSync(c).isFile());
10703
+ const cands = [path17.resolve(kbRoot, expandHome2(ref)), ...[...bases].map((b) => path17.join(b, ref))];
10704
+ return cands.find((c) => fs16.existsSync(c) && fs16.statSync(c).isFile());
10418
10705
  }
10419
10706
  function validateKb(opts) {
10420
10707
  const { rules, errors } = loadRulesDir(opts.rulesDir, "kb", { includeProposed: true });
@@ -10422,7 +10709,7 @@ function validateKb(opts) {
10422
10709
  const dups = errors.filter((e) => e.problems.some((p) => p.startsWith("duplicate id")) && !isPendingProposalDup(e, opts.rulesDir));
10423
10710
  const pendingDups = errors.filter((e) => isPendingProposalDup(e, opts.rulesDir)).length;
10424
10711
  const invalid = errors.filter((e) => !dups.includes(e) && !isPendingProposalDup(e, opts.rulesDir));
10425
- const proposed = rules.filter((r) => path16.relative(opts.rulesDir, r.file).split(path16.sep).includes(PROPOSED_DIR)).length;
10712
+ const proposed = rules.filter((r) => path17.relative(opts.rulesDir, r.file).split(path17.sep).includes(PROPOSED_DIR)).length;
10426
10713
  checks.push({ level: "ok", message: `${rules.length + errors.length} rule files read (${rules.length - proposed} live, ${proposed + pendingDups} proposed parsed OK)` });
10427
10714
  if (invalid.length)
10428
10715
  for (const e of invalid)
@@ -10435,7 +10722,7 @@ function validateKb(opts) {
10435
10722
  else
10436
10723
  checks.push({ level: "ok", message: "no duplicate ids" });
10437
10724
  const cache = /* @__PURE__ */ new Map();
10438
- let verified = 0, unresolved = 0;
10725
+ let verified = 0, unresolved = 0, notIngested = 0;
10439
10726
  for (const r of rules) {
10440
10727
  r.sources.forEach((src, i) => {
10441
10728
  const file = resolveSourceRef(src.ref, opts.kbRoot, opts.sources);
@@ -10443,16 +10730,33 @@ function validateKb(opts) {
10443
10730
  unresolved++;
10444
10731
  return;
10445
10732
  }
10446
- if (!cache.has(file))
10447
- cache.set(file, fs15.readFileSync(file, "utf8"));
10448
- if (cache.get(file).includes(src.quote))
10733
+ const key = `${src.ref}\0${file}`;
10734
+ if (!cache.has(key)) {
10735
+ const conv = readState2(opts.kbRoot, src.ref) ? convertedFile(opts.kbRoot, src.ref) : null;
10736
+ const cached = !!conv && fs16.existsSync(conv);
10737
+ cache.set(key, {
10738
+ hay: normalizedViews(fs16.readFileSync(cached ? conv : file, "utf8")),
10739
+ convertible: !cached && !PASS_THROUGH.has(path17.extname(file).toLowerCase())
10740
+ });
10741
+ }
10742
+ const { hay, convertible } = cache.get(key);
10743
+ const span = findQuote(hay, src.quote);
10744
+ if (span) {
10449
10745
  verified++;
10746
+ const active = r.status === "active" && !path17.relative(opts.rulesDir, r.file).split(path17.sep).includes(PROPOSED_DIR);
10747
+ const err = active ? (opts.errata ?? []).find((e) => e.ref === src.ref && ((o) => !!o && spansOverlap(o, span))(findQuote(hay, e.quote))) : void 0;
10748
+ if (err)
10749
+ checks.push({ level: "warn", message: `${r.id}: grounded in erratum ${err.id} (${err.reason}); re-check or retire it (${r.file})` });
10750
+ } else if (convertible)
10751
+ notIngested++;
10450
10752
  else
10451
10753
  checks.push({ level: "fail", message: `quote not found in ${file}: ${r.id} sources[${i}] (${r.file})` });
10452
10754
  });
10453
10755
  }
10454
10756
  if (!checks.some((c) => c.message.startsWith("quote not found")))
10455
10757
  checks.push({ level: "ok", message: `${verified} source quotes found in their source files` });
10758
+ if (notIngested)
10759
+ checks.push({ level: "skip", message: `${notIngested} source refs need conversion but have no .converted cache, quote not checked (run \`squadrant knowledge ingest <kb>\`)` });
10456
10760
  if (unresolved)
10457
10761
  checks.push({ level: "skip", message: `${unresolved} source refs not resolvable on this machine, quote not checked` });
10458
10762
  if (opts.domains) {
@@ -10463,22 +10767,24 @@ function validateKb(opts) {
10463
10767
  }
10464
10768
  return { checks, ok: !checks.some((c) => c.level === "fail") };
10465
10769
  }
10466
- var NO_JS_FRONTMATTER, expandHome;
10770
+ var NO_JS_FRONTMATTER, expandHome2;
10467
10771
  var init_store2 = __esm({
10468
10772
  "packages/core/dist/knowledge/store.js"() {
10469
10773
  init_dist();
10774
+ init_ingest();
10470
10775
  init_paths();
10776
+ init_verify();
10471
10777
  NO_JS_FRONTMATTER = {
10472
10778
  engines: { javascript: { parse: () => {
10473
10779
  throw new Error("only YAML front matter is allowed");
10474
10780
  } } }
10475
10781
  };
10476
- expandHome = (p) => p === "~" || p.startsWith("~/") ? path16.join(os7.homedir(), p.slice(1)) : p;
10782
+ expandHome2 = (p) => p === "~" || p.startsWith("~/") ? path17.join(os8.homedir(), p.slice(1)) : p;
10477
10783
  }
10478
10784
  });
10479
10785
 
10480
10786
  // packages/core/dist/knowledge/layers.js
10481
- import fs16 from "fs";
10787
+ import fs17 from "fs";
10482
10788
  function loadKbRules(cfg, kb, opts = {}) {
10483
10789
  return loadRulesDir(kbRulesDir(cfg, kb), `kb:${kb}`, opts);
10484
10790
  }
@@ -10490,7 +10796,7 @@ function resolveProjectRules(cfg, project, opts = {}) {
10490
10796
  const errors = [];
10491
10797
  const warnings = [];
10492
10798
  for (const kb of subscribedKbs(cfg, project)) {
10493
- if (!fs16.existsSync(kbDir(cfg, kb))) {
10799
+ if (!fs17.existsSync(kbDir(cfg, kb))) {
10494
10800
  warnings.push(`knowledge base '${kb}' not found (run: squadrant knowledge init ${kb})`);
10495
10801
  continue;
10496
10802
  }
@@ -10528,8 +10834,8 @@ var init_layers = __esm({
10528
10834
  });
10529
10835
 
10530
10836
  // packages/core/dist/knowledge/index-file.js
10531
- import fs17 from "fs";
10532
- import path17 from "path";
10837
+ import fs18 from "fs";
10838
+ import path18 from "path";
10533
10839
  function compileIndex(kb, rules, now = /* @__PURE__ */ new Date()) {
10534
10840
  return {
10535
10841
  kb,
@@ -10538,18 +10844,18 @@ function compileIndex(kb, rules, now = /* @__PURE__ */ new Date()) {
10538
10844
  };
10539
10845
  }
10540
10846
  function writeIndex(kbRoot, index) {
10541
- const file = path17.join(kbRoot, "index.json");
10847
+ const file = path18.join(kbRoot, "index.json");
10542
10848
  const tmp = `${file}.tmp-${process.pid}`;
10543
- fs17.writeFileSync(tmp, JSON.stringify(index, null, 2) + "\n");
10544
- fs17.renameSync(tmp, file);
10849
+ fs18.writeFileSync(tmp, JSON.stringify(index, null, 2) + "\n");
10850
+ fs18.renameSync(tmp, file);
10545
10851
  return file;
10546
10852
  }
10547
10853
  function readIndex(kbRoot, kb) {
10548
- const file = path17.join(kbRoot, "index.json");
10549
- if (!fs17.existsSync(file))
10854
+ const file = path18.join(kbRoot, "index.json");
10855
+ if (!fs18.existsSync(file))
10550
10856
  return { index: null, problem: `index.json missing for kb '${kb}'` };
10551
10857
  try {
10552
- const index = JSON.parse(fs17.readFileSync(file, "utf8"));
10858
+ const index = JSON.parse(fs18.readFileSync(file, "utf8"));
10553
10859
  if (!Array.isArray(index.rules))
10554
10860
  return { index: null, problem: `index.json corrupt for kb '${kb}' (no rules array)` };
10555
10861
  return { index };
@@ -13644,15 +13950,16 @@ var init_js_yaml = __esm({
13644
13950
  });
13645
13951
 
13646
13952
  // packages/core/dist/knowledge/sources.js
13647
- import fs18 from "fs";
13648
- import path18 from "path";
13953
+ import fs19 from "fs";
13954
+ import path19 from "path";
13955
+ import matter3 from "gray-matter";
13649
13956
  function loadSources(kbRoot) {
13650
- const file = path18.join(kbRoot, "sources.yaml");
13651
- if (!fs18.existsSync(file))
13957
+ const file = path19.join(kbRoot, "sources.yaml");
13958
+ if (!fs19.existsSync(file))
13652
13959
  return { sources: [], errors: [] };
13653
13960
  let data;
13654
13961
  try {
13655
- data = yaml.load(fs18.readFileSync(file, "utf8"));
13962
+ data = yaml.load(fs19.readFileSync(file, "utf8"));
13656
13963
  } catch (e) {
13657
13964
  return { sources: [], errors: [`YAML parse error: ${e.message.split("\n")[0]}`] };
13658
13965
  }
@@ -13671,208 +13978,82 @@ function loadSources(kbRoot) {
13671
13978
  });
13672
13979
  return { sources, errors };
13673
13980
  }
13981
+ function dropEntryBlock(text, entryPath) {
13982
+ const lines = text.split("\n");
13983
+ const q = entryPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13984
+ const head = new RegExp(`^-\\s+path:\\s*(['"]?)${q}\\1\\s*(#.*)?$`);
13985
+ const i = lines.findIndex((l) => head.test(l));
13986
+ if (i < 0)
13987
+ return null;
13988
+ let j = i + 1;
13989
+ while (j < lines.length && /^\s+\S/.test(lines[j]))
13990
+ j++;
13991
+ return [...lines.slice(0, i), ...lines.slice(j)].join("\n");
13992
+ }
13993
+ function removeSource(kbRoot, rulesDir, entryPath, opts = {}) {
13994
+ const file = path19.join(kbRoot, "sources.yaml");
13995
+ const { sources } = loadSources(kbRoot);
13996
+ const entry = sources.find((s) => s.path === entryPath);
13997
+ if (!entry)
13998
+ throw new Error(`no sources.yaml entry with path '${entryPath}' (give it exactly as listed by \`knowledge sources\`)`);
13999
+ const edited = dropEntryBlock(fs19.readFileSync(file, "utf8"), entryPath);
14000
+ if (edited === null)
14001
+ throw new Error(`could not edit sources.yaml for '${entryPath}' (not a plain block entry); remove it by hand, then rerun to strip the refs`);
14002
+ const res = { dryRun: !!opts.dryRun, entryRemoved: true, stripped: [], stale: [] };
14003
+ const writes = [];
14004
+ for (const rule2 of loadRulesDir(rulesDir, "kb").rules) {
14005
+ const kept = rule2.sources.filter((s) => !sourceCovers(kbRoot, entry, s.ref));
14006
+ if (kept.length === rule2.sources.length)
14007
+ continue;
14008
+ res.stripped.push(rule2.id);
14009
+ const parsed = matter3(fs19.readFileSync(rule2.file, "utf8"), { engines: { javascript: () => {
14010
+ throw new Error("only YAML front matter is allowed");
14011
+ } } });
14012
+ const fm = { ...parsed.data, sources: kept };
14013
+ if (!kept.length) {
14014
+ res.stale.push(rule2.id);
14015
+ if (fm.status === "active")
14016
+ fm.status = "stale";
14017
+ }
14018
+ writes.push({ file: rule2.file, text: matter3.stringify(parsed.content, JSON.parse(JSON.stringify(fm))) });
14019
+ }
14020
+ res.stripped.sort();
14021
+ res.stale.sort();
14022
+ if (opts.dryRun)
14023
+ return res;
14024
+ for (const w of writes)
14025
+ fs19.writeFileSync(w.file, w.text);
14026
+ fs19.writeFileSync(file, edited);
14027
+ const name = opts.kbName ?? path19.basename(kbRoot);
14028
+ writeIndex(kbRoot, compileIndex(name, loadRulesDir(rulesDir, `kb:${name}`).rules));
14029
+ return res;
14030
+ }
13674
14031
  var SOURCES_TEMPLATE;
13675
14032
  var init_sources = __esm({
13676
14033
  "packages/core/dist/knowledge/sources.js"() {
13677
14034
  init_js_yaml();
13678
14035
  init_dist();
14036
+ init_index_file();
14037
+ init_ingest();
14038
+ init_store2();
13679
14039
  SOURCES_TEMPLATE = `# Trusted sources for this knowledge base (spec \xA73). Only these can produce rules.
13680
14040
  # priority: company > project > agent (decides conflicts).
13681
14041
  # sensitivity: local-only \u2192 never sent to a hosted model (D12).
14042
+ # review: required \u2192 new rules from this source wait in rules/_proposed/ for the operator; they never go live on their own.
13682
14043
  #
13683
14044
  # - path: raw/finance/**
13684
14045
  # priority: company
13685
14046
  # - path: ~/work/flooros/docs/handbook/**
13686
14047
  # priority: project
13687
14048
  # domain: coding
14049
+ # - path: raw/**
14050
+ # priority: agent
14051
+ # review: required
13688
14052
  []
13689
14053
  `;
13690
14054
  }
13691
14055
  });
13692
14056
 
13693
- // packages/core/dist/knowledge/ingest.js
13694
- import crypto3 from "crypto";
13695
- import fs19 from "fs";
13696
- import os8 from "os";
13697
- import path19 from "path";
13698
- import { execFile as execFile2 } from "child_process";
13699
- import { promisify as promisify2 } from "util";
13700
- function refFor(kbRoot, file) {
13701
- const rel = path19.relative(kbRoot, file);
13702
- if (rel && !rel.startsWith("..") && !path19.isAbsolute(rel))
13703
- return rel.split(path19.sep).join("/");
13704
- const home = os8.homedir();
13705
- return file.startsWith(home + path19.sep) ? `~/${path19.relative(home, file).split(path19.sep).join("/")}` : file;
13706
- }
13707
- function readState2(kbRoot, ref) {
13708
- try {
13709
- return JSON.parse(fs19.readFileSync(stampFile(kbRoot, ref), "utf8"));
13710
- } catch {
13711
- return null;
13712
- }
13713
- }
13714
- function writeState(kbRoot, st) {
13715
- fs19.mkdirSync(path19.join(kbRoot, CONVERTED_DIR), { recursive: true });
13716
- fs19.writeFileSync(stampFile(kbRoot, st.ref), JSON.stringify(st) + "\n");
13717
- }
13718
- function globToRegExp(glob) {
13719
- let re = "";
13720
- for (let i = 0; i < glob.length; i++) {
13721
- const c = glob[i];
13722
- if (c === "*") {
13723
- if (glob[i + 1] === "*") {
13724
- re += glob[i + 2] === "/" ? "(?:.*/)?" : ".*";
13725
- i += glob[i + 2] === "/" ? 2 : 1;
13726
- } else
13727
- re += "[^/]*";
13728
- } else if (c === "?")
13729
- re += "[^/]";
13730
- else
13731
- re += c.replace(/[.+^${}()|[\]\\]/g, "\\$&");
13732
- }
13733
- return new RegExp(`^${re}$`);
13734
- }
13735
- function walkFiles(dir, out = []) {
13736
- if (!fs19.existsSync(dir))
13737
- return out;
13738
- for (const ent of fs19.readdirSync(dir, { withFileTypes: true })) {
13739
- if (ent.name === ".git" || ent.name === CONVERTED_DIR || ent.name === "node_modules")
13740
- continue;
13741
- const full = path19.join(dir, ent.name);
13742
- if (ent.isDirectory())
13743
- walkFiles(full, out);
13744
- else if (ent.isFile())
13745
- out.push(full);
13746
- }
13747
- return out;
13748
- }
13749
- function expandSource(kbRoot, entry) {
13750
- const abs = path19.resolve(kbRoot, expandHome2(entry.path));
13751
- if (!/[*?]/.test(entry.path))
13752
- return fs19.existsSync(abs) && fs19.statSync(abs).isFile() ? [abs] : [];
13753
- const base = abs.split(/[*?]/)[0];
13754
- const root = base.endsWith(path19.sep) ? base : path19.dirname(base);
13755
- const re = globToRegExp(abs.split(path19.sep).join("/"));
13756
- return walkFiles(root).filter((f) => re.test(f.split(path19.sep).join("/"))).sort();
13757
- }
13758
- function detectChange(kbRoot, file) {
13759
- const ref = refFor(kbRoot, file);
13760
- const st = fs19.statSync(file);
13761
- const prior = readState2(kbRoot, ref);
13762
- if (prior && prior.size === st.size && prior.mtimeMs === st.mtimeMs) {
13763
- return { ref, changed: false, stamp: { sha: prior.sha, size: st.size, mtimeMs: st.mtimeMs }, prior };
13764
- }
13765
- const sha = sha256(file);
13766
- return { ref, changed: !prior || prior.sha !== sha, stamp: { sha, size: st.size, mtimeMs: st.mtimeMs }, prior };
13767
- }
13768
- function splitSections(text) {
13769
- const cuts = [];
13770
- const re = /^#{1,6}[ \t]+(.+)$/gm;
13771
- for (let m = re.exec(text); m; m = re.exec(text))
13772
- cuts.push({ at: m.index, heading: m[1].trim() });
13773
- if (!cuts.length || cuts[0].at > 0)
13774
- cuts.unshift({ at: 0 });
13775
- const spans = [];
13776
- cuts.forEach((c, i) => {
13777
- const end = i + 1 < cuts.length ? cuts[i + 1].at : text.length;
13778
- let start = c.at;
13779
- while (end - start > SECTION_TARGET_CHARS * 1.5) {
13780
- const brk = text.indexOf("\n\n", start + SECTION_TARGET_CHARS);
13781
- if (brk < 0 || brk >= end)
13782
- break;
13783
- spans.push({ index: 0, start, end: brk, heading: c.heading });
13784
- start = brk + 2;
13785
- }
13786
- if (text.slice(start, end).trim())
13787
- spans.push({ index: 0, start, end, heading: c.heading });
13788
- });
13789
- return spans.map((s, i) => ({ ...s, index: i }));
13790
- }
13791
- async function planIngest(kbRoot, opts) {
13792
- const converter = opts.converter ?? markitdownConverter;
13793
- const cap = opts.maxSectionsPerPass ?? DEFAULT_MAX_SECTIONS_PER_PASS;
13794
- const plan = { sources: [], skipped: [], failed: [], carriedSections: 0, sectionCount: 0, estimatedTokens: 0, existing: opts.existing ?? [] };
13795
- let budget = cap;
13796
- const seen = /* @__PURE__ */ new Set();
13797
- for (const entry of opts.sources) {
13798
- for (const file of expandSource(kbRoot, entry)) {
13799
- const { ref, changed, stamp, prior } = detectChange(kbRoot, file);
13800
- if (seen.has(ref))
13801
- continue;
13802
- seen.add(ref);
13803
- const pending = !!prior && prior.cursor < prior.sections;
13804
- if (!changed && !pending)
13805
- continue;
13806
- if (entry.sensitivity === "local-only" && !opts.localModel) {
13807
- plan.skipped.push({ ref, reason: "sensitivity: local-only and no local model available" });
13808
- continue;
13809
- }
13810
- if (budget <= 0) {
13811
- plan.carriedSections += changed ? 1 : prior.sections - prior.cursor;
13812
- continue;
13813
- }
13814
- let text;
13815
- const conv = convertedFile(kbRoot, ref);
13816
- try {
13817
- if (!changed && fs19.existsSync(conv))
13818
- text = fs19.readFileSync(conv, "utf8");
13819
- else {
13820
- text = PASS_THROUGH.has(path19.extname(file).toLowerCase()) ? fs19.readFileSync(file, "utf8") : await converter(file);
13821
- if (!opts.dryRun) {
13822
- fs19.mkdirSync(path19.dirname(conv), { recursive: true });
13823
- fs19.writeFileSync(conv, text);
13824
- }
13825
- }
13826
- } catch (e) {
13827
- plan.failed.push({ ref, error: e.message.split("\n")[0] });
13828
- continue;
13829
- }
13830
- const all = splitSections(text);
13831
- const from = changed ? 0 : prior.cursor;
13832
- const take = all.slice(from, from + budget);
13833
- budget -= take.length;
13834
- plan.carriedSections += all.length - from - take.length;
13835
- plan.sectionCount += take.length;
13836
- plan.estimatedTokens += Math.ceil(take.reduce((n, s) => n + (s.end - s.start), 0) / CHARS_PER_TOKEN);
13837
- plan.sources.push({
13838
- ref,
13839
- file,
13840
- converted: conv,
13841
- sha: stamp.sha,
13842
- priority: entry.priority,
13843
- domain: entry.domain,
13844
- sensitivity: entry.sensitivity,
13845
- sections: take,
13846
- totalSections: all.length
13847
- });
13848
- if (!opts.dryRun)
13849
- writeState(kbRoot, { ref, ...stamp, sections: all.length, cursor: from + take.length, priority: entry.priority });
13850
- }
13851
- }
13852
- return plan;
13853
- }
13854
- var execFileAsync, CONVERTED_DIR, DEFAULT_MAX_SECTIONS_PER_PASS, CHARS_PER_TOKEN, PASS_THROUGH, SECTION_TARGET_CHARS, markitdownConverter, expandHome2, fileForRef, cacheKey, convertedFile, stampFile, sha256;
13855
- var init_ingest = __esm({
13856
- "packages/core/dist/knowledge/ingest.js"() {
13857
- execFileAsync = promisify2(execFile2);
13858
- CONVERTED_DIR = ".converted";
13859
- DEFAULT_MAX_SECTIONS_PER_PASS = 40;
13860
- CHARS_PER_TOKEN = 4;
13861
- PASS_THROUGH = /* @__PURE__ */ new Set([".md", ".markdown", ".txt"]);
13862
- SECTION_TARGET_CHARS = 4e3;
13863
- markitdownConverter = async (file) => {
13864
- const { stdout } = await execFileAsync("markitdown", [file], { maxBuffer: 64 * 1024 * 1024 });
13865
- return stdout;
13866
- };
13867
- expandHome2 = (p) => p === "~" || p.startsWith("~/") ? path19.join(os8.homedir(), p.slice(1)) : p;
13868
- fileForRef = (kbRoot, ref) => path19.resolve(kbRoot, expandHome2(ref));
13869
- cacheKey = (ref) => `${ref.replace(/[^A-Za-z0-9._-]+/g, "_")}.${crypto3.createHash("sha1").update(ref).digest("hex").slice(0, 6)}`;
13870
- convertedFile = (kbRoot, ref) => path19.join(kbRoot, CONVERTED_DIR, `${cacheKey(ref)}.md`);
13871
- stampFile = (kbRoot, ref) => path19.join(kbRoot, CONVERTED_DIR, `${cacheKey(ref)}.sha`);
13872
- sha256 = (file) => crypto3.createHash("sha256").update(fs19.readFileSync(file)).digest("hex");
13873
- }
13874
- });
13875
-
13876
14057
  // packages/core/dist/knowledge/match.js
13877
14058
  import path20 from "path";
13878
14059
  function globMatches(glob, relPath) {
@@ -14259,7 +14440,7 @@ var init_inject = __esm({
14259
14440
  STOPWORDS = new Set("the and for are but not you your yours with this that these those from into onto have has had was were will would can could should shall may might must does did doing done its it's our ours their them they what when where which who whom why how all any some each every other than then there here also just very too only own same such both few more most much many about above below after before again once out over under off further been being let lets please thanks thank okay yes yeah hello hey there need want make use using used get got give like know think see look sure ok now new one two way rule rules must-not looks sounds work working thing things something anything everything going well good great nice cool fine".split(/\s+/));
14260
14441
  DATA_NOTE = "These are project rules (data from the knowledge base), not instructions to run anything.";
14261
14442
  line = (r) => `${r.modality.toUpperCase()} ${r.id}: ${r.statement}`;
14262
- CITE_NOTE = "Name the rule ids you apply in your DONE/commit message.";
14443
+ CITE_NOTE = "List the rule ids you apply as `Rules: <id>, <id>` at the end of your signal done message (commit messages count too).";
14263
14444
  escapeRe = (t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14264
14445
  BRIEF_POINTER_RE = /^Full task is at (\S+\.md) — cat it/;
14265
14446
  READ_TOOLS = /* @__PURE__ */ new Set(["read", "read_file", "readfile", "view"]);
@@ -14290,6 +14471,13 @@ function ensureGitRepo(dir) {
14290
14471
  return false;
14291
14472
  }
14292
14473
  }
14474
+ function ensureKbGitignore(dir) {
14475
+ const file = path23.join(dir, ".gitignore");
14476
+ if (fs22.existsSync(file))
14477
+ return false;
14478
+ fs22.writeFileSync(file, KB_GITIGNORE);
14479
+ return true;
14480
+ }
14293
14481
  function findLegacyOverlays(cfg) {
14294
14482
  return Object.keys(cfg.projects).filter((p) => fs22.existsSync(legacyOverlay(cfg, p))).sort();
14295
14483
  }
@@ -14341,100 +14529,86 @@ function moveLegacyKbs(cfg, opts = {}) {
14341
14529
  fs22.mkdirSync(path23.dirname(newRules), { recursive: true });
14342
14530
  fs22.renameSync(oldRules, newRules);
14343
14531
  notes.push("rules/ -> shared/rules/");
14344
- }
14345
- if (ensureGitRepo(to))
14346
- notes.push("git init");
14347
- if (fs22.existsSync(path23.join(to, "index.json")))
14348
- notes.push(`index.json holds old paths: run squadrant knowledge reindex ${kb}`);
14349
- if (action === "copied")
14350
- notes.push("cross-device: original left in place, delete it yourself once verified");
14351
- return { kb, from, to, action, ...notes.length ? { note: notes.join("; ") } : {} };
14352
- });
14353
- }
14354
- var legacyRoot, legacyOverlay;
14355
- var init_migrate = __esm({
14356
- "packages/core/dist/knowledge/migrate.js"() {
14357
- init_dist();
14358
- init_paths();
14359
- legacyRoot = (cfg) => path23.join(resolveHome(cfg.hubVault), "knowledge");
14360
- legacyOverlay = (cfg, project) => path23.join(resolveHome(cfg.projects[project].spokeVault), "knowledge", "rules");
14361
- }
14362
- });
14363
-
14364
- // packages/core/dist/knowledge/verify.js
14365
- function normalizeWithMap(src, keepBreakHyphen = false) {
14366
- const out = [];
14367
- const map2 = [];
14368
- const push = (s, at) => {
14369
- for (const ch of s) {
14370
- out.push(ch);
14371
- map2.push(at);
14372
- }
14373
- };
14374
- let i = 0;
14375
- while (i < src.length) {
14376
- const ch = src[i];
14377
- if (ch === "-" && /^-[ \t]*\r?\n[ \t]*[a-z]/.test(src.slice(i, i + 40)) && i > 0 && /[A-Za-z]/.test(src[i - 1])) {
14378
- if (keepBreakHyphen)
14379
- push("-", i);
14380
- i++;
14381
- while (/\s/.test(src[i]))
14382
- i++;
14383
- continue;
14384
- }
14385
- if (/\s/.test(ch)) {
14386
- const start = i;
14387
- while (i < src.length && /\s/.test(src[i]))
14388
- i++;
14389
- if (out.length && out[out.length - 1] !== " ")
14390
- push(" ", start);
14391
- continue;
14392
- }
14393
- if (LIGATURES[ch])
14394
- push(LIGATURES[ch], i);
14395
- else if (QUOTES[ch])
14396
- push(QUOTES[ch], i);
14397
- else if (DASHES.has(ch))
14398
- push("-", i);
14399
- else
14400
- push(ch, i);
14401
- i++;
14532
+ }
14533
+ if (ensureGitRepo(to))
14534
+ notes.push("git init");
14535
+ if (ensureKbGitignore(to))
14536
+ notes.push("wrote .gitignore (raw/, .converted/)");
14537
+ if (fs22.existsSync(path23.join(to, "index.json")))
14538
+ notes.push(`index.json holds old paths: run squadrant knowledge reindex ${kb}`);
14539
+ if (action === "copied")
14540
+ notes.push("cross-device: original left in place, delete it yourself once verified");
14541
+ return { kb, from, to, action, ...notes.length ? { note: notes.join("; ") } : {} };
14542
+ });
14543
+ }
14544
+ var legacyRoot, KB_GITIGNORE, legacyOverlay;
14545
+ var init_migrate = __esm({
14546
+ "packages/core/dist/knowledge/migrate.js"() {
14547
+ init_dist();
14548
+ init_paths();
14549
+ legacyRoot = (cfg) => path23.join(resolveHome(cfg.hubVault), "knowledge");
14550
+ KB_GITIGNORE = "# squadrant KB defaults: source documents stay local\nraw/\n.converted/\n";
14551
+ legacyOverlay = (cfg, project) => path23.join(resolveHome(cfg.projects[project].spokeVault), "knowledge", "rules");
14402
14552
  }
14403
- while (out.length && out[out.length - 1] === " ") {
14404
- out.pop();
14405
- map2.pop();
14553
+ });
14554
+
14555
+ // packages/core/dist/knowledge/errata.js
14556
+ import fs23 from "fs";
14557
+ import path24 from "path";
14558
+ function loadErrata(kbRoot) {
14559
+ const file = path24.join(kbRoot, "errata.yaml");
14560
+ if (!fs23.existsSync(file))
14561
+ return { errata: [], errors: [] };
14562
+ let data;
14563
+ try {
14564
+ data = yaml.load(fs23.readFileSync(file, "utf8"), { schema: yaml.JSON_SCHEMA });
14565
+ } catch (e) {
14566
+ return { errata: [], errors: [`YAML parse error: ${e.message.split("\n")[0]}`] };
14406
14567
  }
14407
- return { text: out.join(""), map: map2 };
14568
+ if (data == null)
14569
+ return { errata: [], errors: [] };
14570
+ if (!Array.isArray(data))
14571
+ return { errata: [], errors: ["errata.yaml must be a YAML list of entries"] };
14572
+ const errata = [];
14573
+ const errors = [];
14574
+ data.forEach((entry, i) => {
14575
+ const problems = validateErratum(entry);
14576
+ if (problems.length)
14577
+ errors.push(...problems.map((p) => `entry ${i}: ${p}`));
14578
+ else
14579
+ errata.push(entry);
14580
+ });
14581
+ return { errata, errors };
14408
14582
  }
14409
- function findQuote(source, quote) {
14410
- const needle = normalizeText(quote);
14411
- if (!needle)
14412
- return null;
14413
- const views = typeof source === "string" ? normalizedViews(source) : source;
14414
- for (const hay of views) {
14415
- const at = hay.text.indexOf(needle);
14416
- if (at >= 0)
14417
- return [hay.map[at], hay.map[at + needle.length - 1] + 1];
14583
+ function orphanedErrata(kbRoot, errata, refs) {
14584
+ const only = refs ? new Set(refs) : void 0;
14585
+ const hays = /* @__PURE__ */ new Map();
14586
+ const out = [];
14587
+ for (const e of errata) {
14588
+ if (only && !only.has(e.ref))
14589
+ continue;
14590
+ if (!hays.has(e.ref)) {
14591
+ const conv = readState2(kbRoot, e.ref) ? convertedFile(kbRoot, e.ref) : null;
14592
+ hays.set(e.ref, conv && fs23.existsSync(conv) ? normalizedViews(fs23.readFileSync(conv, "utf8")) : null);
14593
+ }
14594
+ const hay = hays.get(e.ref);
14595
+ if (hay && !findQuote(hay, e.quote))
14596
+ out.push({ id: e.id, ref: e.ref });
14418
14597
  }
14419
- return null;
14598
+ return out;
14420
14599
  }
14421
- var LIGATURES, QUOTES, DASHES, normalizeText, normalizedViews, DOCLING_DROP_THRESHOLD, needsDocling, spansOverlap;
14422
- var init_verify = __esm({
14423
- "packages/core/dist/knowledge/verify.js"() {
14424
- LIGATURES = { "\uFB01": "fi", "\uFB02": "fl", "\uFB00": "ff", "\uFB03": "ffi", "\uFB04": "ffl", "\uFB05": "st", "\uFB06": "st" };
14425
- QUOTES = { "\u2018": "'", "\u2019": "'", "\u201A": "'", "\u201B": "'", "\u201C": '"', "\u201D": '"', "\u201E": '"', "\u201F": '"' };
14426
- DASHES = /* @__PURE__ */ new Set(["\u2010", "\u2011", "\u2012", "\u2013", "\u2014", "\u2015", "\u2212"]);
14427
- normalizeText = (s) => normalizeWithMap(s).text;
14428
- normalizedViews = (src) => [normalizeWithMap(src), normalizeWithMap(src, true)];
14429
- DOCLING_DROP_THRESHOLD = 0.2;
14430
- needsDocling = (dropped, total) => total > 0 && dropped / total > DOCLING_DROP_THRESHOLD;
14431
- spansOverlap = (a, b) => a[0] < b[1] && b[0] < a[1];
14600
+ var init_errata = __esm({
14601
+ "packages/core/dist/knowledge/errata.js"() {
14602
+ init_js_yaml();
14603
+ init_dist();
14604
+ init_ingest();
14605
+ init_verify();
14432
14606
  }
14433
14607
  });
14434
14608
 
14435
14609
  // packages/core/dist/knowledge/anchor.js
14436
- import fs23 from "fs";
14437
- import path24 from "path";
14610
+ import fs24 from "fs";
14611
+ import path25 from "path";
14438
14612
  function* repoFiles(root) {
14439
14613
  let count = 0;
14440
14614
  const stack = [root];
@@ -14442,43 +14616,43 @@ function* repoFiles(root) {
14442
14616
  const dir = stack.pop();
14443
14617
  let ents;
14444
14618
  try {
14445
- ents = fs23.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name < b.name ? 1 : -1);
14619
+ ents = fs24.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name < b.name ? 1 : -1);
14446
14620
  } catch {
14447
14621
  continue;
14448
14622
  }
14449
14623
  for (const ent of ents) {
14450
14624
  if (ent.isDirectory()) {
14451
14625
  if (!SKIP_DIRS.has(ent.name))
14452
- stack.push(path24.join(dir, ent.name));
14453
- } else if (ent.isFile() && CODE_EXT.has(path24.extname(ent.name))) {
14626
+ stack.push(path25.join(dir, ent.name));
14627
+ } else if (ent.isFile() && CODE_EXT.has(path25.extname(ent.name))) {
14454
14628
  if (++count > MAX_FILES)
14455
14629
  return;
14456
- yield path24.join(dir, ent.name);
14630
+ yield path25.join(dir, ent.name);
14457
14631
  }
14458
14632
  }
14459
14633
  }
14460
14634
  }
14461
14635
  function computeAnchor(repoRoot, triggers) {
14462
14636
  const terms2 = anchorTerms(triggers);
14463
- if (!terms2.length || !fs23.existsSync(repoRoot))
14637
+ if (!terms2.length || !fs24.existsSync(repoRoot))
14464
14638
  return null;
14465
14639
  const hits = [];
14466
14640
  for (const file of repoFiles(repoRoot)) {
14467
14641
  let text;
14468
14642
  try {
14469
- if (fs23.statSync(file).size > MAX_FILE_BYTES)
14643
+ if (fs24.statSync(file).size > MAX_FILE_BYTES)
14470
14644
  continue;
14471
- text = fs23.readFileSync(file, "utf8");
14645
+ text = fs24.readFileSync(file, "utf8");
14472
14646
  } catch {
14473
14647
  continue;
14474
14648
  }
14475
- const hay = `${path24.relative(repoRoot, file)}
14649
+ const hay = `${path25.relative(repoRoot, file)}
14476
14650
  ${text}`.toLowerCase();
14477
14651
  const score = terms2.filter((t) => hay.includes(t)).length;
14478
14652
  if (!score)
14479
14653
  continue;
14480
14654
  const symbols2 = [...text.matchAll(SYMBOL_RE)].map((m) => m[1]).filter((s) => terms2.some((t) => s.toLowerCase().includes(t)));
14481
- hits.push({ rel: path24.relative(repoRoot, file).split(path24.sep).join("/"), score, symbols: symbols2 });
14655
+ hits.push({ rel: path25.relative(repoRoot, file).split(path25.sep).join("/"), score, symbols: symbols2 });
14482
14656
  }
14483
14657
  if (!hits.length)
14484
14658
  return null;
@@ -14502,11 +14676,11 @@ var init_anchor = __esm({
14502
14676
  });
14503
14677
 
14504
14678
  // packages/core/dist/knowledge/extract.js
14505
- import fs24 from "fs";
14506
- import path25 from "path";
14507
- import matter3 from "gray-matter";
14679
+ import fs25 from "fs";
14680
+ import path26 from "path";
14681
+ import matter4 from "gray-matter";
14508
14682
  function serialize(fm, body) {
14509
- return matter3.stringify(body.trim() + "\n", clean(fm));
14683
+ return matter4.stringify(body.trim() + "\n", clean(fm));
14510
14684
  }
14511
14685
  function validateCandidate(c) {
14512
14686
  const p = [];
@@ -14549,9 +14723,9 @@ function upsertSource(fm, src) {
14549
14723
  }
14550
14724
  function applyCandidates(input, opts) {
14551
14725
  const { kb, kbRoot, rulesDir } = opts;
14552
- const res = { created: [], merged: [], proposed: [], superseded: [], stale: [], rejected: [], doclingFlags: [], idOverrides: [] };
14726
+ const res = { created: [], merged: [], proposed: [], superseded: [], stale: [], rejected: [], doclingFlags: [], idOverrides: [], orphanedErrata: [] };
14553
14727
  const events = [];
14554
- const ev2 = (event, itemId, domain, extra = {}) => events.push({ kb, level: "group", project: "", domain, itemId, event, ...extra });
14728
+ const ev2 = (event, itemId, extra = {}) => events.push({ kb, level: "group", project: "", domain: "rules", itemId, event, ...extra });
14555
14729
  const loaded = loadRulesDir(rulesDir, `kb:${kb}`);
14556
14730
  const work = /* @__PURE__ */ new Map();
14557
14731
  for (const r of loaded.rules) {
@@ -14570,13 +14744,13 @@ function applyCandidates(input, opts) {
14570
14744
  }
14571
14745
  const st = readState2(kbRoot, cand.source);
14572
14746
  const convFile = st ? convertedFile(kbRoot, cand.source) : null;
14573
- if (!st || !fs24.existsSync(convFile)) {
14747
+ if (!st || !fs25.existsSync(convFile)) {
14574
14748
  res.rejected.push({ id: cand.id, source: cand.source, reason: "unknown source (not ingested)" });
14575
14749
  continue;
14576
14750
  }
14577
14751
  let hay = norm.get(cand.source);
14578
14752
  if (!hay) {
14579
- hay = normalizedViews(fs24.readFileSync(convFile, "utf8"));
14753
+ hay = normalizedViews(fs25.readFileSync(convFile, "utf8"));
14580
14754
  norm.set(cand.source, hay);
14581
14755
  }
14582
14756
  const tot = totals.get(cand.source) ?? { total: 0, dropped: 0 };
@@ -14586,7 +14760,14 @@ function applyCandidates(input, opts) {
14586
14760
  if (!offset) {
14587
14761
  tot.dropped++;
14588
14762
  res.rejected.push({ id: cand.id, source: cand.source, reason: "ungrounded" });
14589
- ev2("item.rejected", cand.id, cand.domain, { reason: "ungrounded" });
14763
+ ev2("item.rejected", cand.id, { reason: "ungrounded" });
14764
+ continue;
14765
+ }
14766
+ const hit = (opts.errata ?? []).find((e) => e.ref === cand.source && ((o) => !!o && spansOverlap(o, offset))(findQuote(hay, e.quote)));
14767
+ if (hit) {
14768
+ tot.total--;
14769
+ res.rejected.push({ id: cand.id, source: cand.source, reason: `errata:${hit.id}` });
14770
+ ev2("item.rejected", cand.id, { reason: `errata:${hit.id}` });
14590
14771
  continue;
14591
14772
  }
14592
14773
  let id = cand.id;
@@ -14618,7 +14799,7 @@ ${cand.rationale.trim()}` : "");
14618
14799
  }
14619
14800
  continue;
14620
14801
  }
14621
- if (!target && contradicted.length === 0) {
14802
+ if (!target && contradicted.length === 0 && !isGatedRef(kbRoot, opts.sources ?? [], cand.source)) {
14622
14803
  const fm2 = {
14623
14804
  id,
14624
14805
  domain: cand.domain,
@@ -14631,9 +14812,9 @@ ${cand.rationale.trim()}` : "");
14631
14812
  supersedes: [],
14632
14813
  conflictsWith: []
14633
14814
  };
14634
- work.set(id, { fm: fm2, body: bodyOf(), file: path25.join(rulesDir, cand.domain, `${id}.md`), dirty: true, isNew: true });
14815
+ work.set(id, { fm: fm2, body: bodyOf(), file: path26.join(rulesDir, cand.domain, `${id}.md`), dirty: true, isNew: true });
14635
14816
  res.created.push(id);
14636
- ev2("item.applied", id, cand.domain);
14817
+ ev2("item.applied", id);
14637
14818
  continue;
14638
14819
  }
14639
14820
  const fm = {
@@ -14648,26 +14829,26 @@ ${cand.rationale.trim()}` : "");
14648
14829
  supersedes: [],
14649
14830
  conflictsWith: contradicted
14650
14831
  };
14651
- const dir = path25.join(rulesDir, PROPOSED_DIR);
14652
- const file = path25.join(dir, `${id}@${st.sha.slice(0, SHA_LEN)}.md`);
14832
+ const dir = path26.join(rulesDir, PROPOSED_DIR);
14833
+ const file = path26.join(dir, `${id}@${st.sha.slice(0, SHA_LEN)}.md`);
14653
14834
  const text = serialize(fm, bodyOf());
14654
- if (fs24.existsSync(file) && fs24.readFileSync(file, "utf8") === text)
14835
+ if (fs25.existsSync(file) && fs25.readFileSync(file, "utf8") === text)
14655
14836
  continue;
14656
- fs24.mkdirSync(dir, { recursive: true });
14657
- for (const other of fs24.readdirSync(dir)) {
14658
- if (!other.startsWith(`${id}@`) || !other.endsWith(".md") || path25.join(dir, other) === file)
14837
+ fs25.mkdirSync(dir, { recursive: true });
14838
+ for (const other of fs25.readdirSync(dir)) {
14839
+ if (!other.startsWith(`${id}@`) || !other.endsWith(".md") || path26.join(dir, other) === file)
14659
14840
  continue;
14660
- const old = matter3(fs24.readFileSync(path25.join(dir, other), "utf8"));
14841
+ const old = matter4(fs25.readFileSync(path26.join(dir, other), "utf8"));
14661
14842
  if (!old.data.sources?.some((s) => s.ref === cand.source))
14662
14843
  continue;
14663
- fs24.writeFileSync(path25.join(dir, other.replace(/\.md$/, ".superseded")), matter3.stringify(old.content, { ...old.data, supersededBy: path25.basename(file) }));
14664
- fs24.rmSync(path25.join(dir, other));
14844
+ fs25.writeFileSync(path26.join(dir, other.replace(/\.md$/, ".superseded")), matter4.stringify(old.content, { ...old.data, supersededBy: path26.basename(file) }));
14845
+ fs25.rmSync(path26.join(dir, other));
14665
14846
  res.superseded.push(other.replace(/\.md$/, ""));
14666
- ev2("item.superseded", id, fm.domain);
14847
+ ev2("item.superseded", id);
14667
14848
  }
14668
- fs24.writeFileSync(file, text);
14849
+ fs25.writeFileSync(file, text);
14669
14850
  res.proposed.push(`${id}@${st.sha.slice(0, SHA_LEN)}`);
14670
- ev2("item.proposed", id, fm.domain);
14851
+ ev2("item.proposed", id);
14671
14852
  }
14672
14853
  for (const [id, w] of work) {
14673
14854
  if (touched.has(id) || w.fm.status !== "active" || w.isNew)
@@ -14693,11 +14874,12 @@ ${cand.rationale.trim()}` : "");
14693
14874
  for (const w of work.values()) {
14694
14875
  if (!w.dirty)
14695
14876
  continue;
14696
- fs24.mkdirSync(path25.dirname(w.file), { recursive: true });
14877
+ fs25.mkdirSync(path26.dirname(w.file), { recursive: true });
14697
14878
  const text = serialize(w.fm, w.body);
14698
- if (!fs24.existsSync(w.file) || fs24.readFileSync(w.file, "utf8") !== text)
14699
- fs24.writeFileSync(w.file, text);
14879
+ if (!fs25.existsSync(w.file) || fs25.readFileSync(w.file, "utf8") !== text)
14880
+ fs25.writeFileSync(w.file, text);
14700
14881
  }
14882
+ res.orphanedErrata = orphanedErrata(kbRoot, opts.errata ?? [], totals.keys());
14701
14883
  for (const [ref, t] of totals)
14702
14884
  if (needsDocling(t.dropped, t.total))
14703
14885
  res.doclingFlags.push({ ref, ...t });
@@ -14714,6 +14896,7 @@ var init_extract = __esm({
14714
14896
  init_anchor();
14715
14897
  init_index_file();
14716
14898
  init_ingest();
14899
+ init_errata();
14717
14900
  init_paths();
14718
14901
  init_store2();
14719
14902
  init_verify();
@@ -14730,20 +14913,20 @@ var init_extract = __esm({
14730
14913
  });
14731
14914
 
14732
14915
  // packages/core/dist/knowledge/schedule.js
14733
- import fs25 from "fs";
14734
- import path26 from "path";
14916
+ import fs26 from "fs";
14917
+ import path27 from "path";
14735
14918
  function readSchedule(stateRoot) {
14736
14919
  try {
14737
- return JSON.parse(fs25.readFileSync(scheduleFile(stateRoot), "utf8"));
14920
+ return JSON.parse(fs26.readFileSync(scheduleFile(stateRoot), "utf8"));
14738
14921
  } catch {
14739
14922
  return {};
14740
14923
  }
14741
14924
  }
14742
14925
  function writeSchedule(stateRoot, s) {
14743
- fs25.mkdirSync(stateRoot, { recursive: true });
14926
+ fs26.mkdirSync(stateRoot, { recursive: true });
14744
14927
  const tmp = `${scheduleFile(stateRoot)}.${process.pid}.tmp`;
14745
- fs25.writeFileSync(tmp, JSON.stringify(s, null, 2) + "\n");
14746
- fs25.renameSync(tmp, scheduleFile(stateRoot));
14928
+ fs26.writeFileSync(tmp, JSON.stringify(s, null, 2) + "\n");
14929
+ fs26.renameSync(tmp, scheduleFile(stateRoot));
14747
14930
  }
14748
14931
  function snapshotSources(kbRoot, sources) {
14749
14932
  const out = {};
@@ -14823,7 +15006,7 @@ async function tickKnowledgeSchedule(d) {
14823
15006
  continue;
14824
15007
  try {
14825
15008
  const root = kbDir(d.cfg, kb);
14826
- if (!fs25.existsSync(root))
15009
+ if (!fs26.existsSync(root))
14827
15010
  continue;
14828
15011
  const { sources } = loadSources(root);
14829
15012
  if (!sources.length)
@@ -14888,7 +15071,7 @@ var init_schedule = __esm({
14888
15071
  init_sources();
14889
15072
  RUNNING_STALE_MS = 6 * 60 * 60 * 1e3;
14890
15073
  DAY_MS = 24 * 60 * 60 * 1e3;
14891
- scheduleFile = (stateRoot) => path26.join(stateRoot, "knowledge-schedule.json");
15074
+ scheduleFile = (stateRoot) => path27.join(stateRoot, "knowledge-schedule.json");
14892
15075
  reconcileConfig = (cfg, kb) => ({ ...DEFAULT_RECONCILE, ...kbConfigs(cfg)[kb]?.reconcile });
14893
15076
  }
14894
15077
  });
@@ -14896,19 +15079,19 @@ var init_schedule = __esm({
14896
15079
  // packages/core/dist/knowledge/review.js
14897
15080
  import crypto4 from "crypto";
14898
15081
  import { execFileSync as execFileSync7 } from "child_process";
14899
- import fs26 from "fs";
14900
- import path27 from "path";
14901
- import matter4 from "gray-matter";
15082
+ import fs27 from "fs";
15083
+ import path28 from "path";
15084
+ import matter5 from "gray-matter";
14902
15085
  function readEscalations(kbRoot) {
14903
15086
  try {
14904
- return JSON.parse(fs26.readFileSync(escalationsFile(kbRoot), "utf8")).items;
15087
+ return JSON.parse(fs27.readFileSync(escalationsFile(kbRoot), "utf8")).items;
14905
15088
  } catch {
14906
15089
  return [];
14907
15090
  }
14908
15091
  }
14909
15092
  function writeEscalations(kbRoot, items) {
14910
- fs26.mkdirSync(kbRoot, { recursive: true });
14911
- fs26.writeFileSync(escalationsFile(kbRoot), JSON.stringify({ items }, null, 2) + "\n");
15093
+ fs27.mkdirSync(kbRoot, { recursive: true });
15094
+ fs27.writeFileSync(escalationsFile(kbRoot), JSON.stringify({ items }, null, 2) + "\n");
14912
15095
  }
14913
15096
  function addEscalation(kbRoot, e) {
14914
15097
  const items = readEscalations(kbRoot);
@@ -14918,8 +15101,8 @@ function addEscalation(kbRoot, e) {
14918
15101
  return true;
14919
15102
  }
14920
15103
  function appendVerdict(kbRoot, v2, now) {
14921
- fs26.mkdirSync(kbRoot, { recursive: true });
14922
- fs26.appendFileSync(verdictsFile(kbRoot), JSON.stringify({ ts: now.toISOString(), ...v2 }) + "\n");
15104
+ fs27.mkdirSync(kbRoot, { recursive: true });
15105
+ fs27.appendFileSync(verdictsFile(kbRoot), JSON.stringify({ ts: now.toISOString(), ...v2 }) + "\n");
14923
15106
  }
14924
15107
  function priorityOfRef(c, ref) {
14925
15108
  if (ref.startsWith("agent:"))
@@ -14940,20 +15123,20 @@ function loadProposals(c) {
14940
15123
  const byId = new Map(live.map((r) => [r.id, r]));
14941
15124
  const proposals = [];
14942
15125
  const pdirs = /* @__PURE__ */ new Set();
14943
- for (const f of fs26.existsSync(dir) ? walkDirs(dir) : [])
14944
- if (path27.basename(f) === PROPOSED_DIR)
15126
+ for (const f of fs27.existsSync(dir) ? walkDirs(dir) : [])
15127
+ if (path28.basename(f) === PROPOSED_DIR)
14945
15128
  pdirs.add(f);
14946
15129
  for (const pd of pdirs) {
14947
15130
  for (const rule2 of loadRulesDir(pd, `kb:${c.kb}`, { includeProposed: true }).rules) {
14948
- proposals.push({ key: path27.basename(rule2.file).replace(/\.md$/, ""), rule: rule2, live: byId.get(rule2.id) });
15131
+ proposals.push({ key: path28.basename(rule2.file).replace(/\.md$/, ""), rule: rule2, live: byId.get(rule2.id) });
14949
15132
  }
14950
15133
  }
14951
15134
  return { proposals, live };
14952
15135
  }
14953
15136
  function walkDirs(dir, out = []) {
14954
- for (const e of fs26.readdirSync(dir, { withFileTypes: true }))
15137
+ for (const e of fs27.readdirSync(dir, { withFileTypes: true }))
14955
15138
  if (e.isDirectory()) {
14956
- const f = path27.join(dir, e.name);
15139
+ const f = path28.join(dir, e.name);
14957
15140
  out.push(f);
14958
15141
  walkDirs(f, out);
14959
15142
  }
@@ -14982,9 +15165,9 @@ function promoteProposal(c, p, by, justification, events) {
14982
15165
  continue;
14983
15166
  const o = readFm(other.file);
14984
15167
  o.fm.status = "retired";
14985
- fs26.writeFileSync(other.file, fileText(o.fm, o.body));
15168
+ fs27.writeFileSync(other.file, fileText(o.fm, o.body));
14986
15169
  retired.push(id);
14987
- events.push(ev(c, "item.superseded", id, o.fm.domain));
15170
+ events.push(ev(c, "item.superseded", id));
14988
15171
  }
14989
15172
  const next = { ...fm, status: "active", approvedBy: by, justification, supersedes: [.../* @__PURE__ */ new Set([...fm.supersedes ?? [], ...retired])], conflictsWith: [] };
14990
15173
  if (p.live?.decision)
@@ -14992,17 +15175,17 @@ function promoteProposal(c, p, by, justification, events) {
14992
15175
  const anchors = anchorsFor(c, { ...next, anchors: p.live?.anchors ?? fm.anchors });
14993
15176
  if (anchors)
14994
15177
  next.anchors = anchors;
14995
- const target = p.live?.file ?? path27.join(dir, next.domain, `${next.id}.md`);
14996
- fs26.mkdirSync(path27.dirname(target), { recursive: true });
14997
- fs26.writeFileSync(target, fileText(next, body));
14998
- fs26.rmSync(p.rule.file);
14999
- events.push(ev(c, "item.approved", next.id, next.domain), ev(c, "item.applied", next.id, next.domain));
15178
+ const target = p.live?.file ?? path28.join(dir, next.domain, `${next.id}.md`);
15179
+ fs27.mkdirSync(path28.dirname(target), { recursive: true });
15180
+ fs27.writeFileSync(target, fileText(next, body));
15181
+ fs27.rmSync(p.rule.file);
15182
+ events.push(ev(c, "item.approved", next.id), ev(c, "item.applied", next.id));
15000
15183
  if (p.live)
15001
- events.push(ev(c, "item.superseded", next.id, next.domain));
15184
+ events.push(ev(c, "item.superseded", next.id));
15002
15185
  }
15003
15186
  function dropProposal(c, p, events) {
15004
- fs26.rmSync(p.rule.file);
15005
- events.push(ev(c, "item.rejected", p.rule.id, p.rule.domain, { reason: "reviewer" }));
15187
+ fs27.rmSync(p.rule.file);
15188
+ events.push(ev(c, "item.rejected", p.rule.id, { reason: "reviewer" }));
15006
15189
  }
15007
15190
  function mergeInto(c, p, targetId, live, events) {
15008
15191
  const t = live.find((r) => r.id === targetId);
@@ -15012,9 +15195,9 @@ function mergeInto(c, p, targetId, live, events) {
15012
15195
  for (const s of p.rule.sources)
15013
15196
  if (!tf.fm.sources.some((x) => x.ref === s.ref))
15014
15197
  tf.fm.sources.push(s);
15015
- fs26.writeFileSync(t.file, fileText(tf.fm, tf.body));
15016
- fs26.rmSync(p.rule.file);
15017
- events.push(ev(c, "item.superseded", p.rule.id, p.rule.domain), ev(c, "item.applied", targetId, t.domain));
15198
+ fs27.writeFileSync(t.file, fileText(tf.fm, tf.body));
15199
+ fs27.rmSync(p.rule.file);
15200
+ events.push(ev(c, "item.superseded", p.rule.id), ev(c, "item.applied", targetId));
15018
15201
  return true;
15019
15202
  }
15020
15203
  function evaluateCodeConflict(c, rule2, cc) {
@@ -15088,14 +15271,15 @@ function applyDecisions(file, c) {
15088
15271
  f.fm.status = "active";
15089
15272
  if (d.loc && f.fm.sources[0])
15090
15273
  f.fm.sources[0].loc = d.loc;
15091
- if (conf >= threshold && !d.flag && !reasons.length) {
15092
- fs26.writeFileSync(rule2.file, fileText(f.fm, f.body));
15274
+ const noSource = !rule2.sources.length;
15275
+ if (conf >= threshold && !d.flag && !reasons.length && !noSource) {
15276
+ fs27.writeFileSync(rule2.file, fileText(f.fm, f.body));
15093
15277
  res.applied.push(d.item);
15094
- events.push(ev(c, "item.applied", rule2.id, rule2.domain));
15278
+ events.push(ev(c, "item.applied", rule2.id));
15095
15279
  verdict(d, "applied", { confidence: conf });
15096
15280
  continue;
15097
15281
  }
15098
- reasons.push(conf < threshold ? "low-confidence" : "reviewer-flagged");
15282
+ reasons.push(noSource ? "no-source" : conf < threshold ? "low-confidence" : "reviewer-flagged");
15099
15283
  } else if (d.decision === "approve") {
15100
15284
  if (strong(rule2.modality))
15101
15285
  reasons.push("retiring must/must-not");
@@ -15106,9 +15290,9 @@ function applyDecisions(file, c) {
15106
15290
  if (!reasons.length) {
15107
15291
  const f = readFm(rule2.file);
15108
15292
  f.fm.status = "retired";
15109
- fs26.writeFileSync(rule2.file, fileText(f.fm, f.body));
15293
+ fs27.writeFileSync(rule2.file, fileText(f.fm, f.body));
15110
15294
  res.applied.push(d.item);
15111
- events.push(ev(c, "item.archived", rule2.id, rule2.domain));
15295
+ events.push(ev(c, "item.archived", rule2.id));
15112
15296
  verdict(d, "applied", { confidence: conf });
15113
15297
  continue;
15114
15298
  }
@@ -15123,6 +15307,8 @@ function applyDecisions(file, c) {
15123
15307
  reasons.push("reviewer-flagged");
15124
15308
  if (conf < threshold)
15125
15309
  reasons.push("low-confidence");
15310
+ if (p.rule.sources.some((s) => isGatedRef(kbRoot, c.sources, s.ref)))
15311
+ reasons.push("review-required");
15126
15312
  if (d.decision === "approve") {
15127
15313
  const myPri = priorityOfRule(c, p.rule);
15128
15314
  const conflicted = (p.rule.conflictsWith ?? []).map((id) => live.find((r) => r.id === id)).filter((r) => !!r);
@@ -15184,7 +15370,7 @@ function applyPair(pd, c, res, threshold, events, kbRoot, now) {
15184
15370
  for (const [x, y] of [[a, b], [b, a]]) {
15185
15371
  const f = readFm(x.file);
15186
15372
  f.fm.conflictsWith = [.../* @__PURE__ */ new Set([...f.fm.conflictsWith ?? [], y.id])];
15187
- fs26.writeFileSync(x.file, fileText(f.fm, f.body));
15373
+ fs27.writeFileSync(x.file, fileText(f.fm, f.body));
15188
15374
  }
15189
15375
  return;
15190
15376
  }
@@ -15199,11 +15385,11 @@ function applyPair(pd, c, res, threshold, events, kbRoot, now) {
15199
15385
  if (!kf.fm.sources.some((x) => x.ref === s.ref))
15200
15386
  kf.fm.sources.push(s);
15201
15387
  kf.fm.supersedes = [.../* @__PURE__ */ new Set([...kf.fm.supersedes ?? [], drop.id])];
15202
- fs26.writeFileSync(keep.file, fileText(kf.fm, kf.body));
15388
+ fs27.writeFileSync(keep.file, fileText(kf.fm, kf.body));
15203
15389
  const df = readFm(drop.file);
15204
15390
  df.fm.status = "retired";
15205
- fs26.writeFileSync(drop.file, fileText(df.fm, df.body));
15206
- events.push(ev(c, "item.superseded", drop.id, drop.domain), ev(c, "item.applied", keep.id, keep.domain));
15391
+ fs27.writeFileSync(drop.file, fileText(df.fm, df.body));
15392
+ events.push(ev(c, "item.superseded", drop.id), ev(c, "item.applied", keep.id));
15207
15393
  res.applied.push(key);
15208
15394
  }
15209
15395
  function resolveEscalation(c, key, r, by, reason) {
@@ -15233,9 +15419,9 @@ function resolveEscalation(c, key, r, by, reason) {
15233
15419
  f.fm.decision = decision;
15234
15420
  if (r.verdict === "supersede-with-code") {
15235
15421
  f.fm.status = "retired";
15236
- events.push(ev(c, "item.superseded", rule2.id, rule2.domain));
15422
+ events.push(ev(c, "item.superseded", rule2.id));
15237
15423
  }
15238
- fs26.writeFileSync(rule2.file, fileText(f.fm, f.body));
15424
+ fs27.writeFileSync(rule2.file, fileText(f.fm, f.body));
15239
15425
  return done(`${rule2.id}: ${r.verdict}`);
15240
15426
  }
15241
15427
  if (esc2.kind === "proposal") {
@@ -15252,23 +15438,26 @@ function resolveEscalation(c, key, r, by, reason) {
15252
15438
  }
15253
15439
  if (esc2.kind === "stale-retire") {
15254
15440
  const rule2 = live.find((x) => x.id === esc2.ruleId);
15441
+ if (rule2 && r.kind !== "approve" && !rule2.sources.length) {
15442
+ throw new Error(`Rule ${rule2.id} has no source, so it cannot be kept active. Add a source to the rule (or re-extract it), or retire it with --approve.`);
15443
+ }
15255
15444
  if (rule2) {
15256
15445
  const f = readFm(rule2.file);
15257
15446
  f.fm.status = r.kind === "approve" ? "retired" : "active";
15258
- fs26.writeFileSync(rule2.file, fileText(f.fm, f.body));
15259
- events.push(ev(c, r.kind === "approve" ? "item.archived" : "item.applied", rule2.id, rule2.domain));
15447
+ fs27.writeFileSync(rule2.file, fileText(f.fm, f.body));
15448
+ events.push(ev(c, r.kind === "approve" ? "item.archived" : "item.applied", rule2.id));
15260
15449
  }
15261
15450
  return done(`${esc2.ruleId}: ${r.kind === "approve" ? "retired" : "kept"}`);
15262
15451
  }
15263
15452
  if (esc2.kind === "override-base-changed") {
15264
- const ov = esc2.item && fs26.existsSync(esc2.item) ? esc2.item : void 0;
15453
+ const ov = esc2.item && fs27.existsSync(esc2.item) ? esc2.item : void 0;
15265
15454
  if (ov) {
15266
15455
  if (r.kind === "reject")
15267
- fs26.rmSync(ov);
15456
+ fs27.rmSync(ov);
15268
15457
  else {
15269
15458
  const f = readFm(ov);
15270
15459
  f.fm.overridesBase = esc2.evidence?.docRev;
15271
- fs26.writeFileSync(ov, fileText(f.fm, f.body));
15460
+ fs27.writeFileSync(ov, fileText(f.fm, f.body));
15272
15461
  }
15273
15462
  }
15274
15463
  return done(`${esc2.ruleId}: override ${r.kind === "reject" ? "dropped" : "kept"}`);
@@ -15292,11 +15481,11 @@ ${o.evidence}`).digest("hex");
15292
15481
  supersedes: [],
15293
15482
  conflictsWith: []
15294
15483
  };
15295
- const dir = path27.join(kbRulesDir(c.cfg, c.kb), PROPOSED_DIR);
15296
- fs26.mkdirSync(dir, { recursive: true });
15297
- const file = path27.join(dir, `${id}@${sha.slice(0, SHA_LEN2)}.md`);
15298
- fs26.writeFileSync(file, fileText(fm, o.statement));
15299
- appendAudit([ev(c, "item.proposed", id, domain)], c.audit);
15484
+ const dir = path28.join(kbRulesDir(c.cfg, c.kb), PROPOSED_DIR);
15485
+ fs27.mkdirSync(dir, { recursive: true });
15486
+ const file = path28.join(dir, `${id}@${sha.slice(0, SHA_LEN2)}.md`);
15487
+ fs27.writeFileSync(file, fileText(fm, o.statement));
15488
+ appendAudit([ev(c, "item.proposed", id)], c.audit);
15300
15489
  return file;
15301
15490
  }
15302
15491
  function buildReviewPacket(c) {
@@ -15336,8 +15525,8 @@ var init_review = __esm({
15336
15525
  PRIORITY_RANK = { company: 3, project: 2, agent: 1 };
15337
15526
  SHA_LEN2 = 12;
15338
15527
  strong = (m) => m === "must" || m === "must-not";
15339
- escalationsFile = (kbRoot) => path27.join(kbRoot, "escalations.json");
15340
- verdictsFile = (kbRoot) => path27.join(kbRoot, "verdicts.jsonl");
15528
+ escalationsFile = (kbRoot) => path28.join(kbRoot, "escalations.json");
15529
+ verdictsFile = (kbRoot) => path28.join(kbRoot, "verdicts.jsonl");
15341
15530
  needsYouCount = (kbRoot) => readEscalations(kbRoot).filter((e) => e.needsYou).length;
15342
15531
  git = (root, args) => execFileSync7("git", ["-C", root, ...args], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
15343
15532
  realGit = {
@@ -15358,34 +15547,34 @@ var init_review = __esm({
15358
15547
  },
15359
15548
  docDate(file) {
15360
15549
  try {
15361
- const d = git(path27.dirname(file), ["log", "-1", "--format=%cI", "--", path27.basename(file)]);
15550
+ const d = git(path28.dirname(file), ["log", "-1", "--format=%cI", "--", path28.basename(file)]);
15362
15551
  if (d)
15363
15552
  return d;
15364
15553
  } catch {
15365
15554
  }
15366
15555
  try {
15367
- return fs26.statSync(file).mtime.toISOString();
15556
+ return fs27.statSync(file).mtime.toISOString();
15368
15557
  } catch {
15369
15558
  return null;
15370
15559
  }
15371
15560
  }
15372
15561
  };
15373
15562
  nowOf = (c) => c.now?.() ?? /* @__PURE__ */ new Date();
15374
- fileText = (fm, body) => matter4.stringify(body.trim() + "\n", JSON.parse(JSON.stringify(fm)));
15563
+ fileText = (fm, body) => matter5.stringify(body.trim() + "\n", JSON.parse(JSON.stringify(fm)));
15375
15564
  readFm = (file) => {
15376
- const m = matter4(fs26.readFileSync(file, "utf8"));
15565
+ const m = matter5(fs27.readFileSync(file, "utf8"));
15377
15566
  return { fm: m.data, body: m.content };
15378
15567
  };
15379
- ev = (c, event, itemId, domain, extra = {}) => ({ kb: c.kb, level: "group", project: "", domain, itemId, event, ...extra });
15568
+ ev = (c, event, itemId, extra = {}) => ({ kb: c.kb, level: "group", project: "", domain: "rules", itemId, event, ...extra });
15380
15569
  docRevOf = (r) => r.sources.map((s) => `${s.ref}@${s.sha.slice(0, SHA_LEN2)}`).join(",");
15381
15570
  }
15382
15571
  });
15383
15572
 
15384
15573
  // packages/core/dist/knowledge/fullpass.js
15385
15574
  import crypto5 from "crypto";
15386
- import fs27 from "fs";
15387
- import path28 from "path";
15388
- import matter5 from "gray-matter";
15575
+ import fs28 from "fs";
15576
+ import path29 from "path";
15577
+ import matter6 from "gray-matter";
15389
15578
  function dedupPairs(rules) {
15390
15579
  const active = rules.filter((r) => r.status === "active").sort((x, y) => x.id.localeCompare(y.id));
15391
15580
  const out = [];
@@ -15401,12 +15590,12 @@ function dedupPairs(rules) {
15401
15590
  return out.slice(0, MAX_DEDUP_PAIRS);
15402
15591
  }
15403
15592
  function readAuditRecords(kb, since, dir = defaultAuditDir()) {
15404
- if (!fs27.existsSync(dir))
15593
+ if (!fs28.existsSync(dir))
15405
15594
  return [];
15406
15595
  const sinceMonth = since.toISOString().slice(0, 7);
15407
15596
  const out = [];
15408
- for (const f of fs27.readdirSync(dir).filter((n) => n.endsWith(".jsonl") && n.slice(0, 7) >= sinceMonth).sort()) {
15409
- for (const line2 of fs27.readFileSync(path28.join(dir, f), "utf8").split("\n")) {
15597
+ for (const f of fs28.readdirSync(dir).filter((n) => n.endsWith(".jsonl") && n.slice(0, 7) >= sinceMonth).sort()) {
15598
+ for (const line2 of fs28.readFileSync(path29.join(dir, f), "utf8").split("\n")) {
15410
15599
  if (!line2.trim())
15411
15600
  continue;
15412
15601
  try {
@@ -15469,8 +15658,8 @@ function checkOverrides(cfg, kb) {
15469
15658
  continue;
15470
15659
  const h = baseHash(b);
15471
15660
  if (!o.overridesBase) {
15472
- const m = matter5(fs27.readFileSync(o.file, "utf8"));
15473
- fs27.writeFileSync(o.file, matter5.stringify(m.content, { ...m.data, overridesBase: h }));
15661
+ const m = matter6(fs28.readFileSync(o.file, "utf8"));
15662
+ fs28.writeFileSync(o.file, matter6.stringify(m.content, { ...m.data, overridesBase: h }));
15474
15663
  continue;
15475
15664
  }
15476
15665
  out.push({ project, ruleId: o.id, file: o.file, baseHash: h, changed: o.overridesBase !== h });
@@ -15513,8 +15702,8 @@ var init_fullpass = __esm({
15513
15702
  });
15514
15703
 
15515
15704
  // packages/core/dist/knowledge/scores.js
15516
- import fs28 from "fs";
15517
- import path29 from "path";
15705
+ import fs29 from "fs";
15706
+ import path30 from "path";
15518
15707
  function tally(records, into = emptyTally()) {
15519
15708
  for (const e of records) {
15520
15709
  const domain = typeof e.domain === "string" && e.domain ? e.domain : "rules";
@@ -15523,7 +15712,7 @@ function tally(records, into = emptyTally()) {
15523
15712
  d.writes++;
15524
15713
  if (READ_EVENTS.has(e.event ?? ""))
15525
15714
  d.reads++;
15526
- if (!e.itemId)
15715
+ if (!e.itemId || WRITE_EVENTS.has(e.event ?? ""))
15527
15716
  continue;
15528
15717
  const c = into.items[`${domain}|${e.itemId}`] ??= { ...zero(), domain, itemId: e.itemId };
15529
15718
  switch (e.event) {
@@ -15560,7 +15749,7 @@ function tally(records, into = emptyTally()) {
15560
15749
  }
15561
15750
  function parseLines(file) {
15562
15751
  const out = [];
15563
- for (const line2 of fs28.readFileSync(file, "utf8").split("\n")) {
15752
+ for (const line2 of fs29.readFileSync(file, "utf8").split("\n")) {
15564
15753
  if (!line2.trim())
15565
15754
  continue;
15566
15755
  try {
@@ -15572,37 +15761,37 @@ function parseLines(file) {
15572
15761
  }
15573
15762
  function compactAudit(o) {
15574
15763
  const dir = o.dir ?? defaultAuditDir();
15575
- if (!fs28.existsSync(dir))
15764
+ if (!fs29.existsSync(dir))
15576
15765
  return [];
15577
15766
  const cutoffMonth = new Date(o.now.getTime() - SCORE_WINDOW_DAYS * DAY_MS3).toISOString().slice(0, 7);
15578
15767
  const done = [];
15579
- for (const f of fs28.readdirSync(dir).sort()) {
15768
+ for (const f of fs29.readdirSync(dir).sort()) {
15580
15769
  const m = MONTH_FILE_RE.exec(f);
15581
15770
  if (!m || m[1] >= cutoffMonth)
15582
15771
  continue;
15583
15772
  const [, month, machine] = m;
15584
15773
  const byKb = /* @__PURE__ */ new Map();
15585
- for (const r of parseLines(path29.join(dir, f)))
15774
+ for (const r of parseLines(path30.join(dir, f)))
15586
15775
  byKb.set(r.kb ?? "", [...byKb.get(r.kb ?? "") ?? [], r]);
15587
- const statsPath = path29.join(dir, statsFileName(month, machine));
15588
- const prior = fs28.existsSync(statsPath) ? JSON.parse(fs28.readFileSync(statsPath, "utf8")) : null;
15776
+ const statsPath = path30.join(dir, statsFileName(month, machine));
15777
+ const prior = fs29.existsSync(statsPath) ? JSON.parse(fs29.readFileSync(statsPath, "utf8")) : null;
15589
15778
  const kbs = prior?.kbs ?? {};
15590
15779
  for (const [kb, recs] of byKb)
15591
15780
  kbs[kb] = tally(recs, kbs[kb]);
15592
- fs28.writeFileSync(`${statsPath}.tmp`, JSON.stringify({ month, machine, kbs }));
15593
- fs28.renameSync(`${statsPath}.tmp`, statsPath);
15594
- fs28.unlinkSync(path29.join(dir, f));
15781
+ fs29.writeFileSync(`${statsPath}.tmp`, JSON.stringify({ month, machine, kbs }));
15782
+ fs29.renameSync(`${statsPath}.tmp`, statsPath);
15783
+ fs29.unlinkSync(path30.join(dir, f));
15595
15784
  done.push(month);
15596
15785
  }
15597
15786
  return done;
15598
15787
  }
15599
15788
  function readCompacted(kb, dir) {
15600
15789
  const t = emptyTally();
15601
- if (!fs28.existsSync(dir))
15790
+ if (!fs29.existsSync(dir))
15602
15791
  return t;
15603
- for (const f of fs28.readdirSync(dir).filter((n) => n.endsWith(".stats.json"))) {
15792
+ for (const f of fs29.readdirSync(dir).filter((n) => n.endsWith(".stats.json"))) {
15604
15793
  try {
15605
- const k = JSON.parse(fs28.readFileSync(path29.join(dir, f), "utf8")).kbs[kb];
15794
+ const k = JSON.parse(fs29.readFileSync(path30.join(dir, f), "utf8")).kbs[kb];
15606
15795
  if (k)
15607
15796
  mergeTally(t, k);
15608
15797
  } catch {
@@ -15652,7 +15841,7 @@ function computeScores(i) {
15652
15841
  noise: w.noise,
15653
15842
  wrong: w.wrong,
15654
15843
  fireRate: w.surfaced,
15655
- pullRate: w.shown,
15844
+ pullRate: w.shown + w.searched,
15656
15845
  useRate: ratio(w.cited, w.surfaced),
15657
15846
  noiseRate: ratio(w.noise, w.surfaced),
15658
15847
  violationRate: ratio(w.violated, w.surfaced),
@@ -15694,21 +15883,21 @@ function computeScores(i) {
15694
15883
  function scoreKb(o) {
15695
15884
  const dir = o.auditDir ?? defaultAuditDir();
15696
15885
  const records = [];
15697
- if (fs28.existsSync(dir)) {
15698
- for (const f of fs28.readdirSync(dir).filter((n) => MONTH_FILE_RE.test(n)).sort()) {
15699
- records.push(...parseLines(path29.join(dir, f)).filter((r) => r.kb === o.kb));
15886
+ if (fs29.existsSync(dir)) {
15887
+ for (const f of fs29.readdirSync(dir).filter((n) => MONTH_FILE_RE.test(n)).sort()) {
15888
+ records.push(...parseLines(path30.join(dir, f)).filter((r) => r.kb === o.kb));
15700
15889
  }
15701
15890
  }
15702
15891
  let verdicts = [];
15703
15892
  try {
15704
- verdicts = fs28.readFileSync(path29.join(o.kbRoot, "verdicts.jsonl"), "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l));
15893
+ verdicts = fs29.readFileSync(path30.join(o.kbRoot, "verdicts.jsonl"), "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l));
15705
15894
  } catch {
15706
15895
  }
15707
15896
  return computeScores({ kb: o.kb, records, now: o.now, known: o.known, compacted: readCompacted(o.kb, dir), verdicts });
15708
15897
  }
15709
15898
  function writeScores(kbRoot, s) {
15710
- fs28.mkdirSync(kbRoot, { recursive: true });
15711
- fs28.writeFileSync(scoresFile(kbRoot), JSON.stringify(s, null, 2) + "\n");
15899
+ fs29.mkdirSync(kbRoot, { recursive: true });
15900
+ fs29.writeFileSync(scoresFile(kbRoot), JSON.stringify(s, null, 2) + "\n");
15712
15901
  }
15713
15902
  function renderScoresSection(s) {
15714
15903
  const pct = (x) => x === null ? "n/a" : `${Math.round(x * 100)}%`;
@@ -15721,7 +15910,7 @@ function renderScoresSection(s) {
15721
15910
  ...used.length ? [
15722
15911
  "| Item | Surfaced | Pulled | Cited | Use | Noise | Violated |",
15723
15912
  "|---|---|---|---|---|---|---|",
15724
- ...used.map((x) => `| ${x.itemId} | ${x.surfaced} | ${x.shown} | ${x.cited} | ${pct(x.useRate)} | ${pct(x.noiseRate)} | ${pct(x.violationRate)} |`)
15913
+ ...used.map((x) => `| ${x.itemId} | ${x.surfaced} | ${x.pullRate} | ${x.cited} | ${pct(x.useRate)} | ${pct(x.noiseRate)} | ${pct(x.violationRate)} |`)
15725
15914
  ] : ["No items surfaced."],
15726
15915
  "",
15727
15916
  `- Never fired (${never.length}): ${never.slice(0, 20).join(", ") || "none"}${never.length > 20 ? ", \u2026" : ""}`,
@@ -15749,13 +15938,13 @@ var init_scores = __esm({
15749
15938
  MONTH_FILE_RE = /^(\d{4}-\d{2})\.(.+)\.jsonl$/;
15750
15939
  statsFileName = (month, machine) => `${month}.${machine}.stats.json`;
15751
15940
  ratio = (n, d) => d ? Math.round(n / d * 1e3) / 1e3 : null;
15752
- scoresFile = (kbRoot) => path29.join(kbRoot, "scores.json");
15941
+ scoresFile = (kbRoot) => path30.join(kbRoot, "scores.json");
15753
15942
  }
15754
15943
  });
15755
15944
 
15756
15945
  // packages/core/dist/knowledge/report.js
15757
- import fs29 from "fs";
15758
- import path30 from "path";
15946
+ import fs30 from "fs";
15947
+ import path31 from "path";
15759
15948
  function renderReport(cfg, kb, o) {
15760
15949
  const { rules } = loadKbRules(cfg, kb, { includeProposed: true });
15761
15950
  const count = (s) => rules.filter((r) => r.status === s).length;
@@ -15777,6 +15966,11 @@ function renderReport(cfg, kb, o) {
15777
15966
  const info = esc2.filter((e) => !e.needsYou);
15778
15967
  if (info.length)
15779
15968
  L.push("", `## For information (${info.length})`, "", ...info.map((e) => `- **${e.key}**: ${e.explanation}`));
15969
+ const { errata, errors: errataErrors } = loadErrata(kbDir(cfg, kb));
15970
+ const orphans = orphanedErrata(kbDir(cfg, kb), errata);
15971
+ if (orphans.length || errataErrors.length) {
15972
+ L.push("", `## Errata to re-check (${orphans.length + errataErrors.length})`, "", ...orphans.map((e) => `- **${e.id}** (${e.ref}): quote no longer found in the source; the raw file changed. Re-check it in errata.yaml.`), ...errataErrors.map((e) => `- errata.yaml: ${e}`));
15973
+ }
15780
15974
  if (o.full) {
15781
15975
  const { usage: usage2, caps } = o.full;
15782
15976
  L.push("", "## Usage (30 days)", "", `- Surfaced: ${Object.values(usage2.surfaced).reduce((a, b) => a + b, 0)} times across ${Object.keys(usage2.surfaced).length} rules.`, `- Never surfaced (re-check triggers): ${usage2.neverSurfaced.join(", ") || "none"}`, `- Noisy (narrow triggers): ${usage2.noisy.join(", ") || "none"}`, `- Violation feedback (reword): ${usage2.violated.join(", ") || "none"}`, `- Domains over cap: ${caps.map((c) => `${c.domain} (${c.count}/${c.cap})`).join(", ") || "none"}`);
@@ -15786,12 +15980,13 @@ function renderReport(cfg, kb, o) {
15786
15980
  return L.join("\n") + "\n";
15787
15981
  }
15788
15982
  function writeReport(cfg, kb, o) {
15789
- const file = path30.join(kbDir(cfg, kb), "REPORT.md");
15790
- fs29.writeFileSync(file, renderReport(cfg, kb, o));
15983
+ const file = path31.join(kbDir(cfg, kb), "REPORT.md");
15984
+ fs30.writeFileSync(file, renderReport(cfg, kb, o));
15791
15985
  return file;
15792
15986
  }
15793
15987
  var init_report = __esm({
15794
15988
  "packages/core/dist/knowledge/report.js"() {
15989
+ init_errata();
15795
15990
  init_layers();
15796
15991
  init_paths();
15797
15992
  init_review();
@@ -15835,44 +16030,63 @@ var init_reconcile = __esm({
15835
16030
 
15836
16031
  // packages/core/dist/knowledge/cite.js
15837
16032
  function scanAndLogCitations(i) {
15838
- const at = i.at ?? i.sink?.now?.() ?? /* @__PURE__ */ new Date();
15839
- const since = new Date(at.getTime() - (i.lookbackDays ?? CITE_LOOKBACK_DAYS) * DAY_MS4);
15840
- const dir = i.sink?.dir ?? defaultAuditDir();
15841
- const delivered = /* @__PURE__ */ new Map();
15842
- const already = /* @__PURE__ */ new Set();
15843
- for (const r of readAuditRecords(void 0, since, dir)) {
15844
- if (!r.itemId || r.project !== i.project)
15845
- continue;
15846
- if (r.event === "item.cited" && r.ref === i.ref)
15847
- already.add(r.itemId);
15848
- if (!(r.event === "item.surfaced" || r.event === "item.shown") || Date.parse(r.ts) > at.getTime())
15849
- continue;
15850
- if (i.session && r.session && r.session !== i.session)
15851
- continue;
15852
- delivered.set(r.itemId, r);
15853
- }
15854
- const hits = [...delivered].filter(([id]) => !already.has(id) && mentionsId(i.text, id));
15855
- appendAudit(hits.map(([itemId, r]) => ({
15856
- kb: String(r.kb ?? ""),
15857
- level: String(r.level ?? "project"),
15858
- project: i.project,
15859
- domain: String(r.domain ?? "rules"),
15860
- itemId,
15861
- event: "item.cited",
15862
- ref: i.ref,
15863
- ...i.agent ? { agent: i.agent } : {},
15864
- ...i.session ? { session: i.session } : {}
15865
- })), i.sink);
15866
- return hits.map(([id]) => id);
16033
+ return scanCitations([i], i);
16034
+ }
16035
+ function scanCitations(texts, o) {
16036
+ if (!texts.length)
16037
+ return [];
16038
+ const now = o.sink?.now?.() ?? /* @__PURE__ */ new Date();
16039
+ const lookbackMs = (o.lookbackDays ?? CITE_LOOKBACK_DAYS) * DAY_MS4;
16040
+ const ats = texts.map((t) => (t.at ?? now).getTime());
16041
+ const records = readAuditRecords(void 0, new Date(Math.min(...ats) - lookbackMs), o.sink?.dir ?? defaultAuditDir()).filter((r) => r.itemId && r.project === o.project);
16042
+ const already = new Set(records.filter((r) => r.event === "item.cited").map((r) => `${r.ref}|${r.itemId}`));
16043
+ const deliveries = records.filter((r) => (r.event === "item.surfaced" || r.event === "item.shown" || r.event === "item.searched") && !(o.session && r.session && r.session !== o.session));
16044
+ const events = [];
16045
+ texts.forEach((t, n) => {
16046
+ const at = ats[n];
16047
+ const delivered = /* @__PURE__ */ new Map();
16048
+ for (const r of deliveries) {
16049
+ const ts = Date.parse(r.ts);
16050
+ if (ts <= at && ts >= at - lookbackMs)
16051
+ delivered.set(r.itemId, r);
16052
+ }
16053
+ for (const [itemId, r] of delivered) {
16054
+ if (already.has(`${t.ref}|${itemId}`) || !mentionsId(t.text, itemId))
16055
+ continue;
16056
+ already.add(`${t.ref}|${itemId}`);
16057
+ events.push({
16058
+ kb: String(r.kb ?? ""),
16059
+ level: String(r.level ?? "project"),
16060
+ project: o.project,
16061
+ domain: String(r.domain ?? "rules"),
16062
+ itemId,
16063
+ event: "item.cited",
16064
+ ref: t.ref,
16065
+ ...o.agent ? { agent: o.agent } : {},
16066
+ ...o.session ? { session: o.session } : {}
16067
+ });
16068
+ }
16069
+ });
16070
+ appendAudit(events, o.sink);
16071
+ return events.map((e) => e.itemId);
15867
16072
  }
15868
16073
  function readGitMessages(repo, since, run) {
15869
- const out = run(["-C", repo, "log", `--since=${since.toISOString()}`, "--format=%x1e%H%x1f%cI%x1f%B"]);
16074
+ return parseLog(run(["-C", repo, "log", "--all", `--since=${since.toISOString()}`, LOG_FORMAT]));
16075
+ }
16076
+ function readBranchOnlyMessages(repo, run, max = 200) {
16077
+ const branch = run(["-C", repo, "rev-parse", "--abbrev-ref", "HEAD"]).trim();
16078
+ if (!branch)
16079
+ return [];
16080
+ const own = branch === "HEAD" ? [] : [`--exclude=${branch}`];
16081
+ return parseLog(run(["-C", repo, "log", `--max-count=${max}`, "HEAD", "--not", ...own, "--branches", ...own.length ? [`--exclude=*/${branch}`] : [], "--remotes", LOG_FORMAT]));
16082
+ }
16083
+ function parseLog(out) {
15870
16084
  return out.split("").filter((c) => c.trim()).map((c) => {
15871
16085
  const [sha, at, ...rest] = c.split("");
15872
16086
  return { sha: sha.trim(), at: new Date(at), text: rest.join("") };
15873
16087
  });
15874
16088
  }
15875
- var DAY_MS4, CITE_LOOKBACK_DAYS, escapeRe2, mentionsId;
16089
+ var DAY_MS4, CITE_LOOKBACK_DAYS, escapeRe2, mentionsId, LOG_FORMAT;
15876
16090
  var init_cite = __esm({
15877
16091
  "packages/core/dist/knowledge/cite.js"() {
15878
16092
  init_audit();
@@ -15881,6 +16095,7 @@ var init_cite = __esm({
15881
16095
  CITE_LOOKBACK_DAYS = 7;
15882
16096
  escapeRe2 = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
15883
16097
  mentionsId = (text, id) => new RegExp(`(?<![\\w.-])${escapeRe2(id)}(?![\\w-])`).test(text);
16098
+ LOG_FORMAT = "--format=%x1e%H%x1f%cI%x1f%B";
15884
16099
  }
15885
16100
  });
15886
16101
 
@@ -15898,6 +16113,7 @@ var init_knowledge2 = __esm({
15898
16113
  init_migrate();
15899
16114
  init_verify();
15900
16115
  init_ingest();
16116
+ init_errata();
15901
16117
  init_anchor();
15902
16118
  init_extract();
15903
16119
  init_match();
@@ -15957,6 +16173,7 @@ __export(dist_exports2, {
15957
16173
  NOISY_RATE: () => NOISY_RATE,
15958
16174
  OPERATOR_INITIATED_COMMANDS: () => OPERATOR_INITIATED_COMMANDS,
15959
16175
  PANE_IDLE_AFTER_MS: () => PANE_IDLE_AFTER_MS,
16176
+ PASS_THROUGH: () => PASS_THROUGH,
15960
16177
  PROBE_QUIET_MS: () => PROBE_QUIET_MS,
15961
16178
  PROMPT_MIN_SCORE: () => PROMPT_MIN_SCORE,
15962
16179
  PROMPT_RULE_CAP: () => PROMPT_RULE_CAP,
@@ -16096,6 +16313,7 @@ __export(dist_exports2, {
16096
16313
  encodeMsg: () => encodeMsg,
16097
16314
  ensureDaemon: () => ensureDaemon,
16098
16315
  ensureGitRepo: () => ensureGitRepo,
16316
+ ensureKbGitignore: () => ensureKbGitignore,
16099
16317
  ensureSocksDir: () => ensureSocksDir,
16100
16318
  escalationsFile: () => escalationsFile,
16101
16319
  evaluateCodeConflict: () => evaluateCodeConflict,
@@ -16146,6 +16364,7 @@ __export(dist_exports2, {
16146
16364
  isDaemonSocketLive: () => isDaemonSocketLive,
16147
16365
  isGateCacheEnabled: () => isGateCacheEnabled,
16148
16366
  isGateSession: () => isGateSession,
16367
+ isGatedRef: () => isGatedRef,
16149
16368
  isNotifyActive: () => isNotifyActive,
16150
16369
  isOperatorInitiatedCommand: () => isOperatorInitiatedCommand,
16151
16370
  isPendingProposalDup: () => isPendingProposalDup,
@@ -16163,6 +16382,7 @@ __export(dist_exports2, {
16163
16382
  launchOneWorkspace: () => launchOneWorkspace,
16164
16383
  legacyKbDir: () => legacyKbDir,
16165
16384
  listSessions: () => listSessions,
16385
+ loadErrata: () => loadErrata,
16166
16386
  loadKbRules: () => loadKbRules,
16167
16387
  loadPending: () => loadPending,
16168
16388
  loadProposals: () => loadProposals,
@@ -16197,6 +16417,7 @@ __export(dist_exports2, {
16197
16417
  normalizedViews: () => normalizedViews,
16198
16418
  notePaneScreen: () => notePaneScreen,
16199
16419
  notifyToggle: () => notifyToggle,
16420
+ orphanedErrata: () => orphanedErrata,
16200
16421
  parseBearer: () => parseBearer,
16201
16422
  parseClassifierVerdict: () => parseClassifierVerdict,
16202
16423
  parseCommand: () => parseCommand,
@@ -16221,6 +16442,7 @@ __export(dist_exports2, {
16221
16442
  pruneTopics: () => pruneTopics,
16222
16443
  purgeExpiredWorkItems: () => purgeExpiredWorkItems,
16223
16444
  readAuditRecords: () => readAuditRecords,
16445
+ readBranchOnlyMessages: () => readBranchOnlyMessages,
16224
16446
  readCaptainAddress: () => readCaptainAddress,
16225
16447
  readCursor: () => readCursor,
16226
16448
  readEscalations: () => readEscalations,
@@ -16247,6 +16469,7 @@ __export(dist_exports2, {
16247
16469
  refFor: () => refFor,
16248
16470
  releaseDaemonLock: () => releaseDaemonLock,
16249
16471
  removeRunningMarker: () => removeRunningMarker,
16472
+ removeSource: () => removeSource,
16250
16473
  renderEnvAssignments: () => renderEnvAssignments,
16251
16474
  renderPlist: () => renderPlist,
16252
16475
  renderReport: () => renderReport,
@@ -16309,6 +16532,7 @@ __export(dist_exports2, {
16309
16532
  saveSessions: () => saveSessions,
16310
16533
  saveState: () => saveState,
16311
16534
  scanAndLogCitations: () => scanAndLogCitations,
16535
+ scanCitations: () => scanCitations,
16312
16536
  scheduleFile: () => scheduleFile,
16313
16537
  scoreKb: () => scoreKb,
16314
16538
  scoresFile: () => scoresFile,
@@ -16329,6 +16553,7 @@ __export(dist_exports2, {
16329
16553
  sideNextAutoName: () => sideNextAutoName,
16330
16554
  sideTitleFor: () => sideTitleFor,
16331
16555
  snapshotSources: () => snapshotSources,
16556
+ sourceCovers: () => sourceCovers,
16332
16557
  spansOverlap: () => spansOverlap,
16333
16558
  splitSections: () => splitSections,
16334
16559
  splitStatement: () => splitStatement,
@@ -16424,7 +16649,7 @@ var init_dist2 = __esm({
16424
16649
  });
16425
16650
 
16426
16651
  // packages/agents/dist/claude/registry.js
16427
- import fs30 from "fs";
16652
+ import fs31 from "fs";
16428
16653
  import { join as join17 } from "path";
16429
16654
  import { homedir as homedir7 } from "os";
16430
16655
  function parseRegistryDir(files, readFile7) {
@@ -16469,11 +16694,11 @@ function readClaudeStatus(task) {
16469
16694
  return void 0;
16470
16695
  let files;
16471
16696
  try {
16472
- files = fs30.readdirSync(CLAUDE_SESSIONS_DIR);
16697
+ files = fs31.readdirSync(CLAUDE_SESSIONS_DIR);
16473
16698
  } catch {
16474
16699
  return void 0;
16475
16700
  }
16476
- const entries = parseRegistryDir(files, (name) => fs30.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
16701
+ const entries = parseRegistryDir(files, (name) => fs31.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
16477
16702
  let entry;
16478
16703
  if (task.messagingSocketPath) {
16479
16704
  entry = entries.find((e) => e.messagingSocketPath === task.messagingSocketPath);
@@ -16488,11 +16713,11 @@ function readClaudeStatus(task) {
16488
16713
  function readClaudeStatusByCwd(cwd) {
16489
16714
  let files;
16490
16715
  try {
16491
- files = fs30.readdirSync(CLAUDE_SESSIONS_DIR);
16716
+ files = fs31.readdirSync(CLAUDE_SESSIONS_DIR);
16492
16717
  } catch {
16493
16718
  return void 0;
16494
16719
  }
16495
- const entries = parseRegistryDir(files, (name) => fs30.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
16720
+ const entries = parseRegistryDir(files, (name) => fs31.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
16496
16721
  const entry = entries.find((e) => e.cwd === cwd);
16497
16722
  if (!entry)
16498
16723
  return void 0;
@@ -16501,11 +16726,11 @@ function readClaudeStatusByCwd(cwd) {
16501
16726
  function readClaudeStatusBySocketPath(socketPath2) {
16502
16727
  let files;
16503
16728
  try {
16504
- files = fs30.readdirSync(CLAUDE_SESSIONS_DIR);
16729
+ files = fs31.readdirSync(CLAUDE_SESSIONS_DIR);
16505
16730
  } catch {
16506
16731
  return void 0;
16507
16732
  }
16508
- const entries = parseRegistryDir(files, (name) => fs30.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
16733
+ const entries = parseRegistryDir(files, (name) => fs31.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
16509
16734
  const entry = entries.find((e) => e.messagingSocketPath === socketPath2);
16510
16735
  if (!entry)
16511
16736
  return void 0;
@@ -16928,8 +17153,8 @@ var init_registry2 = __esm({
16928
17153
  });
16929
17154
 
16930
17155
  // packages/agents/dist/drivers/launch-cmd.js
16931
- import fs31 from "fs";
16932
- import path31 from "path";
17156
+ import fs32 from "fs";
17157
+ import path32 from "path";
16933
17158
  function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model, templatesDir, messagingSocketPath, sessionName, thinking, captainBoot, settingsPath) {
16934
17159
  const driver = registry.getDriver(agentName);
16935
17160
  if (driver.name === "claude") {
@@ -16956,14 +17181,14 @@ function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model,
16956
17181
  cmd += ` --settings ${settingsPath}`;
16957
17182
  }
16958
17183
  if (templatesDir) {
16959
- const roleFile2 = path31.join(templatesDir, `${role}.claude.md`);
16960
- const legacyRoleFile = path31.join(templatesDir, `${role}.CLAUDE.md`);
16961
- const actualRoleFile = fs31.existsSync(roleFile2) ? roleFile2 : fs31.existsSync(legacyRoleFile) ? legacyRoleFile : null;
17184
+ const roleFile2 = path32.join(templatesDir, `${role}.claude.md`);
17185
+ const legacyRoleFile = path32.join(templatesDir, `${role}.CLAUDE.md`);
17186
+ const actualRoleFile = fs32.existsSync(roleFile2) ? roleFile2 : fs32.existsSync(legacyRoleFile) ? legacyRoleFile : null;
16962
17187
  if (actualRoleFile) {
16963
17188
  cmd += ` --append-system-prompt-file ${actualRoleFile}`;
16964
17189
  }
16965
- const pluginDir = path31.join(templatesDir, "..", "plugin");
16966
- if (fs31.existsSync(pluginDir)) {
17190
+ const pluginDir = path32.join(templatesDir, "..", "plugin");
17191
+ if (fs32.existsSync(pluginDir)) {
16967
17192
  cmd += ` --plugin-dir ${pluginDir}`;
16968
17193
  }
16969
17194
  }
@@ -16972,14 +17197,14 @@ function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model,
16972
17197
  }
16973
17198
  return cmd;
16974
17199
  }
16975
- const roleFile = templatesDir ? path31.join(templatesDir, `${role}.${driver.templateSuffix}.md`) : void 0;
17200
+ const roleFile = templatesDir ? path32.join(templatesDir, `${role}.${driver.templateSuffix}.md`) : void 0;
16976
17201
  return driver.buildCommand({
16977
17202
  prompt: `You are a squadrant ${role}. Read your instructions from ${roleFile ?? role} and begin.`,
16978
17203
  workdir: process.cwd(),
16979
17204
  role,
16980
17205
  model,
16981
17206
  autoApprove: true,
16982
- promptFile: roleFile && fs31.existsSync(roleFile) ? roleFile : void 0,
17207
+ promptFile: roleFile && fs32.existsSync(roleFile) ? roleFile : void 0,
16983
17208
  ...captainBoot && role === "captain" ? {
16984
17209
  interactive: true,
16985
17210
  ...captainBoot.port ? { port: captainBoot.port } : {},
@@ -17009,7 +17234,7 @@ var init_drivers = __esm({
17009
17234
 
17010
17235
  // packages/agents/dist/projection/cursor.js
17011
17236
  import { mkdir, readFile, writeFile } from "fs/promises";
17012
- import path32 from "path";
17237
+ import path33 from "path";
17013
17238
  import os10 from "os";
17014
17239
  function renderMdc(source) {
17015
17240
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
@@ -17058,7 +17283,7 @@ function createCursorEmitter() {
17058
17283
  if (scope === "user") {
17059
17284
  return [
17060
17285
  {
17061
- path: path32.join(os10.homedir(), ".cursor/rules/squadrant-global.mdc"),
17286
+ path: path33.join(os10.homedir(), ".cursor/rules/squadrant-global.mdc"),
17062
17287
  shared: false,
17063
17288
  format: "mdc"
17064
17289
  }
@@ -17068,7 +17293,7 @@ function createCursorEmitter() {
17068
17293
  return [];
17069
17294
  return [
17070
17295
  {
17071
- path: path32.join(projectRoot, ".cursor/rules/squadrant.mdc"),
17296
+ path: path33.join(projectRoot, ".cursor/rules/squadrant.mdc"),
17072
17297
  shared: false,
17073
17298
  format: "mdc"
17074
17299
  }
@@ -17085,7 +17310,7 @@ function createCursorEmitter() {
17085
17310
  diff: buildDiff(existing, generated)
17086
17311
  };
17087
17312
  }
17088
- await mkdir(path32.dirname(dest.path), { recursive: true });
17313
+ await mkdir(path33.dirname(dest.path), { recursive: true });
17089
17314
  await writeFile(dest.path, generated, "utf-8");
17090
17315
  return {
17091
17316
  written: true,
@@ -17136,7 +17361,7 @@ var init_marker = __esm({
17136
17361
 
17137
17362
  // packages/agents/dist/projection/codex.js
17138
17363
  import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
17139
- import path33 from "path";
17364
+ import path34 from "path";
17140
17365
  import os11 from "os";
17141
17366
  function renderMarkdown(source) {
17142
17367
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
@@ -17161,7 +17386,7 @@ function createCodexEmitter() {
17161
17386
  destinations(scope, projectRoot) {
17162
17387
  if (scope === "user") {
17163
17388
  return [{
17164
- path: path33.join(os11.homedir(), ".codex/AGENTS.md"),
17389
+ path: path34.join(os11.homedir(), ".codex/AGENTS.md"),
17165
17390
  shared: true,
17166
17391
  format: "markdown"
17167
17392
  }];
@@ -17169,7 +17394,7 @@ function createCodexEmitter() {
17169
17394
  if (!projectRoot)
17170
17395
  return [];
17171
17396
  return [{
17172
- path: path33.join(projectRoot, "AGENTS.md"),
17397
+ path: path34.join(projectRoot, "AGENTS.md"),
17173
17398
  shared: true,
17174
17399
  format: "markdown"
17175
17400
  }];
@@ -17190,7 +17415,7 @@ ${existing ?? ""}
17190
17415
  ${generated}`
17191
17416
  };
17192
17417
  }
17193
- await mkdir2(path33.dirname(dest.path), { recursive: true });
17418
+ await mkdir2(path34.dirname(dest.path), { recursive: true });
17194
17419
  await writeFile2(dest.path, generated, "utf-8");
17195
17420
  return {
17196
17421
  written: true,
@@ -17208,7 +17433,7 @@ var init_codex2 = __esm({
17208
17433
 
17209
17434
  // packages/agents/dist/projection/gemini.js
17210
17435
  import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
17211
- import path34 from "path";
17436
+ import path35 from "path";
17212
17437
  import os12 from "os";
17213
17438
  function renderMarkdown2(source) {
17214
17439
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
@@ -17233,7 +17458,7 @@ function createGeminiEmitter() {
17233
17458
  destinations(scope, projectRoot) {
17234
17459
  if (scope === "user") {
17235
17460
  return [{
17236
- path: path34.join(os12.homedir(), ".gemini/GEMINI.md"),
17461
+ path: path35.join(os12.homedir(), ".gemini/GEMINI.md"),
17237
17462
  shared: true,
17238
17463
  format: "markdown"
17239
17464
  }];
@@ -17241,7 +17466,7 @@ function createGeminiEmitter() {
17241
17466
  if (!projectRoot)
17242
17467
  return [];
17243
17468
  return [{
17244
- path: path34.join(projectRoot, "GEMINI.md"),
17469
+ path: path35.join(projectRoot, "GEMINI.md"),
17245
17470
  shared: true,
17246
17471
  format: "markdown"
17247
17472
  }];
@@ -17262,7 +17487,7 @@ ${existing ?? ""}
17262
17487
  ${generated}`
17263
17488
  };
17264
17489
  }
17265
- await mkdir3(path34.dirname(dest.path), { recursive: true });
17490
+ await mkdir3(path35.dirname(dest.path), { recursive: true });
17266
17491
  await writeFile3(dest.path, generated, "utf-8");
17267
17492
  return {
17268
17493
  written: true,
@@ -17280,7 +17505,7 @@ var init_gemini2 = __esm({
17280
17505
 
17281
17506
  // packages/agents/dist/projection/opencode.js
17282
17507
  import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
17283
- import path35 from "path";
17508
+ import path36 from "path";
17284
17509
  import os13 from "os";
17285
17510
  function renderMarkdown3(source) {
17286
17511
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
@@ -17305,7 +17530,7 @@ function createOpencodeEmitter() {
17305
17530
  destinations(scope, projectRoot) {
17306
17531
  if (scope === "user") {
17307
17532
  return [{
17308
- path: path35.join(os13.homedir(), ".config", "opencode", "AGENTS.md"),
17533
+ path: path36.join(os13.homedir(), ".config", "opencode", "AGENTS.md"),
17309
17534
  shared: true,
17310
17535
  format: "markdown"
17311
17536
  }];
@@ -17313,7 +17538,7 @@ function createOpencodeEmitter() {
17313
17538
  if (!projectRoot)
17314
17539
  return [];
17315
17540
  return [{
17316
- path: path35.join(projectRoot, "AGENTS.md"),
17541
+ path: path36.join(projectRoot, "AGENTS.md"),
17317
17542
  shared: true,
17318
17543
  format: "markdown"
17319
17544
  }];
@@ -17334,7 +17559,7 @@ ${existing ?? ""}
17334
17559
  ${generated}`
17335
17560
  };
17336
17561
  }
17337
- await mkdir4(path35.dirname(dest.path), { recursive: true });
17562
+ await mkdir4(path36.dirname(dest.path), { recursive: true });
17338
17563
  await writeFile4(dest.path, generated, "utf-8");
17339
17564
  return {
17340
17565
  written: true,
@@ -17351,12 +17576,12 @@ var init_opencode2 = __esm({
17351
17576
  });
17352
17577
 
17353
17578
  // packages/agents/dist/projection/opencode-skills.js
17354
- import fs32 from "fs";
17579
+ import fs33 from "fs";
17355
17580
  import os14 from "os";
17356
- import path36 from "path";
17581
+ import path37 from "path";
17357
17582
  function defaultOpencodeSkillsRoot(env = process.env) {
17358
- const configHome = env.XDG_CONFIG_HOME || path36.join(os14.homedir(), ".config");
17359
- return path36.join(configHome, "opencode", "skills");
17583
+ const configHome = env.XDG_CONFIG_HOME || path37.join(os14.homedir(), ".config");
17584
+ return path37.join(configHome, "opencode", "skills");
17360
17585
  }
17361
17586
  function parseSkillName(raw) {
17362
17587
  const fm = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
@@ -17370,62 +17595,62 @@ function parseSkillName(raw) {
17370
17595
  }
17371
17596
  function listSourceSkills(sourceSkillsDir) {
17372
17597
  const out = /* @__PURE__ */ new Map();
17373
- if (!fs32.existsSync(sourceSkillsDir))
17598
+ if (!fs33.existsSync(sourceSkillsDir))
17374
17599
  return out;
17375
- for (const entry of fs32.readdirSync(sourceSkillsDir, { withFileTypes: true })) {
17600
+ for (const entry of fs33.readdirSync(sourceSkillsDir, { withFileTypes: true })) {
17376
17601
  if (!entry.isDirectory())
17377
17602
  continue;
17378
- const skillPath = path36.join(sourceSkillsDir, entry.name, "SKILL.md");
17379
- if (!fs32.existsSync(skillPath))
17603
+ const skillPath = path37.join(sourceSkillsDir, entry.name, "SKILL.md");
17604
+ if (!fs33.existsSync(skillPath))
17380
17605
  continue;
17381
- const name = parseSkillName(fs32.readFileSync(skillPath, "utf-8"));
17606
+ const name = parseSkillName(fs33.readFileSync(skillPath, "utf-8"));
17382
17607
  if (name && SAFE_NAME.test(name))
17383
17608
  out.set(name, skillPath);
17384
17609
  }
17385
17610
  return out;
17386
17611
  }
17387
17612
  function hasMarker(dir) {
17388
- return fs32.existsSync(path36.join(dir, OPENCODE_SKILL_MARKER));
17613
+ return fs33.existsSync(path37.join(dir, OPENCODE_SKILL_MARKER));
17389
17614
  }
17390
17615
  function syncOpencodeSkills(opts) {
17391
17616
  const result = { written: [], unchanged: [], removed: [], skipped: [] };
17392
17617
  const wanted = listSourceSkills(opts.sourceSkillsDir);
17393
- if (wanted.size === 0 && !fs32.existsSync(opts.sourceSkillsDir))
17618
+ if (wanted.size === 0 && !fs33.existsSync(opts.sourceSkillsDir))
17394
17619
  return result;
17395
17620
  if (!opts.dryRun)
17396
- fs32.mkdirSync(opts.skillsRoot, { recursive: true });
17621
+ fs33.mkdirSync(opts.skillsRoot, { recursive: true });
17397
17622
  for (const [name, srcPath] of wanted) {
17398
- const targetDir = path36.join(opts.skillsRoot, name);
17399
- const targetSkill = path36.join(targetDir, "SKILL.md");
17623
+ const targetDir = path37.join(opts.skillsRoot, name);
17624
+ const targetSkill = path37.join(targetDir, "SKILL.md");
17400
17625
  const managed = hasMarker(targetDir);
17401
- if (fs32.existsSync(targetSkill) && !managed) {
17626
+ if (fs33.existsSync(targetSkill) && !managed) {
17402
17627
  result.skipped.push({ name, reason: "foreign-collision" });
17403
17628
  continue;
17404
17629
  }
17405
- const current = fs32.existsSync(targetSkill) ? fs32.readFileSync(targetSkill) : null;
17406
- if (current && current.equals(fs32.readFileSync(srcPath))) {
17630
+ const current = fs33.existsSync(targetSkill) ? fs33.readFileSync(targetSkill) : null;
17631
+ if (current && current.equals(fs33.readFileSync(srcPath))) {
17407
17632
  if (!managed && !opts.dryRun) {
17408
- fs32.writeFileSync(path36.join(targetDir, OPENCODE_SKILL_MARKER), MARKER_BODY);
17633
+ fs33.writeFileSync(path37.join(targetDir, OPENCODE_SKILL_MARKER), MARKER_BODY);
17409
17634
  }
17410
17635
  result.unchanged.push(name);
17411
17636
  continue;
17412
17637
  }
17413
17638
  if (!opts.dryRun) {
17414
- fs32.mkdirSync(targetDir, { recursive: true });
17415
- fs32.copyFileSync(srcPath, targetSkill);
17416
- fs32.writeFileSync(path36.join(targetDir, OPENCODE_SKILL_MARKER), MARKER_BODY);
17639
+ fs33.mkdirSync(targetDir, { recursive: true });
17640
+ fs33.copyFileSync(srcPath, targetSkill);
17641
+ fs33.writeFileSync(path37.join(targetDir, OPENCODE_SKILL_MARKER), MARKER_BODY);
17417
17642
  }
17418
17643
  result.written.push(name);
17419
17644
  }
17420
- if (fs32.existsSync(opts.skillsRoot)) {
17421
- for (const entry of fs32.readdirSync(opts.skillsRoot, { withFileTypes: true })) {
17645
+ if (fs33.existsSync(opts.skillsRoot)) {
17646
+ for (const entry of fs33.readdirSync(opts.skillsRoot, { withFileTypes: true })) {
17422
17647
  if (!entry.isDirectory() || wanted.has(entry.name))
17423
17648
  continue;
17424
- const dir = path36.join(opts.skillsRoot, entry.name);
17649
+ const dir = path37.join(opts.skillsRoot, entry.name);
17425
17650
  if (!hasMarker(dir))
17426
17651
  continue;
17427
17652
  if (!opts.dryRun)
17428
- fs32.rmSync(dir, { recursive: true, force: true });
17653
+ fs33.rmSync(dir, { recursive: true, force: true });
17429
17654
  result.removed.push(entry.name);
17430
17655
  }
17431
17656
  }
@@ -17433,7 +17658,7 @@ function syncOpencodeSkills(opts) {
17433
17658
  }
17434
17659
  function syncShippedOpencodeSkills(opts) {
17435
17660
  return syncOpencodeSkills({
17436
- sourceSkillsDir: path36.join(opts.pkgRoot, "plugin", "skills"),
17661
+ sourceSkillsDir: path37.join(opts.pkgRoot, "plugin", "skills"),
17437
17662
  skillsRoot: opts.skillsRoot ?? defaultOpencodeSkillsRoot(),
17438
17663
  dryRun: opts.dryRun
17439
17664
  });
@@ -17449,7 +17674,7 @@ var init_opencode_skills = __esm({
17449
17674
 
17450
17675
  // packages/agents/dist/projection/claude.js
17451
17676
  import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
17452
- import path37 from "path";
17677
+ import path38 from "path";
17453
17678
  import os15 from "os";
17454
17679
  function renderMarkdown4(source) {
17455
17680
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
@@ -17474,7 +17699,7 @@ function createClaudeEmitter() {
17474
17699
  destinations(scope, projectRoot) {
17475
17700
  if (scope === "user") {
17476
17701
  return [{
17477
- path: path37.join(os15.homedir(), ".claude/CLAUDE.md"),
17702
+ path: path38.join(os15.homedir(), ".claude/CLAUDE.md"),
17478
17703
  shared: true,
17479
17704
  format: "markdown"
17480
17705
  }];
@@ -17482,7 +17707,7 @@ function createClaudeEmitter() {
17482
17707
  if (!projectRoot)
17483
17708
  return [];
17484
17709
  return [{
17485
- path: path37.join(projectRoot, "CLAUDE.md"),
17710
+ path: path38.join(projectRoot, "CLAUDE.md"),
17486
17711
  shared: true,
17487
17712
  format: "markdown"
17488
17713
  }];
@@ -17503,7 +17728,7 @@ ${existing ?? ""}
17503
17728
  ${generated}`
17504
17729
  };
17505
17730
  }
17506
- await mkdir5(path37.dirname(dest.path), { recursive: true });
17731
+ await mkdir5(path38.dirname(dest.path), { recursive: true });
17507
17732
  await writeFile5(dest.path, generated, "utf-8");
17508
17733
  return {
17509
17734
  written: true,
@@ -18538,8 +18763,8 @@ function resolveLastAssistantText(payload) {
18538
18763
  const derived = deriveTranscriptPath(p?.session_id, cwd);
18539
18764
  if (derived)
18540
18765
  candidates.push(derived);
18541
- for (const path59 of candidates) {
18542
- const text = readLastAssistantText(path59);
18766
+ for (const path60 of candidates) {
18767
+ const text = readLastAssistantText(path60);
18543
18768
  if (text != null)
18544
18769
  return text;
18545
18770
  }
@@ -19428,22 +19653,22 @@ var init_receipt_listener = __esm({
19428
19653
  });
19429
19654
 
19430
19655
  // packages/agents/dist/claude/api-key-approval.js
19431
- import fs33 from "fs";
19656
+ import fs34 from "fs";
19432
19657
  import os16 from "os";
19433
- import path38 from "path";
19658
+ import path39 from "path";
19434
19659
  function ensureClaudeApiKeyApproved(apiKey, opts = {}) {
19435
19660
  const suffix = apiKey.slice(-20);
19436
19661
  if (!suffix)
19437
19662
  return { changed: false, reason: "empty api key" };
19438
- const file = opts.claudeJsonPath ?? path38.join(os16.homedir(), ".claude.json");
19663
+ const file = opts.claudeJsonPath ?? path39.join(os16.homedir(), ".claude.json");
19439
19664
  const log = opts.log ?? (() => {
19440
19665
  });
19441
- if (!fs33.existsSync(file)) {
19666
+ if (!fs34.existsSync(file)) {
19442
19667
  return { changed: false, reason: "no ~/.claude.json yet \u2014 skipping routed-key pre-approval" };
19443
19668
  }
19444
19669
  let doc;
19445
19670
  try {
19446
- doc = JSON.parse(fs33.readFileSync(file, "utf8"));
19671
+ doc = JSON.parse(fs34.readFileSync(file, "utf8"));
19447
19672
  } catch {
19448
19673
  return {
19449
19674
  changed: false,
@@ -19465,11 +19690,11 @@ function ensureClaudeApiKeyApproved(apiKey, opts = {}) {
19465
19690
  };
19466
19691
  const tmp = `${file}.squadrant-tmp`;
19467
19692
  try {
19468
- fs33.writeFileSync(tmp, JSON.stringify(doc, null, 2) + "\n", { mode: fs33.statSync(file).mode & 511 });
19469
- fs33.renameSync(tmp, file);
19693
+ fs34.writeFileSync(tmp, JSON.stringify(doc, null, 2) + "\n", { mode: fs34.statSync(file).mode & 511 });
19694
+ fs34.renameSync(tmp, file);
19470
19695
  } catch (e) {
19471
19696
  try {
19472
- fs33.unlinkSync(tmp);
19697
+ fs34.unlinkSync(tmp);
19473
19698
  } catch {
19474
19699
  }
19475
19700
  return {
@@ -19578,10 +19803,10 @@ var init_http_channel = __esm({
19578
19803
  return cached;
19579
19804
  const directory = this.directoryFor?.(taskId);
19580
19805
  const createdAfterMs = this.createdAfterFor?.(taskId);
19581
- for (const path59 of ["/session?", "/api/session?"]) {
19806
+ for (const path60 of ["/session?", "/api/session?"]) {
19582
19807
  let res;
19583
19808
  try {
19584
- res = await this.request(`http://127.0.0.1:${port}${path59}`, { method: "GET" });
19809
+ res = await this.request(`http://127.0.0.1:${port}${path60}`, { method: "GET" });
19585
19810
  } catch {
19586
19811
  return void 0;
19587
19812
  }
@@ -19661,12 +19886,12 @@ var init_fact_adapter = __esm({
19661
19886
  });
19662
19887
 
19663
19888
  // packages/agents/dist/rules-hooks/install.js
19664
- import fs34 from "fs";
19889
+ import fs35 from "fs";
19665
19890
  import os17 from "os";
19666
- import path39 from "path";
19891
+ import path40 from "path";
19667
19892
  function readJson(file) {
19668
19893
  try {
19669
- const v2 = JSON.parse(fs34.readFileSync(file, "utf8"));
19894
+ const v2 = JSON.parse(fs35.readFileSync(file, "utf8"));
19670
19895
  return typeof v2 === "object" && v2 !== null && !Array.isArray(v2) ? v2 : {};
19671
19896
  } catch (err) {
19672
19897
  if (err.code === "ENOENT")
@@ -19688,24 +19913,24 @@ function mergeHooks(root, agent, specs, cli, timeout) {
19688
19913
  function writeIfChanged(file, next, dryRun) {
19689
19914
  let prev = null;
19690
19915
  try {
19691
- prev = fs34.readFileSync(file, "utf8");
19916
+ prev = fs35.readFileSync(file, "utf8");
19692
19917
  } catch {
19693
19918
  }
19694
19919
  const changed = prev !== next;
19695
19920
  if (changed && !dryRun) {
19696
- fs34.mkdirSync(path39.dirname(file), { recursive: true });
19697
- fs34.writeFileSync(file, next);
19921
+ fs35.mkdirSync(path40.dirname(file), { recursive: true });
19922
+ fs35.writeFileSync(file, next);
19698
19923
  }
19699
19924
  return { path: file, changed };
19700
19925
  }
19701
19926
  function installCodexRulesHooks(opts = {}) {
19702
19927
  const env = opts.env ?? process.env;
19703
- const file = opts.target ?? path39.join(env.CODEX_HOME ?? path39.join(os17.homedir(), ".codex"), "hooks.json");
19928
+ const file = opts.target ?? path40.join(env.CODEX_HOME ?? path40.join(os17.homedir(), ".codex"), "hooks.json");
19704
19929
  const merged = mergeHooks(readJson(file), "codex", CODEX_HOOKS, opts.cli ?? "squadrant", 5);
19705
19930
  return writeIfChanged(file, JSON.stringify(merged, null, 2) + "\n", opts.dryRun);
19706
19931
  }
19707
19932
  function installGeminiRulesHooks(opts = {}) {
19708
- const file = opts.target ?? path39.join(os17.homedir(), ".gemini", "settings.json");
19933
+ const file = opts.target ?? path40.join(os17.homedir(), ".gemini", "settings.json");
19709
19934
  const merged = mergeHooks(readJson(file), "gemini", GEMINI_HOOKS, opts.cli ?? "squadrant", 5e3);
19710
19935
  return writeIfChanged(file, JSON.stringify(merged, null, 2) + "\n", opts.dryRun);
19711
19936
  }
@@ -19746,9 +19971,9 @@ export const SquadrantRules = async ({ directory }) => {
19746
19971
  }
19747
19972
  function installOpencodeRulesHooks(opts = {}) {
19748
19973
  const env = opts.env ?? process.env;
19749
- const file = opts.target ?? path39.join(env.XDG_CONFIG_HOME || path39.join(os17.homedir(), ".config"), "opencode", "plugin", "squadrant-rules.js");
19974
+ const file = opts.target ?? path40.join(env.XDG_CONFIG_HOME || path40.join(os17.homedir(), ".config"), "opencode", "plugin", "squadrant-rules.js");
19750
19975
  try {
19751
- if (!fs34.readFileSync(file, "utf8").startsWith(OPENCODE_PLUGIN_MARKER))
19976
+ if (!fs35.readFileSync(file, "utf8").startsWith(OPENCODE_PLUGIN_MARKER))
19752
19977
  return { path: file, changed: false, skipped: "foreign-file" };
19753
19978
  } catch {
19754
19979
  }
@@ -20724,13 +20949,13 @@ var init_notifiers = __esm({
20724
20949
  });
20725
20950
 
20726
20951
  // packages/workspaces/dist/workspaces/obsidian.js
20727
- import fs35 from "fs/promises";
20952
+ import fs36 from "fs/promises";
20728
20953
  import { existsSync as existsSync11 } from "fs";
20729
- import path40 from "path";
20954
+ import path41 from "path";
20730
20955
  function resolveInRoot(root, relative) {
20731
- const joined = path40.resolve(root, relative);
20732
- const normalized2 = path40.resolve(root) + path40.sep;
20733
- if (joined !== path40.resolve(root) && !joined.startsWith(normalized2)) {
20956
+ const joined = path41.resolve(root, relative);
20957
+ const normalized2 = path41.resolve(root) + path41.sep;
20958
+ if (joined !== path41.resolve(root) && !joined.startsWith(normalized2)) {
20734
20959
  throw new Error(`Path '${relative}' escapes workspace root`);
20735
20960
  }
20736
20961
  return joined;
@@ -20749,16 +20974,16 @@ function createObsidianDriver(scope) {
20749
20974
  };
20750
20975
  },
20751
20976
  async read(rel) {
20752
- return fs35.readFile(resolveInRoot(root, rel), "utf-8");
20977
+ return fs36.readFile(resolveInRoot(root, rel), "utf-8");
20753
20978
  },
20754
20979
  async write(rel, content) {
20755
20980
  const abs = resolveInRoot(root, rel);
20756
- await fs35.mkdir(path40.dirname(abs), { recursive: true });
20757
- await fs35.writeFile(abs, content);
20981
+ await fs36.mkdir(path41.dirname(abs), { recursive: true });
20982
+ await fs36.writeFile(abs, content);
20758
20983
  },
20759
20984
  async exists(rel) {
20760
20985
  try {
20761
- await fs35.access(resolveInRoot(root, rel));
20986
+ await fs36.access(resolveInRoot(root, rel));
20762
20987
  return true;
20763
20988
  } catch {
20764
20989
  return false;
@@ -20766,13 +20991,13 @@ function createObsidianDriver(scope) {
20766
20991
  },
20767
20992
  async list(rel) {
20768
20993
  try {
20769
- return await fs35.readdir(resolveInRoot(root, rel));
20994
+ return await fs36.readdir(resolveInRoot(root, rel));
20770
20995
  } catch {
20771
20996
  return [];
20772
20997
  }
20773
20998
  },
20774
20999
  async mkdir(rel) {
20775
- await fs35.mkdir(resolveInRoot(root, rel), { recursive: true });
21000
+ await fs36.mkdir(resolveInRoot(root, rel), { recursive: true });
20776
21001
  }
20777
21002
  };
20778
21003
  }
@@ -21186,9 +21411,9 @@ function defaultListFiles(dir) {
21186
21411
  return [];
21187
21412
  }
21188
21413
  }
21189
- function defaultReadFile(path59) {
21414
+ function defaultReadFile(path60) {
21190
21415
  try {
21191
- return readFileSync17(path59, "utf-8");
21416
+ return readFileSync17(path60, "utf-8");
21192
21417
  } catch {
21193
21418
  return void 0;
21194
21419
  }
@@ -21681,16 +21906,16 @@ function extractDetail(sub, payload) {
21681
21906
  }
21682
21907
  return void 0;
21683
21908
  }
21684
- function defaultReadFile2(path59) {
21909
+ function defaultReadFile2(path60) {
21685
21910
  try {
21686
- return readFileSync18(path59, "utf-8");
21911
+ return readFileSync18(path60, "utf-8");
21687
21912
  } catch {
21688
21913
  return void 0;
21689
21914
  }
21690
21915
  }
21691
- function defaultWriteFile(path59, content) {
21692
- mkdirSync8(path59.replace(/\/[^/]+$/, ""), { recursive: true });
21693
- writeFileSync11(path59, content, "utf-8");
21916
+ function defaultWriteFile(path60, content) {
21917
+ mkdirSync8(path60.replace(/\/[^/]+$/, ""), { recursive: true });
21918
+ writeFileSync11(path60, content, "utf-8");
21694
21919
  }
21695
21920
  var CLAUDE_HOOK_EVENTS, DEFAULT_CLI_CMD, FOREIGN_AUTO_GATE_MARKER, SUBAGENT_MODEL_KEY, SMALL_FAST_MODEL_KEY, NativeHookSource;
21696
21921
  var init_native_hook_source = __esm({
@@ -22262,9 +22487,9 @@ init_dist();
22262
22487
  init_dist4();
22263
22488
  import { Command } from "commander";
22264
22489
  import { execSync as execSync7 } from "child_process";
22265
- import fs36 from "fs";
22490
+ import fs37 from "fs";
22266
22491
  import { stat } from "fs/promises";
22267
- import path41 from "path";
22492
+ import path42 from "path";
22268
22493
  import chalk3 from "chalk";
22269
22494
 
22270
22495
  // packages/cli/src/commands/health-view.ts
@@ -22366,7 +22591,7 @@ function settingsHaveAgentTeams() {
22366
22591
  try {
22367
22592
  const home = process.env.HOME || "";
22368
22593
  const settings = JSON.parse(
22369
- fs36.readFileSync(`${home}/.claude/settings.json`, "utf-8")
22594
+ fs37.readFileSync(`${home}/.claude/settings.json`, "utf-8")
22370
22595
  );
22371
22596
  return settings?.env?.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS === "1";
22372
22597
  } catch {
@@ -22377,7 +22602,7 @@ function pluginInstalled(pluginKey) {
22377
22602
  try {
22378
22603
  const home = process.env.HOME || "";
22379
22604
  const plugins = JSON.parse(
22380
- fs36.readFileSync(
22605
+ fs37.readFileSync(
22381
22606
  `${home}/.claude/plugins/installed_plugins.json`,
22382
22607
  "utf-8"
22383
22608
  )
@@ -22401,9 +22626,9 @@ function tryGetVersion(cmd) {
22401
22626
  }
22402
22627
  function candidateGlobalInstalls(roots) {
22403
22628
  const out = [];
22404
- if (roots.npm) out.push({ manager: "npm", packageJsonPath: path41.join(roots.npm, "squadrant", "package.json") });
22405
- if (roots.pnpm) out.push({ manager: "pnpm", packageJsonPath: path41.join(roots.pnpm, "squadrant", "package.json") });
22406
- if (roots.yarn) out.push({ manager: "yarn", packageJsonPath: path41.join(roots.yarn, "node_modules", "squadrant", "package.json") });
22629
+ if (roots.npm) out.push({ manager: "npm", packageJsonPath: path42.join(roots.npm, "squadrant", "package.json") });
22630
+ if (roots.pnpm) out.push({ manager: "pnpm", packageJsonPath: path42.join(roots.pnpm, "squadrant", "package.json") });
22631
+ if (roots.yarn) out.push({ manager: "yarn", packageJsonPath: path42.join(roots.yarn, "node_modules", "squadrant", "package.json") });
22407
22632
  return out;
22408
22633
  }
22409
22634
  function findInstalledSquadrants(candidates, readVersion) {
@@ -22423,7 +22648,7 @@ ${lines.join("\n")}
22423
22648
  }
22424
22649
  function readPackageVersion(packageJsonPath) {
22425
22650
  try {
22426
- const pkg2 = JSON.parse(fs36.readFileSync(packageJsonPath, "utf-8"));
22651
+ const pkg2 = JSON.parse(fs37.readFileSync(packageJsonPath, "utf-8"));
22427
22652
  return typeof pkg2.version === "string" ? pkg2.version : null;
22428
22653
  } catch {
22429
22654
  return null;
@@ -22506,7 +22731,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
22506
22731
  ));
22507
22732
  checkOptional(
22508
22733
  "Obsidian installed",
22509
- commandExists("obsidian") || fs36.existsSync("/Applications/Obsidian.app"),
22734
+ commandExists("obsidian") || fs37.existsSync("/Applications/Obsidian.app"),
22510
22735
  "viewer for the hub vault; the vault is plain Markdown",
22511
22736
  "Install from: https://obsidian.md"
22512
22737
  );
@@ -22603,7 +22828,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
22603
22828
  const emitter = projectionRegistry.get(name);
22604
22829
  const [userDest] = emitter.destinations("user");
22605
22830
  if (!userDest) continue;
22606
- const dir = path41.dirname(userDest.path);
22831
+ const dir = path42.dirname(userDest.path);
22607
22832
  let status;
22608
22833
  try {
22609
22834
  await stat(dir);
@@ -22624,7 +22849,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
22624
22849
  results.push(
22625
22850
  check(
22626
22851
  "Squadrant config exists",
22627
- fs36.existsSync(
22852
+ fs37.existsSync(
22628
22853
  process.env.SQUADRANT_CONFIG || `${process.env.HOME}/.config/squadrant/config.json`
22629
22854
  ),
22630
22855
  "Run: squadrant init"
@@ -22708,8 +22933,8 @@ init_dist();
22708
22933
  init_dist4();
22709
22934
  init_dist();
22710
22935
  import { Command as Command2 } from "commander";
22711
- import fs37 from "fs";
22712
- import path42 from "path";
22936
+ import fs38 from "fs";
22937
+ import path43 from "path";
22713
22938
  import os18 from "os";
22714
22939
  import readline from "readline";
22715
22940
  import chalk4 from "chalk";
@@ -22960,22 +23185,22 @@ function detectClaudeAuth(probe = {}) {
22960
23185
  // packages/cli/src/commands/init.ts
22961
23186
  init_dist3();
22962
23187
  function findPackageRoot() {
22963
- let dir = path42.dirname(new URL(import.meta.url).pathname);
23188
+ let dir = path43.dirname(new URL(import.meta.url).pathname);
22964
23189
  while (dir !== "/") {
22965
- if (fs37.existsSync(path42.join(dir, "package.json"))) return dir;
22966
- dir = path42.dirname(dir);
23190
+ if (fs38.existsSync(path43.join(dir, "package.json"))) return dir;
23191
+ dir = path43.dirname(dir);
22967
23192
  }
22968
23193
  return process.cwd();
22969
23194
  }
22970
23195
  function copyDirRecursive(src, dest) {
22971
- fs37.mkdirSync(dest, { recursive: true });
22972
- for (const entry of fs37.readdirSync(src, { withFileTypes: true })) {
22973
- const srcPath = path42.join(src, entry.name);
22974
- const destPath = path42.join(dest, entry.name);
23196
+ fs38.mkdirSync(dest, { recursive: true });
23197
+ for (const entry of fs38.readdirSync(src, { withFileTypes: true })) {
23198
+ const srcPath = path43.join(src, entry.name);
23199
+ const destPath = path43.join(dest, entry.name);
22975
23200
  if (entry.isDirectory()) {
22976
23201
  copyDirRecursive(srcPath, destPath);
22977
23202
  } else {
22978
- fs37.copyFileSync(srcPath, destPath);
23203
+ fs38.copyFileSync(srcPath, destPath);
22979
23204
  }
22980
23205
  }
22981
23206
  }
@@ -23017,7 +23242,7 @@ function warnIfNoAnthropic(preset, auth) {
23017
23242
  var initCommand = new Command2("init").description("Guided first-time setup: provider, hub vault, agents, plugins, projects (re-run-safe)").option("--hub <path>", "Hub vault path", "~/squadrant-hub").option("--preset <id>", "provider preset: a|b|c (non-interactive; d accepted as a deprecated alias for c)").action(async (opts) => {
23018
23243
  const hubPath = resolveHome(opts.hub);
23019
23244
  const pkgRoot = findPackageRoot();
23020
- const configDir = path42.join(os18.homedir(), ".config", "squadrant");
23245
+ const configDir = path43.join(os18.homedir(), ".config", "squadrant");
23021
23246
  const isTTY = process.stdin.isTTY === true;
23022
23247
  console.log(chalk4.bold("\nSquadrant Init\n"));
23023
23248
  let explicitPreset;
@@ -23055,8 +23280,8 @@ var initCommand = new Command2("init").description("Guided first-time setup: pro
23055
23280
  }
23056
23281
  const wsRegistry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
23057
23282
  try {
23058
- if (fs37.existsSync(DEFAULT_CONFIG_PATH)) {
23059
- const existing = JSON.parse(fs37.readFileSync(DEFAULT_CONFIG_PATH, "utf-8"));
23283
+ if (fs38.existsSync(DEFAULT_CONFIG_PATH)) {
23284
+ const existing = JSON.parse(fs38.readFileSync(DEFAULT_CONFIG_PATH, "utf-8"));
23060
23285
  wsRegistry.get(existing.workspace ?? "obsidian");
23061
23286
  }
23062
23287
  } catch (err) {
@@ -23064,7 +23289,7 @@ var initCommand = new Command2("init").description("Guided first-time setup: pro
23064
23289
  return;
23065
23290
  }
23066
23291
  stepHeader(1, 5, "Hub vault");
23067
- const configExists = fs37.existsSync(DEFAULT_CONFIG_PATH);
23292
+ const configExists = fs38.existsSync(DEFAULT_CONFIG_PATH);
23068
23293
  let config;
23069
23294
  if (configExists) {
23070
23295
  console.log(chalk4.yellow(" \u26A0 Config already exists, skipping creation"));
@@ -23102,37 +23327,37 @@ var initCommand = new Command2("init").description("Guided first-time setup: pro
23102
23327
  saveConfig(config);
23103
23328
  console.log(chalk4.green(` \u2714 Config updated at ${DEFAULT_CONFIG_PATH}`));
23104
23329
  }
23105
- const hubTemplate = path42.join(pkgRoot, "obsidian", "hub");
23106
- if (fs37.existsSync(hubPath)) {
23330
+ const hubTemplate = path43.join(pkgRoot, "obsidian", "hub");
23331
+ if (fs38.existsSync(hubPath)) {
23107
23332
  console.log(chalk4.yellow(` \u26A0 Hub vault already exists at ${hubPath}`));
23108
- } else if (fs37.existsSync(hubTemplate)) {
23333
+ } else if (fs38.existsSync(hubTemplate)) {
23109
23334
  copyDirRecursive(hubTemplate, hubPath);
23110
23335
  console.log(chalk4.green(` \u2714 Hub vault scaffolded at ${hubPath}`));
23111
23336
  } else {
23112
- fs37.mkdirSync(hubPath, { recursive: true });
23337
+ fs38.mkdirSync(hubPath, { recursive: true });
23113
23338
  console.log(chalk4.yellow(` \u26A0 Hub template not found; created empty directory at ${hubPath}`));
23114
23339
  }
23115
- const hubDashboardSrc = path42.join(pkgRoot, "obsidian", "hub", "dashboard.md");
23116
- const hubDashboardDest = path42.join(hubPath, "dashboard.md");
23117
- if (fs37.existsSync(hubDashboardSrc)) {
23118
- fs37.copyFileSync(hubDashboardSrc, hubDashboardDest);
23340
+ const hubDashboardSrc = path43.join(pkgRoot, "obsidian", "hub", "dashboard.md");
23341
+ const hubDashboardDest = path43.join(hubPath, "dashboard.md");
23342
+ if (fs38.existsSync(hubDashboardSrc)) {
23343
+ fs38.copyFileSync(hubDashboardSrc, hubDashboardDest);
23119
23344
  console.log(chalk4.green(` \u2714 Dashboard refreshed`));
23120
23345
  }
23121
- fs37.mkdirSync(path42.join(hubPath, "projects"), { recursive: true });
23346
+ fs38.mkdirSync(path43.join(hubPath, "projects"), { recursive: true });
23122
23347
  ensureRuntimeSynced({ sourceRoot: pkgRoot, runtimeRoot: configDir });
23123
23348
  console.log(chalk4.green(` \u2714 Runtime assets synced to ${configDir}`));
23124
23349
  stepHeader(2, 5, "Agent + projection setup");
23125
- const settingsPath = path42.join(os18.homedir(), ".claude", "settings.json");
23350
+ const settingsPath = path43.join(os18.homedir(), ".claude", "settings.json");
23126
23351
  try {
23127
23352
  let settings = {};
23128
- if (fs37.existsSync(settingsPath)) {
23129
- settings = JSON.parse(fs37.readFileSync(settingsPath, "utf-8"));
23353
+ if (fs38.existsSync(settingsPath)) {
23354
+ settings = JSON.parse(fs38.readFileSync(settingsPath, "utf-8"));
23130
23355
  }
23131
23356
  const env = settings.env || {};
23132
23357
  if (env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS !== "1") {
23133
23358
  settings.env = { ...env, CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "1" };
23134
- fs37.mkdirSync(path42.dirname(settingsPath), { recursive: true });
23135
- fs37.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
23359
+ fs38.mkdirSync(path43.dirname(settingsPath), { recursive: true });
23360
+ fs38.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
23136
23361
  console.log(chalk4.green(" \u2714 Agent Teams enabled in ~/.claude/settings.json"));
23137
23362
  } else {
23138
23363
  console.log(chalk4.green(" \u2714 Agent Teams already enabled"));
@@ -23185,7 +23410,7 @@ var initCommand = new Command2("init").description("Guided first-time setup: pro
23185
23410
  chalk4.cyan(" Absolute path to your first project (Enter to skip): ")
23186
23411
  );
23187
23412
  if (projectPath) {
23188
- const projectName = path42.basename(projectPath);
23413
+ const projectName = path43.basename(projectPath);
23189
23414
  console.log(chalk4.bold(`
23190
23415
  Run this to register it:`));
23191
23416
  console.log(chalk4.cyan(` squadrant projects add ${projectName} ${projectPath}
@@ -23213,8 +23438,8 @@ var initCommand = new Command2("init").description("Guided first-time setup: pro
23213
23438
  init_dist();
23214
23439
  init_dist2();
23215
23440
  import { Command as Command3 } from "commander";
23216
- import fs38 from "fs";
23217
- import path43 from "path";
23441
+ import fs39 from "fs";
23442
+ import path44 from "path";
23218
23443
  import chalk5 from "chalk";
23219
23444
  function restartAfterProjectsAdd(opts) {
23220
23445
  const doRestart = opts.doRestart ?? restartDaemonIfRunning;
@@ -23226,22 +23451,22 @@ function restartAfterProjectsAdd(opts) {
23226
23451
  }
23227
23452
  }
23228
23453
  function findPackageRoot2() {
23229
- let dir = path43.dirname(new URL(import.meta.url).pathname);
23454
+ let dir = path44.dirname(new URL(import.meta.url).pathname);
23230
23455
  while (dir !== "/") {
23231
- if (fs38.existsSync(path43.join(dir, "package.json"))) return dir;
23232
- dir = path43.dirname(dir);
23456
+ if (fs39.existsSync(path44.join(dir, "package.json"))) return dir;
23457
+ dir = path44.dirname(dir);
23233
23458
  }
23234
23459
  return process.cwd();
23235
23460
  }
23236
23461
  function copyDirRecursive2(src, dest) {
23237
- fs38.mkdirSync(dest, { recursive: true });
23238
- for (const entry of fs38.readdirSync(src, { withFileTypes: true })) {
23239
- const srcPath = path43.join(src, entry.name);
23240
- const destPath = path43.join(dest, entry.name);
23462
+ fs39.mkdirSync(dest, { recursive: true });
23463
+ for (const entry of fs39.readdirSync(src, { withFileTypes: true })) {
23464
+ const srcPath = path44.join(src, entry.name);
23465
+ const destPath = path44.join(dest, entry.name);
23241
23466
  if (entry.isDirectory()) {
23242
23467
  copyDirRecursive2(srcPath, destPath);
23243
23468
  } else {
23244
- fs38.copyFileSync(srcPath, destPath);
23469
+ fs39.copyFileSync(srcPath, destPath);
23245
23470
  }
23246
23471
  }
23247
23472
  }
@@ -23277,7 +23502,7 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
23277
23502
  process.exit(1);
23278
23503
  }
23279
23504
  const resolvedPath = resolveHome(projectPath);
23280
- if (!fs38.existsSync(path43.join(resolvedPath, ".git"))) {
23505
+ if (!fs39.existsSync(path44.join(resolvedPath, ".git"))) {
23281
23506
  console.log(chalk5.yellow(`
23282
23507
  \u26A0 No .git found at ${resolvedPath}. Make sure this is the project root, not a parent directory.
23283
23508
  `));
@@ -23330,7 +23555,7 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
23330
23555
  \u26A0 Group '${group}' already has '${primary[0]}' as primary. Overriding.`));
23331
23556
  }
23332
23557
  }
23333
- const spokeVault = opts.spoke ? resolveHome(opts.spoke) : path43.join(config.hubVault, "spokes", name);
23558
+ const spokeVault = opts.spoke ? resolveHome(opts.spoke) : path44.join(config.hubVault, "spokes", name);
23334
23559
  const project = {
23335
23560
  path: resolvedPath,
23336
23561
  captainName,
@@ -23345,20 +23570,20 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
23345
23570
  \u2714 Project '${name}' registered`));
23346
23571
  restartAfterProjectsAdd({ noRestart: opts.restart === false });
23347
23572
  const pkgRoot = findPackageRoot2();
23348
- const spokeTemplate = path43.join(pkgRoot, "obsidian", "spoke");
23349
- if (fs38.existsSync(spokeVault)) {
23573
+ const spokeTemplate = path44.join(pkgRoot, "obsidian", "spoke");
23574
+ if (fs39.existsSync(spokeVault)) {
23350
23575
  console.log(chalk5.yellow(` \u26A0 Spoke vault already exists at ${spokeVault}, skipping scaffold`));
23351
- } else if (fs38.existsSync(spokeTemplate)) {
23576
+ } else if (fs39.existsSync(spokeTemplate)) {
23352
23577
  copyDirRecursive2(spokeTemplate, spokeVault);
23353
- const statusPath = path43.join(spokeVault, "status.md");
23354
- if (fs38.existsSync(statusPath)) {
23355
- const content = fs38.readFileSync(statusPath, "utf-8");
23578
+ const statusPath = path44.join(spokeVault, "status.md");
23579
+ if (fs39.existsSync(statusPath)) {
23580
+ const content = fs39.readFileSync(statusPath, "utf-8");
23356
23581
  const updated = content.replace(/^project: unnamed/m, `project: ${name}`);
23357
- fs38.writeFileSync(statusPath, updated);
23582
+ fs39.writeFileSync(statusPath, updated);
23358
23583
  }
23359
23584
  console.log(chalk5.green(` \u2714 Spoke vault scaffolded at ${spokeVault}`));
23360
23585
  } else {
23361
- fs38.mkdirSync(spokeVault, { recursive: true });
23586
+ fs39.mkdirSync(spokeVault, { recursive: true });
23362
23587
  console.log(chalk5.yellow(` \u26A0 Spoke template not found; created empty dir at ${spokeVault}`));
23363
23588
  }
23364
23589
  console.log("");
@@ -23427,10 +23652,10 @@ init_dist3();
23427
23652
  init_dist();
23428
23653
  import { Command as Command5 } from "commander";
23429
23654
  import { execSync as execSync9 } from "child_process";
23430
- import path44 from "path";
23655
+ import path45 from "path";
23431
23656
  import os19 from "os";
23432
23657
  import chalk7 from "chalk";
23433
- var TEMPLATES_DIR2 = path44.join(os19.homedir(), ".config", "squadrant", "templates");
23658
+ var TEMPLATES_DIR2 = path45.join(os19.homedir(), ".config", "squadrant", "templates");
23434
23659
  var TASK_PROMPTS = {
23435
23660
  briefing: "Run your daily briefing using the squadrant:command-ops skill. Read all spoke handoffs, yesterday's logs, current status; produce a concise cross-project briefing; save to {hubVault}/daily-logs/YYYY-MM-DD.md; then exit.",
23436
23661
  "learnings-review": "Run a learnings review using the squadrant:command-ops skill. Scan {spokeVault}/learnings across all projects, identify cross-project patterns, propose captured-skill or fix actions, and exit when done.",
@@ -23463,7 +23688,7 @@ async function runCommandSpawn(input) {
23463
23688
  if (!agent) {
23464
23689
  throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
23465
23690
  }
23466
- const promptFile = path44.join(TEMPLATES_DIR2, `command.${agent.templateSuffix}.md`);
23691
+ const promptFile = path45.join(TEMPLATES_DIR2, `command.${agent.templateSuffix}.md`);
23467
23692
  const cliCommand = agent.buildCommand({
23468
23693
  prompt,
23469
23694
  workdir: process.cwd(),
@@ -23504,7 +23729,7 @@ init_dist3();
23504
23729
  import { Command as Command9 } from "commander";
23505
23730
  import { createConnection as createConnection4 } from "net";
23506
23731
  import { randomUUID as randomUUID5 } from "crypto";
23507
- import { execFileSync as execFileSync11 } from "child_process";
23732
+ import { execFileSync as execFileSync11, spawnSync } from "child_process";
23508
23733
  import { homedir as homedir15 } from "os";
23509
23734
  import { join as join27 } from "path";
23510
23735
  import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync13 } from "fs";
@@ -23986,6 +24211,31 @@ function defaultWriteResult(id, payload) {
23986
24211
  writeFileSync13(file, payload);
23987
24212
  return file;
23988
24213
  }
24214
+ var CITE_GIT_TIMEOUT_MS = 2e3;
24215
+ var citeGit = (args) => {
24216
+ const r = spawnSync("git", args, { encoding: "utf8", timeout: CITE_GIT_TIMEOUT_MS, stdio: ["ignore", "pipe", "ignore"] });
24217
+ if (r.error) throw r.error;
24218
+ return r.status === 0 ? r.stdout : "";
24219
+ };
24220
+ function logSignalCitations(state, o) {
24221
+ const env = o.env ?? process.env;
24222
+ const project = o.project ?? env.SQUADRANT_CREW_PROJECT;
24223
+ if (state !== "done" && state !== "review" || !project) return [];
24224
+ const texts = o.message ? [{ text: o.message, ref: `crew-${state}:${o.taskId ?? env.SQUADRANT_CREW_TASK_ID}` }] : [];
24225
+ try {
24226
+ texts.push(...readBranchOnlyMessages(o.cwd ?? process.cwd(), o.git ?? citeGit).map((c) => ({ text: c.text, ref: `git:${c.sha}`, at: c.at })));
24227
+ } catch (e) {
24228
+ process.stderr.write(`squadrant: rule citation scan of commits skipped: ${e.message}
24229
+ `);
24230
+ }
24231
+ try {
24232
+ return scanCitations(texts, { project, sink: o.audit });
24233
+ } catch (e) {
24234
+ process.stderr.write(`squadrant: rule citation scan skipped: ${e.message}
24235
+ `);
24236
+ return [];
24237
+ }
24238
+ }
23989
24239
  async function runCrewSignal(signal, o, deps) {
23990
24240
  const taskId = o.taskId ?? process.env.SQUADRANT_CREW_TASK_ID;
23991
24241
  const project = o.project ?? process.env.SQUADRANT_CREW_PROJECT;
@@ -24174,13 +24424,7 @@ function addControlPlaneCrewCommands(crew) {
24174
24424
  ...opts.project !== void 0 ? { project: opts.project } : {},
24175
24425
  writeResult: defaultWriteResult
24176
24426
  }, { call: squadrantdCall });
24177
- const citeProject = opts.project ?? process.env.SQUADRANT_CREW_PROJECT;
24178
- if ((state === "done" || state === "review") && opts.message && citeProject) {
24179
- try {
24180
- scanAndLogCitations({ text: opts.message, ref: `crew-${state}:${opts.taskId ?? process.env.SQUADRANT_CREW_TASK_ID}`, project: citeProject });
24181
- } catch {
24182
- }
24183
- }
24427
+ logSignalCitations(state, opts);
24184
24428
  process.exit(0);
24185
24429
  } catch (e) {
24186
24430
  process.stderr.write(`${e.message}
@@ -24269,7 +24513,7 @@ import { connect as netConnect2 } from "net";
24269
24513
  init_dist3();
24270
24514
  init_dist2();
24271
24515
  import { createServer as createServer4, connect as netConnect } from "net";
24272
- import fs39 from "fs";
24516
+ import fs40 from "fs";
24273
24517
  import { join as join28 } from "path";
24274
24518
  import { randomUUID as randomUUID6 } from "crypto";
24275
24519
  import chalk10 from "chalk";
@@ -24277,15 +24521,15 @@ var shared;
24277
24521
  var registryEntryPath = () => join28(CLAUDE_SESSIONS_DIR, `${process.pid}.json`);
24278
24522
  function unregisterSenderIdentity() {
24279
24523
  try {
24280
- fs39.unlinkSync(registryEntryPath());
24524
+ fs40.unlinkSync(registryEntryPath());
24281
24525
  } catch {
24282
24526
  }
24283
24527
  }
24284
24528
  function registerSenderIdentity(socketPath2) {
24285
24529
  try {
24286
24530
  unregisterSenderIdentity();
24287
- fs39.mkdirSync(CLAUDE_SESSIONS_DIR, { recursive: true });
24288
- fs39.writeFileSync(
24531
+ fs40.mkdirSync(CLAUDE_SESSIONS_DIR, { recursive: true });
24532
+ fs40.writeFileSync(
24289
24533
  registryEntryPath(),
24290
24534
  JSON.stringify({
24291
24535
  pid: process.pid,
@@ -24311,7 +24555,7 @@ async function sharedReceiptListener() {
24311
24555
  // must never be the reason we refuse to start.
24312
24556
  unlinkStale: (p) => {
24313
24557
  try {
24314
- fs39.unlinkSync(p);
24558
+ fs40.unlinkSync(p);
24315
24559
  } catch {
24316
24560
  }
24317
24561
  },
@@ -24336,7 +24580,7 @@ async function buildCaptainChannel() {
24336
24580
  // in /tmp/x registers cwd "/private/tmp/x" on macOS, so cwd matching silently
24337
24581
  // missed and every captain ping reported accepted-unconfirmed.
24338
24582
  statusFor: (taskId) => readClaudeStatusBySocketPath(captainSocketPath(taskId)),
24339
- wire: (p, e) => writeLine(p, e, { connect: (path59) => netConnect(path59) }),
24583
+ wire: (p, e) => writeLine(p, e, { connect: (path60) => netConnect(path60) }),
24340
24584
  receipts,
24341
24585
  newMsgId: () => randomUUID6(),
24342
24586
  sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
@@ -24365,7 +24609,7 @@ async function sendClaudeFirstTurnViaPeer(runtime, o, getTaskRecord) {
24365
24609
  socketPathFor: () => o.messagingSocketPath,
24366
24610
  sessionIdFor: () => readClaudeStatusBySocketPath(o.messagingSocketPath)?.sessionId,
24367
24611
  statusFor: () => readClaudeStatusBySocketPath(o.messagingSocketPath),
24368
- wire: (p, e) => writeLine(p, e, { connect: (path59) => netConnect2(path59) }),
24612
+ wire: (p, e) => writeLine(p, e, { connect: (path60) => netConnect2(path60) }),
24369
24613
  receipts,
24370
24614
  newMsgId: () => randomUUID7(),
24371
24615
  sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
@@ -24509,7 +24753,7 @@ async function runCrewSend2(project, name, message, opts) {
24509
24753
  socketPathFor: (taskId) => tasks.find((t) => t.id === taskId)?.messagingSocketPath,
24510
24754
  sessionIdFor: (taskId) => tasks.find((t) => t.id === taskId)?.sessionId,
24511
24755
  statusFor: (taskId) => readClaudeStatus(tasks.find((t) => t.id === taskId)),
24512
- wire: (p, e) => writeLine(p, e, { connect: (path59) => netConnect3(path59) }),
24756
+ wire: (p, e) => writeLine(p, e, { connect: (path60) => netConnect3(path60) }),
24513
24757
  receipts,
24514
24758
  newMsgId: () => randomUUID8(),
24515
24759
  sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
@@ -24903,11 +25147,11 @@ init_dist4();
24903
25147
  init_dist();
24904
25148
  init_dist2();
24905
25149
  import { Command as Command12 } from "commander";
24906
- import fs40 from "fs";
24907
- import path45 from "path";
25150
+ import fs41 from "fs";
25151
+ import path46 from "path";
24908
25152
  import os20 from "os";
24909
25153
  import chalk13 from "chalk";
24910
- var TEMPLATES_DIR3 = path45.join(os20.homedir(), ".config", "squadrant", "templates");
25154
+ var TEMPLATES_DIR3 = path46.join(os20.homedir(), ".config", "squadrant", "templates");
24911
25155
  async function runSideSpawn2(input) {
24912
25156
  const config = loadConfig();
24913
25157
  const proj = config.projects[input.project];
@@ -24932,7 +25176,7 @@ async function runSideSpawn2(input) {
24932
25176
  }
24933
25177
  const sideModel = sideRole?.model;
24934
25178
  const sideThinking = sideRole?.thinking;
24935
- const promptFile = path45.join(
25179
+ const promptFile = path46.join(
24936
25180
  TEMPLATES_DIR3,
24937
25181
  `side.${input.role}.${agent.templateSuffix}.md`
24938
25182
  );
@@ -24940,7 +25184,7 @@ async function runSideSpawn2(input) {
24940
25184
  prompt: input.topic,
24941
25185
  workdir: spawnCwd,
24942
25186
  role: "side",
24943
- promptFile: fs40.existsSync(promptFile) ? promptFile : void 0,
25187
+ promptFile: fs41.existsSync(promptFile) ? promptFile : void 0,
24944
25188
  interactive: true,
24945
25189
  permissionMode: config.defaults.permissions?.crew ?? "auto",
24946
25190
  ...sideModel ? { model: sideModel } : {},
@@ -25203,8 +25447,8 @@ function renderDashboard(rows, opts) {
25203
25447
 
25204
25448
  // packages/web/dist/sync-hub.js
25205
25449
  init_dist();
25206
- import fs41 from "fs";
25207
- import path46 from "path";
25450
+ import fs42 from "fs";
25451
+ import path47 from "path";
25208
25452
  function buildMirrorMarkdown(s) {
25209
25453
  const fenced = "```";
25210
25454
  return [
@@ -25230,15 +25474,15 @@ function buildMirrorMarkdown(s) {
25230
25474
  function syncHub(deps) {
25231
25475
  if (!deps.config.hubVault)
25232
25476
  return [];
25233
- const writeFile6 = deps.writeFile ?? ((p, c) => fs41.writeFileSync(p, c));
25234
- const mkdir6 = deps.mkdir ?? ((p) => fs41.mkdirSync(p, { recursive: true }));
25235
- const projectsDir = path46.join(resolveHome(deps.config.hubVault), "projects");
25477
+ const writeFile6 = deps.writeFile ?? ((p, c) => fs42.writeFileSync(p, c));
25478
+ const mkdir6 = deps.mkdir ?? ((p) => fs42.mkdirSync(p, { recursive: true }));
25479
+ const projectsDir = path47.join(resolveHome(deps.config.hubVault), "projects");
25236
25480
  mkdir6(projectsDir);
25237
25481
  const out = [];
25238
25482
  for (const s of deps.statuses) {
25239
25483
  if (s.state === "unknown")
25240
25484
  continue;
25241
- const hubPath = path46.join(projectsDir, `${s.project}.md`);
25485
+ const hubPath = path47.join(projectsDir, `${s.project}.md`);
25242
25486
  try {
25243
25487
  writeFile6(hubPath, buildMirrorMarkdown(s));
25244
25488
  out.push({ project: s.project, hubPath });
@@ -26244,18 +26488,18 @@ function renderHtml(snap, opts = {}) {
26244
26488
  import { closeSync as closeSync3, openSync as openSync3, readSync as readSync2, statSync as statSync4 } from "fs";
26245
26489
  var LOG_TAIL_BYTES = 256 * 1024;
26246
26490
  var LOG_RING_SIZE = 1e3;
26247
- function defaultSize(path59) {
26491
+ function defaultSize(path60) {
26248
26492
  try {
26249
- return statSync4(path59).size;
26493
+ return statSync4(path60).size;
26250
26494
  } catch {
26251
26495
  return 0;
26252
26496
  }
26253
26497
  }
26254
- function defaultRead(path59, start, len) {
26498
+ function defaultRead(path60, start, len) {
26255
26499
  if (len <= 0)
26256
26500
  return "";
26257
26501
  try {
26258
- const fd = openSync3(path59, "r");
26502
+ const fd = openSync3(path60, "r");
26259
26503
  try {
26260
26504
  const buf = Buffer.alloc(len);
26261
26505
  const n = readSync2(fd, buf, 0, len, start);
@@ -26275,8 +26519,8 @@ var LogTailer = class {
26275
26519
  ring = [];
26276
26520
  size;
26277
26521
  read;
26278
- constructor(path59, ringSize = LOG_RING_SIZE, deps = {}) {
26279
- this.path = path59;
26522
+ constructor(path60, ringSize = LOG_RING_SIZE, deps = {}) {
26523
+ this.path = path60;
26280
26524
  this.ringSize = ringSize;
26281
26525
  this.size = deps.size ?? defaultSize;
26282
26526
  this.read = deps.read ?? defaultRead;
@@ -26552,8 +26796,8 @@ init_dist4();
26552
26796
  init_dist2();
26553
26797
  import { Command as Command14 } from "commander";
26554
26798
  import { execSync as execSync11, execFileSync as execFileSync13 } from "child_process";
26555
- import fs42 from "fs";
26556
- import path47 from "path";
26799
+ import fs43 from "fs";
26800
+ import path48 from "path";
26557
26801
  import os21 from "os";
26558
26802
  import chalk16 from "chalk";
26559
26803
 
@@ -26646,8 +26890,8 @@ function resolveLaunchBackend(o) {
26646
26890
  // packages/cli/src/commands/launch.ts
26647
26891
  init_dist2();
26648
26892
  var CMUX_APP = "/Applications/cmux.app";
26649
- var TEMPLATES_DIR4 = path47.join(os21.homedir(), ".config", "squadrant", "templates");
26650
- var SESSIONS_PATH2 = path47.join(os21.homedir(), ".config", "squadrant", "sessions.json");
26893
+ var TEMPLATES_DIR4 = path48.join(os21.homedir(), ".config", "squadrant", "templates");
26894
+ var SESSIONS_PATH2 = path48.join(os21.homedir(), ".config", "squadrant", "sessions.json");
26651
26895
  function shouldWireCaptainChannel(agentName, config) {
26652
26896
  return agentName === "claude" && resolveCaptainChannelMode(config.defaults) !== "off";
26653
26897
  }
@@ -26744,7 +26988,7 @@ var launchCommand = new Command14("launch").description(
26744
26988
  initialPrompt = "Run your startup checklist: use the squadrant:command-ops skill, complete your daily briefing, then report ready.";
26745
26989
  }
26746
26990
  const runtime = projectName ? runtimes.forProject(projectName, config) : runtimes.global(config);
26747
- const stateRoot = path47.join(os21.homedir(), ".config", "squadrant", "state");
26991
+ const stateRoot = path48.join(os21.homedir(), ".config", "squadrant", "state");
26748
26992
  const isOpencodeCaptain = role === "captain" && agentName === "opencode" && !!projectName;
26749
26993
  if (isOpencodeCaptain && !isOpencodeCaptainDir(cwd)) {
26750
26994
  console.error(chalk16.red(`
@@ -26897,12 +27141,12 @@ var launchCommand = new Command14("launch").description(
26897
27141
  }
26898
27142
  if (opts.all) {
26899
27143
  const hubPath = resolveHome(config.hubVault);
26900
- fs42.mkdirSync(hubPath, { recursive: true });
27144
+ fs43.mkdirSync(hubPath, { recursive: true });
26901
27145
  console.log(chalk16.bold("\nLaunching all captain workspaces\n"));
26902
27146
  for (const [name, proj] of Object.entries(config.projects)) {
26903
27147
  const projPath = resolveHome(proj.path);
26904
27148
  const spokePath = resolveHome(proj.spokeVault);
26905
- if (!fs42.existsSync(spokePath)) {
27149
+ if (!fs43.existsSync(spokePath)) {
26906
27150
  const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
26907
27151
  await ensureSpokeLayout(spokeDriver);
26908
27152
  console.log(chalk16.cyan(` \u2714 Created spoke vault at ${spokePath}`));
@@ -26940,7 +27184,7 @@ Launching ${selected.length} captain workspace(s) in parallel
26940
27184
  const proj = config.projects[name];
26941
27185
  const projPath = resolveHome(proj.path);
26942
27186
  const spokePath = resolveHome(proj.spokeVault);
26943
- if (!fs42.existsSync(spokePath)) {
27187
+ if (!fs43.existsSync(spokePath)) {
26944
27188
  const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
26945
27189
  await ensureSpokeLayout(spokeDriver);
26946
27190
  console.log(chalk16.cyan(` \u2714 Created spoke vault at ${spokePath}`));
@@ -26964,7 +27208,7 @@ Launching ${selected.length} captain workspace(s) in parallel
26964
27208
  const proj = config.projects[project];
26965
27209
  const projPath = resolveHome(proj.path);
26966
27210
  const spokePath = resolveHome(proj.spokeVault);
26967
- if (!fs42.existsSync(spokePath)) {
27211
+ if (!fs43.existsSync(spokePath)) {
26968
27212
  const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(project, config);
26969
27213
  await ensureSpokeLayout(spokeDriver);
26970
27214
  console.log(chalk16.cyan(` \u2714 Created spoke vault at ${spokePath}`));
@@ -27100,24 +27344,24 @@ Shutting down captain workspace for '${project}'...
27100
27344
  // packages/cli/src/commands/feedback.ts
27101
27345
  init_dist();
27102
27346
  import { Command as Command16 } from "commander";
27103
- import fs43 from "fs";
27347
+ import fs44 from "fs";
27104
27348
  import os22 from "os";
27105
- import path48 from "path";
27349
+ import path49 from "path";
27106
27350
  import { fileURLToPath as fileURLToPath3 } from "url";
27107
27351
  import { execSync as execSync12 } from "child_process";
27108
27352
  import chalk18 from "chalk";
27109
27353
  var REPO_URL = "https://github.com/tu11aa/squadrant";
27110
27354
  function readPkgVersion() {
27111
27355
  try {
27112
- const pkgPath = path48.join(path48.dirname(fileURLToPath3(import.meta.url)), "..", "package.json");
27113
- return JSON.parse(fs43.readFileSync(pkgPath, "utf-8")).version ?? "unknown";
27356
+ const pkgPath = path49.join(path49.dirname(fileURLToPath3(import.meta.url)), "..", "package.json");
27357
+ return JSON.parse(fs44.readFileSync(pkgPath, "utf-8")).version ?? "unknown";
27114
27358
  } catch {
27115
27359
  return "unknown";
27116
27360
  }
27117
27361
  }
27118
27362
  function readMetrics(metricsPath) {
27119
27363
  try {
27120
- return JSON.parse(fs43.readFileSync(metricsPath, "utf-8"));
27364
+ return JSON.parse(fs44.readFileSync(metricsPath, "utf-8"));
27121
27365
  } catch {
27122
27366
  return {};
27123
27367
  }
@@ -27155,7 +27399,7 @@ function buildIssueUrl(metrics, squadrantVersion) {
27155
27399
  }
27156
27400
  var feedbackCommand = new Command16("feedback").description("Open a pre-filled GitHub issue for feedback or bug reports").action(() => {
27157
27401
  const config = loadConfig();
27158
- const metricsPath = config.metrics?.path || path48.join(os22.homedir(), ".config", "squadrant", "metrics.json");
27402
+ const metricsPath = config.metrics?.path || path49.join(os22.homedir(), ".config", "squadrant", "metrics.json");
27159
27403
  const metrics = readMetrics(metricsPath);
27160
27404
  const version = readStamp(config) ?? readPkgVersion();
27161
27405
  const issueUrl = buildIssueUrl(metrics, version);
@@ -27455,9 +27699,9 @@ workspaceCommand.command("read").description("Print the contents of a scope-rela
27455
27699
  const config = loadConfig();
27456
27700
  const registry = buildRegistry2();
27457
27701
  try {
27458
- const { projectTarget, path: path59 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
27702
+ const { projectTarget, path: path60 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
27459
27703
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
27460
- const content = await driver.read(path59);
27704
+ const content = await driver.read(path60);
27461
27705
  process.stdout.write(content);
27462
27706
  } catch (err) {
27463
27707
  console.error(chalk21.red(err.message));
@@ -27469,26 +27713,26 @@ workspaceCommand.command("write").description("Write content to a scope-relative
27469
27713
  const registry = buildRegistry2();
27470
27714
  try {
27471
27715
  let projectTarget;
27472
- let path59;
27716
+ let path60;
27473
27717
  let rawContent;
27474
27718
  if (opts.hub) {
27475
27719
  if (arg3 !== void 0) {
27476
27720
  throw new Error("With --hub, pass only the path and content");
27477
27721
  }
27478
27722
  projectTarget = void 0;
27479
- path59 = arg1;
27723
+ path60 = arg1;
27480
27724
  rawContent = arg2;
27481
27725
  } else {
27482
27726
  if (arg3 === void 0) {
27483
27727
  throw new Error("Missing content \u2014 usage: <project> <path> <content>");
27484
27728
  }
27485
27729
  projectTarget = arg1;
27486
- path59 = arg2;
27730
+ path60 = arg2;
27487
27731
  rawContent = arg3;
27488
27732
  }
27489
27733
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
27490
27734
  const payload = rawContent === "-" ? await readStdin() : rawContent;
27491
- await driver.write(path59, payload);
27735
+ await driver.write(path60, payload);
27492
27736
  } catch (err) {
27493
27737
  console.error(chalk21.red(err.message));
27494
27738
  process.exit(1);
@@ -27498,9 +27742,9 @@ workspaceCommand.command("list").description("List entries in a scope-relative d
27498
27742
  const config = loadConfig();
27499
27743
  const registry = buildRegistry2();
27500
27744
  try {
27501
- const { projectTarget, path: path59 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
27745
+ const { projectTarget, path: path60 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
27502
27746
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
27503
- const entries = await driver.list(path59);
27747
+ const entries = await driver.list(path60);
27504
27748
  for (const entry of entries) console.log(entry);
27505
27749
  } catch (err) {
27506
27750
  console.error(chalk21.red(err.message));
@@ -27511,9 +27755,9 @@ workspaceCommand.command("exists").description("Exit 0 if path exists, 1 if not"
27511
27755
  const config = loadConfig();
27512
27756
  const registry = buildRegistry2();
27513
27757
  try {
27514
- const { projectTarget, path: path59 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
27758
+ const { projectTarget, path: path60 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
27515
27759
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
27516
- const ok2 = await driver.exists(path59);
27760
+ const ok2 = await driver.exists(path60);
27517
27761
  process.exit(ok2 ? 0 : 1);
27518
27762
  } catch (err) {
27519
27763
  console.error(chalk21.red(err.message));
@@ -27524,9 +27768,9 @@ workspaceCommand.command("mkdir").description("Recursively create a scope-relati
27524
27768
  const config = loadConfig();
27525
27769
  const registry = buildRegistry2();
27526
27770
  try {
27527
- const { projectTarget, path: path59 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
27771
+ const { projectTarget, path: path60 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
27528
27772
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
27529
- await driver.mkdir(path59);
27773
+ await driver.mkdir(path60);
27530
27774
  } catch (err) {
27531
27775
  console.error(chalk21.red(err.message));
27532
27776
  process.exit(1);
@@ -27566,8 +27810,8 @@ init_dist4();
27566
27810
  init_dist();
27567
27811
  import { Command as Command21 } from "commander";
27568
27812
  import chalk23 from "chalk";
27569
- import fs44 from "fs";
27570
- import path49 from "path";
27813
+ import fs45 from "fs";
27814
+ import path50 from "path";
27571
27815
  import { fileURLToPath as fileURLToPath4 } from "url";
27572
27816
  function parseScope(v2) {
27573
27817
  if (v2 !== "user" && v2 !== "project") {
@@ -27576,10 +27820,10 @@ function parseScope(v2) {
27576
27820
  return v2;
27577
27821
  }
27578
27822
  function findPackageRoot3() {
27579
- let dir = path49.dirname(fileURLToPath4(import.meta.url));
27823
+ let dir = path50.dirname(fileURLToPath4(import.meta.url));
27580
27824
  while (dir !== "/" && dir !== "") {
27581
- if (fs44.existsSync(path49.join(dir, "package.json"))) return dir;
27582
- dir = path49.dirname(dir);
27825
+ if (fs45.existsSync(path50.join(dir, "package.json"))) return dir;
27826
+ dir = path50.dirname(dir);
27583
27827
  }
27584
27828
  return process.cwd();
27585
27829
  }
@@ -27794,12 +28038,12 @@ init_dist();
27794
28038
  init_dist();
27795
28039
  init_dist2();
27796
28040
  import { Command as Command23 } from "commander";
27797
- import fs45 from "fs";
28041
+ import fs46 from "fs";
27798
28042
  import { fileURLToPath as fileURLToPath5 } from "url";
27799
28043
  import { dirname as dirname8, join as join31 } from "path";
27800
28044
  import chalk24 from "chalk";
27801
28045
  function runConfigCheck(opts) {
27802
- const raw = JSON.parse(fs45.readFileSync(opts.configPath, "utf-8"));
28046
+ const raw = JSON.parse(fs46.readFileSync(opts.configPath, "utf-8"));
27803
28047
  const def = getDefaultConfig();
27804
28048
  const items = detectDrift(raw, def);
27805
28049
  let working = raw;
@@ -27887,7 +28131,7 @@ function printItems(items) {
27887
28131
  var configCommand = new Command23("config").description("Inspect and reconcile squadrant config");
27888
28132
  configCommand.command("check").description("Detect config drift vs the current default schema").option("--fix", "Apply the safe tier (add missing, remove deprecated)", false).option("--accept", "Stamp the current version without changing config (dismiss advisories)", false).option("--json", "Output drift items as JSON", false).action((opts) => {
27889
28133
  const pkgVersion = readPkgVersion2();
27890
- if (!fs45.existsSync(DEFAULT_CONFIG_PATH)) {
28134
+ if (!fs46.existsSync(DEFAULT_CONFIG_PATH)) {
27891
28135
  console.log(chalk24.yellow("No config found \u2014 run `squadrant init` first."));
27892
28136
  return;
27893
28137
  }
@@ -27933,7 +28177,7 @@ configCommand.command("set").description("Write a config value by dotted key (e.
27933
28177
  });
27934
28178
  function readPkgVersion2() {
27935
28179
  const pkgPath = join31(dirname8(fileURLToPath5(import.meta.url)), "..", "package.json");
27936
- return JSON.parse(fs45.readFileSync(pkgPath, "utf-8")).version;
28180
+ return JSON.parse(fs46.readFileSync(pkgPath, "utf-8")).version;
27937
28181
  }
27938
28182
 
27939
28183
  // packages/cli/src/commands/heal.ts
@@ -28378,8 +28622,8 @@ var cmuxCommand = new Command28("cmux").description("cmux integration helpers").
28378
28622
  // packages/cli/src/commands/effort.ts
28379
28623
  init_dist();
28380
28624
  init_dist2();
28381
- import fs46 from "fs";
28382
- import path50 from "path";
28625
+ import fs47 from "fs";
28626
+ import path51 from "path";
28383
28627
  import { Command as Command29 } from "commander";
28384
28628
  import chalk30 from "chalk";
28385
28629
  var VALID_EFFORTS = ["max", "balance", "low"];
@@ -28422,9 +28666,9 @@ function effortScopeLabel(projectName) {
28422
28666
  }
28423
28667
  function canonical(p) {
28424
28668
  try {
28425
- return fs46.realpathSync(p);
28669
+ return fs47.realpathSync(p);
28426
28670
  } catch {
28427
- return path50.resolve(p);
28671
+ return path51.resolve(p);
28428
28672
  }
28429
28673
  }
28430
28674
  async function notifyCaptainsOfEffort(effort, config, driver, cwd = process.cwd(), append, scopeProject, projectConfigRoot) {
@@ -28474,7 +28718,7 @@ var effortCommand = new Command29("effort").description("Get or set the crew tok
28474
28718
  const config = loadConfig();
28475
28719
  const registry = new RuntimeRegistry2({ cmux: createCmuxDriver2() });
28476
28720
  const driver = registry.global(config);
28477
- const stateRoot = path50.join(path50.dirname(DEFAULT_CONFIG_PATH), "state");
28721
+ const stateRoot = path51.join(path51.dirname(DEFAULT_CONFIG_PATH), "state");
28478
28722
  const append = (project, text) => appendCaptainMessage({ stateRoot, project, text, source: "daemon" });
28479
28723
  await notifyCaptainsOfEffort(effort, config, driver, process.cwd(), append, options.project);
28480
28724
  } catch {
@@ -28484,13 +28728,13 @@ var effortCommand = new Command29("effort").description("Get or set the crew tok
28484
28728
 
28485
28729
  // packages/cli/src/commands/tokens.ts
28486
28730
  init_dist();
28487
- import fs47 from "fs";
28488
- import path51 from "path";
28731
+ import fs48 from "fs";
28732
+ import path52 from "path";
28489
28733
  import os23 from "os";
28490
28734
  import readline3 from "readline";
28491
28735
  import { Command as Command30 } from "commander";
28492
28736
  import chalk31 from "chalk";
28493
- var CLAUDE_PROJECTS_DIR = path51.join(os23.homedir(), ".claude", "projects");
28737
+ var CLAUDE_PROJECTS_DIR = path52.join(os23.homedir(), ".claude", "projects");
28494
28738
  function parseTranscriptLine(rawLine) {
28495
28739
  const line2 = rawLine.trim();
28496
28740
  if (!line2) return { timestamp: null, usage: null };
@@ -28540,7 +28784,7 @@ function foldTranscriptLine(agg, rawLine, state) {
28540
28784
  async function aggregateTranscriptFile(filePath) {
28541
28785
  const agg = emptySessionAggregate();
28542
28786
  const state = { lastCacheRead: null };
28543
- const rl = readline3.createInterface({ input: fs47.createReadStream(filePath), crlfDelay: Infinity });
28787
+ const rl = readline3.createInterface({ input: fs48.createReadStream(filePath), crlfDelay: Infinity });
28544
28788
  for await (const line2 of rl) {
28545
28789
  foldTranscriptLine(agg, line2, state);
28546
28790
  }
@@ -28608,22 +28852,22 @@ function buildRoleReport(role, sessions) {
28608
28852
  }
28609
28853
  async function readdirSafe(dir) {
28610
28854
  try {
28611
- return await fs47.promises.readdir(dir);
28855
+ return await fs48.promises.readdir(dir);
28612
28856
  } catch {
28613
28857
  return [];
28614
28858
  }
28615
28859
  }
28616
28860
  async function listJsonlFiles(dir) {
28617
28861
  const entries = await readdirSafe(dir);
28618
- return entries.filter((e) => e.endsWith(".jsonl")).map((e) => path51.join(dir, e));
28862
+ return entries.filter((e) => e.endsWith(".jsonl")).map((e) => path52.join(dir, e));
28619
28863
  }
28620
28864
  async function findTranscriptDirs(claudeProjectsDir, captainSlug) {
28621
28865
  const entries = await readdirSafe(claudeProjectsDir);
28622
28866
  const captainDirs = [];
28623
28867
  const crewDirs = [];
28624
28868
  for (const entry of entries) {
28625
- if (entry === captainSlug) captainDirs.push(path51.join(claudeProjectsDir, entry));
28626
- else if (isCrewDirName(entry, captainSlug)) crewDirs.push(path51.join(claudeProjectsDir, entry));
28869
+ if (entry === captainSlug) captainDirs.push(path52.join(claudeProjectsDir, entry));
28870
+ else if (isCrewDirName(entry, captainSlug)) crewDirs.push(path52.join(claudeProjectsDir, entry));
28627
28871
  }
28628
28872
  return { captainDirs, crewDirs };
28629
28873
  }
@@ -29110,8 +29354,8 @@ import { join as join36 } from "path";
29110
29354
  import { homedir as homedir20 } from "os";
29111
29355
 
29112
29356
  // packages/cli/src/lib/captain-session-registry.ts
29113
- import fs48 from "fs";
29114
- import path52 from "path";
29357
+ import fs49 from "fs";
29358
+ import path53 from "path";
29115
29359
 
29116
29360
  // packages/cli/src/lib/handoff-facts.ts
29117
29361
  var STALE_FETCH_WARNING_MS = 24 * 60 * 60 * 1e3;
@@ -29162,15 +29406,15 @@ function assembleHandoffFacts(live, claudeMem, gapSessions, checkpoint, now, ext
29162
29406
  // packages/cli/src/lib/captain-session-registry.ts
29163
29407
  var CAPTAIN_SESSION_REGISTRY_FILE = "captain-sessions.jsonl";
29164
29408
  function appendCaptainSession(spokeVault, record) {
29165
- fs48.mkdirSync(spokeVault, { recursive: true });
29166
- const file = path52.join(spokeVault, CAPTAIN_SESSION_REGISTRY_FILE);
29167
- fs48.appendFileSync(file, JSON.stringify(record) + "\n");
29409
+ fs49.mkdirSync(spokeVault, { recursive: true });
29410
+ const file = path53.join(spokeVault, CAPTAIN_SESSION_REGISTRY_FILE);
29411
+ fs49.appendFileSync(file, JSON.stringify(record) + "\n");
29168
29412
  }
29169
29413
  function readCaptainSessionRegistry(spokeVault) {
29170
- const file = path52.join(spokeVault, CAPTAIN_SESSION_REGISTRY_FILE);
29171
- if (!fs48.existsSync(file)) return [];
29414
+ const file = path53.join(spokeVault, CAPTAIN_SESSION_REGISTRY_FILE);
29415
+ if (!fs49.existsSync(file)) return [];
29172
29416
  const records = [];
29173
- for (const line2 of fs48.readFileSync(file, "utf-8").split("\n")) {
29417
+ for (const line2 of fs49.readFileSync(file, "utf-8").split("\n")) {
29174
29418
  if (!line2.trim()) continue;
29175
29419
  try {
29176
29420
  records.push(JSON.parse(line2));
@@ -29620,13 +29864,13 @@ function hooksCommand() {
29620
29864
  // packages/cli/src/commands/work.ts
29621
29865
  init_dist();
29622
29866
  init_dist2();
29623
- import path53 from "path";
29867
+ import path54 from "path";
29624
29868
  import { Command as Command34 } from "commander";
29625
29869
  import chalk34 from "chalk";
29626
29870
  function detectCurrentProject(config, cwd = process.cwd()) {
29627
29871
  for (const [name, proj] of Object.entries(config.projects)) {
29628
29872
  const projPath = resolveHome(proj.path);
29629
- if (cwd === projPath || cwd.startsWith(projPath + path53.sep)) return name;
29873
+ if (cwd === projPath || cwd.startsWith(projPath + path54.sep)) return name;
29630
29874
  }
29631
29875
  return void 0;
29632
29876
  }
@@ -29758,14 +30002,14 @@ var workCommand = new Command34("work").description("Track your own in-flight wo
29758
30002
  // packages/cli/src/commands/handoff.ts
29759
30003
  init_dist();
29760
30004
  import { Command as Command35 } from "commander";
29761
- import path56 from "path";
30005
+ import path57 from "path";
29762
30006
  import os24 from "os";
29763
30007
 
29764
30008
  // packages/cli/src/lib/handoff-live-repo.ts
29765
30009
  init_dist();
29766
30010
  import { execFileSync as execFileSync15 } from "child_process";
29767
- import fs49 from "fs";
29768
- import path54 from "path";
30011
+ import fs50 from "fs";
30012
+ import path55 from "path";
29769
30013
 
29770
30014
  // packages/cli/src/lib/handoff-branch-state.ts
29771
30015
  function tryRun(runner, cmd, args, cwd) {
@@ -29904,7 +30148,7 @@ function localAheadOfBase(runner, projectPath, base) {
29904
30148
  }
29905
30149
  function readFetchAgeMs(projectPath, now) {
29906
30150
  try {
29907
- const stat2 = fs49.statSync(path54.join(projectPath, ".git", "FETCH_HEAD"));
30151
+ const stat2 = fs50.statSync(path55.join(projectPath, ".git", "FETCH_HEAD"));
29908
30152
  return Math.max(0, now - stat2.mtime.getTime());
29909
30153
  } catch {
29910
30154
  return null;
@@ -29975,7 +30219,7 @@ function gatherLiveRepoState(projectPath, fallbackBaseBranch, tasks, runner = de
29975
30219
 
29976
30220
  // packages/cli/src/lib/handoff-claude-mem.ts
29977
30221
  import { createRequire } from "module";
29978
- import fs50 from "fs";
30222
+ import fs51 from "fs";
29979
30223
  var { DatabaseSync } = createRequire(import.meta.url)("node:sqlite");
29980
30224
  var CLAUDE_MEM_RECENCY_LIMIT = 20;
29981
30225
  function decisionText(row) {
@@ -29989,7 +30233,7 @@ function decisionText(row) {
29989
30233
  return row.narrative ?? "";
29990
30234
  }
29991
30235
  function queryClaudeMem(dbPath, project) {
29992
- if (!fs50.existsSync(dbPath)) return null;
30236
+ if (!fs51.existsSync(dbPath)) return null;
29993
30237
  let db;
29994
30238
  try {
29995
30239
  db = new DatabaseSync(dbPath, { readOnly: true });
@@ -30032,7 +30276,7 @@ function queryClaudeMem(dbPath, project) {
30032
30276
  }
30033
30277
 
30034
30278
  // packages/cli/src/lib/handoff-transcript.ts
30035
- import fs51 from "fs";
30279
+ import fs52 from "fs";
30036
30280
  var TRANSCRIPT_BYTE_CAP = 2e5;
30037
30281
  var DIGEST_BYTE_CAP = 8 * 1024;
30038
30282
  var PROMPT_TRUNCATE_CHARS = 300;
@@ -30141,27 +30385,27 @@ function extractMessages(tailText) {
30141
30385
  return { lastUserMessage, lastAssistantText, digest };
30142
30386
  }
30143
30387
  function extractTranscriptTail(transcriptPath, byteCap = TRANSCRIPT_BYTE_CAP) {
30144
- if (!fs51.existsSync(transcriptPath)) return null;
30145
- const content = fs51.readFileSync(transcriptPath, "utf-8");
30388
+ if (!fs52.existsSync(transcriptPath)) return null;
30389
+ const content = fs52.readFileSync(transcriptPath, "utf-8");
30146
30390
  const { lastUserMessage, lastAssistantText, digest } = extractMessages(tailOf(content, byteCap));
30147
- const mtimeIso = fs51.statSync(transcriptPath).mtime.toISOString();
30391
+ const mtimeIso = fs52.statSync(transcriptPath).mtime.toISOString();
30148
30392
  return { path: transcriptPath, mtimeIso, lastUserMessage, lastAssistantText, digest };
30149
30393
  }
30150
30394
 
30151
30395
  // packages/cli/src/lib/handoff-archive.ts
30152
- import fs52 from "fs";
30153
- import path55 from "path";
30396
+ import fs53 from "fs";
30397
+ import path56 from "path";
30154
30398
  function readNewestArchivedHandoff(spokeVault, now) {
30155
- const dir = path55.join(spokeVault, "handoffs");
30156
- if (!fs52.existsSync(dir)) return null;
30157
- const candidates = fs52.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".json")).map((e) => {
30158
- const full = path55.join(dir, e.name);
30159
- return { name: e.name, full, mtime: fs52.statSync(full).mtime };
30399
+ const dir = path56.join(spokeVault, "handoffs");
30400
+ if (!fs53.existsSync(dir)) return null;
30401
+ const candidates = fs53.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".json")).map((e) => {
30402
+ const full = path56.join(dir, e.name);
30403
+ return { name: e.name, full, mtime: fs53.statSync(full).mtime };
30160
30404
  }).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
30161
30405
  for (const candidate of candidates) {
30162
30406
  let content;
30163
30407
  try {
30164
- content = JSON.parse(fs52.readFileSync(candidate.full, "utf-8"));
30408
+ content = JSON.parse(fs53.readFileSync(candidate.full, "utf-8"));
30165
30409
  } catch {
30166
30410
  continue;
30167
30411
  }
@@ -30171,7 +30415,7 @@ function readNewestArchivedHandoff(spokeVault, now) {
30171
30415
  }
30172
30416
 
30173
30417
  // packages/cli/src/commands/handoff.ts
30174
- var CLAUDE_MEM_DB_PATH = path56.join(os24.homedir(), ".claude-mem", "claude-mem.db");
30418
+ var CLAUDE_MEM_DB_PATH = path57.join(os24.homedir(), ".claude-mem", "claude-mem.db");
30175
30419
  async function defaultFetchTasks(project) {
30176
30420
  return await squadrantdCall({ kind: "list", project });
30177
30421
  }
@@ -30239,9 +30483,9 @@ function filterSessions(rows, f) {
30239
30483
  if (f.agent && r.agent !== f.agent) return false;
30240
30484
  if (f.liveOnly && r.status === "stale") return false;
30241
30485
  if (f.project) {
30242
- const path59 = f.projects?.[f.project];
30486
+ const path60 = f.projects?.[f.project];
30243
30487
  const byRecord = r.project === f.project;
30244
- const byCwd = !!path59 && !!r.cwd && (r.cwd === normalized(path59) || r.cwd.startsWith(normalized(path59) + "/"));
30488
+ const byCwd = !!path60 && !!r.cwd && (r.cwd === normalized(path60) || r.cwd.startsWith(normalized(path60) + "/"));
30245
30489
  if (!byRecord && !byCwd) return false;
30246
30490
  }
30247
30491
  return true;
@@ -30332,7 +30576,7 @@ init_dist();
30332
30576
  init_dist2();
30333
30577
  init_dist3();
30334
30578
  import os25 from "os";
30335
- import path57 from "path";
30579
+ import path58 from "path";
30336
30580
  import { Command as Command37 } from "commander";
30337
30581
  import chalk36 from "chalk";
30338
30582
 
@@ -30450,7 +30694,7 @@ function buildWhoamiDeps(opts = {}) {
30450
30694
  const projects = Object.fromEntries(
30451
30695
  Object.entries(config.projects).map(([n, p]) => [n, resolveHome(p.path)])
30452
30696
  );
30453
- const root = stateRoot ?? path57.join(os25.homedir(), ".config", "squadrant", "state");
30697
+ const root = stateRoot ?? path58.join(os25.homedir(), ".config", "squadrant", "state");
30454
30698
  const store = createStore(root);
30455
30699
  const merged = {
30456
30700
  env: process.env,
@@ -30502,11 +30746,11 @@ var whoamiCommand = new Command37("whoami").description("Show the agent session
30502
30746
  // packages/cli/src/commands/knowledge.ts
30503
30747
  init_dist();
30504
30748
  init_dist2();
30505
- import fs53 from "fs";
30749
+ import fs54 from "fs";
30506
30750
  import os26 from "os";
30507
- import path58 from "path";
30751
+ import path59 from "path";
30508
30752
  import readline4 from "readline/promises";
30509
- import { spawnSync } from "child_process";
30753
+ import { spawnSync as spawnSync2 } from "child_process";
30510
30754
  import { Command as Command39 } from "commander";
30511
30755
  import chalk38 from "chalk";
30512
30756
 
@@ -30571,7 +30815,8 @@ function runRulesSearch(query, opts, configPath = DEFAULT_CONFIG_PATH) {
30571
30815
  const rules = loadFor(opts, configPath);
30572
30816
  const hits = searchRules(rules, query, { statuses: opts.all ? [...RULE_STATUSES] : DEFAULT_SEARCH_STATUSES });
30573
30817
  const project = resolveRulesProject(loadConfig(configPath), opts);
30574
- appendAudit([{ kb: "", level: "project", project, domain: "rules", itemId: "", event: "item.searched", query, score: hits[0]?.score }], opts.audit);
30818
+ const shown = hits.slice(0, parseLimit(opts.limit));
30819
+ appendAudit(shown.length ? shown.map((h) => ({ ...ruleAuditScope(h.rule), project, domain: "rules", itemId: h.rule.id, event: "item.searched", query, score: h.score })) : [{ kb: "", level: "project", project, domain: "rules", itemId: "", event: "item.searched", query }], opts.audit);
30575
30820
  return hits;
30576
30821
  }
30577
30822
  function runRulesList(opts, configPath = DEFAULT_CONFIG_PATH) {
@@ -30654,22 +30899,23 @@ function runKnowledgeInit(kb, configPath = DEFAULT_CONFIG_PATH) {
30654
30899
  const cfg = loadConfig(configPath);
30655
30900
  const dir = kbDir(cfg, kb);
30656
30901
  const created = [];
30657
- for (const d of [path58.join(dir, "raw"), kbRulesDir(cfg, kb), path58.join(kbRulesDir(cfg, kb), PROPOSED_DIR)]) {
30658
- if (!fs53.existsSync(d)) {
30659
- fs53.mkdirSync(d, { recursive: true });
30660
- created.push(path58.relative(dir, d));
30902
+ for (const d of [path59.join(dir, "raw"), kbRulesDir(cfg, kb), path59.join(kbRulesDir(cfg, kb), PROPOSED_DIR)]) {
30903
+ if (!fs54.existsSync(d)) {
30904
+ fs54.mkdirSync(d, { recursive: true });
30905
+ created.push(path59.relative(dir, d));
30661
30906
  }
30662
30907
  }
30663
- const sourcesFile = path58.join(dir, "sources.yaml");
30664
- if (!fs53.existsSync(sourcesFile)) {
30665
- fs53.writeFileSync(sourcesFile, SOURCES_TEMPLATE);
30908
+ const sourcesFile = path59.join(dir, "sources.yaml");
30909
+ if (!fs54.existsSync(sourcesFile)) {
30910
+ fs54.writeFileSync(sourcesFile, SOURCES_TEMPLATE);
30666
30911
  created.push("sources.yaml");
30667
30912
  }
30668
- if (!fs53.existsSync(path58.join(dir, "index.json"))) {
30913
+ if (!fs54.existsSync(path59.join(dir, "index.json"))) {
30669
30914
  writeIndex(dir, compileIndex(kb, []));
30670
30915
  created.push("index.json");
30671
30916
  }
30672
30917
  if (ensureGitRepo(dir)) created.push(".git");
30918
+ if (ensureKbGitignore(dir)) created.push(".gitignore");
30673
30919
  if (!kbConfigs(cfg)[kb]) {
30674
30920
  cfg.knowledgeBases = { ...cfg.knowledgeBases ?? {}, [kb]: {} };
30675
30921
  saveConfig(cfg, configPath);
@@ -30681,7 +30927,7 @@ function runKnowledgeSubscribe(kb, project, configPath = DEFAULT_CONFIG_PATH) {
30681
30927
  const cfg = loadConfig(configPath);
30682
30928
  const pc = cfg.projects[project];
30683
30929
  if (!pc) throw new Error(`Unknown project '${project}'`);
30684
- if (!fs53.existsSync(kbDir(cfg, kb))) {
30930
+ if (!fs54.existsSync(kbDir(cfg, kb))) {
30685
30931
  throw new Error(`Knowledge base '${kb}' does not exist. Run: squadrant knowledge init ${kb}`);
30686
30932
  }
30687
30933
  const list = pc.knowledge ?? [];
@@ -30696,21 +30942,37 @@ function runKnowledgeSources(kb, configPath = DEFAULT_CONFIG_PATH) {
30696
30942
  const cfg = loadConfig(configPath);
30697
30943
  return loadSources(kbDir(cfg, kb));
30698
30944
  }
30945
+ function runKnowledgeSourcesRemove(kb, entryPath, opts = {}, configPath = DEFAULT_CONFIG_PATH) {
30946
+ assertKbName(kb);
30947
+ const cfg = loadConfig(configPath);
30948
+ if (!fs54.existsSync(kbDir(cfg, kb))) throw new Error(`Knowledge base '${kb}' does not exist. Run: squadrant knowledge init ${kb}`);
30949
+ return removeSource(kbDir(cfg, kb), kbRulesDir(cfg, kb), entryPath, { ...opts, kbName: kb });
30950
+ }
30699
30951
  var knowledgeCommand = new Command39("knowledge").description("Manage rules knowledge bases (#893)");
30700
30952
  knowledgeCommand.command("init <kb>").description("Create a knowledge base (its own git repo) under ~/squadrant/kb/<kb>/").action((kb) => {
30701
30953
  const { dir, created } = runKnowledgeInit(kb);
30702
30954
  console.log(created.length ? chalk38.green(`Initialised ${dir} (${created.join(", ")})`) : `Already initialised: ${dir}`);
30955
+ console.log(chalk38.dim("raw/ and .converted/ are git-ignored by default (edit .gitignore to opt in)."));
30703
30956
  console.log(chalk38.dim(KNOWLEDGE_PRIVACY_NOTICE));
30704
30957
  });
30705
30958
  knowledgeCommand.command("subscribe <kb>").description("Subscribe a project to a knowledge base").requiredOption("--project <name>", "project to subscribe").action((kb, opts) => {
30706
30959
  const list = runKnowledgeSubscribe(kb, opts.project);
30707
30960
  console.log(`${opts.project} knowledge: ${list.join(", ")}`);
30708
30961
  });
30709
- knowledgeCommand.command("sources <kb>").description("List the trusted sources of a knowledge base").action((kb) => {
30962
+ knowledgeCommand.command("sources <kb>").description("List the trusted sources of a knowledge base; --remove drops one and strips its refs from rules").option("--remove <path>", "remove the sources.yaml entry with this exact path; rules left with no source become stale").option("--dry-run", "with --remove: print what would change, write nothing").action((kb, o) => {
30963
+ if (o.dryRun && !o.remove) throw new Error("--dry-run only applies to --remove");
30964
+ if (o.remove) {
30965
+ const r = runKnowledgeSourcesRemove(kb, o.remove, { dryRun: o.dryRun });
30966
+ const stripped = r.stripped.length - r.stale.length;
30967
+ console.log(`${r.dryRun ? "would remove" : "removed"} ${o.remove}: ${r.stripped.length} rules lose a ref, ${r.stale.length} left with no source (stale), ${stripped} keep another source`);
30968
+ for (const id of r.stale) console.log(chalk38.yellow(` stale: ${id}`));
30969
+ if (r.dryRun) console.log(chalk38.dim("dry run: nothing written"));
30970
+ return;
30971
+ }
30710
30972
  const { sources, errors } = runKnowledgeSources(kb);
30711
30973
  if (!sources.length && !errors.length) console.log("(no sources \u2014 edit sources.yaml)");
30712
30974
  for (const s of sources) {
30713
- console.log(` ${s.priority.padEnd(8)} ${s.path}${s.domain ? ` [${s.domain}]` : ""}${s.sensitivity ? chalk38.yellow(" local-only") : ""}`);
30975
+ console.log(` ${s.priority.padEnd(8)} ${s.path}${s.domain ? ` [${s.domain}]` : ""}${s.sensitivity ? chalk38.yellow(" local-only") : ""}${s.review ? chalk38.cyan(" review: required") : ""}`);
30714
30976
  }
30715
30977
  for (const e of errors) console.log(chalk38.red(` \u2718 ${e}`));
30716
30978
  if (errors.length) process.exitCode = 1;
@@ -30718,9 +30980,9 @@ knowledgeCommand.command("sources <kb>").description("List the trusted sources o
30718
30980
  function runKnowledgeReindex(kb, configPath = DEFAULT_CONFIG_PATH) {
30719
30981
  assertKbName(kb);
30720
30982
  const cfg = loadConfig(configPath);
30721
- if (!fs53.existsSync(kbDir(cfg, kb))) throw new Error(`Knowledge base '${kb}' does not exist. Run: squadrant knowledge init ${kb}`);
30983
+ if (!fs54.existsSync(kbDir(cfg, kb))) throw new Error(`Knowledge base '${kb}' does not exist. Run: squadrant knowledge init ${kb}`);
30722
30984
  const { rules: all, errors: allErrors } = loadKbRules(cfg, kb, { includeProposed: true });
30723
- const proposedRoot = path58.join(kbRulesDir(cfg, kb), PROPOSED_DIR) + path58.sep;
30985
+ const proposedRoot = path59.join(kbRulesDir(cfg, kb), PROPOSED_DIR) + path59.sep;
30724
30986
  const live = all.filter((r) => !r.file.startsWith(proposedRoot));
30725
30987
  const pendingDups = allErrors.filter((e) => isPendingProposalDup(e, kbRulesDir(cfg, kb)));
30726
30988
  const errors = allErrors.filter((e) => !pendingDups.includes(e));
@@ -30730,11 +30992,15 @@ function runKnowledgeReindex(kb, configPath = DEFAULT_CONFIG_PATH) {
30730
30992
  function runKnowledgeValidate(kb, configPath = DEFAULT_CONFIG_PATH) {
30731
30993
  assertKbName(kb);
30732
30994
  const cfg = loadConfig(configPath);
30733
- if (!fs53.existsSync(kbDir(cfg, kb))) throw new Error(`Knowledge base '${kb}' does not exist. Run: squadrant knowledge init ${kb}`);
30995
+ if (!fs54.existsSync(kbDir(cfg, kb))) throw new Error(`Knowledge base '${kb}' does not exist. Run: squadrant knowledge init ${kb}`);
30734
30996
  const { sources, errors } = loadSources(kbDir(cfg, kb));
30735
- const res = validateKb({ rulesDir: kbRulesDir(cfg, kb), kbRoot: kbDir(cfg, kb), sources, domains: kbConfigs(cfg)[kb]?.domains });
30736
- const srcChecks = errors.map((e) => ({ level: "fail", message: `sources.yaml: ${e}` }));
30737
- return { checks: [...srcChecks, ...res.checks], ok: res.ok && !errors.length };
30997
+ const { errata, errors: errataErrors } = loadErrata(kbDir(cfg, kb));
30998
+ const res = validateKb({ rulesDir: kbRulesDir(cfg, kb), kbRoot: kbDir(cfg, kb), sources, errata, domains: kbConfigs(cfg)[kb]?.domains });
30999
+ const srcChecks = [
31000
+ ...errors.map((e) => ({ level: "fail", message: `sources.yaml: ${e}` })),
31001
+ ...errataErrors.map((e) => ({ level: "fail", message: `errata.yaml: ${e}` }))
31002
+ ];
31003
+ return { checks: [...srcChecks, ...res.checks], ok: res.ok && !srcChecks.length };
30738
31004
  }
30739
31005
  knowledgeCommand.command("reindex <kb>").description("Recompile index.json from the KB's rule files").action((kb) => {
30740
31006
  const r = runKnowledgeReindex(kb);
@@ -30760,7 +31026,7 @@ knowledgeCommand.command("migrate").description("Move <hubVault>/knowledge/<kb>/
30760
31026
  function requireKb(kb, configPath) {
30761
31027
  assertKbName(kb);
30762
31028
  const cfg = loadConfig(configPath);
30763
- if (!fs53.existsSync(kbDir(cfg, kb))) throw new Error(`Knowledge base '${kb}' does not exist. Run: squadrant knowledge init ${kb}`);
31029
+ if (!fs54.existsSync(kbDir(cfg, kb))) throw new Error(`Knowledge base '${kb}' does not exist. Run: squadrant knowledge init ${kb}`);
30764
31030
  return cfg;
30765
31031
  }
30766
31032
  async function runKnowledgeIngest(kb, opts = {}, configPath = DEFAULT_CONFIG_PATH) {
@@ -30779,10 +31045,14 @@ async function runKnowledgeIngest(kb, opts = {}, configPath = DEFAULT_CONFIG_PAT
30779
31045
  }
30780
31046
  function runKnowledgeApply(kb, candidatesFile, configPath = DEFAULT_CONFIG_PATH) {
30781
31047
  const cfg = requireKb(kb, configPath);
30782
- const input = JSON.parse(fs53.readFileSync(candidatesFile, "utf8"));
31048
+ const input = JSON.parse(fs54.readFileSync(candidatesFile, "utf8"));
30783
31049
  if (!input || !Array.isArray(input.candidates)) throw new Error("candidates file must be { complete?: string[], candidates: [...] }");
30784
31050
  const projects = Object.entries(cfg.projects).filter(([name, pc]) => pc.path && subscribedKbs(cfg, name).includes(kb)).map(([name, pc]) => ({ name, root: resolveHome(pc.path) }));
30785
- return applyCandidates(input, { kb, kbRoot: kbDir(cfg, kb), rulesDir: kbRulesDir(cfg, kb), projects });
31051
+ const { errata, errors } = loadErrata(kbDir(cfg, kb));
31052
+ if (errors.length) throw new Error(`errata.yaml: ${errors.join("; ")}`);
31053
+ const { sources, errors: sourceErrors } = loadSources(kbDir(cfg, kb));
31054
+ if (sourceErrors.length) throw new Error(`sources.yaml: ${sourceErrors.join("; ")}`);
31055
+ return applyCandidates(input, { kb, kbRoot: kbDir(cfg, kb), rulesDir: kbRulesDir(cfg, kb), projects, sources, errata });
30786
31056
  }
30787
31057
  knowledgeCommand.command("ingest <kb>").description("Detect changed sources, convert them (markitdown) into .converted/, and print the section plan for the knowledge-extract skill").option("--dry-run", "estimate tokens only; write nothing").option("--json", "print the full plan as JSON").option("--local-model", "a local model is available, so sensitivity: local-only sources are planned instead of skipped").option("--max-sections <n>", "override maxSectionsPerPass", (v2) => parseInt(v2, 10)).action(async (kb, opts) => {
30788
31058
  const plan = await runKnowledgeIngest(kb, opts);
@@ -30801,16 +31071,19 @@ knowledgeCommand.command("apply <kb> <candidates>").description("Verify extracte
30801
31071
  console.log(`created ${r.created.length}, merged ${r.merged.length}, proposed ${r.proposed.length}, superseded ${r.superseded.length}, stale ${r.stale.length}, rejected ${r.rejected.length}`);
30802
31072
  for (const x of r.rejected) console.log(chalk38.red(` \u2718 ${x.id} (${x.source}): ${x.reason}`));
30803
31073
  for (const x of r.idOverrides) console.log(chalk38.dim(` id ${x.from} \u2192 ${x.to} (matched by quote offset)`));
31074
+ for (const e of r.orphanedErrata) console.log(chalk38.yellow(` errata ${e.id} (${e.ref}): quote no longer found; the raw file changed, re-check it`));
30804
31075
  for (const d of r.doclingFlags) console.log(chalk38.yellow(` ${d.ref}: ${d.dropped}/${d.total} candidates dropped; conversion likely poor, try docling`));
30805
31076
  });
30806
- var defaultStateRoot2 = () => path58.join(path58.dirname(DEFAULT_CONFIG_PATH), "state");
31077
+ var defaultStateRoot2 = () => path59.join(path59.dirname(DEFAULT_CONFIG_PATH), "state");
30807
31078
  function reviewContext(kb, configPath) {
30808
31079
  const cfg = requireKb(kb, configPath);
30809
31080
  const projects = Object.entries(cfg.projects).filter(([name, pc]) => pc.path && subscribedKbs(cfg, name).includes(kb)).map(([name, pc]) => ({ name, root: resolveHome(pc.path) }));
30810
- return { cfg, kb, sources: loadSources(kbDir(cfg, kb)).sources, projects };
31081
+ const { sources, errors } = loadSources(kbDir(cfg, kb));
31082
+ if (errors.length) throw new Error(`sources.yaml: ${errors.join("; ")}`);
31083
+ return { cfg, kb, sources, projects };
30811
31084
  }
30812
31085
  function runKnowledgeReviewApply(kb, file, configPath = DEFAULT_CONFIG_PATH) {
30813
- return applyDecisions(JSON.parse(fs53.readFileSync(file, "utf8")), reviewContext(kb, configPath));
31086
+ return applyDecisions(JSON.parse(fs54.readFileSync(file, "utf8")), reviewContext(kb, configPath));
30814
31087
  }
30815
31088
  function runKnowledgeResolve(kb, key, r, by, reason, configPath = DEFAULT_CONFIG_PATH) {
30816
31089
  return resolveEscalation(reviewContext(kb, configPath), key, r, by, reason);
@@ -30858,8 +31131,8 @@ ${e.explanation}`);
30858
31131
  else if (ans === "r") r = { kind: "reject" };
30859
31132
  else if (ans === "e" && e.kind === "proposal") {
30860
31133
  const ctx = reviewContext(kb, DEFAULT_CONFIG_PATH);
30861
- const file = path58.join(kbRulesDir(ctx.cfg, kb), PROPOSED_DIR, `${e.item}.md`);
30862
- spawnSync(process.env.EDITOR ?? "vi", [file], { stdio: "inherit" });
31134
+ const file = path59.join(kbRulesDir(ctx.cfg, kb), PROPOSED_DIR, `${e.item}.md`);
31135
+ spawnSync2(process.env.EDITOR ?? "vi", [file], { stdio: "inherit" });
30863
31136
  r = { kind: "approve" };
30864
31137
  }
30865
31138
  if (r) console.log(chalk38.green(runKnowledgeResolve(kb, e.key, r, by, reason)));
@@ -30930,7 +31203,7 @@ function renderStats(all) {
30930
31203
  const pct = (x) => x === null ? "-" : `${Math.round(x * 100)}%`;
30931
31204
  return all.flatMap((s) => [
30932
31205
  `${s.kb} (last ${s.windowDays} days)`,
30933
- ...s.items.map((x) => ` ${x.itemId} surfaced ${x.surfaced} pulled ${x.shown} cited ${x.cited} use ${pct(x.useRate)} noise ${pct(x.noiseRate)} violated ${pct(x.violationRate)}${x.neverFired30d ? " NEVER FIRED" : ""}`),
31206
+ ...s.items.map((x) => ` ${x.itemId} surfaced ${x.surfaced} pulled ${x.pullRate} cited ${x.cited} use ${pct(x.useRate)} noise ${pct(x.noiseRate)} violated ${pct(x.violationRate)}${x.neverFired30d ? " NEVER FIRED" : ""}`),
30934
31207
  ...s.domains.map((d) => ` domain ${d.domain}: writes ${d.writesPerMonth}/mo reads ${d.readsPerMonth}/mo${d.deadDomain ? " DEAD DOMAIN" : ""}`),
30935
31208
  ` agents: ${Object.entries(s.perAgentCoverage).map(([k, v2]) => `${k} ${v2}`).join(", ") || "none"}`
30936
31209
  ]);
@@ -30941,10 +31214,10 @@ knowledgeCommand.command("stats").description("Scores computed from the audit lo
30941
31214
  else for (const l of renderStats(all)) console.log(l);
30942
31215
  });
30943
31216
  function runKnowledgeCiteScan(o) {
30944
- const run = o.git_run ?? ((args) => spawnSync("git", args, { encoding: "utf8" }).stdout ?? "");
31217
+ const run = o.git_run ?? ((args) => spawnSync2("git", args, { encoding: "utf8" }).stdout ?? "");
30945
31218
  if (o.git) {
30946
31219
  const since = new Date((o.audit?.now?.() ?? /* @__PURE__ */ new Date()).getTime() - (o.sinceDays ?? 30) * 864e5);
30947
- return readGitMessages(o.git, since, run).flatMap((c) => scanAndLogCitations({ text: c.text, ref: `git:${c.sha}`, project: o.project, at: c.at, sink: o.audit }));
31220
+ return scanCitations(readGitMessages(o.git, since, run).map((c) => ({ text: c.text, ref: `git:${c.sha}`, at: c.at })), { project: o.project, sink: o.audit });
30948
31221
  }
30949
31222
  if (!o.text) throw new Error("Pass --text <message> or --git <repo>");
30950
31223
  return scanAndLogCitations({ text: o.text, ref: o.ref ?? "manual", project: o.project, sink: o.audit });