squadrant 0.27.1 → 0.28.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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 {
@@ -10406,6 +10428,35 @@ function expandSource(kbRoot, entry) {
10406
10428
  const re = globToRegExp(abs.split(path16.sep).join("/"));
10407
10429
  return walkFiles(root).filter((f) => re.test(f.split(path16.sep).join("/"))).sort();
10408
10430
  }
10431
+ function refCandidates(kbRoot, sources, ref) {
10432
+ const asIs = path16.resolve(kbRoot, expandHome(ref));
10433
+ if (path16.isAbsolute(expandHome(ref)))
10434
+ return [asIs];
10435
+ const bases = /* @__PURE__ */ new Set([kbRoot]);
10436
+ for (const s of sources) {
10437
+ let d = path16.resolve(kbRoot, expandHome(s.path.split(/[*?[{]/)[0]));
10438
+ for (let i = 0; i < 6 && d !== path16.dirname(d); i++) {
10439
+ d = path16.dirname(d);
10440
+ bases.add(d);
10441
+ }
10442
+ }
10443
+ return [.../* @__PURE__ */ new Set([asIs, ...[...bases].map((b) => path16.join(b, ref))])];
10444
+ }
10445
+ function refPath(kbRoot, sources, ref) {
10446
+ const cands = refCandidates(kbRoot, sources, ref);
10447
+ return cands.find(isFile) ?? cands.find((f) => sources.some((e) => entryCovers(kbRoot, e, f))) ?? cands[0];
10448
+ }
10449
+ function refCanon(kbRoot, sources) {
10450
+ const memo = /* @__PURE__ */ new Map();
10451
+ return (ref) => {
10452
+ let c = memo.get(ref);
10453
+ if (c === void 0) {
10454
+ c = refFor(kbRoot, refPath(kbRoot, sources, ref));
10455
+ memo.set(ref, c);
10456
+ }
10457
+ return c;
10458
+ };
10459
+ }
10409
10460
  function detectChange(kbRoot, file) {
10410
10461
  const ref = refFor(kbRoot, file);
10411
10462
  const st = fs15.statSync(file);
@@ -10502,7 +10553,7 @@ async function planIngest(kbRoot, opts) {
10502
10553
  }
10503
10554
  return plan;
10504
10555
  }
10505
- var execFileAsync, CONVERTED_DIR, DEFAULT_MAX_SECTIONS_PER_PASS, CHARS_PER_TOKEN, PASS_THROUGH, SECTION_TARGET_CHARS, markitdownConverter, expandHome, fileForRef, cacheKey, convertedFile, stampFile, sha256;
10556
+ var execFileAsync, CONVERTED_DIR, DEFAULT_MAX_SECTIONS_PER_PASS, CHARS_PER_TOKEN, PASS_THROUGH, SECTION_TARGET_CHARS, markitdownConverter, expandHome, cacheKey, convertedFile, stampFile, isFile, resolveSourceRef, entryCovers, sourceCovers, isGatedRef, sha256;
10506
10557
  var init_ingest = __esm({
10507
10558
  "packages/core/dist/knowledge/ingest.js"() {
10508
10559
  execFileAsync = promisify2(execFile2);
@@ -10516,10 +10567,17 @@ var init_ingest = __esm({
10516
10567
  return stdout;
10517
10568
  };
10518
10569
  expandHome = (p) => p === "~" || p.startsWith("~/") ? path16.join(os7.homedir(), p.slice(1)) : p;
10519
- fileForRef = (kbRoot, ref) => path16.resolve(kbRoot, expandHome(ref));
10520
10570
  cacheKey = (ref) => `${ref.replace(/[^A-Za-z0-9._-]+/g, "_")}.${crypto3.createHash("sha1").update(ref).digest("hex").slice(0, 6)}`;
10521
10571
  convertedFile = (kbRoot, ref) => path16.join(kbRoot, CONVERTED_DIR, `${cacheKey(ref)}.md`);
10522
10572
  stampFile = (kbRoot, ref) => path16.join(kbRoot, CONVERTED_DIR, `${cacheKey(ref)}.sha`);
10573
+ isFile = (f) => fs15.existsSync(f) && fs15.statSync(f).isFile();
10574
+ resolveSourceRef = (kbRoot, sources, ref) => refCandidates(kbRoot, sources, ref).find(isFile);
10575
+ entryCovers = (kbRoot, entry, file) => {
10576
+ const abs = path16.resolve(kbRoot, expandHome(entry.path));
10577
+ return /[*?]/.test(entry.path) ? globToRegExp(abs.split(path16.sep).join("/")).test(file.split(path16.sep).join("/")) : abs === file;
10578
+ };
10579
+ sourceCovers = (kbRoot, sources, entry, ref) => entryCovers(kbRoot, entry, refPath(kbRoot, sources, ref));
10580
+ isGatedRef = (kbRoot, sources, ref) => sources.some((e) => e.review === "required" && sourceCovers(kbRoot, sources, e, ref));
10523
10581
  sha256 = (file) => crypto3.createHash("sha256").update(fs15.readFileSync(file)).digest("hex");
10524
10582
  }
10525
10583
  });
@@ -10602,7 +10660,6 @@ var init_verify = __esm({
10602
10660
 
10603
10661
  // packages/core/dist/knowledge/store.js
10604
10662
  import fs16 from "fs";
10605
- import os8 from "os";
10606
10663
  import path17 from "path";
10607
10664
  import matter2 from "gray-matter";
10608
10665
  function duplicateIdProblem(id, ignored, used) {
@@ -10663,18 +10720,6 @@ function loadRulesDir(dir, layer, opts = {}) {
10663
10720
  }
10664
10721
  return { rules, errors };
10665
10722
  }
10666
- function resolveSourceRef(ref, kbRoot, sources) {
10667
- const bases = /* @__PURE__ */ new Set([kbRoot]);
10668
- for (const s of sources) {
10669
- let d = path17.resolve(kbRoot, expandHome2(s.path.split(/[*?[{]/)[0]));
10670
- for (let i = 0; i < 6 && d !== path17.dirname(d); i++) {
10671
- d = path17.dirname(d);
10672
- bases.add(d);
10673
- }
10674
- }
10675
- const cands = [path17.resolve(kbRoot, expandHome2(ref)), ...[...bases].map((b) => path17.join(b, ref))];
10676
- return cands.find((c) => fs16.existsSync(c) && fs16.statSync(c).isFile());
10677
- }
10678
10723
  function validateKb(opts) {
10679
10724
  const { rules, errors } = loadRulesDir(opts.rulesDir, "kb", { includeProposed: true });
10680
10725
  const checks = [];
@@ -10694,17 +10739,18 @@ function validateKb(opts) {
10694
10739
  else
10695
10740
  checks.push({ level: "ok", message: "no duplicate ids" });
10696
10741
  const cache = /* @__PURE__ */ new Map();
10742
+ const canon = refCanon(opts.kbRoot, opts.sources);
10697
10743
  let verified = 0, unresolved = 0, notIngested = 0;
10698
10744
  for (const r of rules) {
10699
10745
  r.sources.forEach((src, i) => {
10700
- const file = resolveSourceRef(src.ref, opts.kbRoot, opts.sources);
10746
+ const file = resolveSourceRef(opts.kbRoot, opts.sources, src.ref);
10701
10747
  if (!file) {
10702
10748
  unresolved++;
10703
10749
  return;
10704
10750
  }
10705
10751
  const key = `${src.ref}\0${file}`;
10706
10752
  if (!cache.has(key)) {
10707
- const conv = readState2(opts.kbRoot, src.ref) ? convertedFile(opts.kbRoot, src.ref) : null;
10753
+ const conv = readState2(opts.kbRoot, canon(src.ref)) ? convertedFile(opts.kbRoot, canon(src.ref)) : null;
10708
10754
  const cached = !!conv && fs16.existsSync(conv);
10709
10755
  cache.set(key, {
10710
10756
  hay: normalizedViews(fs16.readFileSync(cached ? conv : file, "utf8")),
@@ -10712,9 +10758,14 @@ function validateKb(opts) {
10712
10758
  });
10713
10759
  }
10714
10760
  const { hay, convertible } = cache.get(key);
10715
- if (findQuote(hay, src.quote))
10761
+ const span = findQuote(hay, src.quote);
10762
+ if (span) {
10716
10763
  verified++;
10717
- else if (convertible)
10764
+ const active = r.status === "active" && !path17.relative(opts.rulesDir, r.file).split(path17.sep).includes(PROPOSED_DIR);
10765
+ const err = active ? (opts.errata ?? []).find((e) => canon(e.ref) === canon(src.ref) && ((o) => !!o && spansOverlap(o, span))(findQuote(hay, e.quote))) : void 0;
10766
+ if (err)
10767
+ checks.push({ level: "warn", message: `${r.id}: grounded in erratum ${err.id} (${err.reason}); re-check or retire it (${r.file})` });
10768
+ } else if (convertible)
10718
10769
  notIngested++;
10719
10770
  else
10720
10771
  checks.push({ level: "fail", message: `quote not found in ${file}: ${r.id} sources[${i}] (${r.file})` });
@@ -10734,7 +10785,7 @@ function validateKb(opts) {
10734
10785
  }
10735
10786
  return { checks, ok: !checks.some((c) => c.level === "fail") };
10736
10787
  }
10737
- var NO_JS_FRONTMATTER, expandHome2;
10788
+ var NO_JS_FRONTMATTER;
10738
10789
  var init_store2 = __esm({
10739
10790
  "packages/core/dist/knowledge/store.js"() {
10740
10791
  init_dist();
@@ -10746,7 +10797,6 @@ var init_store2 = __esm({
10746
10797
  throw new Error("only YAML front matter is allowed");
10747
10798
  } } }
10748
10799
  };
10749
- expandHome2 = (p) => p === "~" || p.startsWith("~/") ? path17.join(os8.homedir(), p.slice(1)) : p;
10750
10800
  }
10751
10801
  });
10752
10802
 
@@ -13919,6 +13969,7 @@ var init_js_yaml = __esm({
13919
13969
  // packages/core/dist/knowledge/sources.js
13920
13970
  import fs19 from "fs";
13921
13971
  import path19 from "path";
13972
+ import matter3 from "gray-matter";
13922
13973
  function loadSources(kbRoot) {
13923
13974
  const file = path19.join(kbRoot, "sources.yaml");
13924
13975
  if (!fs19.existsSync(file))
@@ -13944,20 +13995,77 @@ function loadSources(kbRoot) {
13944
13995
  });
13945
13996
  return { sources, errors };
13946
13997
  }
13998
+ function dropEntryBlock(text, entryPath) {
13999
+ const lines = text.split("\n");
14000
+ const q = entryPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14001
+ const head = new RegExp(`^-\\s+path:\\s*(['"]?)${q}\\1\\s*(#.*)?$`);
14002
+ const i = lines.findIndex((l) => head.test(l));
14003
+ if (i < 0)
14004
+ return null;
14005
+ let j = i + 1;
14006
+ while (j < lines.length && /^\s+\S/.test(lines[j]))
14007
+ j++;
14008
+ return [...lines.slice(0, i), ...lines.slice(j)].join("\n");
14009
+ }
14010
+ function removeSource(kbRoot, rulesDir, entryPath, opts = {}) {
14011
+ const file = path19.join(kbRoot, "sources.yaml");
14012
+ const { sources } = loadSources(kbRoot);
14013
+ const entry = sources.find((s) => s.path === entryPath);
14014
+ if (!entry)
14015
+ throw new Error(`no sources.yaml entry with path '${entryPath}' (give it exactly as listed by \`knowledge sources\`)`);
14016
+ const edited = dropEntryBlock(fs19.readFileSync(file, "utf8"), entryPath);
14017
+ if (edited === null)
14018
+ 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`);
14019
+ const res = { dryRun: !!opts.dryRun, entryRemoved: true, stripped: [], stale: [] };
14020
+ const writes = [];
14021
+ for (const rule2 of loadRulesDir(rulesDir, "kb").rules) {
14022
+ const kept = rule2.sources.filter((s) => !sourceCovers(kbRoot, sources, entry, s.ref));
14023
+ if (kept.length === rule2.sources.length)
14024
+ continue;
14025
+ res.stripped.push(rule2.id);
14026
+ const parsed = matter3(fs19.readFileSync(rule2.file, "utf8"), { engines: { javascript: () => {
14027
+ throw new Error("only YAML front matter is allowed");
14028
+ } } });
14029
+ const fm = { ...parsed.data, sources: kept };
14030
+ if (!kept.length) {
14031
+ res.stale.push(rule2.id);
14032
+ if (fm.status === "active")
14033
+ fm.status = "stale";
14034
+ }
14035
+ writes.push({ file: rule2.file, text: matter3.stringify(parsed.content, JSON.parse(JSON.stringify(fm))) });
14036
+ }
14037
+ res.stripped.sort();
14038
+ res.stale.sort();
14039
+ if (opts.dryRun)
14040
+ return res;
14041
+ for (const w of writes)
14042
+ fs19.writeFileSync(w.file, w.text);
14043
+ fs19.writeFileSync(file, edited);
14044
+ const name = opts.kbName ?? path19.basename(kbRoot);
14045
+ writeIndex(kbRoot, compileIndex(name, loadRulesDir(rulesDir, `kb:${name}`).rules));
14046
+ return res;
14047
+ }
13947
14048
  var SOURCES_TEMPLATE;
13948
14049
  var init_sources = __esm({
13949
14050
  "packages/core/dist/knowledge/sources.js"() {
13950
14051
  init_js_yaml();
13951
14052
  init_dist();
14053
+ init_index_file();
14054
+ init_ingest();
14055
+ init_store2();
13952
14056
  SOURCES_TEMPLATE = `# Trusted sources for this knowledge base (spec \xA73). Only these can produce rules.
13953
14057
  # priority: company > project > agent (decides conflicts).
13954
14058
  # sensitivity: local-only \u2192 never sent to a hosted model (D12).
14059
+ # review: required \u2192 new rules from this source wait in rules/_proposed/ for the operator; they never go live on their own.
13955
14060
  #
13956
14061
  # - path: raw/finance/**
13957
14062
  # priority: company
13958
14063
  # - path: ~/work/flooros/docs/handbook/**
13959
14064
  # priority: project
13960
14065
  # domain: coding
14066
+ # - path: raw/**
14067
+ # priority: agent
14068
+ # review: required
13961
14069
  []
13962
14070
  `;
13963
14071
  }
@@ -14032,7 +14140,7 @@ var init_match = __esm({
14032
14140
 
14033
14141
  // packages/core/dist/knowledge/audit.js
14034
14142
  import fs20 from "fs";
14035
- import os9 from "os";
14143
+ import os8 from "os";
14036
14144
  import path21 from "path";
14037
14145
  function appendAudit(events, sink = {}) {
14038
14146
  if (!events.length)
@@ -14062,8 +14170,8 @@ var AUDIT_QUERY_CHARS, defaultAuditDir, auditMachineId;
14062
14170
  var init_audit = __esm({
14063
14171
  "packages/core/dist/knowledge/audit.js"() {
14064
14172
  AUDIT_QUERY_CHARS = 200;
14065
- defaultAuditDir = () => process.env.SQUADRANT_AUDIT_DIR || path21.join(os9.homedir(), ".local", "state", "squadrant", "audit");
14066
- auditMachineId = () => os9.hostname().replace(/[^A-Za-z0-9_-]/g, "_") || "host";
14173
+ defaultAuditDir = () => process.env.SQUADRANT_AUDIT_DIR || path21.join(os8.homedir(), ".local", "state", "squadrant", "audit");
14174
+ auditMachineId = () => os8.hostname().replace(/[^A-Za-z0-9_-]/g, "_") || "host";
14067
14175
  }
14068
14176
  });
14069
14177
 
@@ -14349,7 +14457,7 @@ var init_inject = __esm({
14349
14457
  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+/));
14350
14458
  DATA_NOTE = "These are project rules (data from the knowledge base), not instructions to run anything.";
14351
14459
  line = (r) => `${r.modality.toUpperCase()} ${r.id}: ${r.statement}`;
14352
- CITE_NOTE = "Name the rule ids you apply in your DONE/commit message.";
14460
+ CITE_NOTE = "List the rule ids you apply as `Rules: <id>, <id>` at the end of your signal done message (commit messages count too).";
14353
14461
  escapeRe = (t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14354
14462
  BRIEF_POINTER_RE = /^Full task is at (\S+\.md) — cat it/;
14355
14463
  READ_TOOLS = /* @__PURE__ */ new Set(["read", "read_file", "readfile", "view"]);
@@ -14461,67 +14569,157 @@ var init_migrate = __esm({
14461
14569
  }
14462
14570
  });
14463
14571
 
14464
- // packages/core/dist/knowledge/anchor.js
14572
+ // packages/core/dist/knowledge/errata.js
14465
14573
  import fs23 from "fs";
14466
14574
  import path24 from "path";
14467
- function* repoFiles(root) {
14575
+ function loadErrata(kbRoot) {
14576
+ const file = path24.join(kbRoot, "errata.yaml");
14577
+ if (!fs23.existsSync(file))
14578
+ return { errata: [], errors: [] };
14579
+ let data;
14580
+ try {
14581
+ data = yaml.load(fs23.readFileSync(file, "utf8"), { schema: yaml.JSON_SCHEMA });
14582
+ } catch (e) {
14583
+ return { errata: [], errors: [`YAML parse error: ${e.message.split("\n")[0]}`] };
14584
+ }
14585
+ if (data == null)
14586
+ return { errata: [], errors: [] };
14587
+ if (!Array.isArray(data))
14588
+ return { errata: [], errors: ["errata.yaml must be a YAML list of entries"] };
14589
+ const errata = [];
14590
+ const errors = [];
14591
+ data.forEach((entry, i) => {
14592
+ const problems = validateErratum(entry);
14593
+ if (problems.length)
14594
+ errors.push(...problems.map((p) => `entry ${i}: ${p}`));
14595
+ else
14596
+ errata.push(entry);
14597
+ });
14598
+ return { errata, errors };
14599
+ }
14600
+ function orphanedErrata(kbRoot, sources, errata, refs) {
14601
+ const canon = refCanon(kbRoot, sources);
14602
+ const only = refs ? new Set([...refs].map(canon)) : void 0;
14603
+ const hays = /* @__PURE__ */ new Map();
14604
+ const out = [];
14605
+ for (const e of errata) {
14606
+ const ref = canon(e.ref);
14607
+ if (only && !only.has(ref))
14608
+ continue;
14609
+ if (!hays.has(ref)) {
14610
+ const conv = readState2(kbRoot, ref) ? convertedFile(kbRoot, ref) : null;
14611
+ hays.set(ref, conv && fs23.existsSync(conv) ? normalizedViews(fs23.readFileSync(conv, "utf8")) : null);
14612
+ }
14613
+ const hay = hays.get(ref);
14614
+ if (hay && !findQuote(hay, e.quote))
14615
+ out.push({ id: e.id, ref: e.ref });
14616
+ }
14617
+ return out;
14618
+ }
14619
+ var init_errata = __esm({
14620
+ "packages/core/dist/knowledge/errata.js"() {
14621
+ init_js_yaml();
14622
+ init_dist();
14623
+ init_ingest();
14624
+ init_verify();
14625
+ }
14626
+ });
14627
+
14628
+ // packages/core/dist/knowledge/anchor.js
14629
+ import fs24 from "fs";
14630
+ import { execFileSync as execFileSync7 } from "child_process";
14631
+ import path25 from "path";
14632
+ function gitFiles(root) {
14633
+ try {
14634
+ const out = execFileSync7("git", ["ls-files", "-co", "--exclude-standard", "-z"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], maxBuffer: 64 * 1024 * 1024 });
14635
+ return out.split("\0").filter(Boolean).sort();
14636
+ } catch {
14637
+ return null;
14638
+ }
14639
+ }
14640
+ function* repoFiles(root, maxFiles) {
14468
14641
  let count = 0;
14642
+ const listed = gitFiles(root);
14643
+ if (listed) {
14644
+ for (const rel of listed) {
14645
+ if (!CODE_EXT.has(path25.extname(rel)) || rel.split("/").slice(0, -1).some((d) => SKIP_DIRS.has(d)))
14646
+ continue;
14647
+ if (++count > maxFiles) {
14648
+ console.warn(`anchor: ${root} has more than ${maxFiles} code files; anchors cover only the first ${maxFiles}`);
14649
+ return;
14650
+ }
14651
+ yield path25.join(root, rel);
14652
+ }
14653
+ return;
14654
+ }
14469
14655
  const stack = [root];
14470
14656
  while (stack.length) {
14471
14657
  const dir = stack.pop();
14472
14658
  let ents;
14473
14659
  try {
14474
- ents = fs23.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name < b.name ? 1 : -1);
14660
+ ents = fs24.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name < b.name ? 1 : -1);
14475
14661
  } catch {
14476
14662
  continue;
14477
14663
  }
14478
14664
  for (const ent of ents) {
14479
14665
  if (ent.isDirectory()) {
14480
14666
  if (!SKIP_DIRS.has(ent.name))
14481
- stack.push(path24.join(dir, ent.name));
14482
- } else if (ent.isFile() && CODE_EXT.has(path24.extname(ent.name))) {
14483
- if (++count > MAX_FILES)
14667
+ stack.push(path25.join(dir, ent.name));
14668
+ } else if (ent.isFile() && CODE_EXT.has(path25.extname(ent.name))) {
14669
+ if (++count > maxFiles) {
14670
+ console.warn(`anchor: ${root} has more than ${maxFiles} code files; anchors cover only the first ${maxFiles}`);
14484
14671
  return;
14485
- yield path24.join(dir, ent.name);
14672
+ }
14673
+ yield path25.join(dir, ent.name);
14486
14674
  }
14487
14675
  }
14488
14676
  }
14489
14677
  }
14490
- function computeAnchor(repoRoot, triggers) {
14491
- const terms2 = anchorTerms(triggers);
14492
- if (!terms2.length || !fs23.existsSync(repoRoot))
14493
- return null;
14494
- const hits = [];
14495
- for (const file of repoFiles(repoRoot)) {
14496
- let text;
14678
+ function scanRepo(root, maxFiles) {
14679
+ const out = [];
14680
+ for (const file of repoFiles(root, maxFiles)) {
14497
14681
  try {
14498
- if (fs23.statSync(file).size > MAX_FILE_BYTES)
14682
+ if (fs24.statSync(file).size > MAX_FILE_BYTES)
14499
14683
  continue;
14500
- text = fs23.readFileSync(file, "utf8");
14684
+ const text = fs24.readFileSync(file, "utf8");
14685
+ const rel = path25.relative(root, file).split(path25.sep).join("/");
14686
+ out.push({ rel, text, hay: `${path25.relative(root, file)}
14687
+ ${text}`.toLowerCase() });
14501
14688
  } catch {
14502
14689
  continue;
14503
14690
  }
14504
- const hay = `${path24.relative(repoRoot, file)}
14505
- ${text}`.toLowerCase();
14691
+ }
14692
+ return out;
14693
+ }
14694
+ function computeAnchor(repoRoot, triggers, cache, maxFiles = MAX_FILES) {
14695
+ const terms2 = anchorTerms(triggers);
14696
+ if (!terms2.length || !fs24.existsSync(repoRoot))
14697
+ return null;
14698
+ let files = cache?.get(repoRoot);
14699
+ if (!files) {
14700
+ files = scanRepo(repoRoot, maxFiles);
14701
+ cache?.set(repoRoot, files);
14702
+ }
14703
+ const hits = [];
14704
+ for (const { rel, text, hay } of files) {
14506
14705
  const score = terms2.filter((t) => hay.includes(t)).length;
14507
14706
  if (!score)
14508
14707
  continue;
14509
- const symbols2 = [...text.matchAll(SYMBOL_RE)].map((m) => m[1]).filter((s) => terms2.some((t) => s.toLowerCase().includes(t)));
14510
- hits.push({ rel: path24.relative(repoRoot, file).split(path24.sep).join("/"), score, symbols: symbols2 });
14708
+ hits.push({ rel, score, text });
14511
14709
  }
14512
14710
  if (!hits.length)
14513
14711
  return null;
14514
14712
  hits.sort((a, b) => b.score - a.score || (a.rel < b.rel ? -1 : 1));
14515
14713
  const top = hits.slice(0, MAX_PATHS);
14516
- const symbols = [...new Set(top.flatMap((h) => h.symbols))].sort().slice(0, MAX_SYMBOLS);
14714
+ const symbols = [...new Set(top.flatMap((h) => [...h.text.matchAll(SYMBOL_RE)].map((m) => m[1]).filter((s) => terms2.some((t) => s.toLowerCase().includes(t)))))].sort().slice(0, MAX_SYMBOLS);
14517
14715
  return { paths: top.map((h) => h.rel).sort(), ...symbols.length ? { symbols } : {} };
14518
14716
  }
14519
14717
  var SKIP_DIRS, CODE_EXT, MAX_FILES, MAX_FILE_BYTES, MAX_PATHS, MAX_SYMBOLS, SYMBOL_RE, anchorTerms;
14520
14718
  var init_anchor = __esm({
14521
14719
  "packages/core/dist/knowledge/anchor.js"() {
14522
- SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", "coverage", ".next", ".worktrees", ".planning", ".converted"]);
14720
+ SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", "coverage", ".next", ".worktrees", ".planning", ".converted", ".venv", "venv", "__pycache__"]);
14523
14721
  CODE_EXT = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs", ".java", ".kt", ".swift", ".rb", ".php", ".cs", ".sol", ".sql", ".md"]);
14524
- MAX_FILES = 5e3;
14722
+ MAX_FILES = 2e4;
14525
14723
  MAX_FILE_BYTES = 256 * 1024;
14526
14724
  MAX_PATHS = 5;
14527
14725
  MAX_SYMBOLS = 10;
@@ -14531,11 +14729,11 @@ var init_anchor = __esm({
14531
14729
  });
14532
14730
 
14533
14731
  // packages/core/dist/knowledge/extract.js
14534
- import fs24 from "fs";
14535
- import path25 from "path";
14536
- import matter3 from "gray-matter";
14732
+ import fs25 from "fs";
14733
+ import path26 from "path";
14734
+ import matter4 from "gray-matter";
14537
14735
  function serialize(fm, body) {
14538
- return matter3.stringify(body.trim() + "\n", clean(fm));
14736
+ return matter4.stringify(body.trim() + "\n", clean(fm));
14539
14737
  }
14540
14738
  function validateCandidate(c) {
14541
14739
  const p = [];
@@ -14565,8 +14763,8 @@ function validateCandidate(c) {
14565
14763
  }
14566
14764
  return p;
14567
14765
  }
14568
- function upsertSource(fm, src) {
14569
- const i = fm.sources.findIndex((s) => s.ref === src.ref);
14766
+ function upsertSource(fm, src, canon) {
14767
+ const i = fm.sources.findIndex((s) => canon(s.ref) === canon(src.ref));
14570
14768
  if (i < 0) {
14571
14769
  fm.sources.push(src);
14572
14770
  return true;
@@ -14578,9 +14776,9 @@ function upsertSource(fm, src) {
14578
14776
  }
14579
14777
  function applyCandidates(input, opts) {
14580
14778
  const { kb, kbRoot, rulesDir } = opts;
14581
- const res = { created: [], merged: [], proposed: [], superseded: [], stale: [], rejected: [], doclingFlags: [], idOverrides: [] };
14779
+ const res = { created: [], merged: [], proposed: [], superseded: [], stale: [], rejected: [], doclingFlags: [], idOverrides: [], orphanedErrata: [] };
14582
14780
  const events = [];
14583
- const ev2 = (event, itemId, domain, extra = {}) => events.push({ kb, level: "group", project: "", domain, itemId, event, ...extra });
14781
+ const ev2 = (event, itemId, extra = {}) => events.push({ kb, level: "group", project: "", domain: "rules", itemId, event, ...extra });
14584
14782
  const loaded = loadRulesDir(rulesDir, `kb:${kb}`);
14585
14783
  const work = /* @__PURE__ */ new Map();
14586
14784
  for (const r of loaded.rules) {
@@ -14590,7 +14788,8 @@ function applyCandidates(input, opts) {
14590
14788
  const norm = /* @__PURE__ */ new Map();
14591
14789
  const totals = /* @__PURE__ */ new Map();
14592
14790
  const touched = /* @__PURE__ */ new Set();
14593
- const complete = new Set(input.complete ?? []);
14791
+ const canon = refCanon(kbRoot, opts.sources ?? []);
14792
+ const complete = new Set((input.complete ?? []).map(canon));
14594
14793
  for (const cand of input.candidates) {
14595
14794
  const problems = validateCandidate(cand);
14596
14795
  if (problems.length) {
@@ -14599,13 +14798,13 @@ function applyCandidates(input, opts) {
14599
14798
  }
14600
14799
  const st = readState2(kbRoot, cand.source);
14601
14800
  const convFile = st ? convertedFile(kbRoot, cand.source) : null;
14602
- if (!st || !fs24.existsSync(convFile)) {
14801
+ if (!st || !fs25.existsSync(convFile)) {
14603
14802
  res.rejected.push({ id: cand.id, source: cand.source, reason: "unknown source (not ingested)" });
14604
14803
  continue;
14605
14804
  }
14606
14805
  let hay = norm.get(cand.source);
14607
14806
  if (!hay) {
14608
- hay = normalizedViews(fs24.readFileSync(convFile, "utf8"));
14807
+ hay = normalizedViews(fs25.readFileSync(convFile, "utf8"));
14609
14808
  norm.set(cand.source, hay);
14610
14809
  }
14611
14810
  const tot = totals.get(cand.source) ?? { total: 0, dropped: 0 };
@@ -14615,13 +14814,20 @@ function applyCandidates(input, opts) {
14615
14814
  if (!offset) {
14616
14815
  tot.dropped++;
14617
14816
  res.rejected.push({ id: cand.id, source: cand.source, reason: "ungrounded" });
14618
- ev2("item.rejected", cand.id, cand.domain, { reason: "ungrounded" });
14817
+ ev2("item.rejected", cand.id, { reason: "ungrounded" });
14818
+ continue;
14819
+ }
14820
+ const hit = (opts.errata ?? []).find((e) => canon(e.ref) === canon(cand.source) && ((o) => !!o && spansOverlap(o, offset))(findQuote(hay, e.quote)));
14821
+ if (hit) {
14822
+ tot.total--;
14823
+ res.rejected.push({ id: cand.id, source: cand.source, reason: `errata:${hit.id}` });
14824
+ ev2("item.rejected", cand.id, { reason: `errata:${hit.id}` });
14619
14825
  continue;
14620
14826
  }
14621
14827
  let id = cand.id;
14622
14828
  for (const [wid, w] of work) {
14623
14829
  const span = (s) => s.sha === st.sha ? s.offset : findQuote(hay, s.quote) ?? s.offset;
14624
- if (w.fm.sources.some((s) => s.ref === cand.source && s.offset && ((o) => !!o && spansOverlap(o, offset))(span(s)))) {
14830
+ if (w.fm.sources.some((s) => canon(s.ref) === canon(cand.source) && s.offset && ((o) => !!o && spansOverlap(o, offset))(span(s)))) {
14625
14831
  id = wid;
14626
14832
  break;
14627
14833
  }
@@ -14636,7 +14842,7 @@ function applyCandidates(input, opts) {
14636
14842
 
14637
14843
  ${cand.rationale.trim()}` : "");
14638
14844
  if (target && contradicted.length === 0 && sameMeaning(target, cand)) {
14639
- let changed = upsertSource(target.fm, src);
14845
+ let changed = upsertSource(target.fm, src, canon);
14640
14846
  if (target.fm.status === "stale") {
14641
14847
  target.fm.status = "active";
14642
14848
  changed = true;
@@ -14647,7 +14853,7 @@ ${cand.rationale.trim()}` : "");
14647
14853
  }
14648
14854
  continue;
14649
14855
  }
14650
- if (!target && contradicted.length === 0) {
14856
+ if (!target && contradicted.length === 0 && !isGatedRef(kbRoot, opts.sources ?? [], cand.source)) {
14651
14857
  const fm2 = {
14652
14858
  id,
14653
14859
  domain: cand.domain,
@@ -14660,9 +14866,9 @@ ${cand.rationale.trim()}` : "");
14660
14866
  supersedes: [],
14661
14867
  conflictsWith: []
14662
14868
  };
14663
- work.set(id, { fm: fm2, body: bodyOf(), file: path25.join(rulesDir, cand.domain, `${id}.md`), dirty: true, isNew: true });
14869
+ work.set(id, { fm: fm2, body: bodyOf(), file: path26.join(rulesDir, cand.domain, `${id}.md`), dirty: true, isNew: true });
14664
14870
  res.created.push(id);
14665
- ev2("item.applied", id, cand.domain);
14871
+ ev2("item.applied", id);
14666
14872
  continue;
14667
14873
  }
14668
14874
  const fm = {
@@ -14677,42 +14883,43 @@ ${cand.rationale.trim()}` : "");
14677
14883
  supersedes: [],
14678
14884
  conflictsWith: contradicted
14679
14885
  };
14680
- const dir = path25.join(rulesDir, PROPOSED_DIR);
14681
- const file = path25.join(dir, `${id}@${st.sha.slice(0, SHA_LEN)}.md`);
14886
+ const dir = path26.join(rulesDir, PROPOSED_DIR);
14887
+ const file = path26.join(dir, `${id}@${st.sha.slice(0, SHA_LEN)}.md`);
14682
14888
  const text = serialize(fm, bodyOf());
14683
- if (fs24.existsSync(file) && fs24.readFileSync(file, "utf8") === text)
14889
+ if (fs25.existsSync(file) && fs25.readFileSync(file, "utf8") === text)
14684
14890
  continue;
14685
- fs24.mkdirSync(dir, { recursive: true });
14686
- for (const other of fs24.readdirSync(dir)) {
14687
- if (!other.startsWith(`${id}@`) || !other.endsWith(".md") || path25.join(dir, other) === file)
14891
+ fs25.mkdirSync(dir, { recursive: true });
14892
+ for (const other of fs25.readdirSync(dir)) {
14893
+ if (!other.startsWith(`${id}@`) || !other.endsWith(".md") || path26.join(dir, other) === file)
14688
14894
  continue;
14689
- const old = matter3(fs24.readFileSync(path25.join(dir, other), "utf8"));
14690
- if (!old.data.sources?.some((s) => s.ref === cand.source))
14895
+ const old = matter4(fs25.readFileSync(path26.join(dir, other), "utf8"));
14896
+ if (!old.data.sources?.some((s) => canon(s.ref) === canon(cand.source)))
14691
14897
  continue;
14692
- fs24.writeFileSync(path25.join(dir, other.replace(/\.md$/, ".superseded")), matter3.stringify(old.content, { ...old.data, supersededBy: path25.basename(file) }));
14693
- fs24.rmSync(path25.join(dir, other));
14898
+ fs25.writeFileSync(path26.join(dir, other.replace(/\.md$/, ".superseded")), matter4.stringify(old.content, { ...old.data, supersededBy: path26.basename(file) }));
14899
+ fs25.rmSync(path26.join(dir, other));
14694
14900
  res.superseded.push(other.replace(/\.md$/, ""));
14695
- ev2("item.superseded", id, fm.domain);
14901
+ ev2("item.superseded", id);
14696
14902
  }
14697
- fs24.writeFileSync(file, text);
14903
+ fs25.writeFileSync(file, text);
14698
14904
  res.proposed.push(`${id}@${st.sha.slice(0, SHA_LEN)}`);
14699
- ev2("item.proposed", id, fm.domain);
14905
+ ev2("item.proposed", id);
14700
14906
  }
14701
14907
  for (const [id, w] of work) {
14702
14908
  if (touched.has(id) || w.fm.status !== "active" || w.isNew)
14703
14909
  continue;
14704
- if (w.fm.sources.length && w.fm.sources.every((s) => complete.has(s.ref))) {
14910
+ if (w.fm.sources.length && w.fm.sources.every((s) => complete.has(canon(s.ref)))) {
14705
14911
  w.fm.status = "stale";
14706
14912
  w.dirty = true;
14707
14913
  res.stale.push(id);
14708
14914
  }
14709
14915
  }
14916
+ const anchorCache = /* @__PURE__ */ new Map();
14710
14917
  for (const w of work.values()) {
14711
14918
  if (!w.dirty || !opts.projects?.length)
14712
14919
  continue;
14713
14920
  const anchors = { ...w.fm.anchors ?? {} };
14714
14921
  for (const p of opts.projects) {
14715
- const a = computeAnchor(p.root, w.fm.triggers);
14922
+ const a = computeAnchor(p.root, w.fm.triggers, anchorCache);
14716
14923
  if (a)
14717
14924
  anchors[p.name] = a;
14718
14925
  }
@@ -14722,11 +14929,12 @@ ${cand.rationale.trim()}` : "");
14722
14929
  for (const w of work.values()) {
14723
14930
  if (!w.dirty)
14724
14931
  continue;
14725
- fs24.mkdirSync(path25.dirname(w.file), { recursive: true });
14932
+ fs25.mkdirSync(path26.dirname(w.file), { recursive: true });
14726
14933
  const text = serialize(w.fm, w.body);
14727
- if (!fs24.existsSync(w.file) || fs24.readFileSync(w.file, "utf8") !== text)
14728
- fs24.writeFileSync(w.file, text);
14934
+ if (!fs25.existsSync(w.file) || fs25.readFileSync(w.file, "utf8") !== text)
14935
+ fs25.writeFileSync(w.file, text);
14729
14936
  }
14937
+ res.orphanedErrata = orphanedErrata(kbRoot, opts.sources ?? [], opts.errata ?? [], totals.keys());
14730
14938
  for (const [ref, t] of totals)
14731
14939
  if (needsDocling(t.dropped, t.total))
14732
14940
  res.doclingFlags.push({ ref, ...t });
@@ -14743,6 +14951,7 @@ var init_extract = __esm({
14743
14951
  init_anchor();
14744
14952
  init_index_file();
14745
14953
  init_ingest();
14954
+ init_errata();
14746
14955
  init_paths();
14747
14956
  init_store2();
14748
14957
  init_verify();
@@ -14759,20 +14968,20 @@ var init_extract = __esm({
14759
14968
  });
14760
14969
 
14761
14970
  // packages/core/dist/knowledge/schedule.js
14762
- import fs25 from "fs";
14763
- import path26 from "path";
14971
+ import fs26 from "fs";
14972
+ import path27 from "path";
14764
14973
  function readSchedule(stateRoot) {
14765
14974
  try {
14766
- return JSON.parse(fs25.readFileSync(scheduleFile(stateRoot), "utf8"));
14975
+ return JSON.parse(fs26.readFileSync(scheduleFile(stateRoot), "utf8"));
14767
14976
  } catch {
14768
14977
  return {};
14769
14978
  }
14770
14979
  }
14771
14980
  function writeSchedule(stateRoot, s) {
14772
- fs25.mkdirSync(stateRoot, { recursive: true });
14981
+ fs26.mkdirSync(stateRoot, { recursive: true });
14773
14982
  const tmp = `${scheduleFile(stateRoot)}.${process.pid}.tmp`;
14774
- fs25.writeFileSync(tmp, JSON.stringify(s, null, 2) + "\n");
14775
- fs25.renameSync(tmp, scheduleFile(stateRoot));
14983
+ fs26.writeFileSync(tmp, JSON.stringify(s, null, 2) + "\n");
14984
+ fs26.renameSync(tmp, scheduleFile(stateRoot));
14776
14985
  }
14777
14986
  function snapshotSources(kbRoot, sources) {
14778
14987
  const out = {};
@@ -14852,7 +15061,7 @@ async function tickKnowledgeSchedule(d) {
14852
15061
  continue;
14853
15062
  try {
14854
15063
  const root = kbDir(d.cfg, kb);
14855
- if (!fs25.existsSync(root))
15064
+ if (!fs26.existsSync(root))
14856
15065
  continue;
14857
15066
  const { sources } = loadSources(root);
14858
15067
  if (!sources.length)
@@ -14917,27 +15126,27 @@ var init_schedule = __esm({
14917
15126
  init_sources();
14918
15127
  RUNNING_STALE_MS = 6 * 60 * 60 * 1e3;
14919
15128
  DAY_MS = 24 * 60 * 60 * 1e3;
14920
- scheduleFile = (stateRoot) => path26.join(stateRoot, "knowledge-schedule.json");
15129
+ scheduleFile = (stateRoot) => path27.join(stateRoot, "knowledge-schedule.json");
14921
15130
  reconcileConfig = (cfg, kb) => ({ ...DEFAULT_RECONCILE, ...kbConfigs(cfg)[kb]?.reconcile });
14922
15131
  }
14923
15132
  });
14924
15133
 
14925
15134
  // packages/core/dist/knowledge/review.js
14926
15135
  import crypto4 from "crypto";
14927
- import { execFileSync as execFileSync7 } from "child_process";
14928
- import fs26 from "fs";
14929
- import path27 from "path";
14930
- import matter4 from "gray-matter";
15136
+ import { execFileSync as execFileSync8 } from "child_process";
15137
+ import fs27 from "fs";
15138
+ import path28 from "path";
15139
+ import matter5 from "gray-matter";
14931
15140
  function readEscalations(kbRoot) {
14932
15141
  try {
14933
- return JSON.parse(fs26.readFileSync(escalationsFile(kbRoot), "utf8")).items;
15142
+ return JSON.parse(fs27.readFileSync(escalationsFile(kbRoot), "utf8")).items;
14934
15143
  } catch {
14935
15144
  return [];
14936
15145
  }
14937
15146
  }
14938
15147
  function writeEscalations(kbRoot, items) {
14939
- fs26.mkdirSync(kbRoot, { recursive: true });
14940
- fs26.writeFileSync(escalationsFile(kbRoot), JSON.stringify({ items }, null, 2) + "\n");
15148
+ fs27.mkdirSync(kbRoot, { recursive: true });
15149
+ fs27.writeFileSync(escalationsFile(kbRoot), JSON.stringify({ items }, null, 2) + "\n");
14941
15150
  }
14942
15151
  function addEscalation(kbRoot, e) {
14943
15152
  const items = readEscalations(kbRoot);
@@ -14947,18 +15156,14 @@ function addEscalation(kbRoot, e) {
14947
15156
  return true;
14948
15157
  }
14949
15158
  function appendVerdict(kbRoot, v2, now) {
14950
- fs26.mkdirSync(kbRoot, { recursive: true });
14951
- fs26.appendFileSync(verdictsFile(kbRoot), JSON.stringify({ ts: now.toISOString(), ...v2 }) + "\n");
15159
+ fs27.mkdirSync(kbRoot, { recursive: true });
15160
+ fs27.appendFileSync(verdictsFile(kbRoot), JSON.stringify({ ts: now.toISOString(), ...v2 }) + "\n");
14952
15161
  }
14953
15162
  function priorityOfRef(c, ref) {
14954
15163
  if (ref.startsWith("agent:"))
14955
15164
  return "agent";
14956
15165
  const root = kbDir(c.cfg, c.kb);
14957
- for (const e of c.sources)
14958
- for (const f of expandSource(root, e))
14959
- if (refFor(root, f) === ref)
14960
- return e.priority;
14961
- return "project";
15166
+ return c.sources.find((e) => sourceCovers(root, c.sources, e, ref))?.priority ?? "project";
14962
15167
  }
14963
15168
  function priorityOfRule(c, r) {
14964
15169
  return r.sources.map((s) => priorityOfRef(c, s.ref)).sort((a, b) => PRIORITY_RANK[b] - PRIORITY_RANK[a])[0] ?? "project";
@@ -14969,20 +15174,20 @@ function loadProposals(c) {
14969
15174
  const byId = new Map(live.map((r) => [r.id, r]));
14970
15175
  const proposals = [];
14971
15176
  const pdirs = /* @__PURE__ */ new Set();
14972
- for (const f of fs26.existsSync(dir) ? walkDirs(dir) : [])
14973
- if (path27.basename(f) === PROPOSED_DIR)
15177
+ for (const f of fs27.existsSync(dir) ? walkDirs(dir) : [])
15178
+ if (path28.basename(f) === PROPOSED_DIR)
14974
15179
  pdirs.add(f);
14975
15180
  for (const pd of pdirs) {
14976
15181
  for (const rule2 of loadRulesDir(pd, `kb:${c.kb}`, { includeProposed: true }).rules) {
14977
- proposals.push({ key: path27.basename(rule2.file).replace(/\.md$/, ""), rule: rule2, live: byId.get(rule2.id) });
15182
+ proposals.push({ key: path28.basename(rule2.file).replace(/\.md$/, ""), rule: rule2, live: byId.get(rule2.id) });
14978
15183
  }
14979
15184
  }
14980
15185
  return { proposals, live };
14981
15186
  }
14982
15187
  function walkDirs(dir, out = []) {
14983
- for (const e of fs26.readdirSync(dir, { withFileTypes: true }))
15188
+ for (const e of fs27.readdirSync(dir, { withFileTypes: true }))
14984
15189
  if (e.isDirectory()) {
14985
- const f = path27.join(dir, e.name);
15190
+ const f = path28.join(dir, e.name);
14986
15191
  out.push(f);
14987
15192
  walkDirs(f, out);
14988
15193
  }
@@ -14995,7 +15200,7 @@ function reindex(c) {
14995
15200
  function anchorsFor(c, fm) {
14996
15201
  const anchors = { ...fm.anchors ?? {} };
14997
15202
  for (const p of c.projects ?? []) {
14998
- const a = computeAnchor(p.root, fm.triggers);
15203
+ const a = computeAnchor(p.root, fm.triggers, c.anchorCache);
14999
15204
  if (a)
15000
15205
  anchors[p.name] = a;
15001
15206
  }
@@ -15011,9 +15216,9 @@ function promoteProposal(c, p, by, justification, events) {
15011
15216
  continue;
15012
15217
  const o = readFm(other.file);
15013
15218
  o.fm.status = "retired";
15014
- fs26.writeFileSync(other.file, fileText(o.fm, o.body));
15219
+ fs27.writeFileSync(other.file, fileText(o.fm, o.body));
15015
15220
  retired.push(id);
15016
- events.push(ev(c, "item.superseded", id, o.fm.domain));
15221
+ events.push(ev(c, "item.superseded", id));
15017
15222
  }
15018
15223
  const next = { ...fm, status: "active", approvedBy: by, justification, supersedes: [.../* @__PURE__ */ new Set([...fm.supersedes ?? [], ...retired])], conflictsWith: [] };
15019
15224
  if (p.live?.decision)
@@ -15021,29 +15226,30 @@ function promoteProposal(c, p, by, justification, events) {
15021
15226
  const anchors = anchorsFor(c, { ...next, anchors: p.live?.anchors ?? fm.anchors });
15022
15227
  if (anchors)
15023
15228
  next.anchors = anchors;
15024
- const target = p.live?.file ?? path27.join(dir, next.domain, `${next.id}.md`);
15025
- fs26.mkdirSync(path27.dirname(target), { recursive: true });
15026
- fs26.writeFileSync(target, fileText(next, body));
15027
- fs26.rmSync(p.rule.file);
15028
- events.push(ev(c, "item.approved", next.id, next.domain), ev(c, "item.applied", next.id, next.domain));
15229
+ const target = p.live?.file ?? path28.join(dir, next.domain, `${next.id}.md`);
15230
+ fs27.mkdirSync(path28.dirname(target), { recursive: true });
15231
+ fs27.writeFileSync(target, fileText(next, body));
15232
+ fs27.rmSync(p.rule.file);
15233
+ events.push(ev(c, "item.approved", next.id), ev(c, "item.applied", next.id));
15029
15234
  if (p.live)
15030
- events.push(ev(c, "item.superseded", next.id, next.domain));
15235
+ events.push(ev(c, "item.superseded", next.id));
15031
15236
  }
15032
15237
  function dropProposal(c, p, events) {
15033
- fs26.rmSync(p.rule.file);
15034
- events.push(ev(c, "item.rejected", p.rule.id, p.rule.domain, { reason: "reviewer" }));
15238
+ fs27.rmSync(p.rule.file);
15239
+ events.push(ev(c, "item.rejected", p.rule.id, { reason: "reviewer" }));
15035
15240
  }
15036
15241
  function mergeInto(c, p, targetId, live, events) {
15037
15242
  const t = live.find((r) => r.id === targetId);
15038
15243
  if (!t)
15039
15244
  return false;
15040
15245
  const tf = readFm(t.file);
15246
+ const canon = refCanon(kbDir(c.cfg, c.kb), c.sources);
15041
15247
  for (const s of p.rule.sources)
15042
- if (!tf.fm.sources.some((x) => x.ref === s.ref))
15248
+ if (!tf.fm.sources.some((x) => canon(x.ref) === canon(s.ref)))
15043
15249
  tf.fm.sources.push(s);
15044
- fs26.writeFileSync(t.file, fileText(tf.fm, tf.body));
15045
- fs26.rmSync(p.rule.file);
15046
- events.push(ev(c, "item.superseded", p.rule.id, p.rule.domain), ev(c, "item.applied", targetId, t.domain));
15250
+ fs27.writeFileSync(t.file, fileText(tf.fm, tf.body));
15251
+ fs27.rmSync(p.rule.file);
15252
+ events.push(ev(c, "item.superseded", p.rule.id), ev(c, "item.applied", targetId));
15047
15253
  return true;
15048
15254
  }
15049
15255
  function evaluateCodeConflict(c, rule2, cc) {
@@ -15052,7 +15258,7 @@ function evaluateCodeConflict(c, rule2, cc) {
15052
15258
  const root = proj?.root;
15053
15259
  const code = root ? g.lastCommit(root, cc.files) : null;
15054
15260
  const kbRoot = kbDir(c.cfg, c.kb);
15055
- const docDates = rule2.sources.map((s) => g.docDate(fileForRef(kbRoot, s.ref))).filter((d2) => !!d2).sort();
15261
+ const docDates = rule2.sources.map((s) => g.docDate(refPath(kbRoot, c.sources, s.ref))).filter((d2) => !!d2).sort();
15056
15262
  const docDate = docDates[docDates.length - 1];
15057
15263
  const docRev = docRevOf(rule2);
15058
15264
  const evidence = { docRev, codeRev: code?.rev ?? "unknown", files: cc.files, project: cc.project };
@@ -15074,6 +15280,7 @@ function evaluateCodeConflict(c, rule2, cc) {
15074
15280
  };
15075
15281
  }
15076
15282
  function applyDecisions(file, c) {
15283
+ c = { ...c, anchorCache: /* @__PURE__ */ new Map() };
15077
15284
  const now = nowOf(c);
15078
15285
  const kbRoot = kbDir(c.cfg, c.kb);
15079
15286
  const threshold = reconcileConfig(c.cfg, c.kb).autoApproveConfidence;
@@ -15117,14 +15324,15 @@ function applyDecisions(file, c) {
15117
15324
  f.fm.status = "active";
15118
15325
  if (d.loc && f.fm.sources[0])
15119
15326
  f.fm.sources[0].loc = d.loc;
15120
- if (conf >= threshold && !d.flag && !reasons.length) {
15121
- fs26.writeFileSync(rule2.file, fileText(f.fm, f.body));
15327
+ const noSource = !rule2.sources.length;
15328
+ if (conf >= threshold && !d.flag && !reasons.length && !noSource) {
15329
+ fs27.writeFileSync(rule2.file, fileText(f.fm, f.body));
15122
15330
  res.applied.push(d.item);
15123
- events.push(ev(c, "item.applied", rule2.id, rule2.domain));
15331
+ events.push(ev(c, "item.applied", rule2.id));
15124
15332
  verdict(d, "applied", { confidence: conf });
15125
15333
  continue;
15126
15334
  }
15127
- reasons.push(conf < threshold ? "low-confidence" : "reviewer-flagged");
15335
+ reasons.push(noSource ? "no-source" : conf < threshold ? "low-confidence" : "reviewer-flagged");
15128
15336
  } else if (d.decision === "approve") {
15129
15337
  if (strong(rule2.modality))
15130
15338
  reasons.push("retiring must/must-not");
@@ -15135,9 +15343,9 @@ function applyDecisions(file, c) {
15135
15343
  if (!reasons.length) {
15136
15344
  const f = readFm(rule2.file);
15137
15345
  f.fm.status = "retired";
15138
- fs26.writeFileSync(rule2.file, fileText(f.fm, f.body));
15346
+ fs27.writeFileSync(rule2.file, fileText(f.fm, f.body));
15139
15347
  res.applied.push(d.item);
15140
- events.push(ev(c, "item.archived", rule2.id, rule2.domain));
15348
+ events.push(ev(c, "item.archived", rule2.id));
15141
15349
  verdict(d, "applied", { confidence: conf });
15142
15350
  continue;
15143
15351
  }
@@ -15152,6 +15360,8 @@ function applyDecisions(file, c) {
15152
15360
  reasons.push("reviewer-flagged");
15153
15361
  if (conf < threshold)
15154
15362
  reasons.push("low-confidence");
15363
+ if (p.rule.sources.some((s) => isGatedRef(kbRoot, c.sources, s.ref)))
15364
+ reasons.push("review-required");
15155
15365
  if (d.decision === "approve") {
15156
15366
  const myPri = priorityOfRule(c, p.rule);
15157
15367
  const conflicted = (p.rule.conflictsWith ?? []).map((id) => live.find((r) => r.id === id)).filter((r) => !!r);
@@ -15213,7 +15423,7 @@ function applyPair(pd, c, res, threshold, events, kbRoot, now) {
15213
15423
  for (const [x, y] of [[a, b], [b, a]]) {
15214
15424
  const f = readFm(x.file);
15215
15425
  f.fm.conflictsWith = [.../* @__PURE__ */ new Set([...f.fm.conflictsWith ?? [], y.id])];
15216
- fs26.writeFileSync(x.file, fileText(f.fm, f.body));
15426
+ fs27.writeFileSync(x.file, fileText(f.fm, f.body));
15217
15427
  }
15218
15428
  return;
15219
15429
  }
@@ -15224,15 +15434,16 @@ function applyPair(pd, c, res, threshold, events, kbRoot, now) {
15224
15434
  return;
15225
15435
  }
15226
15436
  const kf = readFm(keep.file);
15437
+ const canon = refCanon(kbRoot, c.sources);
15227
15438
  for (const s of drop.sources)
15228
- if (!kf.fm.sources.some((x) => x.ref === s.ref))
15439
+ if (!kf.fm.sources.some((x) => canon(x.ref) === canon(s.ref)))
15229
15440
  kf.fm.sources.push(s);
15230
15441
  kf.fm.supersedes = [.../* @__PURE__ */ new Set([...kf.fm.supersedes ?? [], drop.id])];
15231
- fs26.writeFileSync(keep.file, fileText(kf.fm, kf.body));
15442
+ fs27.writeFileSync(keep.file, fileText(kf.fm, kf.body));
15232
15443
  const df = readFm(drop.file);
15233
15444
  df.fm.status = "retired";
15234
- fs26.writeFileSync(drop.file, fileText(df.fm, df.body));
15235
- events.push(ev(c, "item.superseded", drop.id, drop.domain), ev(c, "item.applied", keep.id, keep.domain));
15445
+ fs27.writeFileSync(drop.file, fileText(df.fm, df.body));
15446
+ events.push(ev(c, "item.superseded", drop.id), ev(c, "item.applied", keep.id));
15236
15447
  res.applied.push(key);
15237
15448
  }
15238
15449
  function resolveEscalation(c, key, r, by, reason) {
@@ -15262,9 +15473,9 @@ function resolveEscalation(c, key, r, by, reason) {
15262
15473
  f.fm.decision = decision;
15263
15474
  if (r.verdict === "supersede-with-code") {
15264
15475
  f.fm.status = "retired";
15265
- events.push(ev(c, "item.superseded", rule2.id, rule2.domain));
15476
+ events.push(ev(c, "item.superseded", rule2.id));
15266
15477
  }
15267
- fs26.writeFileSync(rule2.file, fileText(f.fm, f.body));
15478
+ fs27.writeFileSync(rule2.file, fileText(f.fm, f.body));
15268
15479
  return done(`${rule2.id}: ${r.verdict}`);
15269
15480
  }
15270
15481
  if (esc2.kind === "proposal") {
@@ -15281,23 +15492,26 @@ function resolveEscalation(c, key, r, by, reason) {
15281
15492
  }
15282
15493
  if (esc2.kind === "stale-retire") {
15283
15494
  const rule2 = live.find((x) => x.id === esc2.ruleId);
15495
+ if (rule2 && r.kind !== "approve" && !rule2.sources.length) {
15496
+ 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.`);
15497
+ }
15284
15498
  if (rule2) {
15285
15499
  const f = readFm(rule2.file);
15286
15500
  f.fm.status = r.kind === "approve" ? "retired" : "active";
15287
- fs26.writeFileSync(rule2.file, fileText(f.fm, f.body));
15288
- events.push(ev(c, r.kind === "approve" ? "item.archived" : "item.applied", rule2.id, rule2.domain));
15501
+ fs27.writeFileSync(rule2.file, fileText(f.fm, f.body));
15502
+ events.push(ev(c, r.kind === "approve" ? "item.archived" : "item.applied", rule2.id));
15289
15503
  }
15290
15504
  return done(`${esc2.ruleId}: ${r.kind === "approve" ? "retired" : "kept"}`);
15291
15505
  }
15292
15506
  if (esc2.kind === "override-base-changed") {
15293
- const ov = esc2.item && fs26.existsSync(esc2.item) ? esc2.item : void 0;
15507
+ const ov = esc2.item && fs27.existsSync(esc2.item) ? esc2.item : void 0;
15294
15508
  if (ov) {
15295
15509
  if (r.kind === "reject")
15296
- fs26.rmSync(ov);
15510
+ fs27.rmSync(ov);
15297
15511
  else {
15298
15512
  const f = readFm(ov);
15299
15513
  f.fm.overridesBase = esc2.evidence?.docRev;
15300
- fs26.writeFileSync(ov, fileText(f.fm, f.body));
15514
+ fs27.writeFileSync(ov, fileText(f.fm, f.body));
15301
15515
  }
15302
15516
  }
15303
15517
  return done(`${esc2.ruleId}: override ${r.kind === "reject" ? "dropped" : "kept"}`);
@@ -15321,11 +15535,11 @@ ${o.evidence}`).digest("hex");
15321
15535
  supersedes: [],
15322
15536
  conflictsWith: []
15323
15537
  };
15324
- const dir = path27.join(kbRulesDir(c.cfg, c.kb), PROPOSED_DIR);
15325
- fs26.mkdirSync(dir, { recursive: true });
15326
- const file = path27.join(dir, `${id}@${sha.slice(0, SHA_LEN2)}.md`);
15327
- fs26.writeFileSync(file, fileText(fm, o.statement));
15328
- appendAudit([ev(c, "item.proposed", id, domain)], c.audit);
15538
+ const dir = path28.join(kbRulesDir(c.cfg, c.kb), PROPOSED_DIR);
15539
+ fs27.mkdirSync(dir, { recursive: true });
15540
+ const file = path28.join(dir, `${id}@${sha.slice(0, SHA_LEN2)}.md`);
15541
+ fs27.writeFileSync(file, fileText(fm, o.statement));
15542
+ appendAudit([ev(c, "item.proposed", id)], c.audit);
15329
15543
  return file;
15330
15544
  }
15331
15545
  function buildReviewPacket(c) {
@@ -15365,10 +15579,10 @@ var init_review = __esm({
15365
15579
  PRIORITY_RANK = { company: 3, project: 2, agent: 1 };
15366
15580
  SHA_LEN2 = 12;
15367
15581
  strong = (m) => m === "must" || m === "must-not";
15368
- escalationsFile = (kbRoot) => path27.join(kbRoot, "escalations.json");
15369
- verdictsFile = (kbRoot) => path27.join(kbRoot, "verdicts.jsonl");
15582
+ escalationsFile = (kbRoot) => path28.join(kbRoot, "escalations.json");
15583
+ verdictsFile = (kbRoot) => path28.join(kbRoot, "verdicts.jsonl");
15370
15584
  needsYouCount = (kbRoot) => readEscalations(kbRoot).filter((e) => e.needsYou).length;
15371
- git = (root, args) => execFileSync7("git", ["-C", root, ...args], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
15585
+ git = (root, args) => execFileSync8("git", ["-C", root, ...args], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
15372
15586
  realGit = {
15373
15587
  lastCommit(root, files) {
15374
15588
  try {
@@ -15387,34 +15601,34 @@ var init_review = __esm({
15387
15601
  },
15388
15602
  docDate(file) {
15389
15603
  try {
15390
- const d = git(path27.dirname(file), ["log", "-1", "--format=%cI", "--", path27.basename(file)]);
15604
+ const d = git(path28.dirname(file), ["log", "-1", "--format=%cI", "--", path28.basename(file)]);
15391
15605
  if (d)
15392
15606
  return d;
15393
15607
  } catch {
15394
15608
  }
15395
15609
  try {
15396
- return fs26.statSync(file).mtime.toISOString();
15610
+ return fs27.statSync(file).mtime.toISOString();
15397
15611
  } catch {
15398
15612
  return null;
15399
15613
  }
15400
15614
  }
15401
15615
  };
15402
15616
  nowOf = (c) => c.now?.() ?? /* @__PURE__ */ new Date();
15403
- fileText = (fm, body) => matter4.stringify(body.trim() + "\n", JSON.parse(JSON.stringify(fm)));
15617
+ fileText = (fm, body) => matter5.stringify(body.trim() + "\n", JSON.parse(JSON.stringify(fm)));
15404
15618
  readFm = (file) => {
15405
- const m = matter4(fs26.readFileSync(file, "utf8"));
15619
+ const m = matter5(fs27.readFileSync(file, "utf8"));
15406
15620
  return { fm: m.data, body: m.content };
15407
15621
  };
15408
- ev = (c, event, itemId, domain, extra = {}) => ({ kb: c.kb, level: "group", project: "", domain, itemId, event, ...extra });
15622
+ ev = (c, event, itemId, extra = {}) => ({ kb: c.kb, level: "group", project: "", domain: "rules", itemId, event, ...extra });
15409
15623
  docRevOf = (r) => r.sources.map((s) => `${s.ref}@${s.sha.slice(0, SHA_LEN2)}`).join(",");
15410
15624
  }
15411
15625
  });
15412
15626
 
15413
15627
  // packages/core/dist/knowledge/fullpass.js
15414
15628
  import crypto5 from "crypto";
15415
- import fs27 from "fs";
15416
- import path28 from "path";
15417
- import matter5 from "gray-matter";
15629
+ import fs28 from "fs";
15630
+ import path29 from "path";
15631
+ import matter6 from "gray-matter";
15418
15632
  function dedupPairs(rules) {
15419
15633
  const active = rules.filter((r) => r.status === "active").sort((x, y) => x.id.localeCompare(y.id));
15420
15634
  const out = [];
@@ -15430,12 +15644,12 @@ function dedupPairs(rules) {
15430
15644
  return out.slice(0, MAX_DEDUP_PAIRS);
15431
15645
  }
15432
15646
  function readAuditRecords(kb, since, dir = defaultAuditDir()) {
15433
- if (!fs27.existsSync(dir))
15647
+ if (!fs28.existsSync(dir))
15434
15648
  return [];
15435
15649
  const sinceMonth = since.toISOString().slice(0, 7);
15436
15650
  const out = [];
15437
- for (const f of fs27.readdirSync(dir).filter((n) => n.endsWith(".jsonl") && n.slice(0, 7) >= sinceMonth).sort()) {
15438
- for (const line2 of fs27.readFileSync(path28.join(dir, f), "utf8").split("\n")) {
15651
+ for (const f of fs28.readdirSync(dir).filter((n) => n.endsWith(".jsonl") && n.slice(0, 7) >= sinceMonth).sort()) {
15652
+ for (const line2 of fs28.readFileSync(path29.join(dir, f), "utf8").split("\n")) {
15439
15653
  if (!line2.trim())
15440
15654
  continue;
15441
15655
  try {
@@ -15498,8 +15712,8 @@ function checkOverrides(cfg, kb) {
15498
15712
  continue;
15499
15713
  const h = baseHash(b);
15500
15714
  if (!o.overridesBase) {
15501
- const m = matter5(fs27.readFileSync(o.file, "utf8"));
15502
- fs27.writeFileSync(o.file, matter5.stringify(m.content, { ...m.data, overridesBase: h }));
15715
+ const m = matter6(fs28.readFileSync(o.file, "utf8"));
15716
+ fs28.writeFileSync(o.file, matter6.stringify(m.content, { ...m.data, overridesBase: h }));
15503
15717
  continue;
15504
15718
  }
15505
15719
  out.push({ project, ruleId: o.id, file: o.file, baseHash: h, changed: o.overridesBase !== h });
@@ -15542,8 +15756,8 @@ var init_fullpass = __esm({
15542
15756
  });
15543
15757
 
15544
15758
  // packages/core/dist/knowledge/scores.js
15545
- import fs28 from "fs";
15546
- import path29 from "path";
15759
+ import fs29 from "fs";
15760
+ import path30 from "path";
15547
15761
  function tally(records, into = emptyTally()) {
15548
15762
  for (const e of records) {
15549
15763
  const domain = typeof e.domain === "string" && e.domain ? e.domain : "rules";
@@ -15552,7 +15766,7 @@ function tally(records, into = emptyTally()) {
15552
15766
  d.writes++;
15553
15767
  if (READ_EVENTS.has(e.event ?? ""))
15554
15768
  d.reads++;
15555
- if (!e.itemId)
15769
+ if (!e.itemId || WRITE_EVENTS.has(e.event ?? ""))
15556
15770
  continue;
15557
15771
  const c = into.items[`${domain}|${e.itemId}`] ??= { ...zero(), domain, itemId: e.itemId };
15558
15772
  switch (e.event) {
@@ -15589,7 +15803,7 @@ function tally(records, into = emptyTally()) {
15589
15803
  }
15590
15804
  function parseLines(file) {
15591
15805
  const out = [];
15592
- for (const line2 of fs28.readFileSync(file, "utf8").split("\n")) {
15806
+ for (const line2 of fs29.readFileSync(file, "utf8").split("\n")) {
15593
15807
  if (!line2.trim())
15594
15808
  continue;
15595
15809
  try {
@@ -15601,37 +15815,37 @@ function parseLines(file) {
15601
15815
  }
15602
15816
  function compactAudit(o) {
15603
15817
  const dir = o.dir ?? defaultAuditDir();
15604
- if (!fs28.existsSync(dir))
15818
+ if (!fs29.existsSync(dir))
15605
15819
  return [];
15606
15820
  const cutoffMonth = new Date(o.now.getTime() - SCORE_WINDOW_DAYS * DAY_MS3).toISOString().slice(0, 7);
15607
15821
  const done = [];
15608
- for (const f of fs28.readdirSync(dir).sort()) {
15822
+ for (const f of fs29.readdirSync(dir).sort()) {
15609
15823
  const m = MONTH_FILE_RE.exec(f);
15610
15824
  if (!m || m[1] >= cutoffMonth)
15611
15825
  continue;
15612
15826
  const [, month, machine] = m;
15613
15827
  const byKb = /* @__PURE__ */ new Map();
15614
- for (const r of parseLines(path29.join(dir, f)))
15828
+ for (const r of parseLines(path30.join(dir, f)))
15615
15829
  byKb.set(r.kb ?? "", [...byKb.get(r.kb ?? "") ?? [], r]);
15616
- const statsPath = path29.join(dir, statsFileName(month, machine));
15617
- const prior = fs28.existsSync(statsPath) ? JSON.parse(fs28.readFileSync(statsPath, "utf8")) : null;
15830
+ const statsPath = path30.join(dir, statsFileName(month, machine));
15831
+ const prior = fs29.existsSync(statsPath) ? JSON.parse(fs29.readFileSync(statsPath, "utf8")) : null;
15618
15832
  const kbs = prior?.kbs ?? {};
15619
15833
  for (const [kb, recs] of byKb)
15620
15834
  kbs[kb] = tally(recs, kbs[kb]);
15621
- fs28.writeFileSync(`${statsPath}.tmp`, JSON.stringify({ month, machine, kbs }));
15622
- fs28.renameSync(`${statsPath}.tmp`, statsPath);
15623
- fs28.unlinkSync(path29.join(dir, f));
15835
+ fs29.writeFileSync(`${statsPath}.tmp`, JSON.stringify({ month, machine, kbs }));
15836
+ fs29.renameSync(`${statsPath}.tmp`, statsPath);
15837
+ fs29.unlinkSync(path30.join(dir, f));
15624
15838
  done.push(month);
15625
15839
  }
15626
15840
  return done;
15627
15841
  }
15628
15842
  function readCompacted(kb, dir) {
15629
15843
  const t = emptyTally();
15630
- if (!fs28.existsSync(dir))
15844
+ if (!fs29.existsSync(dir))
15631
15845
  return t;
15632
- for (const f of fs28.readdirSync(dir).filter((n) => n.endsWith(".stats.json"))) {
15846
+ for (const f of fs29.readdirSync(dir).filter((n) => n.endsWith(".stats.json"))) {
15633
15847
  try {
15634
- const k = JSON.parse(fs28.readFileSync(path29.join(dir, f), "utf8")).kbs[kb];
15848
+ const k = JSON.parse(fs29.readFileSync(path30.join(dir, f), "utf8")).kbs[kb];
15635
15849
  if (k)
15636
15850
  mergeTally(t, k);
15637
15851
  } catch {
@@ -15681,7 +15895,7 @@ function computeScores(i) {
15681
15895
  noise: w.noise,
15682
15896
  wrong: w.wrong,
15683
15897
  fireRate: w.surfaced,
15684
- pullRate: w.shown,
15898
+ pullRate: w.shown + w.searched,
15685
15899
  useRate: ratio(w.cited, w.surfaced),
15686
15900
  noiseRate: ratio(w.noise, w.surfaced),
15687
15901
  violationRate: ratio(w.violated, w.surfaced),
@@ -15723,21 +15937,21 @@ function computeScores(i) {
15723
15937
  function scoreKb(o) {
15724
15938
  const dir = o.auditDir ?? defaultAuditDir();
15725
15939
  const records = [];
15726
- if (fs28.existsSync(dir)) {
15727
- for (const f of fs28.readdirSync(dir).filter((n) => MONTH_FILE_RE.test(n)).sort()) {
15728
- records.push(...parseLines(path29.join(dir, f)).filter((r) => r.kb === o.kb));
15940
+ if (fs29.existsSync(dir)) {
15941
+ for (const f of fs29.readdirSync(dir).filter((n) => MONTH_FILE_RE.test(n)).sort()) {
15942
+ records.push(...parseLines(path30.join(dir, f)).filter((r) => r.kb === o.kb));
15729
15943
  }
15730
15944
  }
15731
15945
  let verdicts = [];
15732
15946
  try {
15733
- verdicts = fs28.readFileSync(path29.join(o.kbRoot, "verdicts.jsonl"), "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l));
15947
+ verdicts = fs29.readFileSync(path30.join(o.kbRoot, "verdicts.jsonl"), "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l));
15734
15948
  } catch {
15735
15949
  }
15736
15950
  return computeScores({ kb: o.kb, records, now: o.now, known: o.known, compacted: readCompacted(o.kb, dir), verdicts });
15737
15951
  }
15738
15952
  function writeScores(kbRoot, s) {
15739
- fs28.mkdirSync(kbRoot, { recursive: true });
15740
- fs28.writeFileSync(scoresFile(kbRoot), JSON.stringify(s, null, 2) + "\n");
15953
+ fs29.mkdirSync(kbRoot, { recursive: true });
15954
+ fs29.writeFileSync(scoresFile(kbRoot), JSON.stringify(s, null, 2) + "\n");
15741
15955
  }
15742
15956
  function renderScoresSection(s) {
15743
15957
  const pct = (x) => x === null ? "n/a" : `${Math.round(x * 100)}%`;
@@ -15750,7 +15964,7 @@ function renderScoresSection(s) {
15750
15964
  ...used.length ? [
15751
15965
  "| Item | Surfaced | Pulled | Cited | Use | Noise | Violated |",
15752
15966
  "|---|---|---|---|---|---|---|",
15753
- ...used.map((x) => `| ${x.itemId} | ${x.surfaced} | ${x.shown} | ${x.cited} | ${pct(x.useRate)} | ${pct(x.noiseRate)} | ${pct(x.violationRate)} |`)
15967
+ ...used.map((x) => `| ${x.itemId} | ${x.surfaced} | ${x.pullRate} | ${x.cited} | ${pct(x.useRate)} | ${pct(x.noiseRate)} | ${pct(x.violationRate)} |`)
15754
15968
  ] : ["No items surfaced."],
15755
15969
  "",
15756
15970
  `- Never fired (${never.length}): ${never.slice(0, 20).join(", ") || "none"}${never.length > 20 ? ", \u2026" : ""}`,
@@ -15778,13 +15992,13 @@ var init_scores = __esm({
15778
15992
  MONTH_FILE_RE = /^(\d{4}-\d{2})\.(.+)\.jsonl$/;
15779
15993
  statsFileName = (month, machine) => `${month}.${machine}.stats.json`;
15780
15994
  ratio = (n, d) => d ? Math.round(n / d * 1e3) / 1e3 : null;
15781
- scoresFile = (kbRoot) => path29.join(kbRoot, "scores.json");
15995
+ scoresFile = (kbRoot) => path30.join(kbRoot, "scores.json");
15782
15996
  }
15783
15997
  });
15784
15998
 
15785
15999
  // packages/core/dist/knowledge/report.js
15786
- import fs29 from "fs";
15787
- import path30 from "path";
16000
+ import fs30 from "fs";
16001
+ import path31 from "path";
15788
16002
  function renderReport(cfg, kb, o) {
15789
16003
  const { rules } = loadKbRules(cfg, kb, { includeProposed: true });
15790
16004
  const count = (s) => rules.filter((r) => r.status === s).length;
@@ -15806,6 +16020,11 @@ function renderReport(cfg, kb, o) {
15806
16020
  const info = esc2.filter((e) => !e.needsYou);
15807
16021
  if (info.length)
15808
16022
  L.push("", `## For information (${info.length})`, "", ...info.map((e) => `- **${e.key}**: ${e.explanation}`));
16023
+ const { errata, errors: errataErrors } = loadErrata(kbDir(cfg, kb));
16024
+ const orphans = orphanedErrata(kbDir(cfg, kb), loadSources(kbDir(cfg, kb)).sources, errata);
16025
+ if (orphans.length || errataErrors.length) {
16026
+ 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}`));
16027
+ }
15809
16028
  if (o.full) {
15810
16029
  const { usage: usage2, caps } = o.full;
15811
16030
  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"}`);
@@ -15815,14 +16034,16 @@ function renderReport(cfg, kb, o) {
15815
16034
  return L.join("\n") + "\n";
15816
16035
  }
15817
16036
  function writeReport(cfg, kb, o) {
15818
- const file = path30.join(kbDir(cfg, kb), "REPORT.md");
15819
- fs29.writeFileSync(file, renderReport(cfg, kb, o));
16037
+ const file = path31.join(kbDir(cfg, kb), "REPORT.md");
16038
+ fs30.writeFileSync(file, renderReport(cfg, kb, o));
15820
16039
  return file;
15821
16040
  }
15822
16041
  var init_report = __esm({
15823
16042
  "packages/core/dist/knowledge/report.js"() {
16043
+ init_errata();
15824
16044
  init_layers();
15825
16045
  init_paths();
16046
+ init_sources();
15826
16047
  init_review();
15827
16048
  init_scores();
15828
16049
  }
@@ -15864,44 +16085,63 @@ var init_reconcile = __esm({
15864
16085
 
15865
16086
  // packages/core/dist/knowledge/cite.js
15866
16087
  function scanAndLogCitations(i) {
15867
- const at = i.at ?? i.sink?.now?.() ?? /* @__PURE__ */ new Date();
15868
- const since = new Date(at.getTime() - (i.lookbackDays ?? CITE_LOOKBACK_DAYS) * DAY_MS4);
15869
- const dir = i.sink?.dir ?? defaultAuditDir();
15870
- const delivered = /* @__PURE__ */ new Map();
15871
- const already = /* @__PURE__ */ new Set();
15872
- for (const r of readAuditRecords(void 0, since, dir)) {
15873
- if (!r.itemId || r.project !== i.project)
15874
- continue;
15875
- if (r.event === "item.cited" && r.ref === i.ref)
15876
- already.add(r.itemId);
15877
- if (!(r.event === "item.surfaced" || r.event === "item.shown") || Date.parse(r.ts) > at.getTime())
15878
- continue;
15879
- if (i.session && r.session && r.session !== i.session)
15880
- continue;
15881
- delivered.set(r.itemId, r);
15882
- }
15883
- const hits = [...delivered].filter(([id]) => !already.has(id) && mentionsId(i.text, id));
15884
- appendAudit(hits.map(([itemId, r]) => ({
15885
- kb: String(r.kb ?? ""),
15886
- level: String(r.level ?? "project"),
15887
- project: i.project,
15888
- domain: String(r.domain ?? "rules"),
15889
- itemId,
15890
- event: "item.cited",
15891
- ref: i.ref,
15892
- ...i.agent ? { agent: i.agent } : {},
15893
- ...i.session ? { session: i.session } : {}
15894
- })), i.sink);
15895
- return hits.map(([id]) => id);
16088
+ return scanCitations([i], i);
16089
+ }
16090
+ function scanCitations(texts, o) {
16091
+ if (!texts.length)
16092
+ return [];
16093
+ const now = o.sink?.now?.() ?? /* @__PURE__ */ new Date();
16094
+ const lookbackMs = (o.lookbackDays ?? CITE_LOOKBACK_DAYS) * DAY_MS4;
16095
+ const ats = texts.map((t) => (t.at ?? now).getTime());
16096
+ const records = readAuditRecords(void 0, new Date(Math.min(...ats) - lookbackMs), o.sink?.dir ?? defaultAuditDir()).filter((r) => r.itemId && r.project === o.project);
16097
+ const already = new Set(records.filter((r) => r.event === "item.cited").map((r) => `${r.ref}|${r.itemId}`));
16098
+ 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));
16099
+ const events = [];
16100
+ texts.forEach((t, n) => {
16101
+ const at = ats[n];
16102
+ const delivered = /* @__PURE__ */ new Map();
16103
+ for (const r of deliveries) {
16104
+ const ts = Date.parse(r.ts);
16105
+ if (ts <= at && ts >= at - lookbackMs)
16106
+ delivered.set(r.itemId, r);
16107
+ }
16108
+ for (const [itemId, r] of delivered) {
16109
+ if (already.has(`${t.ref}|${itemId}`) || !mentionsId(t.text, itemId))
16110
+ continue;
16111
+ already.add(`${t.ref}|${itemId}`);
16112
+ events.push({
16113
+ kb: String(r.kb ?? ""),
16114
+ level: String(r.level ?? "project"),
16115
+ project: o.project,
16116
+ domain: String(r.domain ?? "rules"),
16117
+ itemId,
16118
+ event: "item.cited",
16119
+ ref: t.ref,
16120
+ ...o.agent ? { agent: o.agent } : {},
16121
+ ...o.session ? { session: o.session } : {}
16122
+ });
16123
+ }
16124
+ });
16125
+ appendAudit(events, o.sink);
16126
+ return events.map((e) => e.itemId);
15896
16127
  }
15897
16128
  function readGitMessages(repo, since, run) {
15898
- const out = run(["-C", repo, "log", `--since=${since.toISOString()}`, "--format=%x1e%H%x1f%cI%x1f%B"]);
16129
+ return parseLog(run(["-C", repo, "log", "--all", `--since=${since.toISOString()}`, LOG_FORMAT]));
16130
+ }
16131
+ function readBranchOnlyMessages(repo, run, max = 200) {
16132
+ const branch = run(["-C", repo, "rev-parse", "--abbrev-ref", "HEAD"]).trim();
16133
+ if (!branch)
16134
+ return [];
16135
+ const own = branch === "HEAD" ? [] : [`--exclude=${branch}`];
16136
+ return parseLog(run(["-C", repo, "log", `--max-count=${max}`, "HEAD", "--not", ...own, "--branches", ...own.length ? [`--exclude=*/${branch}`] : [], "--remotes", LOG_FORMAT]));
16137
+ }
16138
+ function parseLog(out) {
15899
16139
  return out.split("").filter((c) => c.trim()).map((c) => {
15900
16140
  const [sha, at, ...rest] = c.split("");
15901
16141
  return { sha: sha.trim(), at: new Date(at), text: rest.join("") };
15902
16142
  });
15903
16143
  }
15904
- var DAY_MS4, CITE_LOOKBACK_DAYS, escapeRe2, mentionsId;
16144
+ var DAY_MS4, CITE_LOOKBACK_DAYS, escapeRe2, mentionsId, LOG_FORMAT;
15905
16145
  var init_cite = __esm({
15906
16146
  "packages/core/dist/knowledge/cite.js"() {
15907
16147
  init_audit();
@@ -15910,6 +16150,7 @@ var init_cite = __esm({
15910
16150
  CITE_LOOKBACK_DAYS = 7;
15911
16151
  escapeRe2 = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
15912
16152
  mentionsId = (text, id) => new RegExp(`(?<![\\w.-])${escapeRe2(id)}(?![\\w-])`).test(text);
16153
+ LOG_FORMAT = "--format=%x1e%H%x1f%cI%x1f%B";
15913
16154
  }
15914
16155
  });
15915
16156
 
@@ -15927,6 +16168,7 @@ var init_knowledge2 = __esm({
15927
16168
  init_migrate();
15928
16169
  init_verify();
15929
16170
  init_ingest();
16171
+ init_errata();
15930
16172
  init_anchor();
15931
16173
  init_extract();
15932
16174
  init_match();
@@ -16137,7 +16379,6 @@ __export(dist_exports2, {
16137
16379
  extractToolPayload: () => extractToolPayload,
16138
16380
  extractUserIntentFromTranscript: () => extractUserIntentFromTranscript,
16139
16381
  fallsBackToPane: () => fallsBackToPane,
16140
- fileForRef: () => fileForRef,
16141
16382
  findCrewPane: () => findCrewPane,
16142
16383
  findLegacyKbs: () => findLegacyKbs,
16143
16384
  findLegacyOverlays: () => findLegacyOverlays,
@@ -16177,6 +16418,7 @@ __export(dist_exports2, {
16177
16418
  isDaemonSocketLive: () => isDaemonSocketLive,
16178
16419
  isGateCacheEnabled: () => isGateCacheEnabled,
16179
16420
  isGateSession: () => isGateSession,
16421
+ isGatedRef: () => isGatedRef,
16180
16422
  isNotifyActive: () => isNotifyActive,
16181
16423
  isOperatorInitiatedCommand: () => isOperatorInitiatedCommand,
16182
16424
  isPendingProposalDup: () => isPendingProposalDup,
@@ -16194,6 +16436,7 @@ __export(dist_exports2, {
16194
16436
  launchOneWorkspace: () => launchOneWorkspace,
16195
16437
  legacyKbDir: () => legacyKbDir,
16196
16438
  listSessions: () => listSessions,
16439
+ loadErrata: () => loadErrata,
16197
16440
  loadKbRules: () => loadKbRules,
16198
16441
  loadPending: () => loadPending,
16199
16442
  loadProposals: () => loadProposals,
@@ -16228,6 +16471,7 @@ __export(dist_exports2, {
16228
16471
  normalizedViews: () => normalizedViews,
16229
16472
  notePaneScreen: () => notePaneScreen,
16230
16473
  notifyToggle: () => notifyToggle,
16474
+ orphanedErrata: () => orphanedErrata,
16231
16475
  parseBearer: () => parseBearer,
16232
16476
  parseClassifierVerdict: () => parseClassifierVerdict,
16233
16477
  parseCommand: () => parseCommand,
@@ -16252,6 +16496,7 @@ __export(dist_exports2, {
16252
16496
  pruneTopics: () => pruneTopics,
16253
16497
  purgeExpiredWorkItems: () => purgeExpiredWorkItems,
16254
16498
  readAuditRecords: () => readAuditRecords,
16499
+ readBranchOnlyMessages: () => readBranchOnlyMessages,
16255
16500
  readCaptainAddress: () => readCaptainAddress,
16256
16501
  readCursor: () => readCursor,
16257
16502
  readEscalations: () => readEscalations,
@@ -16275,9 +16520,13 @@ __export(dist_exports2, {
16275
16520
  recoverStall: () => recoverStall,
16276
16521
  reduce: () => reduce,
16277
16522
  reduceLifecycle: () => reduceLifecycle,
16523
+ refCandidates: () => refCandidates,
16524
+ refCanon: () => refCanon,
16278
16525
  refFor: () => refFor,
16526
+ refPath: () => refPath,
16279
16527
  releaseDaemonLock: () => releaseDaemonLock,
16280
16528
  removeRunningMarker: () => removeRunningMarker,
16529
+ removeSource: () => removeSource,
16281
16530
  renderEnvAssignments: () => renderEnvAssignments,
16282
16531
  renderPlist: () => renderPlist,
16283
16532
  renderReport: () => renderReport,
@@ -16307,6 +16556,7 @@ __export(dist_exports2, {
16307
16556
  resolveSetupGroup: () => resolveSetupGroup,
16308
16557
  resolveSetupToken: () => resolveSetupToken,
16309
16558
  resolveSetupUserId: () => resolveSetupUserId,
16559
+ resolveSourceRef: () => resolveSourceRef,
16310
16560
  restartDaemonIfRunning: () => restartDaemonIfRunning,
16311
16561
  rotateIfNeeded: () => rotateIfNeeded,
16312
16562
  ruleAuditScope: () => ruleAuditScope,
@@ -16340,6 +16590,7 @@ __export(dist_exports2, {
16340
16590
  saveSessions: () => saveSessions,
16341
16591
  saveState: () => saveState,
16342
16592
  scanAndLogCitations: () => scanAndLogCitations,
16593
+ scanCitations: () => scanCitations,
16343
16594
  scheduleFile: () => scheduleFile,
16344
16595
  scoreKb: () => scoreKb,
16345
16596
  scoresFile: () => scoresFile,
@@ -16360,6 +16611,7 @@ __export(dist_exports2, {
16360
16611
  sideNextAutoName: () => sideNextAutoName,
16361
16612
  sideTitleFor: () => sideTitleFor,
16362
16613
  snapshotSources: () => snapshotSources,
16614
+ sourceCovers: () => sourceCovers,
16363
16615
  spansOverlap: () => spansOverlap,
16364
16616
  splitSections: () => splitSections,
16365
16617
  splitStatement: () => splitStatement,
@@ -16455,7 +16707,7 @@ var init_dist2 = __esm({
16455
16707
  });
16456
16708
 
16457
16709
  // packages/agents/dist/claude/registry.js
16458
- import fs30 from "fs";
16710
+ import fs31 from "fs";
16459
16711
  import { join as join17 } from "path";
16460
16712
  import { homedir as homedir7 } from "os";
16461
16713
  function parseRegistryDir(files, readFile7) {
@@ -16500,11 +16752,11 @@ function readClaudeStatus(task) {
16500
16752
  return void 0;
16501
16753
  let files;
16502
16754
  try {
16503
- files = fs30.readdirSync(CLAUDE_SESSIONS_DIR);
16755
+ files = fs31.readdirSync(CLAUDE_SESSIONS_DIR);
16504
16756
  } catch {
16505
16757
  return void 0;
16506
16758
  }
16507
- const entries = parseRegistryDir(files, (name) => fs30.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
16759
+ const entries = parseRegistryDir(files, (name) => fs31.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
16508
16760
  let entry;
16509
16761
  if (task.messagingSocketPath) {
16510
16762
  entry = entries.find((e) => e.messagingSocketPath === task.messagingSocketPath);
@@ -16519,11 +16771,11 @@ function readClaudeStatus(task) {
16519
16771
  function readClaudeStatusByCwd(cwd) {
16520
16772
  let files;
16521
16773
  try {
16522
- files = fs30.readdirSync(CLAUDE_SESSIONS_DIR);
16774
+ files = fs31.readdirSync(CLAUDE_SESSIONS_DIR);
16523
16775
  } catch {
16524
16776
  return void 0;
16525
16777
  }
16526
- const entries = parseRegistryDir(files, (name) => fs30.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
16778
+ const entries = parseRegistryDir(files, (name) => fs31.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
16527
16779
  const entry = entries.find((e) => e.cwd === cwd);
16528
16780
  if (!entry)
16529
16781
  return void 0;
@@ -16532,11 +16784,11 @@ function readClaudeStatusByCwd(cwd) {
16532
16784
  function readClaudeStatusBySocketPath(socketPath2) {
16533
16785
  let files;
16534
16786
  try {
16535
- files = fs30.readdirSync(CLAUDE_SESSIONS_DIR);
16787
+ files = fs31.readdirSync(CLAUDE_SESSIONS_DIR);
16536
16788
  } catch {
16537
16789
  return void 0;
16538
16790
  }
16539
- const entries = parseRegistryDir(files, (name) => fs30.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
16791
+ const entries = parseRegistryDir(files, (name) => fs31.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
16540
16792
  const entry = entries.find((e) => e.messagingSocketPath === socketPath2);
16541
16793
  if (!entry)
16542
16794
  return void 0;
@@ -16959,8 +17211,8 @@ var init_registry2 = __esm({
16959
17211
  });
16960
17212
 
16961
17213
  // packages/agents/dist/drivers/launch-cmd.js
16962
- import fs31 from "fs";
16963
- import path31 from "path";
17214
+ import fs32 from "fs";
17215
+ import path32 from "path";
16964
17216
  function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model, templatesDir, messagingSocketPath, sessionName, thinking, captainBoot, settingsPath) {
16965
17217
  const driver = registry.getDriver(agentName);
16966
17218
  if (driver.name === "claude") {
@@ -16987,14 +17239,14 @@ function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model,
16987
17239
  cmd += ` --settings ${settingsPath}`;
16988
17240
  }
16989
17241
  if (templatesDir) {
16990
- const roleFile2 = path31.join(templatesDir, `${role}.claude.md`);
16991
- const legacyRoleFile = path31.join(templatesDir, `${role}.CLAUDE.md`);
16992
- const actualRoleFile = fs31.existsSync(roleFile2) ? roleFile2 : fs31.existsSync(legacyRoleFile) ? legacyRoleFile : null;
17242
+ const roleFile2 = path32.join(templatesDir, `${role}.claude.md`);
17243
+ const legacyRoleFile = path32.join(templatesDir, `${role}.CLAUDE.md`);
17244
+ const actualRoleFile = fs32.existsSync(roleFile2) ? roleFile2 : fs32.existsSync(legacyRoleFile) ? legacyRoleFile : null;
16993
17245
  if (actualRoleFile) {
16994
17246
  cmd += ` --append-system-prompt-file ${actualRoleFile}`;
16995
17247
  }
16996
- const pluginDir = path31.join(templatesDir, "..", "plugin");
16997
- if (fs31.existsSync(pluginDir)) {
17248
+ const pluginDir = path32.join(templatesDir, "..", "plugin");
17249
+ if (fs32.existsSync(pluginDir)) {
16998
17250
  cmd += ` --plugin-dir ${pluginDir}`;
16999
17251
  }
17000
17252
  }
@@ -17003,14 +17255,14 @@ function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model,
17003
17255
  }
17004
17256
  return cmd;
17005
17257
  }
17006
- const roleFile = templatesDir ? path31.join(templatesDir, `${role}.${driver.templateSuffix}.md`) : void 0;
17258
+ const roleFile = templatesDir ? path32.join(templatesDir, `${role}.${driver.templateSuffix}.md`) : void 0;
17007
17259
  return driver.buildCommand({
17008
17260
  prompt: `You are a squadrant ${role}. Read your instructions from ${roleFile ?? role} and begin.`,
17009
17261
  workdir: process.cwd(),
17010
17262
  role,
17011
17263
  model,
17012
17264
  autoApprove: true,
17013
- promptFile: roleFile && fs31.existsSync(roleFile) ? roleFile : void 0,
17265
+ promptFile: roleFile && fs32.existsSync(roleFile) ? roleFile : void 0,
17014
17266
  ...captainBoot && role === "captain" ? {
17015
17267
  interactive: true,
17016
17268
  ...captainBoot.port ? { port: captainBoot.port } : {},
@@ -17040,8 +17292,8 @@ var init_drivers = __esm({
17040
17292
 
17041
17293
  // packages/agents/dist/projection/cursor.js
17042
17294
  import { mkdir, readFile, writeFile } from "fs/promises";
17043
- import path32 from "path";
17044
- import os10 from "os";
17295
+ import path33 from "path";
17296
+ import os9 from "os";
17045
17297
  function renderMdc(source) {
17046
17298
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
17047
17299
 
@@ -17089,7 +17341,7 @@ function createCursorEmitter() {
17089
17341
  if (scope === "user") {
17090
17342
  return [
17091
17343
  {
17092
- path: path32.join(os10.homedir(), ".cursor/rules/squadrant-global.mdc"),
17344
+ path: path33.join(os9.homedir(), ".cursor/rules/squadrant-global.mdc"),
17093
17345
  shared: false,
17094
17346
  format: "mdc"
17095
17347
  }
@@ -17099,7 +17351,7 @@ function createCursorEmitter() {
17099
17351
  return [];
17100
17352
  return [
17101
17353
  {
17102
- path: path32.join(projectRoot, ".cursor/rules/squadrant.mdc"),
17354
+ path: path33.join(projectRoot, ".cursor/rules/squadrant.mdc"),
17103
17355
  shared: false,
17104
17356
  format: "mdc"
17105
17357
  }
@@ -17116,7 +17368,7 @@ function createCursorEmitter() {
17116
17368
  diff: buildDiff(existing, generated)
17117
17369
  };
17118
17370
  }
17119
- await mkdir(path32.dirname(dest.path), { recursive: true });
17371
+ await mkdir(path33.dirname(dest.path), { recursive: true });
17120
17372
  await writeFile(dest.path, generated, "utf-8");
17121
17373
  return {
17122
17374
  written: true,
@@ -17167,8 +17419,8 @@ var init_marker = __esm({
17167
17419
 
17168
17420
  // packages/agents/dist/projection/codex.js
17169
17421
  import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
17170
- import path33 from "path";
17171
- import os11 from "os";
17422
+ import path34 from "path";
17423
+ import os10 from "os";
17172
17424
  function renderMarkdown(source) {
17173
17425
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
17174
17426
 
@@ -17192,7 +17444,7 @@ function createCodexEmitter() {
17192
17444
  destinations(scope, projectRoot) {
17193
17445
  if (scope === "user") {
17194
17446
  return [{
17195
- path: path33.join(os11.homedir(), ".codex/AGENTS.md"),
17447
+ path: path34.join(os10.homedir(), ".codex/AGENTS.md"),
17196
17448
  shared: true,
17197
17449
  format: "markdown"
17198
17450
  }];
@@ -17200,7 +17452,7 @@ function createCodexEmitter() {
17200
17452
  if (!projectRoot)
17201
17453
  return [];
17202
17454
  return [{
17203
- path: path33.join(projectRoot, "AGENTS.md"),
17455
+ path: path34.join(projectRoot, "AGENTS.md"),
17204
17456
  shared: true,
17205
17457
  format: "markdown"
17206
17458
  }];
@@ -17221,7 +17473,7 @@ ${existing ?? ""}
17221
17473
  ${generated}`
17222
17474
  };
17223
17475
  }
17224
- await mkdir2(path33.dirname(dest.path), { recursive: true });
17476
+ await mkdir2(path34.dirname(dest.path), { recursive: true });
17225
17477
  await writeFile2(dest.path, generated, "utf-8");
17226
17478
  return {
17227
17479
  written: true,
@@ -17239,8 +17491,8 @@ var init_codex2 = __esm({
17239
17491
 
17240
17492
  // packages/agents/dist/projection/gemini.js
17241
17493
  import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
17242
- import path34 from "path";
17243
- import os12 from "os";
17494
+ import path35 from "path";
17495
+ import os11 from "os";
17244
17496
  function renderMarkdown2(source) {
17245
17497
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
17246
17498
 
@@ -17264,7 +17516,7 @@ function createGeminiEmitter() {
17264
17516
  destinations(scope, projectRoot) {
17265
17517
  if (scope === "user") {
17266
17518
  return [{
17267
- path: path34.join(os12.homedir(), ".gemini/GEMINI.md"),
17519
+ path: path35.join(os11.homedir(), ".gemini/GEMINI.md"),
17268
17520
  shared: true,
17269
17521
  format: "markdown"
17270
17522
  }];
@@ -17272,7 +17524,7 @@ function createGeminiEmitter() {
17272
17524
  if (!projectRoot)
17273
17525
  return [];
17274
17526
  return [{
17275
- path: path34.join(projectRoot, "GEMINI.md"),
17527
+ path: path35.join(projectRoot, "GEMINI.md"),
17276
17528
  shared: true,
17277
17529
  format: "markdown"
17278
17530
  }];
@@ -17293,7 +17545,7 @@ ${existing ?? ""}
17293
17545
  ${generated}`
17294
17546
  };
17295
17547
  }
17296
- await mkdir3(path34.dirname(dest.path), { recursive: true });
17548
+ await mkdir3(path35.dirname(dest.path), { recursive: true });
17297
17549
  await writeFile3(dest.path, generated, "utf-8");
17298
17550
  return {
17299
17551
  written: true,
@@ -17311,8 +17563,8 @@ var init_gemini2 = __esm({
17311
17563
 
17312
17564
  // packages/agents/dist/projection/opencode.js
17313
17565
  import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
17314
- import path35 from "path";
17315
- import os13 from "os";
17566
+ import path36 from "path";
17567
+ import os12 from "os";
17316
17568
  function renderMarkdown3(source) {
17317
17569
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
17318
17570
 
@@ -17336,7 +17588,7 @@ function createOpencodeEmitter() {
17336
17588
  destinations(scope, projectRoot) {
17337
17589
  if (scope === "user") {
17338
17590
  return [{
17339
- path: path35.join(os13.homedir(), ".config", "opencode", "AGENTS.md"),
17591
+ path: path36.join(os12.homedir(), ".config", "opencode", "AGENTS.md"),
17340
17592
  shared: true,
17341
17593
  format: "markdown"
17342
17594
  }];
@@ -17344,7 +17596,7 @@ function createOpencodeEmitter() {
17344
17596
  if (!projectRoot)
17345
17597
  return [];
17346
17598
  return [{
17347
- path: path35.join(projectRoot, "AGENTS.md"),
17599
+ path: path36.join(projectRoot, "AGENTS.md"),
17348
17600
  shared: true,
17349
17601
  format: "markdown"
17350
17602
  }];
@@ -17365,7 +17617,7 @@ ${existing ?? ""}
17365
17617
  ${generated}`
17366
17618
  };
17367
17619
  }
17368
- await mkdir4(path35.dirname(dest.path), { recursive: true });
17620
+ await mkdir4(path36.dirname(dest.path), { recursive: true });
17369
17621
  await writeFile4(dest.path, generated, "utf-8");
17370
17622
  return {
17371
17623
  written: true,
@@ -17382,12 +17634,12 @@ var init_opencode2 = __esm({
17382
17634
  });
17383
17635
 
17384
17636
  // packages/agents/dist/projection/opencode-skills.js
17385
- import fs32 from "fs";
17386
- import os14 from "os";
17387
- import path36 from "path";
17637
+ import fs33 from "fs";
17638
+ import os13 from "os";
17639
+ import path37 from "path";
17388
17640
  function defaultOpencodeSkillsRoot(env = process.env) {
17389
- const configHome = env.XDG_CONFIG_HOME || path36.join(os14.homedir(), ".config");
17390
- return path36.join(configHome, "opencode", "skills");
17641
+ const configHome = env.XDG_CONFIG_HOME || path37.join(os13.homedir(), ".config");
17642
+ return path37.join(configHome, "opencode", "skills");
17391
17643
  }
17392
17644
  function parseSkillName(raw) {
17393
17645
  const fm = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
@@ -17401,62 +17653,62 @@ function parseSkillName(raw) {
17401
17653
  }
17402
17654
  function listSourceSkills(sourceSkillsDir) {
17403
17655
  const out = /* @__PURE__ */ new Map();
17404
- if (!fs32.existsSync(sourceSkillsDir))
17656
+ if (!fs33.existsSync(sourceSkillsDir))
17405
17657
  return out;
17406
- for (const entry of fs32.readdirSync(sourceSkillsDir, { withFileTypes: true })) {
17658
+ for (const entry of fs33.readdirSync(sourceSkillsDir, { withFileTypes: true })) {
17407
17659
  if (!entry.isDirectory())
17408
17660
  continue;
17409
- const skillPath = path36.join(sourceSkillsDir, entry.name, "SKILL.md");
17410
- if (!fs32.existsSync(skillPath))
17661
+ const skillPath = path37.join(sourceSkillsDir, entry.name, "SKILL.md");
17662
+ if (!fs33.existsSync(skillPath))
17411
17663
  continue;
17412
- const name = parseSkillName(fs32.readFileSync(skillPath, "utf-8"));
17664
+ const name = parseSkillName(fs33.readFileSync(skillPath, "utf-8"));
17413
17665
  if (name && SAFE_NAME.test(name))
17414
17666
  out.set(name, skillPath);
17415
17667
  }
17416
17668
  return out;
17417
17669
  }
17418
17670
  function hasMarker(dir) {
17419
- return fs32.existsSync(path36.join(dir, OPENCODE_SKILL_MARKER));
17671
+ return fs33.existsSync(path37.join(dir, OPENCODE_SKILL_MARKER));
17420
17672
  }
17421
17673
  function syncOpencodeSkills(opts) {
17422
17674
  const result = { written: [], unchanged: [], removed: [], skipped: [] };
17423
17675
  const wanted = listSourceSkills(opts.sourceSkillsDir);
17424
- if (wanted.size === 0 && !fs32.existsSync(opts.sourceSkillsDir))
17676
+ if (wanted.size === 0 && !fs33.existsSync(opts.sourceSkillsDir))
17425
17677
  return result;
17426
17678
  if (!opts.dryRun)
17427
- fs32.mkdirSync(opts.skillsRoot, { recursive: true });
17679
+ fs33.mkdirSync(opts.skillsRoot, { recursive: true });
17428
17680
  for (const [name, srcPath] of wanted) {
17429
- const targetDir = path36.join(opts.skillsRoot, name);
17430
- const targetSkill = path36.join(targetDir, "SKILL.md");
17681
+ const targetDir = path37.join(opts.skillsRoot, name);
17682
+ const targetSkill = path37.join(targetDir, "SKILL.md");
17431
17683
  const managed = hasMarker(targetDir);
17432
- if (fs32.existsSync(targetSkill) && !managed) {
17684
+ if (fs33.existsSync(targetSkill) && !managed) {
17433
17685
  result.skipped.push({ name, reason: "foreign-collision" });
17434
17686
  continue;
17435
17687
  }
17436
- const current = fs32.existsSync(targetSkill) ? fs32.readFileSync(targetSkill) : null;
17437
- if (current && current.equals(fs32.readFileSync(srcPath))) {
17688
+ const current = fs33.existsSync(targetSkill) ? fs33.readFileSync(targetSkill) : null;
17689
+ if (current && current.equals(fs33.readFileSync(srcPath))) {
17438
17690
  if (!managed && !opts.dryRun) {
17439
- fs32.writeFileSync(path36.join(targetDir, OPENCODE_SKILL_MARKER), MARKER_BODY);
17691
+ fs33.writeFileSync(path37.join(targetDir, OPENCODE_SKILL_MARKER), MARKER_BODY);
17440
17692
  }
17441
17693
  result.unchanged.push(name);
17442
17694
  continue;
17443
17695
  }
17444
17696
  if (!opts.dryRun) {
17445
- fs32.mkdirSync(targetDir, { recursive: true });
17446
- fs32.copyFileSync(srcPath, targetSkill);
17447
- fs32.writeFileSync(path36.join(targetDir, OPENCODE_SKILL_MARKER), MARKER_BODY);
17697
+ fs33.mkdirSync(targetDir, { recursive: true });
17698
+ fs33.copyFileSync(srcPath, targetSkill);
17699
+ fs33.writeFileSync(path37.join(targetDir, OPENCODE_SKILL_MARKER), MARKER_BODY);
17448
17700
  }
17449
17701
  result.written.push(name);
17450
17702
  }
17451
- if (fs32.existsSync(opts.skillsRoot)) {
17452
- for (const entry of fs32.readdirSync(opts.skillsRoot, { withFileTypes: true })) {
17703
+ if (fs33.existsSync(opts.skillsRoot)) {
17704
+ for (const entry of fs33.readdirSync(opts.skillsRoot, { withFileTypes: true })) {
17453
17705
  if (!entry.isDirectory() || wanted.has(entry.name))
17454
17706
  continue;
17455
- const dir = path36.join(opts.skillsRoot, entry.name);
17707
+ const dir = path37.join(opts.skillsRoot, entry.name);
17456
17708
  if (!hasMarker(dir))
17457
17709
  continue;
17458
17710
  if (!opts.dryRun)
17459
- fs32.rmSync(dir, { recursive: true, force: true });
17711
+ fs33.rmSync(dir, { recursive: true, force: true });
17460
17712
  result.removed.push(entry.name);
17461
17713
  }
17462
17714
  }
@@ -17464,7 +17716,7 @@ function syncOpencodeSkills(opts) {
17464
17716
  }
17465
17717
  function syncShippedOpencodeSkills(opts) {
17466
17718
  return syncOpencodeSkills({
17467
- sourceSkillsDir: path36.join(opts.pkgRoot, "plugin", "skills"),
17719
+ sourceSkillsDir: path37.join(opts.pkgRoot, "plugin", "skills"),
17468
17720
  skillsRoot: opts.skillsRoot ?? defaultOpencodeSkillsRoot(),
17469
17721
  dryRun: opts.dryRun
17470
17722
  });
@@ -17480,8 +17732,8 @@ var init_opencode_skills = __esm({
17480
17732
 
17481
17733
  // packages/agents/dist/projection/claude.js
17482
17734
  import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
17483
- import path37 from "path";
17484
- import os15 from "os";
17735
+ import path38 from "path";
17736
+ import os14 from "os";
17485
17737
  function renderMarkdown4(source) {
17486
17738
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
17487
17739
 
@@ -17505,7 +17757,7 @@ function createClaudeEmitter() {
17505
17757
  destinations(scope, projectRoot) {
17506
17758
  if (scope === "user") {
17507
17759
  return [{
17508
- path: path37.join(os15.homedir(), ".claude/CLAUDE.md"),
17760
+ path: path38.join(os14.homedir(), ".claude/CLAUDE.md"),
17509
17761
  shared: true,
17510
17762
  format: "markdown"
17511
17763
  }];
@@ -17513,7 +17765,7 @@ function createClaudeEmitter() {
17513
17765
  if (!projectRoot)
17514
17766
  return [];
17515
17767
  return [{
17516
- path: path37.join(projectRoot, "CLAUDE.md"),
17768
+ path: path38.join(projectRoot, "CLAUDE.md"),
17517
17769
  shared: true,
17518
17770
  format: "markdown"
17519
17771
  }];
@@ -17534,7 +17786,7 @@ ${existing ?? ""}
17534
17786
  ${generated}`
17535
17787
  };
17536
17788
  }
17537
- await mkdir5(path37.dirname(dest.path), { recursive: true });
17789
+ await mkdir5(path38.dirname(dest.path), { recursive: true });
17538
17790
  await writeFile5(dest.path, generated, "utf-8");
17539
17791
  return {
17540
17792
  written: true,
@@ -18569,8 +18821,8 @@ function resolveLastAssistantText(payload) {
18569
18821
  const derived = deriveTranscriptPath(p?.session_id, cwd);
18570
18822
  if (derived)
18571
18823
  candidates.push(derived);
18572
- for (const path59 of candidates) {
18573
- const text = readLastAssistantText(path59);
18824
+ for (const path60 of candidates) {
18825
+ const text = readLastAssistantText(path60);
18574
18826
  if (text != null)
18575
18827
  return text;
18576
18828
  }
@@ -19459,22 +19711,22 @@ var init_receipt_listener = __esm({
19459
19711
  });
19460
19712
 
19461
19713
  // packages/agents/dist/claude/api-key-approval.js
19462
- import fs33 from "fs";
19463
- import os16 from "os";
19464
- import path38 from "path";
19714
+ import fs34 from "fs";
19715
+ import os15 from "os";
19716
+ import path39 from "path";
19465
19717
  function ensureClaudeApiKeyApproved(apiKey, opts = {}) {
19466
19718
  const suffix = apiKey.slice(-20);
19467
19719
  if (!suffix)
19468
19720
  return { changed: false, reason: "empty api key" };
19469
- const file = opts.claudeJsonPath ?? path38.join(os16.homedir(), ".claude.json");
19721
+ const file = opts.claudeJsonPath ?? path39.join(os15.homedir(), ".claude.json");
19470
19722
  const log = opts.log ?? (() => {
19471
19723
  });
19472
- if (!fs33.existsSync(file)) {
19724
+ if (!fs34.existsSync(file)) {
19473
19725
  return { changed: false, reason: "no ~/.claude.json yet \u2014 skipping routed-key pre-approval" };
19474
19726
  }
19475
19727
  let doc;
19476
19728
  try {
19477
- doc = JSON.parse(fs33.readFileSync(file, "utf8"));
19729
+ doc = JSON.parse(fs34.readFileSync(file, "utf8"));
19478
19730
  } catch {
19479
19731
  return {
19480
19732
  changed: false,
@@ -19496,11 +19748,11 @@ function ensureClaudeApiKeyApproved(apiKey, opts = {}) {
19496
19748
  };
19497
19749
  const tmp = `${file}.squadrant-tmp`;
19498
19750
  try {
19499
- fs33.writeFileSync(tmp, JSON.stringify(doc, null, 2) + "\n", { mode: fs33.statSync(file).mode & 511 });
19500
- fs33.renameSync(tmp, file);
19751
+ fs34.writeFileSync(tmp, JSON.stringify(doc, null, 2) + "\n", { mode: fs34.statSync(file).mode & 511 });
19752
+ fs34.renameSync(tmp, file);
19501
19753
  } catch (e) {
19502
19754
  try {
19503
- fs33.unlinkSync(tmp);
19755
+ fs34.unlinkSync(tmp);
19504
19756
  } catch {
19505
19757
  }
19506
19758
  return {
@@ -19609,10 +19861,10 @@ var init_http_channel = __esm({
19609
19861
  return cached;
19610
19862
  const directory = this.directoryFor?.(taskId);
19611
19863
  const createdAfterMs = this.createdAfterFor?.(taskId);
19612
- for (const path59 of ["/session?", "/api/session?"]) {
19864
+ for (const path60 of ["/session?", "/api/session?"]) {
19613
19865
  let res;
19614
19866
  try {
19615
- res = await this.request(`http://127.0.0.1:${port}${path59}`, { method: "GET" });
19867
+ res = await this.request(`http://127.0.0.1:${port}${path60}`, { method: "GET" });
19616
19868
  } catch {
19617
19869
  return void 0;
19618
19870
  }
@@ -19692,12 +19944,12 @@ var init_fact_adapter = __esm({
19692
19944
  });
19693
19945
 
19694
19946
  // packages/agents/dist/rules-hooks/install.js
19695
- import fs34 from "fs";
19696
- import os17 from "os";
19697
- import path39 from "path";
19947
+ import fs35 from "fs";
19948
+ import os16 from "os";
19949
+ import path40 from "path";
19698
19950
  function readJson(file) {
19699
19951
  try {
19700
- const v2 = JSON.parse(fs34.readFileSync(file, "utf8"));
19952
+ const v2 = JSON.parse(fs35.readFileSync(file, "utf8"));
19701
19953
  return typeof v2 === "object" && v2 !== null && !Array.isArray(v2) ? v2 : {};
19702
19954
  } catch (err) {
19703
19955
  if (err.code === "ENOENT")
@@ -19719,24 +19971,24 @@ function mergeHooks(root, agent, specs, cli, timeout) {
19719
19971
  function writeIfChanged(file, next, dryRun) {
19720
19972
  let prev = null;
19721
19973
  try {
19722
- prev = fs34.readFileSync(file, "utf8");
19974
+ prev = fs35.readFileSync(file, "utf8");
19723
19975
  } catch {
19724
19976
  }
19725
19977
  const changed = prev !== next;
19726
19978
  if (changed && !dryRun) {
19727
- fs34.mkdirSync(path39.dirname(file), { recursive: true });
19728
- fs34.writeFileSync(file, next);
19979
+ fs35.mkdirSync(path40.dirname(file), { recursive: true });
19980
+ fs35.writeFileSync(file, next);
19729
19981
  }
19730
19982
  return { path: file, changed };
19731
19983
  }
19732
19984
  function installCodexRulesHooks(opts = {}) {
19733
19985
  const env = opts.env ?? process.env;
19734
- const file = opts.target ?? path39.join(env.CODEX_HOME ?? path39.join(os17.homedir(), ".codex"), "hooks.json");
19986
+ const file = opts.target ?? path40.join(env.CODEX_HOME ?? path40.join(os16.homedir(), ".codex"), "hooks.json");
19735
19987
  const merged = mergeHooks(readJson(file), "codex", CODEX_HOOKS, opts.cli ?? "squadrant", 5);
19736
19988
  return writeIfChanged(file, JSON.stringify(merged, null, 2) + "\n", opts.dryRun);
19737
19989
  }
19738
19990
  function installGeminiRulesHooks(opts = {}) {
19739
- const file = opts.target ?? path39.join(os17.homedir(), ".gemini", "settings.json");
19991
+ const file = opts.target ?? path40.join(os16.homedir(), ".gemini", "settings.json");
19740
19992
  const merged = mergeHooks(readJson(file), "gemini", GEMINI_HOOKS, opts.cli ?? "squadrant", 5e3);
19741
19993
  return writeIfChanged(file, JSON.stringify(merged, null, 2) + "\n", opts.dryRun);
19742
19994
  }
@@ -19777,9 +20029,9 @@ export const SquadrantRules = async ({ directory }) => {
19777
20029
  }
19778
20030
  function installOpencodeRulesHooks(opts = {}) {
19779
20031
  const env = opts.env ?? process.env;
19780
- const file = opts.target ?? path39.join(env.XDG_CONFIG_HOME || path39.join(os17.homedir(), ".config"), "opencode", "plugin", "squadrant-rules.js");
20032
+ const file = opts.target ?? path40.join(env.XDG_CONFIG_HOME || path40.join(os16.homedir(), ".config"), "opencode", "plugin", "squadrant-rules.js");
19781
20033
  try {
19782
- if (!fs34.readFileSync(file, "utf8").startsWith(OPENCODE_PLUGIN_MARKER))
20034
+ if (!fs35.readFileSync(file, "utf8").startsWith(OPENCODE_PLUGIN_MARKER))
19783
20035
  return { path: file, changed: false, skipped: "foreign-file" };
19784
20036
  } catch {
19785
20037
  }
@@ -19908,12 +20160,12 @@ var init_dist3 = __esm({
19908
20160
  });
19909
20161
 
19910
20162
  // packages/workspaces/dist/runtimes/cmux.js
19911
- import { execFile as execFile3, execFileSync as execFileSync8 } from "child_process";
20163
+ import { execFile as execFile3, execFileSync as execFileSync9 } from "child_process";
19912
20164
  function isInsideCmux() {
19913
20165
  return !!process.env.CMUX_WORKSPACE_ID;
19914
20166
  }
19915
20167
  function cmuxLocal(args) {
19916
- return execFileSync8(resolveCmuxBin(), args, {
20168
+ return execFileSync9(resolveCmuxBin(), args, {
19917
20169
  encoding: "utf-8",
19918
20170
  stdio: ["ignore", "pipe", "pipe"],
19919
20171
  timeout: CMUX_TIMEOUT
@@ -20679,14 +20931,14 @@ var init_runtimes = __esm({
20679
20931
  });
20680
20932
 
20681
20933
  // packages/workspaces/dist/notifiers/cmux.js
20682
- import { execFile as execFileCb, execFileSync as execFileSync9 } from "child_process";
20934
+ import { execFile as execFileCb, execFileSync as execFileSync10 } from "child_process";
20683
20935
  import { promisify as promisify3 } from "util";
20684
20936
  function createCmuxNotifier(_scope) {
20685
20937
  return {
20686
20938
  name: "cmux",
20687
20939
  async probe() {
20688
20940
  try {
20689
- execFileSync9(resolveCmuxBin(), ["capabilities"], { encoding: "utf-8", stdio: "pipe", timeout: CMUX_TIMEOUT });
20941
+ execFileSync10(resolveCmuxBin(), ["capabilities"], { encoding: "utf-8", stdio: "pipe", timeout: CMUX_TIMEOUT });
20690
20942
  return { installed: true, reachable: true };
20691
20943
  } catch (err) {
20692
20944
  const code = err.code;
@@ -20755,13 +21007,13 @@ var init_notifiers = __esm({
20755
21007
  });
20756
21008
 
20757
21009
  // packages/workspaces/dist/workspaces/obsidian.js
20758
- import fs35 from "fs/promises";
21010
+ import fs36 from "fs/promises";
20759
21011
  import { existsSync as existsSync11 } from "fs";
20760
- import path40 from "path";
21012
+ import path41 from "path";
20761
21013
  function resolveInRoot(root, relative) {
20762
- const joined = path40.resolve(root, relative);
20763
- const normalized2 = path40.resolve(root) + path40.sep;
20764
- if (joined !== path40.resolve(root) && !joined.startsWith(normalized2)) {
21014
+ const joined = path41.resolve(root, relative);
21015
+ const normalized2 = path41.resolve(root) + path41.sep;
21016
+ if (joined !== path41.resolve(root) && !joined.startsWith(normalized2)) {
20765
21017
  throw new Error(`Path '${relative}' escapes workspace root`);
20766
21018
  }
20767
21019
  return joined;
@@ -20780,16 +21032,16 @@ function createObsidianDriver(scope) {
20780
21032
  };
20781
21033
  },
20782
21034
  async read(rel) {
20783
- return fs35.readFile(resolveInRoot(root, rel), "utf-8");
21035
+ return fs36.readFile(resolveInRoot(root, rel), "utf-8");
20784
21036
  },
20785
21037
  async write(rel, content) {
20786
21038
  const abs = resolveInRoot(root, rel);
20787
- await fs35.mkdir(path40.dirname(abs), { recursive: true });
20788
- await fs35.writeFile(abs, content);
21039
+ await fs36.mkdir(path41.dirname(abs), { recursive: true });
21040
+ await fs36.writeFile(abs, content);
20789
21041
  },
20790
21042
  async exists(rel) {
20791
21043
  try {
20792
- await fs35.access(resolveInRoot(root, rel));
21044
+ await fs36.access(resolveInRoot(root, rel));
20793
21045
  return true;
20794
21046
  } catch {
20795
21047
  return false;
@@ -20797,13 +21049,13 @@ function createObsidianDriver(scope) {
20797
21049
  },
20798
21050
  async list(rel) {
20799
21051
  try {
20800
- return await fs35.readdir(resolveInRoot(root, rel));
21052
+ return await fs36.readdir(resolveInRoot(root, rel));
20801
21053
  } catch {
20802
21054
  return [];
20803
21055
  }
20804
21056
  },
20805
21057
  async mkdir(rel) {
20806
- await fs35.mkdir(resolveInRoot(root, rel), { recursive: true });
21058
+ await fs36.mkdir(resolveInRoot(root, rel), { recursive: true });
20807
21059
  }
20808
21060
  };
20809
21061
  }
@@ -21012,9 +21264,9 @@ var init_events_bridge = __esm({
21012
21264
  });
21013
21265
 
21014
21266
  // packages/workspaces/dist/cmux-daemon/store-fingerprint.js
21015
- import { execFileSync as execFileSync10 } from "child_process";
21267
+ import { execFileSync as execFileSync11 } from "child_process";
21016
21268
  function defaultExecPs(pid) {
21017
- return execFileSync10("ps", ["-o", "command=", "-p", String(pid)], {
21269
+ return execFileSync11("ps", ["-o", "command=", "-p", String(pid)], {
21018
21270
  encoding: "utf-8",
21019
21271
  stdio: ["ignore", "pipe", "ignore"]
21020
21272
  });
@@ -21217,9 +21469,9 @@ function defaultListFiles(dir) {
21217
21469
  return [];
21218
21470
  }
21219
21471
  }
21220
- function defaultReadFile(path59) {
21472
+ function defaultReadFile(path60) {
21221
21473
  try {
21222
- return readFileSync17(path59, "utf-8");
21474
+ return readFileSync17(path60, "utf-8");
21223
21475
  } catch {
21224
21476
  return void 0;
21225
21477
  }
@@ -21712,16 +21964,16 @@ function extractDetail(sub, payload) {
21712
21964
  }
21713
21965
  return void 0;
21714
21966
  }
21715
- function defaultReadFile2(path59) {
21967
+ function defaultReadFile2(path60) {
21716
21968
  try {
21717
- return readFileSync18(path59, "utf-8");
21969
+ return readFileSync18(path60, "utf-8");
21718
21970
  } catch {
21719
21971
  return void 0;
21720
21972
  }
21721
21973
  }
21722
- function defaultWriteFile(path59, content) {
21723
- mkdirSync8(path59.replace(/\/[^/]+$/, ""), { recursive: true });
21724
- writeFileSync11(path59, content, "utf-8");
21974
+ function defaultWriteFile(path60, content) {
21975
+ mkdirSync8(path60.replace(/\/[^/]+$/, ""), { recursive: true });
21976
+ writeFileSync11(path60, content, "utf-8");
21725
21977
  }
21726
21978
  var CLAUDE_HOOK_EVENTS, DEFAULT_CLI_CMD, FOREIGN_AUTO_GATE_MARKER, SUBAGENT_MODEL_KEY, SMALL_FAST_MODEL_KEY, NativeHookSource;
21727
21979
  var init_native_hook_source = __esm({
@@ -22293,9 +22545,9 @@ init_dist();
22293
22545
  init_dist4();
22294
22546
  import { Command } from "commander";
22295
22547
  import { execSync as execSync7 } from "child_process";
22296
- import fs36 from "fs";
22548
+ import fs37 from "fs";
22297
22549
  import { stat } from "fs/promises";
22298
- import path41 from "path";
22550
+ import path42 from "path";
22299
22551
  import chalk3 from "chalk";
22300
22552
 
22301
22553
  // packages/cli/src/commands/health-view.ts
@@ -22397,7 +22649,7 @@ function settingsHaveAgentTeams() {
22397
22649
  try {
22398
22650
  const home = process.env.HOME || "";
22399
22651
  const settings = JSON.parse(
22400
- fs36.readFileSync(`${home}/.claude/settings.json`, "utf-8")
22652
+ fs37.readFileSync(`${home}/.claude/settings.json`, "utf-8")
22401
22653
  );
22402
22654
  return settings?.env?.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS === "1";
22403
22655
  } catch {
@@ -22408,7 +22660,7 @@ function pluginInstalled(pluginKey) {
22408
22660
  try {
22409
22661
  const home = process.env.HOME || "";
22410
22662
  const plugins = JSON.parse(
22411
- fs36.readFileSync(
22663
+ fs37.readFileSync(
22412
22664
  `${home}/.claude/plugins/installed_plugins.json`,
22413
22665
  "utf-8"
22414
22666
  )
@@ -22432,9 +22684,9 @@ function tryGetVersion(cmd) {
22432
22684
  }
22433
22685
  function candidateGlobalInstalls(roots) {
22434
22686
  const out = [];
22435
- if (roots.npm) out.push({ manager: "npm", packageJsonPath: path41.join(roots.npm, "squadrant", "package.json") });
22436
- if (roots.pnpm) out.push({ manager: "pnpm", packageJsonPath: path41.join(roots.pnpm, "squadrant", "package.json") });
22437
- if (roots.yarn) out.push({ manager: "yarn", packageJsonPath: path41.join(roots.yarn, "node_modules", "squadrant", "package.json") });
22687
+ if (roots.npm) out.push({ manager: "npm", packageJsonPath: path42.join(roots.npm, "squadrant", "package.json") });
22688
+ if (roots.pnpm) out.push({ manager: "pnpm", packageJsonPath: path42.join(roots.pnpm, "squadrant", "package.json") });
22689
+ if (roots.yarn) out.push({ manager: "yarn", packageJsonPath: path42.join(roots.yarn, "node_modules", "squadrant", "package.json") });
22438
22690
  return out;
22439
22691
  }
22440
22692
  function findInstalledSquadrants(candidates, readVersion) {
@@ -22454,7 +22706,7 @@ ${lines.join("\n")}
22454
22706
  }
22455
22707
  function readPackageVersion(packageJsonPath) {
22456
22708
  try {
22457
- const pkg2 = JSON.parse(fs36.readFileSync(packageJsonPath, "utf-8"));
22709
+ const pkg2 = JSON.parse(fs37.readFileSync(packageJsonPath, "utf-8"));
22458
22710
  return typeof pkg2.version === "string" ? pkg2.version : null;
22459
22711
  } catch {
22460
22712
  return null;
@@ -22537,7 +22789,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
22537
22789
  ));
22538
22790
  checkOptional(
22539
22791
  "Obsidian installed",
22540
- commandExists("obsidian") || fs36.existsSync("/Applications/Obsidian.app"),
22792
+ commandExists("obsidian") || fs37.existsSync("/Applications/Obsidian.app"),
22541
22793
  "viewer for the hub vault; the vault is plain Markdown",
22542
22794
  "Install from: https://obsidian.md"
22543
22795
  );
@@ -22634,7 +22886,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
22634
22886
  const emitter = projectionRegistry.get(name);
22635
22887
  const [userDest] = emitter.destinations("user");
22636
22888
  if (!userDest) continue;
22637
- const dir = path41.dirname(userDest.path);
22889
+ const dir = path42.dirname(userDest.path);
22638
22890
  let status;
22639
22891
  try {
22640
22892
  await stat(dir);
@@ -22655,7 +22907,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
22655
22907
  results.push(
22656
22908
  check(
22657
22909
  "Squadrant config exists",
22658
- fs36.existsSync(
22910
+ fs37.existsSync(
22659
22911
  process.env.SQUADRANT_CONFIG || `${process.env.HOME}/.config/squadrant/config.json`
22660
22912
  ),
22661
22913
  "Run: squadrant init"
@@ -22739,9 +22991,9 @@ init_dist();
22739
22991
  init_dist4();
22740
22992
  init_dist();
22741
22993
  import { Command as Command2 } from "commander";
22742
- import fs37 from "fs";
22743
- import path42 from "path";
22744
- import os18 from "os";
22994
+ import fs38 from "fs";
22995
+ import path43 from "path";
22996
+ import os17 from "os";
22745
22997
  import readline from "readline";
22746
22998
  import chalk4 from "chalk";
22747
22999
 
@@ -22991,22 +23243,22 @@ function detectClaudeAuth(probe = {}) {
22991
23243
  // packages/cli/src/commands/init.ts
22992
23244
  init_dist3();
22993
23245
  function findPackageRoot() {
22994
- let dir = path42.dirname(new URL(import.meta.url).pathname);
23246
+ let dir = path43.dirname(new URL(import.meta.url).pathname);
22995
23247
  while (dir !== "/") {
22996
- if (fs37.existsSync(path42.join(dir, "package.json"))) return dir;
22997
- dir = path42.dirname(dir);
23248
+ if (fs38.existsSync(path43.join(dir, "package.json"))) return dir;
23249
+ dir = path43.dirname(dir);
22998
23250
  }
22999
23251
  return process.cwd();
23000
23252
  }
23001
23253
  function copyDirRecursive(src, dest) {
23002
- fs37.mkdirSync(dest, { recursive: true });
23003
- for (const entry of fs37.readdirSync(src, { withFileTypes: true })) {
23004
- const srcPath = path42.join(src, entry.name);
23005
- const destPath = path42.join(dest, entry.name);
23254
+ fs38.mkdirSync(dest, { recursive: true });
23255
+ for (const entry of fs38.readdirSync(src, { withFileTypes: true })) {
23256
+ const srcPath = path43.join(src, entry.name);
23257
+ const destPath = path43.join(dest, entry.name);
23006
23258
  if (entry.isDirectory()) {
23007
23259
  copyDirRecursive(srcPath, destPath);
23008
23260
  } else {
23009
- fs37.copyFileSync(srcPath, destPath);
23261
+ fs38.copyFileSync(srcPath, destPath);
23010
23262
  }
23011
23263
  }
23012
23264
  }
@@ -23048,7 +23300,7 @@ function warnIfNoAnthropic(preset, auth) {
23048
23300
  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) => {
23049
23301
  const hubPath = resolveHome(opts.hub);
23050
23302
  const pkgRoot = findPackageRoot();
23051
- const configDir = path42.join(os18.homedir(), ".config", "squadrant");
23303
+ const configDir = path43.join(os17.homedir(), ".config", "squadrant");
23052
23304
  const isTTY = process.stdin.isTTY === true;
23053
23305
  console.log(chalk4.bold("\nSquadrant Init\n"));
23054
23306
  let explicitPreset;
@@ -23086,8 +23338,8 @@ var initCommand = new Command2("init").description("Guided first-time setup: pro
23086
23338
  }
23087
23339
  const wsRegistry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
23088
23340
  try {
23089
- if (fs37.existsSync(DEFAULT_CONFIG_PATH)) {
23090
- const existing = JSON.parse(fs37.readFileSync(DEFAULT_CONFIG_PATH, "utf-8"));
23341
+ if (fs38.existsSync(DEFAULT_CONFIG_PATH)) {
23342
+ const existing = JSON.parse(fs38.readFileSync(DEFAULT_CONFIG_PATH, "utf-8"));
23091
23343
  wsRegistry.get(existing.workspace ?? "obsidian");
23092
23344
  }
23093
23345
  } catch (err) {
@@ -23095,7 +23347,7 @@ var initCommand = new Command2("init").description("Guided first-time setup: pro
23095
23347
  return;
23096
23348
  }
23097
23349
  stepHeader(1, 5, "Hub vault");
23098
- const configExists = fs37.existsSync(DEFAULT_CONFIG_PATH);
23350
+ const configExists = fs38.existsSync(DEFAULT_CONFIG_PATH);
23099
23351
  let config;
23100
23352
  if (configExists) {
23101
23353
  console.log(chalk4.yellow(" \u26A0 Config already exists, skipping creation"));
@@ -23133,37 +23385,37 @@ var initCommand = new Command2("init").description("Guided first-time setup: pro
23133
23385
  saveConfig(config);
23134
23386
  console.log(chalk4.green(` \u2714 Config updated at ${DEFAULT_CONFIG_PATH}`));
23135
23387
  }
23136
- const hubTemplate = path42.join(pkgRoot, "obsidian", "hub");
23137
- if (fs37.existsSync(hubPath)) {
23388
+ const hubTemplate = path43.join(pkgRoot, "obsidian", "hub");
23389
+ if (fs38.existsSync(hubPath)) {
23138
23390
  console.log(chalk4.yellow(` \u26A0 Hub vault already exists at ${hubPath}`));
23139
- } else if (fs37.existsSync(hubTemplate)) {
23391
+ } else if (fs38.existsSync(hubTemplate)) {
23140
23392
  copyDirRecursive(hubTemplate, hubPath);
23141
23393
  console.log(chalk4.green(` \u2714 Hub vault scaffolded at ${hubPath}`));
23142
23394
  } else {
23143
- fs37.mkdirSync(hubPath, { recursive: true });
23395
+ fs38.mkdirSync(hubPath, { recursive: true });
23144
23396
  console.log(chalk4.yellow(` \u26A0 Hub template not found; created empty directory at ${hubPath}`));
23145
23397
  }
23146
- const hubDashboardSrc = path42.join(pkgRoot, "obsidian", "hub", "dashboard.md");
23147
- const hubDashboardDest = path42.join(hubPath, "dashboard.md");
23148
- if (fs37.existsSync(hubDashboardSrc)) {
23149
- fs37.copyFileSync(hubDashboardSrc, hubDashboardDest);
23398
+ const hubDashboardSrc = path43.join(pkgRoot, "obsidian", "hub", "dashboard.md");
23399
+ const hubDashboardDest = path43.join(hubPath, "dashboard.md");
23400
+ if (fs38.existsSync(hubDashboardSrc)) {
23401
+ fs38.copyFileSync(hubDashboardSrc, hubDashboardDest);
23150
23402
  console.log(chalk4.green(` \u2714 Dashboard refreshed`));
23151
23403
  }
23152
- fs37.mkdirSync(path42.join(hubPath, "projects"), { recursive: true });
23404
+ fs38.mkdirSync(path43.join(hubPath, "projects"), { recursive: true });
23153
23405
  ensureRuntimeSynced({ sourceRoot: pkgRoot, runtimeRoot: configDir });
23154
23406
  console.log(chalk4.green(` \u2714 Runtime assets synced to ${configDir}`));
23155
23407
  stepHeader(2, 5, "Agent + projection setup");
23156
- const settingsPath = path42.join(os18.homedir(), ".claude", "settings.json");
23408
+ const settingsPath = path43.join(os17.homedir(), ".claude", "settings.json");
23157
23409
  try {
23158
23410
  let settings = {};
23159
- if (fs37.existsSync(settingsPath)) {
23160
- settings = JSON.parse(fs37.readFileSync(settingsPath, "utf-8"));
23411
+ if (fs38.existsSync(settingsPath)) {
23412
+ settings = JSON.parse(fs38.readFileSync(settingsPath, "utf-8"));
23161
23413
  }
23162
23414
  const env = settings.env || {};
23163
23415
  if (env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS !== "1") {
23164
23416
  settings.env = { ...env, CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "1" };
23165
- fs37.mkdirSync(path42.dirname(settingsPath), { recursive: true });
23166
- fs37.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
23417
+ fs38.mkdirSync(path43.dirname(settingsPath), { recursive: true });
23418
+ fs38.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
23167
23419
  console.log(chalk4.green(" \u2714 Agent Teams enabled in ~/.claude/settings.json"));
23168
23420
  } else {
23169
23421
  console.log(chalk4.green(" \u2714 Agent Teams already enabled"));
@@ -23216,7 +23468,7 @@ var initCommand = new Command2("init").description("Guided first-time setup: pro
23216
23468
  chalk4.cyan(" Absolute path to your first project (Enter to skip): ")
23217
23469
  );
23218
23470
  if (projectPath) {
23219
- const projectName = path42.basename(projectPath);
23471
+ const projectName = path43.basename(projectPath);
23220
23472
  console.log(chalk4.bold(`
23221
23473
  Run this to register it:`));
23222
23474
  console.log(chalk4.cyan(` squadrant projects add ${projectName} ${projectPath}
@@ -23244,8 +23496,8 @@ var initCommand = new Command2("init").description("Guided first-time setup: pro
23244
23496
  init_dist();
23245
23497
  init_dist2();
23246
23498
  import { Command as Command3 } from "commander";
23247
- import fs38 from "fs";
23248
- import path43 from "path";
23499
+ import fs39 from "fs";
23500
+ import path44 from "path";
23249
23501
  import chalk5 from "chalk";
23250
23502
  function restartAfterProjectsAdd(opts) {
23251
23503
  const doRestart = opts.doRestart ?? restartDaemonIfRunning;
@@ -23257,22 +23509,22 @@ function restartAfterProjectsAdd(opts) {
23257
23509
  }
23258
23510
  }
23259
23511
  function findPackageRoot2() {
23260
- let dir = path43.dirname(new URL(import.meta.url).pathname);
23512
+ let dir = path44.dirname(new URL(import.meta.url).pathname);
23261
23513
  while (dir !== "/") {
23262
- if (fs38.existsSync(path43.join(dir, "package.json"))) return dir;
23263
- dir = path43.dirname(dir);
23514
+ if (fs39.existsSync(path44.join(dir, "package.json"))) return dir;
23515
+ dir = path44.dirname(dir);
23264
23516
  }
23265
23517
  return process.cwd();
23266
23518
  }
23267
23519
  function copyDirRecursive2(src, dest) {
23268
- fs38.mkdirSync(dest, { recursive: true });
23269
- for (const entry of fs38.readdirSync(src, { withFileTypes: true })) {
23270
- const srcPath = path43.join(src, entry.name);
23271
- const destPath = path43.join(dest, entry.name);
23520
+ fs39.mkdirSync(dest, { recursive: true });
23521
+ for (const entry of fs39.readdirSync(src, { withFileTypes: true })) {
23522
+ const srcPath = path44.join(src, entry.name);
23523
+ const destPath = path44.join(dest, entry.name);
23272
23524
  if (entry.isDirectory()) {
23273
23525
  copyDirRecursive2(srcPath, destPath);
23274
23526
  } else {
23275
- fs38.copyFileSync(srcPath, destPath);
23527
+ fs39.copyFileSync(srcPath, destPath);
23276
23528
  }
23277
23529
  }
23278
23530
  }
@@ -23308,7 +23560,7 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
23308
23560
  process.exit(1);
23309
23561
  }
23310
23562
  const resolvedPath = resolveHome(projectPath);
23311
- if (!fs38.existsSync(path43.join(resolvedPath, ".git"))) {
23563
+ if (!fs39.existsSync(path44.join(resolvedPath, ".git"))) {
23312
23564
  console.log(chalk5.yellow(`
23313
23565
  \u26A0 No .git found at ${resolvedPath}. Make sure this is the project root, not a parent directory.
23314
23566
  `));
@@ -23361,7 +23613,7 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
23361
23613
  \u26A0 Group '${group}' already has '${primary[0]}' as primary. Overriding.`));
23362
23614
  }
23363
23615
  }
23364
- const spokeVault = opts.spoke ? resolveHome(opts.spoke) : path43.join(config.hubVault, "spokes", name);
23616
+ const spokeVault = opts.spoke ? resolveHome(opts.spoke) : path44.join(config.hubVault, "spokes", name);
23365
23617
  const project = {
23366
23618
  path: resolvedPath,
23367
23619
  captainName,
@@ -23376,20 +23628,20 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
23376
23628
  \u2714 Project '${name}' registered`));
23377
23629
  restartAfterProjectsAdd({ noRestart: opts.restart === false });
23378
23630
  const pkgRoot = findPackageRoot2();
23379
- const spokeTemplate = path43.join(pkgRoot, "obsidian", "spoke");
23380
- if (fs38.existsSync(spokeVault)) {
23631
+ const spokeTemplate = path44.join(pkgRoot, "obsidian", "spoke");
23632
+ if (fs39.existsSync(spokeVault)) {
23381
23633
  console.log(chalk5.yellow(` \u26A0 Spoke vault already exists at ${spokeVault}, skipping scaffold`));
23382
- } else if (fs38.existsSync(spokeTemplate)) {
23634
+ } else if (fs39.existsSync(spokeTemplate)) {
23383
23635
  copyDirRecursive2(spokeTemplate, spokeVault);
23384
- const statusPath = path43.join(spokeVault, "status.md");
23385
- if (fs38.existsSync(statusPath)) {
23386
- const content = fs38.readFileSync(statusPath, "utf-8");
23636
+ const statusPath = path44.join(spokeVault, "status.md");
23637
+ if (fs39.existsSync(statusPath)) {
23638
+ const content = fs39.readFileSync(statusPath, "utf-8");
23387
23639
  const updated = content.replace(/^project: unnamed/m, `project: ${name}`);
23388
- fs38.writeFileSync(statusPath, updated);
23640
+ fs39.writeFileSync(statusPath, updated);
23389
23641
  }
23390
23642
  console.log(chalk5.green(` \u2714 Spoke vault scaffolded at ${spokeVault}`));
23391
23643
  } else {
23392
- fs38.mkdirSync(spokeVault, { recursive: true });
23644
+ fs39.mkdirSync(spokeVault, { recursive: true });
23393
23645
  console.log(chalk5.yellow(` \u26A0 Spoke template not found; created empty dir at ${spokeVault}`));
23394
23646
  }
23395
23647
  console.log("");
@@ -23458,10 +23710,10 @@ init_dist3();
23458
23710
  init_dist();
23459
23711
  import { Command as Command5 } from "commander";
23460
23712
  import { execSync as execSync9 } from "child_process";
23461
- import path44 from "path";
23462
- import os19 from "os";
23713
+ import path45 from "path";
23714
+ import os18 from "os";
23463
23715
  import chalk7 from "chalk";
23464
- var TEMPLATES_DIR2 = path44.join(os19.homedir(), ".config", "squadrant", "templates");
23716
+ var TEMPLATES_DIR2 = path45.join(os18.homedir(), ".config", "squadrant", "templates");
23465
23717
  var TASK_PROMPTS = {
23466
23718
  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.",
23467
23719
  "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.",
@@ -23494,7 +23746,7 @@ async function runCommandSpawn(input) {
23494
23746
  if (!agent) {
23495
23747
  throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
23496
23748
  }
23497
- const promptFile = path44.join(TEMPLATES_DIR2, `command.${agent.templateSuffix}.md`);
23749
+ const promptFile = path45.join(TEMPLATES_DIR2, `command.${agent.templateSuffix}.md`);
23498
23750
  const cliCommand = agent.buildCommand({
23499
23751
  prompt,
23500
23752
  workdir: process.cwd(),
@@ -23535,7 +23787,7 @@ init_dist3();
23535
23787
  import { Command as Command9 } from "commander";
23536
23788
  import { createConnection as createConnection4 } from "net";
23537
23789
  import { randomUUID as randomUUID5 } from "crypto";
23538
- import { execFileSync as execFileSync11 } from "child_process";
23790
+ import { execFileSync as execFileSync12, spawnSync } from "child_process";
23539
23791
  import { homedir as homedir15 } from "os";
23540
23792
  import { join as join27 } from "path";
23541
23793
  import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync13 } from "fs";
@@ -24017,6 +24269,31 @@ function defaultWriteResult(id, payload) {
24017
24269
  writeFileSync13(file, payload);
24018
24270
  return file;
24019
24271
  }
24272
+ var CITE_GIT_TIMEOUT_MS = 2e3;
24273
+ var citeGit = (args) => {
24274
+ const r = spawnSync("git", args, { encoding: "utf8", timeout: CITE_GIT_TIMEOUT_MS, stdio: ["ignore", "pipe", "ignore"] });
24275
+ if (r.error) throw r.error;
24276
+ return r.status === 0 ? r.stdout : "";
24277
+ };
24278
+ function logSignalCitations(state, o) {
24279
+ const env = o.env ?? process.env;
24280
+ const project = o.project ?? env.SQUADRANT_CREW_PROJECT;
24281
+ if (state !== "done" && state !== "review" || !project) return [];
24282
+ const texts = o.message ? [{ text: o.message, ref: `crew-${state}:${o.taskId ?? env.SQUADRANT_CREW_TASK_ID}` }] : [];
24283
+ try {
24284
+ texts.push(...readBranchOnlyMessages(o.cwd ?? process.cwd(), o.git ?? citeGit).map((c) => ({ text: c.text, ref: `git:${c.sha}`, at: c.at })));
24285
+ } catch (e) {
24286
+ process.stderr.write(`squadrant: rule citation scan of commits skipped: ${e.message}
24287
+ `);
24288
+ }
24289
+ try {
24290
+ return scanCitations(texts, { project, sink: o.audit });
24291
+ } catch (e) {
24292
+ process.stderr.write(`squadrant: rule citation scan skipped: ${e.message}
24293
+ `);
24294
+ return [];
24295
+ }
24296
+ }
24020
24297
  async function runCrewSignal(signal, o, deps) {
24021
24298
  const taskId = o.taskId ?? process.env.SQUADRANT_CREW_TASK_ID;
24022
24299
  const project = o.project ?? process.env.SQUADRANT_CREW_PROJECT;
@@ -24039,10 +24316,10 @@ function resolveApproveTarget(tasks, crew) {
24039
24316
  return matches.reduce((a, b) => (b.createdAt ?? 0) > (a.createdAt ?? 0) ? b : a);
24040
24317
  }
24041
24318
  function defaultPushBranch(cwd, branch) {
24042
- execFileSync11("git", ["-C", cwd, "push", "-u", "origin", branch], { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
24319
+ execFileSync12("git", ["-C", cwd, "push", "-u", "origin", branch], { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
24043
24320
  }
24044
24321
  function defaultCreatePr(cwd, o) {
24045
- return execFileSync11(
24322
+ return execFileSync12(
24046
24323
  "gh",
24047
24324
  ["pr", "create", "--base", o.base, "--head", o.branch, "--title", o.title, "--body", o.body],
24048
24325
  { cwd, encoding: "utf-8" }
@@ -24050,7 +24327,7 @@ function defaultCreatePr(cwd, o) {
24050
24327
  }
24051
24328
  function defaultGetCommitSubject(cwd) {
24052
24329
  try {
24053
- return execFileSync11("git", ["-C", cwd, "log", "-1", "--format=%s"], {
24330
+ return execFileSync12("git", ["-C", cwd, "log", "-1", "--format=%s"], {
24054
24331
  encoding: "utf-8",
24055
24332
  stdio: ["ignore", "pipe", "pipe"]
24056
24333
  }).trim();
@@ -24205,13 +24482,7 @@ function addControlPlaneCrewCommands(crew) {
24205
24482
  ...opts.project !== void 0 ? { project: opts.project } : {},
24206
24483
  writeResult: defaultWriteResult
24207
24484
  }, { call: squadrantdCall });
24208
- const citeProject = opts.project ?? process.env.SQUADRANT_CREW_PROJECT;
24209
- if ((state === "done" || state === "review") && opts.message && citeProject) {
24210
- try {
24211
- scanAndLogCitations({ text: opts.message, ref: `crew-${state}:${opts.taskId ?? process.env.SQUADRANT_CREW_TASK_ID}`, project: citeProject });
24212
- } catch {
24213
- }
24214
- }
24485
+ logSignalCitations(state, opts);
24215
24486
  process.exit(0);
24216
24487
  } catch (e) {
24217
24488
  process.stderr.write(`${e.message}
@@ -24300,7 +24571,7 @@ import { connect as netConnect2 } from "net";
24300
24571
  init_dist3();
24301
24572
  init_dist2();
24302
24573
  import { createServer as createServer4, connect as netConnect } from "net";
24303
- import fs39 from "fs";
24574
+ import fs40 from "fs";
24304
24575
  import { join as join28 } from "path";
24305
24576
  import { randomUUID as randomUUID6 } from "crypto";
24306
24577
  import chalk10 from "chalk";
@@ -24308,15 +24579,15 @@ var shared;
24308
24579
  var registryEntryPath = () => join28(CLAUDE_SESSIONS_DIR, `${process.pid}.json`);
24309
24580
  function unregisterSenderIdentity() {
24310
24581
  try {
24311
- fs39.unlinkSync(registryEntryPath());
24582
+ fs40.unlinkSync(registryEntryPath());
24312
24583
  } catch {
24313
24584
  }
24314
24585
  }
24315
24586
  function registerSenderIdentity(socketPath2) {
24316
24587
  try {
24317
24588
  unregisterSenderIdentity();
24318
- fs39.mkdirSync(CLAUDE_SESSIONS_DIR, { recursive: true });
24319
- fs39.writeFileSync(
24589
+ fs40.mkdirSync(CLAUDE_SESSIONS_DIR, { recursive: true });
24590
+ fs40.writeFileSync(
24320
24591
  registryEntryPath(),
24321
24592
  JSON.stringify({
24322
24593
  pid: process.pid,
@@ -24342,7 +24613,7 @@ async function sharedReceiptListener() {
24342
24613
  // must never be the reason we refuse to start.
24343
24614
  unlinkStale: (p) => {
24344
24615
  try {
24345
- fs39.unlinkSync(p);
24616
+ fs40.unlinkSync(p);
24346
24617
  } catch {
24347
24618
  }
24348
24619
  },
@@ -24367,7 +24638,7 @@ async function buildCaptainChannel() {
24367
24638
  // in /tmp/x registers cwd "/private/tmp/x" on macOS, so cwd matching silently
24368
24639
  // missed and every captain ping reported accepted-unconfirmed.
24369
24640
  statusFor: (taskId) => readClaudeStatusBySocketPath(captainSocketPath(taskId)),
24370
- wire: (p, e) => writeLine(p, e, { connect: (path59) => netConnect(path59) }),
24641
+ wire: (p, e) => writeLine(p, e, { connect: (path60) => netConnect(path60) }),
24371
24642
  receipts,
24372
24643
  newMsgId: () => randomUUID6(),
24373
24644
  sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
@@ -24396,7 +24667,7 @@ async function sendClaudeFirstTurnViaPeer(runtime, o, getTaskRecord) {
24396
24667
  socketPathFor: () => o.messagingSocketPath,
24397
24668
  sessionIdFor: () => readClaudeStatusBySocketPath(o.messagingSocketPath)?.sessionId,
24398
24669
  statusFor: () => readClaudeStatusBySocketPath(o.messagingSocketPath),
24399
- wire: (p, e) => writeLine(p, e, { connect: (path59) => netConnect2(path59) }),
24670
+ wire: (p, e) => writeLine(p, e, { connect: (path60) => netConnect2(path60) }),
24400
24671
  receipts,
24401
24672
  newMsgId: () => randomUUID7(),
24402
24673
  sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
@@ -24540,7 +24811,7 @@ async function runCrewSend2(project, name, message, opts) {
24540
24811
  socketPathFor: (taskId) => tasks.find((t) => t.id === taskId)?.messagingSocketPath,
24541
24812
  sessionIdFor: (taskId) => tasks.find((t) => t.id === taskId)?.sessionId,
24542
24813
  statusFor: (taskId) => readClaudeStatus(tasks.find((t) => t.id === taskId)),
24543
- wire: (p, e) => writeLine(p, e, { connect: (path59) => netConnect3(path59) }),
24814
+ wire: (p, e) => writeLine(p, e, { connect: (path60) => netConnect3(path60) }),
24544
24815
  receipts,
24545
24816
  newMsgId: () => randomUUID8(),
24546
24817
  sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
@@ -24738,7 +25009,7 @@ init_dist();
24738
25009
  init_dist4();
24739
25010
  import { Command as Command11 } from "commander";
24740
25011
  import chalk12 from "chalk";
24741
- import { execFileSync as execFileSync12 } from "child_process";
25012
+ import { execFileSync as execFileSync13 } from "child_process";
24742
25013
  import readline2 from "readline";
24743
25014
  function resolveDiffTarget(tasks, crew, projectPath) {
24744
25015
  const matches = tasks.filter((t) => t.name === crew);
@@ -24809,14 +25080,14 @@ function promptLine2(question) {
24809
25080
  }
24810
25081
  function getPrDiff(projectPath, pr) {
24811
25082
  try {
24812
- return execFileSync12("gh", ["pr", "diff", pr], { cwd: projectPath, encoding: "utf-8" });
25083
+ return execFileSync13("gh", ["pr", "diff", pr], { cwd: projectPath, encoding: "utf-8" });
24813
25084
  } catch (e) {
24814
25085
  throw new Error(`Could not fetch PR #${pr} diff (gh pr diff failed): ${e.message}`);
24815
25086
  }
24816
25087
  }
24817
25088
  function getRefsDiff(projectPath, base, head) {
24818
25089
  try {
24819
- return execFileSync12("git", ["-C", projectPath, "diff", `${base}...${head}`], { encoding: "utf-8" });
25090
+ return execFileSync13("git", ["-C", projectPath, "diff", `${base}...${head}`], { encoding: "utf-8" });
24820
25091
  } catch (e) {
24821
25092
  throw new Error(`Could not diff ${base}...${head}: ${e.message}`);
24822
25093
  }
@@ -24837,7 +25108,7 @@ async function openCrewDiff(project, proj, crew, opts, runtime, workspaceId) {
24837
25108
  let opened = 0;
24838
25109
  for (const source of sources) {
24839
25110
  const statArgs = source === "staged" ? ["diff", "--stat", "--cached"] : ["diff", "--stat"];
24840
- const stat2 = execFileSync12("git", ["-C", target.cwd, ...statArgs], { encoding: "utf-8" }).trim();
25111
+ const stat2 = execFileSync13("git", ["-C", target.cwd, ...statArgs], { encoding: "utf-8" }).trim();
24841
25112
  if (!stat2) continue;
24842
25113
  await runtime.showDiff({
24843
25114
  workspaceId,
@@ -24858,7 +25129,7 @@ async function openCrewDiff(project, proj, crew, opts, runtime, workspaceId) {
24858
25129
  }
24859
25130
  return;
24860
25131
  }
24861
- const diffStat = execFileSync12(
25132
+ const diffStat = execFileSync13(
24862
25133
  "git",
24863
25134
  ["-C", target.cwd, "diff", "--stat", `${base}...HEAD`],
24864
25135
  { encoding: "utf-8" }
@@ -24907,7 +25178,7 @@ async function runDiff(project, crewArg, opts) {
24907
25178
  const base = resolveWorktreeBase(proj.path);
24908
25179
  const stats = buildCrewDiffStats(tasks, proj.path, base, (cwd, b) => {
24909
25180
  try {
24910
- return execFileSync12("git", ["-C", cwd, "diff", "--stat", `${b}...HEAD`], { encoding: "utf-8" });
25181
+ return execFileSync13("git", ["-C", cwd, "diff", "--stat", `${b}...HEAD`], { encoding: "utf-8" });
24911
25182
  } catch {
24912
25183
  return "";
24913
25184
  }
@@ -24934,11 +25205,11 @@ init_dist4();
24934
25205
  init_dist();
24935
25206
  init_dist2();
24936
25207
  import { Command as Command12 } from "commander";
24937
- import fs40 from "fs";
24938
- import path45 from "path";
24939
- import os20 from "os";
25208
+ import fs41 from "fs";
25209
+ import path46 from "path";
25210
+ import os19 from "os";
24940
25211
  import chalk13 from "chalk";
24941
- var TEMPLATES_DIR3 = path45.join(os20.homedir(), ".config", "squadrant", "templates");
25212
+ var TEMPLATES_DIR3 = path46.join(os19.homedir(), ".config", "squadrant", "templates");
24942
25213
  async function runSideSpawn2(input) {
24943
25214
  const config = loadConfig();
24944
25215
  const proj = config.projects[input.project];
@@ -24963,7 +25234,7 @@ async function runSideSpawn2(input) {
24963
25234
  }
24964
25235
  const sideModel = sideRole?.model;
24965
25236
  const sideThinking = sideRole?.thinking;
24966
- const promptFile = path45.join(
25237
+ const promptFile = path46.join(
24967
25238
  TEMPLATES_DIR3,
24968
25239
  `side.${input.role}.${agent.templateSuffix}.md`
24969
25240
  );
@@ -24971,7 +25242,7 @@ async function runSideSpawn2(input) {
24971
25242
  prompt: input.topic,
24972
25243
  workdir: spawnCwd,
24973
25244
  role: "side",
24974
- promptFile: fs40.existsSync(promptFile) ? promptFile : void 0,
25245
+ promptFile: fs41.existsSync(promptFile) ? promptFile : void 0,
24975
25246
  interactive: true,
24976
25247
  permissionMode: config.defaults.permissions?.crew ?? "auto",
24977
25248
  ...sideModel ? { model: sideModel } : {},
@@ -25234,8 +25505,8 @@ function renderDashboard(rows, opts) {
25234
25505
 
25235
25506
  // packages/web/dist/sync-hub.js
25236
25507
  init_dist();
25237
- import fs41 from "fs";
25238
- import path46 from "path";
25508
+ import fs42 from "fs";
25509
+ import path47 from "path";
25239
25510
  function buildMirrorMarkdown(s) {
25240
25511
  const fenced = "```";
25241
25512
  return [
@@ -25261,15 +25532,15 @@ function buildMirrorMarkdown(s) {
25261
25532
  function syncHub(deps) {
25262
25533
  if (!deps.config.hubVault)
25263
25534
  return [];
25264
- const writeFile6 = deps.writeFile ?? ((p, c) => fs41.writeFileSync(p, c));
25265
- const mkdir6 = deps.mkdir ?? ((p) => fs41.mkdirSync(p, { recursive: true }));
25266
- const projectsDir = path46.join(resolveHome(deps.config.hubVault), "projects");
25535
+ const writeFile6 = deps.writeFile ?? ((p, c) => fs42.writeFileSync(p, c));
25536
+ const mkdir6 = deps.mkdir ?? ((p) => fs42.mkdirSync(p, { recursive: true }));
25537
+ const projectsDir = path47.join(resolveHome(deps.config.hubVault), "projects");
25267
25538
  mkdir6(projectsDir);
25268
25539
  const out = [];
25269
25540
  for (const s of deps.statuses) {
25270
25541
  if (s.state === "unknown")
25271
25542
  continue;
25272
- const hubPath = path46.join(projectsDir, `${s.project}.md`);
25543
+ const hubPath = path47.join(projectsDir, `${s.project}.md`);
25273
25544
  try {
25274
25545
  writeFile6(hubPath, buildMirrorMarkdown(s));
25275
25546
  out.push({ project: s.project, hubPath });
@@ -26275,18 +26546,18 @@ function renderHtml(snap, opts = {}) {
26275
26546
  import { closeSync as closeSync3, openSync as openSync3, readSync as readSync2, statSync as statSync4 } from "fs";
26276
26547
  var LOG_TAIL_BYTES = 256 * 1024;
26277
26548
  var LOG_RING_SIZE = 1e3;
26278
- function defaultSize(path59) {
26549
+ function defaultSize(path60) {
26279
26550
  try {
26280
- return statSync4(path59).size;
26551
+ return statSync4(path60).size;
26281
26552
  } catch {
26282
26553
  return 0;
26283
26554
  }
26284
26555
  }
26285
- function defaultRead(path59, start, len) {
26556
+ function defaultRead(path60, start, len) {
26286
26557
  if (len <= 0)
26287
26558
  return "";
26288
26559
  try {
26289
- const fd = openSync3(path59, "r");
26560
+ const fd = openSync3(path60, "r");
26290
26561
  try {
26291
26562
  const buf = Buffer.alloc(len);
26292
26563
  const n = readSync2(fd, buf, 0, len, start);
@@ -26306,8 +26577,8 @@ var LogTailer = class {
26306
26577
  ring = [];
26307
26578
  size;
26308
26579
  read;
26309
- constructor(path59, ringSize = LOG_RING_SIZE, deps = {}) {
26310
- this.path = path59;
26580
+ constructor(path60, ringSize = LOG_RING_SIZE, deps = {}) {
26581
+ this.path = path60;
26311
26582
  this.ringSize = ringSize;
26312
26583
  this.size = deps.size ?? defaultSize;
26313
26584
  this.read = deps.read ?? defaultRead;
@@ -26582,10 +26853,10 @@ init_dist3();
26582
26853
  init_dist4();
26583
26854
  init_dist2();
26584
26855
  import { Command as Command14 } from "commander";
26585
- import { execSync as execSync11, execFileSync as execFileSync13 } from "child_process";
26586
- import fs42 from "fs";
26587
- import path47 from "path";
26588
- import os21 from "os";
26856
+ import { execSync as execSync11, execFileSync as execFileSync14 } from "child_process";
26857
+ import fs43 from "fs";
26858
+ import path48 from "path";
26859
+ import os20 from "os";
26589
26860
  import chalk16 from "chalk";
26590
26861
 
26591
26862
  // packages/cli/src/commands/launch-interactive.ts
@@ -26677,8 +26948,8 @@ function resolveLaunchBackend(o) {
26677
26948
  // packages/cli/src/commands/launch.ts
26678
26949
  init_dist2();
26679
26950
  var CMUX_APP = "/Applications/cmux.app";
26680
- var TEMPLATES_DIR4 = path47.join(os21.homedir(), ".config", "squadrant", "templates");
26681
- var SESSIONS_PATH2 = path47.join(os21.homedir(), ".config", "squadrant", "sessions.json");
26951
+ var TEMPLATES_DIR4 = path48.join(os20.homedir(), ".config", "squadrant", "templates");
26952
+ var SESSIONS_PATH2 = path48.join(os20.homedir(), ".config", "squadrant", "sessions.json");
26682
26953
  function shouldWireCaptainChannel(agentName, config) {
26683
26954
  return agentName === "claude" && resolveCaptainChannelMode(config.defaults) !== "off";
26684
26955
  }
@@ -26700,7 +26971,7 @@ function pickResumeSessionId(record, agentName, fresh = false) {
26700
26971
  }
26701
26972
  function isOpencodeCaptainDir(dir) {
26702
26973
  try {
26703
- execFileSync13("git", ["-C", dir, "rev-parse", "--verify", "HEAD"], { stdio: "ignore" });
26974
+ execFileSync14("git", ["-C", dir, "rev-parse", "--verify", "HEAD"], { stdio: "ignore" });
26704
26975
  return true;
26705
26976
  } catch {
26706
26977
  return false;
@@ -26775,7 +27046,7 @@ var launchCommand = new Command14("launch").description(
26775
27046
  initialPrompt = "Run your startup checklist: use the squadrant:command-ops skill, complete your daily briefing, then report ready.";
26776
27047
  }
26777
27048
  const runtime = projectName ? runtimes.forProject(projectName, config) : runtimes.global(config);
26778
- const stateRoot = path47.join(os21.homedir(), ".config", "squadrant", "state");
27049
+ const stateRoot = path48.join(os20.homedir(), ".config", "squadrant", "state");
26779
27050
  const isOpencodeCaptain = role === "captain" && agentName === "opencode" && !!projectName;
26780
27051
  if (isOpencodeCaptain && !isOpencodeCaptainDir(cwd)) {
26781
27052
  console.error(chalk16.red(`
@@ -26928,12 +27199,12 @@ var launchCommand = new Command14("launch").description(
26928
27199
  }
26929
27200
  if (opts.all) {
26930
27201
  const hubPath = resolveHome(config.hubVault);
26931
- fs42.mkdirSync(hubPath, { recursive: true });
27202
+ fs43.mkdirSync(hubPath, { recursive: true });
26932
27203
  console.log(chalk16.bold("\nLaunching all captain workspaces\n"));
26933
27204
  for (const [name, proj] of Object.entries(config.projects)) {
26934
27205
  const projPath = resolveHome(proj.path);
26935
27206
  const spokePath = resolveHome(proj.spokeVault);
26936
- if (!fs42.existsSync(spokePath)) {
27207
+ if (!fs43.existsSync(spokePath)) {
26937
27208
  const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
26938
27209
  await ensureSpokeLayout(spokeDriver);
26939
27210
  console.log(chalk16.cyan(` \u2714 Created spoke vault at ${spokePath}`));
@@ -26971,7 +27242,7 @@ Launching ${selected.length} captain workspace(s) in parallel
26971
27242
  const proj = config.projects[name];
26972
27243
  const projPath = resolveHome(proj.path);
26973
27244
  const spokePath = resolveHome(proj.spokeVault);
26974
- if (!fs42.existsSync(spokePath)) {
27245
+ if (!fs43.existsSync(spokePath)) {
26975
27246
  const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
26976
27247
  await ensureSpokeLayout(spokeDriver);
26977
27248
  console.log(chalk16.cyan(` \u2714 Created spoke vault at ${spokePath}`));
@@ -26995,7 +27266,7 @@ Launching ${selected.length} captain workspace(s) in parallel
26995
27266
  const proj = config.projects[project];
26996
27267
  const projPath = resolveHome(proj.path);
26997
27268
  const spokePath = resolveHome(proj.spokeVault);
26998
- if (!fs42.existsSync(spokePath)) {
27269
+ if (!fs43.existsSync(spokePath)) {
26999
27270
  const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(project, config);
27000
27271
  await ensureSpokeLayout(spokeDriver);
27001
27272
  console.log(chalk16.cyan(` \u2714 Created spoke vault at ${spokePath}`));
@@ -27131,24 +27402,24 @@ Shutting down captain workspace for '${project}'...
27131
27402
  // packages/cli/src/commands/feedback.ts
27132
27403
  init_dist();
27133
27404
  import { Command as Command16 } from "commander";
27134
- import fs43 from "fs";
27135
- import os22 from "os";
27136
- import path48 from "path";
27405
+ import fs44 from "fs";
27406
+ import os21 from "os";
27407
+ import path49 from "path";
27137
27408
  import { fileURLToPath as fileURLToPath3 } from "url";
27138
27409
  import { execSync as execSync12 } from "child_process";
27139
27410
  import chalk18 from "chalk";
27140
27411
  var REPO_URL = "https://github.com/tu11aa/squadrant";
27141
27412
  function readPkgVersion() {
27142
27413
  try {
27143
- const pkgPath = path48.join(path48.dirname(fileURLToPath3(import.meta.url)), "..", "package.json");
27144
- return JSON.parse(fs43.readFileSync(pkgPath, "utf-8")).version ?? "unknown";
27414
+ const pkgPath = path49.join(path49.dirname(fileURLToPath3(import.meta.url)), "..", "package.json");
27415
+ return JSON.parse(fs44.readFileSync(pkgPath, "utf-8")).version ?? "unknown";
27145
27416
  } catch {
27146
27417
  return "unknown";
27147
27418
  }
27148
27419
  }
27149
27420
  function readMetrics(metricsPath) {
27150
27421
  try {
27151
- return JSON.parse(fs43.readFileSync(metricsPath, "utf-8"));
27422
+ return JSON.parse(fs44.readFileSync(metricsPath, "utf-8"));
27152
27423
  } catch {
27153
27424
  return {};
27154
27425
  }
@@ -27186,7 +27457,7 @@ function buildIssueUrl(metrics, squadrantVersion) {
27186
27457
  }
27187
27458
  var feedbackCommand = new Command16("feedback").description("Open a pre-filled GitHub issue for feedback or bug reports").action(() => {
27188
27459
  const config = loadConfig();
27189
- const metricsPath = config.metrics?.path || path48.join(os22.homedir(), ".config", "squadrant", "metrics.json");
27460
+ const metricsPath = config.metrics?.path || path49.join(os21.homedir(), ".config", "squadrant", "metrics.json");
27190
27461
  const metrics = readMetrics(metricsPath);
27191
27462
  const version = readStamp(config) ?? readPkgVersion();
27192
27463
  const issueUrl = buildIssueUrl(metrics, version);
@@ -27486,9 +27757,9 @@ workspaceCommand.command("read").description("Print the contents of a scope-rela
27486
27757
  const config = loadConfig();
27487
27758
  const registry = buildRegistry2();
27488
27759
  try {
27489
- const { projectTarget, path: path59 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
27760
+ const { projectTarget, path: path60 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
27490
27761
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
27491
- const content = await driver.read(path59);
27762
+ const content = await driver.read(path60);
27492
27763
  process.stdout.write(content);
27493
27764
  } catch (err) {
27494
27765
  console.error(chalk21.red(err.message));
@@ -27500,26 +27771,26 @@ workspaceCommand.command("write").description("Write content to a scope-relative
27500
27771
  const registry = buildRegistry2();
27501
27772
  try {
27502
27773
  let projectTarget;
27503
- let path59;
27774
+ let path60;
27504
27775
  let rawContent;
27505
27776
  if (opts.hub) {
27506
27777
  if (arg3 !== void 0) {
27507
27778
  throw new Error("With --hub, pass only the path and content");
27508
27779
  }
27509
27780
  projectTarget = void 0;
27510
- path59 = arg1;
27781
+ path60 = arg1;
27511
27782
  rawContent = arg2;
27512
27783
  } else {
27513
27784
  if (arg3 === void 0) {
27514
27785
  throw new Error("Missing content \u2014 usage: <project> <path> <content>");
27515
27786
  }
27516
27787
  projectTarget = arg1;
27517
- path59 = arg2;
27788
+ path60 = arg2;
27518
27789
  rawContent = arg3;
27519
27790
  }
27520
27791
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
27521
27792
  const payload = rawContent === "-" ? await readStdin() : rawContent;
27522
- await driver.write(path59, payload);
27793
+ await driver.write(path60, payload);
27523
27794
  } catch (err) {
27524
27795
  console.error(chalk21.red(err.message));
27525
27796
  process.exit(1);
@@ -27529,9 +27800,9 @@ workspaceCommand.command("list").description("List entries in a scope-relative d
27529
27800
  const config = loadConfig();
27530
27801
  const registry = buildRegistry2();
27531
27802
  try {
27532
- const { projectTarget, path: path59 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
27803
+ const { projectTarget, path: path60 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
27533
27804
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
27534
- const entries = await driver.list(path59);
27805
+ const entries = await driver.list(path60);
27535
27806
  for (const entry of entries) console.log(entry);
27536
27807
  } catch (err) {
27537
27808
  console.error(chalk21.red(err.message));
@@ -27542,9 +27813,9 @@ workspaceCommand.command("exists").description("Exit 0 if path exists, 1 if not"
27542
27813
  const config = loadConfig();
27543
27814
  const registry = buildRegistry2();
27544
27815
  try {
27545
- const { projectTarget, path: path59 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
27816
+ const { projectTarget, path: path60 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
27546
27817
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
27547
- const ok2 = await driver.exists(path59);
27818
+ const ok2 = await driver.exists(path60);
27548
27819
  process.exit(ok2 ? 0 : 1);
27549
27820
  } catch (err) {
27550
27821
  console.error(chalk21.red(err.message));
@@ -27555,9 +27826,9 @@ workspaceCommand.command("mkdir").description("Recursively create a scope-relati
27555
27826
  const config = loadConfig();
27556
27827
  const registry = buildRegistry2();
27557
27828
  try {
27558
- const { projectTarget, path: path59 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
27829
+ const { projectTarget, path: path60 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
27559
27830
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
27560
- await driver.mkdir(path59);
27831
+ await driver.mkdir(path60);
27561
27832
  } catch (err) {
27562
27833
  console.error(chalk21.red(err.message));
27563
27834
  process.exit(1);
@@ -27597,8 +27868,8 @@ init_dist4();
27597
27868
  init_dist();
27598
27869
  import { Command as Command21 } from "commander";
27599
27870
  import chalk23 from "chalk";
27600
- import fs44 from "fs";
27601
- import path49 from "path";
27871
+ import fs45 from "fs";
27872
+ import path50 from "path";
27602
27873
  import { fileURLToPath as fileURLToPath4 } from "url";
27603
27874
  function parseScope(v2) {
27604
27875
  if (v2 !== "user" && v2 !== "project") {
@@ -27607,10 +27878,10 @@ function parseScope(v2) {
27607
27878
  return v2;
27608
27879
  }
27609
27880
  function findPackageRoot3() {
27610
- let dir = path49.dirname(fileURLToPath4(import.meta.url));
27881
+ let dir = path50.dirname(fileURLToPath4(import.meta.url));
27611
27882
  while (dir !== "/" && dir !== "") {
27612
- if (fs44.existsSync(path49.join(dir, "package.json"))) return dir;
27613
- dir = path49.dirname(dir);
27883
+ if (fs45.existsSync(path50.join(dir, "package.json"))) return dir;
27884
+ dir = path50.dirname(dir);
27614
27885
  }
27615
27886
  return process.cwd();
27616
27887
  }
@@ -27825,12 +28096,12 @@ init_dist();
27825
28096
  init_dist();
27826
28097
  init_dist2();
27827
28098
  import { Command as Command23 } from "commander";
27828
- import fs45 from "fs";
28099
+ import fs46 from "fs";
27829
28100
  import { fileURLToPath as fileURLToPath5 } from "url";
27830
28101
  import { dirname as dirname8, join as join31 } from "path";
27831
28102
  import chalk24 from "chalk";
27832
28103
  function runConfigCheck(opts) {
27833
- const raw = JSON.parse(fs45.readFileSync(opts.configPath, "utf-8"));
28104
+ const raw = JSON.parse(fs46.readFileSync(opts.configPath, "utf-8"));
27834
28105
  const def = getDefaultConfig();
27835
28106
  const items = detectDrift(raw, def);
27836
28107
  let working = raw;
@@ -27918,7 +28189,7 @@ function printItems(items) {
27918
28189
  var configCommand = new Command23("config").description("Inspect and reconcile squadrant config");
27919
28190
  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) => {
27920
28191
  const pkgVersion = readPkgVersion2();
27921
- if (!fs45.existsSync(DEFAULT_CONFIG_PATH)) {
28192
+ if (!fs46.existsSync(DEFAULT_CONFIG_PATH)) {
27922
28193
  console.log(chalk24.yellow("No config found \u2014 run `squadrant init` first."));
27923
28194
  return;
27924
28195
  }
@@ -27964,12 +28235,12 @@ configCommand.command("set").description("Write a config value by dotted key (e.
27964
28235
  });
27965
28236
  function readPkgVersion2() {
27966
28237
  const pkgPath = join31(dirname8(fileURLToPath5(import.meta.url)), "..", "package.json");
27967
- return JSON.parse(fs45.readFileSync(pkgPath, "utf-8")).version;
28238
+ return JSON.parse(fs46.readFileSync(pkgPath, "utf-8")).version;
27968
28239
  }
27969
28240
 
27970
28241
  // packages/cli/src/commands/heal.ts
27971
28242
  init_dist();
27972
- import { execFileSync as execFileSync14 } from "child_process";
28243
+ import { execFileSync as execFileSync15 } from "child_process";
27973
28244
  import { join as join32 } from "path";
27974
28245
  import { homedir as homedir17 } from "os";
27975
28246
  import { Command as Command24 } from "commander";
@@ -28178,7 +28449,7 @@ async function runHealCaptain(projects, deps) {
28178
28449
  }
28179
28450
  function kickstartDaemonForHealCaptain() {
28180
28451
  const uid = process.getuid?.() ?? 0;
28181
- execFileSync14("launchctl", ["kickstart", "-k", `gui/${uid}/${LABEL}`], { stdio: "ignore" });
28452
+ execFileSync15("launchctl", ["kickstart", "-k", `gui/${uid}/${LABEL}`], { stdio: "ignore" });
28182
28453
  }
28183
28454
  var healCommand = new Command24("heal").description("Targeted, idempotent remediation for squadrant components (daemon, health)").addHelpText("after", "\nDeferred: 'squadrant heal crew <id>' (re-attach) \u2014 see issue #100.").addCommand(
28184
28455
  new Command24("status").description("Dry-run: print unhealthy components and the exact heal command for each").option("-p, --project <project>", "scope to one project").option("--json", "output machine-readable JSON (exit 0=healthy, 1=error, 2=unhealthy)").action(async (opts) => {
@@ -28409,8 +28680,8 @@ var cmuxCommand = new Command28("cmux").description("cmux integration helpers").
28409
28680
  // packages/cli/src/commands/effort.ts
28410
28681
  init_dist();
28411
28682
  init_dist2();
28412
- import fs46 from "fs";
28413
- import path50 from "path";
28683
+ import fs47 from "fs";
28684
+ import path51 from "path";
28414
28685
  import { Command as Command29 } from "commander";
28415
28686
  import chalk30 from "chalk";
28416
28687
  var VALID_EFFORTS = ["max", "balance", "low"];
@@ -28453,9 +28724,9 @@ function effortScopeLabel(projectName) {
28453
28724
  }
28454
28725
  function canonical(p) {
28455
28726
  try {
28456
- return fs46.realpathSync(p);
28727
+ return fs47.realpathSync(p);
28457
28728
  } catch {
28458
- return path50.resolve(p);
28729
+ return path51.resolve(p);
28459
28730
  }
28460
28731
  }
28461
28732
  async function notifyCaptainsOfEffort(effort, config, driver, cwd = process.cwd(), append, scopeProject, projectConfigRoot) {
@@ -28505,7 +28776,7 @@ var effortCommand = new Command29("effort").description("Get or set the crew tok
28505
28776
  const config = loadConfig();
28506
28777
  const registry = new RuntimeRegistry2({ cmux: createCmuxDriver2() });
28507
28778
  const driver = registry.global(config);
28508
- const stateRoot = path50.join(path50.dirname(DEFAULT_CONFIG_PATH), "state");
28779
+ const stateRoot = path51.join(path51.dirname(DEFAULT_CONFIG_PATH), "state");
28509
28780
  const append = (project, text) => appendCaptainMessage({ stateRoot, project, text, source: "daemon" });
28510
28781
  await notifyCaptainsOfEffort(effort, config, driver, process.cwd(), append, options.project);
28511
28782
  } catch {
@@ -28515,13 +28786,13 @@ var effortCommand = new Command29("effort").description("Get or set the crew tok
28515
28786
 
28516
28787
  // packages/cli/src/commands/tokens.ts
28517
28788
  init_dist();
28518
- import fs47 from "fs";
28519
- import path51 from "path";
28520
- import os23 from "os";
28789
+ import fs48 from "fs";
28790
+ import path52 from "path";
28791
+ import os22 from "os";
28521
28792
  import readline3 from "readline";
28522
28793
  import { Command as Command30 } from "commander";
28523
28794
  import chalk31 from "chalk";
28524
- var CLAUDE_PROJECTS_DIR = path51.join(os23.homedir(), ".claude", "projects");
28795
+ var CLAUDE_PROJECTS_DIR = path52.join(os22.homedir(), ".claude", "projects");
28525
28796
  function parseTranscriptLine(rawLine) {
28526
28797
  const line2 = rawLine.trim();
28527
28798
  if (!line2) return { timestamp: null, usage: null };
@@ -28571,7 +28842,7 @@ function foldTranscriptLine(agg, rawLine, state) {
28571
28842
  async function aggregateTranscriptFile(filePath) {
28572
28843
  const agg = emptySessionAggregate();
28573
28844
  const state = { lastCacheRead: null };
28574
- const rl = readline3.createInterface({ input: fs47.createReadStream(filePath), crlfDelay: Infinity });
28845
+ const rl = readline3.createInterface({ input: fs48.createReadStream(filePath), crlfDelay: Infinity });
28575
28846
  for await (const line2 of rl) {
28576
28847
  foldTranscriptLine(agg, line2, state);
28577
28848
  }
@@ -28639,22 +28910,22 @@ function buildRoleReport(role, sessions) {
28639
28910
  }
28640
28911
  async function readdirSafe(dir) {
28641
28912
  try {
28642
- return await fs47.promises.readdir(dir);
28913
+ return await fs48.promises.readdir(dir);
28643
28914
  } catch {
28644
28915
  return [];
28645
28916
  }
28646
28917
  }
28647
28918
  async function listJsonlFiles(dir) {
28648
28919
  const entries = await readdirSafe(dir);
28649
- return entries.filter((e) => e.endsWith(".jsonl")).map((e) => path51.join(dir, e));
28920
+ return entries.filter((e) => e.endsWith(".jsonl")).map((e) => path52.join(dir, e));
28650
28921
  }
28651
28922
  async function findTranscriptDirs(claudeProjectsDir, captainSlug) {
28652
28923
  const entries = await readdirSafe(claudeProjectsDir);
28653
28924
  const captainDirs = [];
28654
28925
  const crewDirs = [];
28655
28926
  for (const entry of entries) {
28656
- if (entry === captainSlug) captainDirs.push(path51.join(claudeProjectsDir, entry));
28657
- else if (isCrewDirName(entry, captainSlug)) crewDirs.push(path51.join(claudeProjectsDir, entry));
28927
+ if (entry === captainSlug) captainDirs.push(path52.join(claudeProjectsDir, entry));
28928
+ else if (isCrewDirName(entry, captainSlug)) crewDirs.push(path52.join(claudeProjectsDir, entry));
28658
28929
  }
28659
28930
  return { captainDirs, crewDirs };
28660
28931
  }
@@ -29141,8 +29412,8 @@ import { join as join36 } from "path";
29141
29412
  import { homedir as homedir20 } from "os";
29142
29413
 
29143
29414
  // packages/cli/src/lib/captain-session-registry.ts
29144
- import fs48 from "fs";
29145
- import path52 from "path";
29415
+ import fs49 from "fs";
29416
+ import path53 from "path";
29146
29417
 
29147
29418
  // packages/cli/src/lib/handoff-facts.ts
29148
29419
  var STALE_FETCH_WARNING_MS = 24 * 60 * 60 * 1e3;
@@ -29193,15 +29464,15 @@ function assembleHandoffFacts(live, claudeMem, gapSessions, checkpoint, now, ext
29193
29464
  // packages/cli/src/lib/captain-session-registry.ts
29194
29465
  var CAPTAIN_SESSION_REGISTRY_FILE = "captain-sessions.jsonl";
29195
29466
  function appendCaptainSession(spokeVault, record) {
29196
- fs48.mkdirSync(spokeVault, { recursive: true });
29197
- const file = path52.join(spokeVault, CAPTAIN_SESSION_REGISTRY_FILE);
29198
- fs48.appendFileSync(file, JSON.stringify(record) + "\n");
29467
+ fs49.mkdirSync(spokeVault, { recursive: true });
29468
+ const file = path53.join(spokeVault, CAPTAIN_SESSION_REGISTRY_FILE);
29469
+ fs49.appendFileSync(file, JSON.stringify(record) + "\n");
29199
29470
  }
29200
29471
  function readCaptainSessionRegistry(spokeVault) {
29201
- const file = path52.join(spokeVault, CAPTAIN_SESSION_REGISTRY_FILE);
29202
- if (!fs48.existsSync(file)) return [];
29472
+ const file = path53.join(spokeVault, CAPTAIN_SESSION_REGISTRY_FILE);
29473
+ if (!fs49.existsSync(file)) return [];
29203
29474
  const records = [];
29204
- for (const line2 of fs48.readFileSync(file, "utf-8").split("\n")) {
29475
+ for (const line2 of fs49.readFileSync(file, "utf-8").split("\n")) {
29205
29476
  if (!line2.trim()) continue;
29206
29477
  try {
29207
29478
  records.push(JSON.parse(line2));
@@ -29651,13 +29922,13 @@ function hooksCommand() {
29651
29922
  // packages/cli/src/commands/work.ts
29652
29923
  init_dist();
29653
29924
  init_dist2();
29654
- import path53 from "path";
29925
+ import path54 from "path";
29655
29926
  import { Command as Command34 } from "commander";
29656
29927
  import chalk34 from "chalk";
29657
29928
  function detectCurrentProject(config, cwd = process.cwd()) {
29658
29929
  for (const [name, proj] of Object.entries(config.projects)) {
29659
29930
  const projPath = resolveHome(proj.path);
29660
- if (cwd === projPath || cwd.startsWith(projPath + path53.sep)) return name;
29931
+ if (cwd === projPath || cwd.startsWith(projPath + path54.sep)) return name;
29661
29932
  }
29662
29933
  return void 0;
29663
29934
  }
@@ -29789,14 +30060,14 @@ var workCommand = new Command34("work").description("Track your own in-flight wo
29789
30060
  // packages/cli/src/commands/handoff.ts
29790
30061
  init_dist();
29791
30062
  import { Command as Command35 } from "commander";
29792
- import path56 from "path";
29793
- import os24 from "os";
30063
+ import path57 from "path";
30064
+ import os23 from "os";
29794
30065
 
29795
30066
  // packages/cli/src/lib/handoff-live-repo.ts
29796
30067
  init_dist();
29797
- import { execFileSync as execFileSync15 } from "child_process";
29798
- import fs49 from "fs";
29799
- import path54 from "path";
30068
+ import { execFileSync as execFileSync16 } from "child_process";
30069
+ import fs50 from "fs";
30070
+ import path55 from "path";
29800
30071
 
29801
30072
  // packages/cli/src/lib/handoff-branch-state.ts
29802
30073
  function tryRun(runner, cmd, args, cwd) {
@@ -29873,7 +30144,7 @@ var OPEN_PR_LIMIT = 20;
29873
30144
  var RELEASE_BRANCH = "main";
29874
30145
  var defaultCommandRunner = {
29875
30146
  run(cmd, args, cwd) {
29876
- return execFileSync15(cmd, args, { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
30147
+ return execFileSync16(cmd, args, { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
29877
30148
  }
29878
30149
  };
29879
30150
  function tryRun2(runner, cmd, args, cwd) {
@@ -29935,7 +30206,7 @@ function localAheadOfBase(runner, projectPath, base) {
29935
30206
  }
29936
30207
  function readFetchAgeMs(projectPath, now) {
29937
30208
  try {
29938
- const stat2 = fs49.statSync(path54.join(projectPath, ".git", "FETCH_HEAD"));
30209
+ const stat2 = fs50.statSync(path55.join(projectPath, ".git", "FETCH_HEAD"));
29939
30210
  return Math.max(0, now - stat2.mtime.getTime());
29940
30211
  } catch {
29941
30212
  return null;
@@ -30006,7 +30277,7 @@ function gatherLiveRepoState(projectPath, fallbackBaseBranch, tasks, runner = de
30006
30277
 
30007
30278
  // packages/cli/src/lib/handoff-claude-mem.ts
30008
30279
  import { createRequire } from "module";
30009
- import fs50 from "fs";
30280
+ import fs51 from "fs";
30010
30281
  var { DatabaseSync } = createRequire(import.meta.url)("node:sqlite");
30011
30282
  var CLAUDE_MEM_RECENCY_LIMIT = 20;
30012
30283
  function decisionText(row) {
@@ -30020,7 +30291,7 @@ function decisionText(row) {
30020
30291
  return row.narrative ?? "";
30021
30292
  }
30022
30293
  function queryClaudeMem(dbPath, project) {
30023
- if (!fs50.existsSync(dbPath)) return null;
30294
+ if (!fs51.existsSync(dbPath)) return null;
30024
30295
  let db;
30025
30296
  try {
30026
30297
  db = new DatabaseSync(dbPath, { readOnly: true });
@@ -30063,7 +30334,7 @@ function queryClaudeMem(dbPath, project) {
30063
30334
  }
30064
30335
 
30065
30336
  // packages/cli/src/lib/handoff-transcript.ts
30066
- import fs51 from "fs";
30337
+ import fs52 from "fs";
30067
30338
  var TRANSCRIPT_BYTE_CAP = 2e5;
30068
30339
  var DIGEST_BYTE_CAP = 8 * 1024;
30069
30340
  var PROMPT_TRUNCATE_CHARS = 300;
@@ -30172,27 +30443,27 @@ function extractMessages(tailText) {
30172
30443
  return { lastUserMessage, lastAssistantText, digest };
30173
30444
  }
30174
30445
  function extractTranscriptTail(transcriptPath, byteCap = TRANSCRIPT_BYTE_CAP) {
30175
- if (!fs51.existsSync(transcriptPath)) return null;
30176
- const content = fs51.readFileSync(transcriptPath, "utf-8");
30446
+ if (!fs52.existsSync(transcriptPath)) return null;
30447
+ const content = fs52.readFileSync(transcriptPath, "utf-8");
30177
30448
  const { lastUserMessage, lastAssistantText, digest } = extractMessages(tailOf(content, byteCap));
30178
- const mtimeIso = fs51.statSync(transcriptPath).mtime.toISOString();
30449
+ const mtimeIso = fs52.statSync(transcriptPath).mtime.toISOString();
30179
30450
  return { path: transcriptPath, mtimeIso, lastUserMessage, lastAssistantText, digest };
30180
30451
  }
30181
30452
 
30182
30453
  // packages/cli/src/lib/handoff-archive.ts
30183
- import fs52 from "fs";
30184
- import path55 from "path";
30454
+ import fs53 from "fs";
30455
+ import path56 from "path";
30185
30456
  function readNewestArchivedHandoff(spokeVault, now) {
30186
- const dir = path55.join(spokeVault, "handoffs");
30187
- if (!fs52.existsSync(dir)) return null;
30188
- const candidates = fs52.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".json")).map((e) => {
30189
- const full = path55.join(dir, e.name);
30190
- return { name: e.name, full, mtime: fs52.statSync(full).mtime };
30457
+ const dir = path56.join(spokeVault, "handoffs");
30458
+ if (!fs53.existsSync(dir)) return null;
30459
+ const candidates = fs53.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".json")).map((e) => {
30460
+ const full = path56.join(dir, e.name);
30461
+ return { name: e.name, full, mtime: fs53.statSync(full).mtime };
30191
30462
  }).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
30192
30463
  for (const candidate of candidates) {
30193
30464
  let content;
30194
30465
  try {
30195
- content = JSON.parse(fs52.readFileSync(candidate.full, "utf-8"));
30466
+ content = JSON.parse(fs53.readFileSync(candidate.full, "utf-8"));
30196
30467
  } catch {
30197
30468
  continue;
30198
30469
  }
@@ -30202,7 +30473,7 @@ function readNewestArchivedHandoff(spokeVault, now) {
30202
30473
  }
30203
30474
 
30204
30475
  // packages/cli/src/commands/handoff.ts
30205
- var CLAUDE_MEM_DB_PATH = path56.join(os24.homedir(), ".claude-mem", "claude-mem.db");
30476
+ var CLAUDE_MEM_DB_PATH = path57.join(os23.homedir(), ".claude-mem", "claude-mem.db");
30206
30477
  async function defaultFetchTasks(project) {
30207
30478
  return await squadrantdCall({ kind: "list", project });
30208
30479
  }
@@ -30270,9 +30541,9 @@ function filterSessions(rows, f) {
30270
30541
  if (f.agent && r.agent !== f.agent) return false;
30271
30542
  if (f.liveOnly && r.status === "stale") return false;
30272
30543
  if (f.project) {
30273
- const path59 = f.projects?.[f.project];
30544
+ const path60 = f.projects?.[f.project];
30274
30545
  const byRecord = r.project === f.project;
30275
- const byCwd = !!path59 && !!r.cwd && (r.cwd === normalized(path59) || r.cwd.startsWith(normalized(path59) + "/"));
30546
+ const byCwd = !!path60 && !!r.cwd && (r.cwd === normalized(path60) || r.cwd.startsWith(normalized(path60) + "/"));
30276
30547
  if (!byRecord && !byCwd) return false;
30277
30548
  }
30278
30549
  return true;
@@ -30362,8 +30633,8 @@ var sessionsCommand = new Command36("sessions").description("List live agent ses
30362
30633
  init_dist();
30363
30634
  init_dist2();
30364
30635
  init_dist3();
30365
- import os25 from "os";
30366
- import path57 from "path";
30636
+ import os24 from "os";
30637
+ import path58 from "path";
30367
30638
  import { Command as Command37 } from "commander";
30368
30639
  import chalk36 from "chalk";
30369
30640
 
@@ -30481,7 +30752,7 @@ function buildWhoamiDeps(opts = {}) {
30481
30752
  const projects = Object.fromEntries(
30482
30753
  Object.entries(config.projects).map(([n, p]) => [n, resolveHome(p.path)])
30483
30754
  );
30484
- const root = stateRoot ?? path57.join(os25.homedir(), ".config", "squadrant", "state");
30755
+ const root = stateRoot ?? path58.join(os24.homedir(), ".config", "squadrant", "state");
30485
30756
  const store = createStore(root);
30486
30757
  const merged = {
30487
30758
  env: process.env,
@@ -30533,11 +30804,11 @@ var whoamiCommand = new Command37("whoami").description("Show the agent session
30533
30804
  // packages/cli/src/commands/knowledge.ts
30534
30805
  init_dist();
30535
30806
  init_dist2();
30536
- import fs53 from "fs";
30537
- import os26 from "os";
30538
- import path58 from "path";
30807
+ import fs54 from "fs";
30808
+ import os25 from "os";
30809
+ import path59 from "path";
30539
30810
  import readline4 from "readline/promises";
30540
- import { spawnSync } from "child_process";
30811
+ import { spawnSync as spawnSync2 } from "child_process";
30541
30812
  import { Command as Command39 } from "commander";
30542
30813
  import chalk38 from "chalk";
30543
30814
 
@@ -30602,7 +30873,8 @@ function runRulesSearch(query, opts, configPath = DEFAULT_CONFIG_PATH) {
30602
30873
  const rules = loadFor(opts, configPath);
30603
30874
  const hits = searchRules(rules, query, { statuses: opts.all ? [...RULE_STATUSES] : DEFAULT_SEARCH_STATUSES });
30604
30875
  const project = resolveRulesProject(loadConfig(configPath), opts);
30605
- appendAudit([{ kb: "", level: "project", project, domain: "rules", itemId: "", event: "item.searched", query, score: hits[0]?.score }], opts.audit);
30876
+ const shown = hits.slice(0, parseLimit(opts.limit));
30877
+ 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);
30606
30878
  return hits;
30607
30879
  }
30608
30880
  function runRulesList(opts, configPath = DEFAULT_CONFIG_PATH) {
@@ -30685,18 +30957,18 @@ function runKnowledgeInit(kb, configPath = DEFAULT_CONFIG_PATH) {
30685
30957
  const cfg = loadConfig(configPath);
30686
30958
  const dir = kbDir(cfg, kb);
30687
30959
  const created = [];
30688
- for (const d of [path58.join(dir, "raw"), kbRulesDir(cfg, kb), path58.join(kbRulesDir(cfg, kb), PROPOSED_DIR)]) {
30689
- if (!fs53.existsSync(d)) {
30690
- fs53.mkdirSync(d, { recursive: true });
30691
- created.push(path58.relative(dir, d));
30960
+ for (const d of [path59.join(dir, "raw"), kbRulesDir(cfg, kb), path59.join(kbRulesDir(cfg, kb), PROPOSED_DIR)]) {
30961
+ if (!fs54.existsSync(d)) {
30962
+ fs54.mkdirSync(d, { recursive: true });
30963
+ created.push(path59.relative(dir, d));
30692
30964
  }
30693
30965
  }
30694
- const sourcesFile = path58.join(dir, "sources.yaml");
30695
- if (!fs53.existsSync(sourcesFile)) {
30696
- fs53.writeFileSync(sourcesFile, SOURCES_TEMPLATE);
30966
+ const sourcesFile = path59.join(dir, "sources.yaml");
30967
+ if (!fs54.existsSync(sourcesFile)) {
30968
+ fs54.writeFileSync(sourcesFile, SOURCES_TEMPLATE);
30697
30969
  created.push("sources.yaml");
30698
30970
  }
30699
- if (!fs53.existsSync(path58.join(dir, "index.json"))) {
30971
+ if (!fs54.existsSync(path59.join(dir, "index.json"))) {
30700
30972
  writeIndex(dir, compileIndex(kb, []));
30701
30973
  created.push("index.json");
30702
30974
  }
@@ -30713,7 +30985,7 @@ function runKnowledgeSubscribe(kb, project, configPath = DEFAULT_CONFIG_PATH) {
30713
30985
  const cfg = loadConfig(configPath);
30714
30986
  const pc = cfg.projects[project];
30715
30987
  if (!pc) throw new Error(`Unknown project '${project}'`);
30716
- if (!fs53.existsSync(kbDir(cfg, kb))) {
30988
+ if (!fs54.existsSync(kbDir(cfg, kb))) {
30717
30989
  throw new Error(`Knowledge base '${kb}' does not exist. Run: squadrant knowledge init ${kb}`);
30718
30990
  }
30719
30991
  const list = pc.knowledge ?? [];
@@ -30728,6 +31000,12 @@ function runKnowledgeSources(kb, configPath = DEFAULT_CONFIG_PATH) {
30728
31000
  const cfg = loadConfig(configPath);
30729
31001
  return loadSources(kbDir(cfg, kb));
30730
31002
  }
31003
+ function runKnowledgeSourcesRemove(kb, entryPath, opts = {}, configPath = DEFAULT_CONFIG_PATH) {
31004
+ assertKbName(kb);
31005
+ const cfg = loadConfig(configPath);
31006
+ if (!fs54.existsSync(kbDir(cfg, kb))) throw new Error(`Knowledge base '${kb}' does not exist. Run: squadrant knowledge init ${kb}`);
31007
+ return removeSource(kbDir(cfg, kb), kbRulesDir(cfg, kb), entryPath, { ...opts, kbName: kb });
31008
+ }
30731
31009
  var knowledgeCommand = new Command39("knowledge").description("Manage rules knowledge bases (#893)");
30732
31010
  knowledgeCommand.command("init <kb>").description("Create a knowledge base (its own git repo) under ~/squadrant/kb/<kb>/").action((kb) => {
30733
31011
  const { dir, created } = runKnowledgeInit(kb);
@@ -30739,11 +31017,20 @@ knowledgeCommand.command("subscribe <kb>").description("Subscribe a project to a
30739
31017
  const list = runKnowledgeSubscribe(kb, opts.project);
30740
31018
  console.log(`${opts.project} knowledge: ${list.join(", ")}`);
30741
31019
  });
30742
- knowledgeCommand.command("sources <kb>").description("List the trusted sources of a knowledge base").action((kb) => {
31020
+ 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) => {
31021
+ if (o.dryRun && !o.remove) throw new Error("--dry-run only applies to --remove");
31022
+ if (o.remove) {
31023
+ const r = runKnowledgeSourcesRemove(kb, o.remove, { dryRun: o.dryRun });
31024
+ const stripped = r.stripped.length - r.stale.length;
31025
+ 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`);
31026
+ for (const id of r.stale) console.log(chalk38.yellow(` stale: ${id}`));
31027
+ if (r.dryRun) console.log(chalk38.dim("dry run: nothing written"));
31028
+ return;
31029
+ }
30743
31030
  const { sources, errors } = runKnowledgeSources(kb);
30744
31031
  if (!sources.length && !errors.length) console.log("(no sources \u2014 edit sources.yaml)");
30745
31032
  for (const s of sources) {
30746
- console.log(` ${s.priority.padEnd(8)} ${s.path}${s.domain ? ` [${s.domain}]` : ""}${s.sensitivity ? chalk38.yellow(" local-only") : ""}`);
31033
+ console.log(` ${s.priority.padEnd(8)} ${s.path}${s.domain ? ` [${s.domain}]` : ""}${s.sensitivity ? chalk38.yellow(" local-only") : ""}${s.review ? chalk38.cyan(" review: required") : ""}`);
30747
31034
  }
30748
31035
  for (const e of errors) console.log(chalk38.red(` \u2718 ${e}`));
30749
31036
  if (errors.length) process.exitCode = 1;
@@ -30751,9 +31038,9 @@ knowledgeCommand.command("sources <kb>").description("List the trusted sources o
30751
31038
  function runKnowledgeReindex(kb, configPath = DEFAULT_CONFIG_PATH) {
30752
31039
  assertKbName(kb);
30753
31040
  const cfg = loadConfig(configPath);
30754
- if (!fs53.existsSync(kbDir(cfg, kb))) throw new Error(`Knowledge base '${kb}' does not exist. Run: squadrant knowledge init ${kb}`);
31041
+ if (!fs54.existsSync(kbDir(cfg, kb))) throw new Error(`Knowledge base '${kb}' does not exist. Run: squadrant knowledge init ${kb}`);
30755
31042
  const { rules: all, errors: allErrors } = loadKbRules(cfg, kb, { includeProposed: true });
30756
- const proposedRoot = path58.join(kbRulesDir(cfg, kb), PROPOSED_DIR) + path58.sep;
31043
+ const proposedRoot = path59.join(kbRulesDir(cfg, kb), PROPOSED_DIR) + path59.sep;
30757
31044
  const live = all.filter((r) => !r.file.startsWith(proposedRoot));
30758
31045
  const pendingDups = allErrors.filter((e) => isPendingProposalDup(e, kbRulesDir(cfg, kb)));
30759
31046
  const errors = allErrors.filter((e) => !pendingDups.includes(e));
@@ -30763,11 +31050,15 @@ function runKnowledgeReindex(kb, configPath = DEFAULT_CONFIG_PATH) {
30763
31050
  function runKnowledgeValidate(kb, configPath = DEFAULT_CONFIG_PATH) {
30764
31051
  assertKbName(kb);
30765
31052
  const cfg = loadConfig(configPath);
30766
- if (!fs53.existsSync(kbDir(cfg, kb))) throw new Error(`Knowledge base '${kb}' does not exist. Run: squadrant knowledge init ${kb}`);
31053
+ if (!fs54.existsSync(kbDir(cfg, kb))) throw new Error(`Knowledge base '${kb}' does not exist. Run: squadrant knowledge init ${kb}`);
30767
31054
  const { sources, errors } = loadSources(kbDir(cfg, kb));
30768
- const res = validateKb({ rulesDir: kbRulesDir(cfg, kb), kbRoot: kbDir(cfg, kb), sources, domains: kbConfigs(cfg)[kb]?.domains });
30769
- const srcChecks = errors.map((e) => ({ level: "fail", message: `sources.yaml: ${e}` }));
30770
- return { checks: [...srcChecks, ...res.checks], ok: res.ok && !errors.length };
31055
+ const { errata, errors: errataErrors } = loadErrata(kbDir(cfg, kb));
31056
+ const res = validateKb({ rulesDir: kbRulesDir(cfg, kb), kbRoot: kbDir(cfg, kb), sources, errata, domains: kbConfigs(cfg)[kb]?.domains });
31057
+ const srcChecks = [
31058
+ ...errors.map((e) => ({ level: "fail", message: `sources.yaml: ${e}` })),
31059
+ ...errataErrors.map((e) => ({ level: "fail", message: `errata.yaml: ${e}` }))
31060
+ ];
31061
+ return { checks: [...srcChecks, ...res.checks], ok: res.ok && !srcChecks.length };
30771
31062
  }
30772
31063
  knowledgeCommand.command("reindex <kb>").description("Recompile index.json from the KB's rule files").action((kb) => {
30773
31064
  const r = runKnowledgeReindex(kb);
@@ -30793,7 +31084,7 @@ knowledgeCommand.command("migrate").description("Move <hubVault>/knowledge/<kb>/
30793
31084
  function requireKb(kb, configPath) {
30794
31085
  assertKbName(kb);
30795
31086
  const cfg = loadConfig(configPath);
30796
- if (!fs53.existsSync(kbDir(cfg, kb))) throw new Error(`Knowledge base '${kb}' does not exist. Run: squadrant knowledge init ${kb}`);
31087
+ if (!fs54.existsSync(kbDir(cfg, kb))) throw new Error(`Knowledge base '${kb}' does not exist. Run: squadrant knowledge init ${kb}`);
30797
31088
  return cfg;
30798
31089
  }
30799
31090
  async function runKnowledgeIngest(kb, opts = {}, configPath = DEFAULT_CONFIG_PATH) {
@@ -30812,10 +31103,14 @@ async function runKnowledgeIngest(kb, opts = {}, configPath = DEFAULT_CONFIG_PAT
30812
31103
  }
30813
31104
  function runKnowledgeApply(kb, candidatesFile, configPath = DEFAULT_CONFIG_PATH) {
30814
31105
  const cfg = requireKb(kb, configPath);
30815
- const input = JSON.parse(fs53.readFileSync(candidatesFile, "utf8"));
31106
+ const input = JSON.parse(fs54.readFileSync(candidatesFile, "utf8"));
30816
31107
  if (!input || !Array.isArray(input.candidates)) throw new Error("candidates file must be { complete?: string[], candidates: [...] }");
30817
31108
  const projects = Object.entries(cfg.projects).filter(([name, pc]) => pc.path && subscribedKbs(cfg, name).includes(kb)).map(([name, pc]) => ({ name, root: resolveHome(pc.path) }));
30818
- return applyCandidates(input, { kb, kbRoot: kbDir(cfg, kb), rulesDir: kbRulesDir(cfg, kb), projects });
31109
+ const { errata, errors } = loadErrata(kbDir(cfg, kb));
31110
+ if (errors.length) throw new Error(`errata.yaml: ${errors.join("; ")}`);
31111
+ const { sources, errors: sourceErrors } = loadSources(kbDir(cfg, kb));
31112
+ if (sourceErrors.length) throw new Error(`sources.yaml: ${sourceErrors.join("; ")}`);
31113
+ return applyCandidates(input, { kb, kbRoot: kbDir(cfg, kb), rulesDir: kbRulesDir(cfg, kb), projects, sources, errata });
30819
31114
  }
30820
31115
  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) => {
30821
31116
  const plan = await runKnowledgeIngest(kb, opts);
@@ -30834,16 +31129,19 @@ knowledgeCommand.command("apply <kb> <candidates>").description("Verify extracte
30834
31129
  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}`);
30835
31130
  for (const x of r.rejected) console.log(chalk38.red(` \u2718 ${x.id} (${x.source}): ${x.reason}`));
30836
31131
  for (const x of r.idOverrides) console.log(chalk38.dim(` id ${x.from} \u2192 ${x.to} (matched by quote offset)`));
31132
+ 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`));
30837
31133
  for (const d of r.doclingFlags) console.log(chalk38.yellow(` ${d.ref}: ${d.dropped}/${d.total} candidates dropped; conversion likely poor, try docling`));
30838
31134
  });
30839
- var defaultStateRoot2 = () => path58.join(path58.dirname(DEFAULT_CONFIG_PATH), "state");
31135
+ var defaultStateRoot2 = () => path59.join(path59.dirname(DEFAULT_CONFIG_PATH), "state");
30840
31136
  function reviewContext(kb, configPath) {
30841
31137
  const cfg = requireKb(kb, configPath);
30842
31138
  const projects = Object.entries(cfg.projects).filter(([name, pc]) => pc.path && subscribedKbs(cfg, name).includes(kb)).map(([name, pc]) => ({ name, root: resolveHome(pc.path) }));
30843
- return { cfg, kb, sources: loadSources(kbDir(cfg, kb)).sources, projects };
31139
+ const { sources, errors } = loadSources(kbDir(cfg, kb));
31140
+ if (errors.length) throw new Error(`sources.yaml: ${errors.join("; ")}`);
31141
+ return { cfg, kb, sources, projects };
30844
31142
  }
30845
31143
  function runKnowledgeReviewApply(kb, file, configPath = DEFAULT_CONFIG_PATH) {
30846
- return applyDecisions(JSON.parse(fs53.readFileSync(file, "utf8")), reviewContext(kb, configPath));
31144
+ return applyDecisions(JSON.parse(fs54.readFileSync(file, "utf8")), reviewContext(kb, configPath));
30847
31145
  }
30848
31146
  function runKnowledgeResolve(kb, key, r, by, reason, configPath = DEFAULT_CONFIG_PATH) {
30849
31147
  return resolveEscalation(reviewContext(kb, configPath), key, r, by, reason);
@@ -30863,7 +31161,7 @@ knowledgeCommand.command("review <kb>").description("Escalations for the operato
30863
31161
  for (const x of r.skipped) console.log(chalk38.red(` \u2718 ${x.item}: ${x.reason}`));
30864
31162
  return;
30865
31163
  }
30866
- const by = os26.userInfo().username;
31164
+ const by = os25.userInfo().username;
30867
31165
  const resolution = () => o.keepDoc ? { kind: "verdict", verdict: "keep-doc" } : o.supersedeWithCode ? { kind: "verdict", verdict: "supersede-with-code" } : o.approve ? { kind: "approve" } : o.reject ? { kind: "reject" } : null;
30868
31166
  if (o.resolve) {
30869
31167
  const r = resolution();
@@ -30891,8 +31189,8 @@ ${e.explanation}`);
30891
31189
  else if (ans === "r") r = { kind: "reject" };
30892
31190
  else if (ans === "e" && e.kind === "proposal") {
30893
31191
  const ctx = reviewContext(kb, DEFAULT_CONFIG_PATH);
30894
- const file = path58.join(kbRulesDir(ctx.cfg, kb), PROPOSED_DIR, `${e.item}.md`);
30895
- spawnSync(process.env.EDITOR ?? "vi", [file], { stdio: "inherit" });
31192
+ const file = path59.join(kbRulesDir(ctx.cfg, kb), PROPOSED_DIR, `${e.item}.md`);
31193
+ spawnSync2(process.env.EDITOR ?? "vi", [file], { stdio: "inherit" });
30896
31194
  r = { kind: "approve" };
30897
31195
  }
30898
31196
  if (r) console.log(chalk38.green(runKnowledgeResolve(kb, e.key, r, by, reason)));
@@ -30963,7 +31261,7 @@ function renderStats(all) {
30963
31261
  const pct = (x) => x === null ? "-" : `${Math.round(x * 100)}%`;
30964
31262
  return all.flatMap((s) => [
30965
31263
  `${s.kb} (last ${s.windowDays} days)`,
30966
- ...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" : ""}`),
31264
+ ...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" : ""}`),
30967
31265
  ...s.domains.map((d) => ` domain ${d.domain}: writes ${d.writesPerMonth}/mo reads ${d.readsPerMonth}/mo${d.deadDomain ? " DEAD DOMAIN" : ""}`),
30968
31266
  ` agents: ${Object.entries(s.perAgentCoverage).map(([k, v2]) => `${k} ${v2}`).join(", ") || "none"}`
30969
31267
  ]);
@@ -30974,10 +31272,10 @@ knowledgeCommand.command("stats").description("Scores computed from the audit lo
30974
31272
  else for (const l of renderStats(all)) console.log(l);
30975
31273
  });
30976
31274
  function runKnowledgeCiteScan(o) {
30977
- const run = o.git_run ?? ((args) => spawnSync("git", args, { encoding: "utf8" }).stdout ?? "");
31275
+ const run = o.git_run ?? ((args) => spawnSync2("git", args, { encoding: "utf8" }).stdout ?? "");
30978
31276
  if (o.git) {
30979
31277
  const since = new Date((o.audit?.now?.() ?? /* @__PURE__ */ new Date()).getTime() - (o.sinceDays ?? 30) * 864e5);
30980
- 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 }));
31278
+ 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 });
30981
31279
  }
30982
31280
  if (!o.text) throw new Error("Pass --text <message> or --git <repo>");
30983
31281
  return scanAndLogCitations({ text: o.text, ref: o.ref ?? "manual", project: o.project, sink: o.audit });