squadrant 0.27.1 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -466,7 +466,8 @@ function validateRuleFrontmatter(fm) {
466
466
  if (fm[k] !== void 0 && !isStrArr(fm[k]))
467
467
  p.push(`${k} must be a string array`);
468
468
  }
469
- if (!Array.isArray(fm.sources) || fm.sources.length === 0) {
469
+ const mayBeSourceless = fm.status === "stale" || fm.status === "retired";
470
+ if (!Array.isArray(fm.sources) || fm.sources.length === 0 && !mayBeSourceless) {
470
471
  p.push("sources must be a non-empty array");
471
472
  } else {
472
473
  fm.sources.forEach((s, i) => {
@@ -500,6 +501,26 @@ function validateSourceEntry(e) {
500
501
  p.push("domain must be a string");
501
502
  if (e.sensitivity !== void 0 && e.sensitivity !== "local-only")
502
503
  p.push("sensitivity must be 'local-only' when set");
504
+ if (e.review !== void 0 && e.review !== "required")
505
+ p.push("review must be 'required' when set");
506
+ return p;
507
+ }
508
+ function validateErratum(e) {
509
+ if (!isObj(e))
510
+ return ["erratum must be an object"];
511
+ const p = [];
512
+ if (typeof e.id !== "string" || !RULE_ID_RE.test(e.id))
513
+ p.push(`id must match ${RULE_ID_RE}`);
514
+ for (const k of ["ref", "quote", "reason"]) {
515
+ if (typeof e[k] !== "string" || e[k] === "")
516
+ p.push(`${k} must be a non-empty string`);
517
+ }
518
+ if (e.evidence !== void 0 && !isStrArr(e.evidence))
519
+ p.push("evidence must be a string array");
520
+ for (const k of ["checked", "by", "at", "decision"]) {
521
+ if (e[k] !== void 0 && typeof e[k] !== "string")
522
+ p.push(`${k} must be a string`);
523
+ }
503
524
  return p;
504
525
  }
505
526
  function kbConfigs(cfg) {
@@ -624,22 +645,22 @@ function defaultCmuxConfigPath() {
624
645
  return join(homedir(), ".config", "cmux", "cmux.json");
625
646
  }
626
647
  function ensureSocketAutomation(opts = {}) {
627
- const path59 = opts.path ?? defaultCmuxConfigPath();
628
- if (!existsSync(path59)) {
629
- mkdirSync(dirname(path59), { recursive: true });
630
- writeFileSync(path59, MINIMAL_TEMPLATE);
631
- return { path: path59, changed: true, alreadySet: false };
648
+ const path60 = opts.path ?? defaultCmuxConfigPath();
649
+ if (!existsSync(path60)) {
650
+ mkdirSync(dirname(path60), { recursive: true });
651
+ writeFileSync(path60, MINIMAL_TEMPLATE);
652
+ return { path: path60, changed: true, alreadySet: false };
632
653
  }
633
- const text = readFileSync(path59, "utf-8");
654
+ const text = readFileSync(path60, "utf-8");
634
655
  const current = parse(text)?.automation?.socketControlMode;
635
656
  if (current === AUTOMATION_MODE) {
636
- return { path: path59, changed: false, alreadySet: true };
657
+ return { path: path60, changed: false, alreadySet: true };
637
658
  }
638
659
  const edits = modify(text, [...SOCKET_CONTROL_MODE_PATH], AUTOMATION_MODE, {
639
660
  formattingOptions: { insertSpaces: true, tabSize: 2 }
640
661
  });
641
- writeFileSync(path59, applyEdits(text, edits));
642
- return { path: path59, changed: true, alreadySet: false };
662
+ writeFileSync(path60, applyEdits(text, edits));
663
+ return { path: path60, changed: true, alreadySet: false };
643
664
  }
644
665
  var SOCKET_CONTROL_MODE_PATH, AUTOMATION_MODE, MINIMAL_TEMPLATE;
645
666
  var init_cmux_config = __esm({
@@ -792,9 +813,9 @@ import { join as join4 } from "path";
792
813
  function defaultStatePath() {
793
814
  return join4(homedir3(), ".config", "squadrant", "state", "cmux-autoconfig.json");
794
815
  }
795
- function readState(path59) {
816
+ function readState(path60) {
796
817
  try {
797
- return JSON.parse(readConfigFileSync(path59));
818
+ return JSON.parse(readConfigFileSync(path60));
798
819
  } catch {
799
820
  return {};
800
821
  }
@@ -955,18 +976,18 @@ function detectDrift(user, def) {
955
976
  }
956
977
  }
957
978
  }
958
- const checkBackend = (path59, backend, agentName) => {
979
+ const checkBackend = (path60, backend, agentName) => {
959
980
  if (backend === void 0)
960
981
  return;
961
982
  if (!isBackendMode(backend)) {
962
- items.push({ path: path59, kind: "invalid", severity: "warn", current: backend, note: "unknown backend; expected native|direct|proxy" });
983
+ items.push({ path: path60, kind: "invalid", severity: "warn", current: backend, note: "unknown backend; expected native|direct|proxy" });
963
984
  return;
964
985
  }
965
986
  if (backend !== "native") {
966
987
  if (agentName !== "claude") {
967
- items.push({ path: path59, kind: "invalid", severity: "warn", current: backend, note: `backend '${backend}' is claude-only (agent '${agentName ?? "?"}')` });
988
+ items.push({ path: path60, kind: "invalid", severity: "warn", current: backend, note: `backend '${backend}' is claude-only (agent '${agentName ?? "?"}')` });
968
989
  } else if (!router) {
969
- items.push({ path: path59, kind: "invalid", severity: "warn", current: backend, note: `backend '${backend}' requires defaults.router` });
990
+ items.push({ path: path60, kind: "invalid", severity: "warn", current: backend, note: `backend '${backend}' requires defaults.router` });
970
991
  }
971
992
  }
972
993
  };
@@ -1859,6 +1880,7 @@ __export(dist_exports, {
1859
1880
  saveConfig: () => saveConfig,
1860
1881
  saveProjectOverride: () => saveProjectOverride,
1861
1882
  subscribedKbs: () => subscribedKbs,
1883
+ validateErratum: () => validateErratum,
1862
1884
  validateRuleFrontmatter: () => validateRuleFrontmatter,
1863
1885
  validateSourceEntry: () => validateSourceEntry,
1864
1886
  withStamp: () => withStamp,
@@ -3510,10 +3532,10 @@ function detectForeignInstall(parsed, thisEntry, registeredEntryExists) {
3510
3532
  isUpgrade: isVersionUpgrade(parsed.daemonEntry, thisEntry)
3511
3533
  };
3512
3534
  }
3513
- function sanitizePathForPlist(path59) {
3535
+ function sanitizePathForPlist(path60) {
3514
3536
  const seen = /* @__PURE__ */ new Set();
3515
3537
  const stable = [];
3516
- for (const p of path59.split(":")) {
3538
+ for (const p of path60.split(":")) {
3517
3539
  if (!p)
3518
3540
  continue;
3519
3541
  if (p.includes("/.claude/plugins/"))
@@ -6053,11 +6075,11 @@ var init_captain_record = __esm({
6053
6075
  // packages/core/dist/opencode-session.js
6054
6076
  import { execFileSync as execFileSync4 } from "child_process";
6055
6077
  async function listSessions(port, fetchImpl = fetch, timeoutMs = 5e3) {
6056
- for (const path59 of ["/session", "/api/session"]) {
6078
+ for (const path60 of ["/session", "/api/session"]) {
6057
6079
  const ac = new AbortController();
6058
6080
  const t = setTimeout(() => ac.abort(), timeoutMs);
6059
6081
  try {
6060
- const res = await fetchImpl(`http://127.0.0.1:${port}${path59}`, { signal: ac.signal });
6082
+ const res = await fetchImpl(`http://127.0.0.1:${port}${path60}`, { signal: ac.signal });
6061
6083
  if (!res.ok)
6062
6084
  continue;
6063
6085
  const rows = await res.json();
@@ -6750,10 +6772,10 @@ function distBuiltAt() {
6750
6772
  return 0;
6751
6773
  }
6752
6774
  }
6753
- function gatherLogStats(path59, now, windowMs) {
6775
+ function gatherLogStats(path60, now, windowMs) {
6754
6776
  let sizeBytes = 0;
6755
6777
  try {
6756
- sizeBytes = statSync3(path59).size;
6778
+ sizeBytes = statSync3(path60).size;
6757
6779
  } catch {
6758
6780
  return { errorCount: 0, sizeBytes: 0, windowMs };
6759
6781
  }
@@ -6764,7 +6786,7 @@ function gatherLogStats(path59, now, windowMs) {
6764
6786
  const len = sizeBytes - start;
6765
6787
  let text = "";
6766
6788
  try {
6767
- const fd = openSync2(path59, "r");
6789
+ const fd = openSync2(path60, "r");
6768
6790
  try {
6769
6791
  const buf = Buffer.alloc(len);
6770
6792
  readSync(fd, buf, 0, len, start);
@@ -9010,8 +9032,8 @@ var init_stream = __esm({
9010
9032
  // packages/core/dist/router/shim.js
9011
9033
  import { createServer as createServer3 } from "http";
9012
9034
  import { Readable } from "stream";
9013
- function joinUrl(base, path59) {
9014
- return base.replace(/\/+$/, "") + path59;
9035
+ function joinUrl(base, path60) {
9036
+ return base.replace(/\/+$/, "") + path60;
9015
9037
  }
9016
9038
  async function readBody(req) {
9017
9039
  const chunks = [];
@@ -9187,8 +9209,8 @@ function createRouterShim(opts) {
9187
9209
  writeJson(res, 200, { input_tokens: Math.max(1, Math.ceil(text.length / 4)) });
9188
9210
  }
9189
9211
  function handler(req, res) {
9190
- const path59 = pathnameOf(req.url);
9191
- if (req.method === "POST" && path59 === "/v1/messages") {
9212
+ const path60 = pathnameOf(req.url);
9213
+ if (req.method === "POST" && path60 === "/v1/messages") {
9192
9214
  void handleMessages(req, res).catch((err) => {
9193
9215
  log(`router internal error: ${err instanceof Error ? err.message : String(err)}`);
9194
9216
  if (!res.headersSent) {
@@ -9200,18 +9222,18 @@ function createRouterShim(opts) {
9200
9222
  });
9201
9223
  return;
9202
9224
  }
9203
- if (req.method === "POST" && path59 === "/v1/messages/count_tokens") {
9225
+ if (req.method === "POST" && path60 === "/v1/messages/count_tokens") {
9204
9226
  void handleCountTokens(req, res).catch(() => {
9205
9227
  if (!res.headersSent)
9206
9228
  writeJson(res, 502, anthropicError(502, "api_error", "router internal error").body);
9207
9229
  });
9208
9230
  return;
9209
9231
  }
9210
- if (req.method === "GET" && path59 === "/v1/models") {
9232
+ if (req.method === "GET" && path60 === "/v1/models") {
9211
9233
  writeJson(res, 200, modelsList([...advertisedModels]));
9212
9234
  return;
9213
9235
  }
9214
- if (req.method === "GET" && path59 === "/healthz") {
9236
+ if (req.method === "GET" && path60 === "/healthz") {
9215
9237
  writeJson(res, 200, { ready: server?.listening === true, upstreamReachable, lastError });
9216
9238
  return;
9217
9239
  }
@@ -9480,11 +9502,11 @@ async function probeCaptainChannel(project, opts = {}) {
9480
9502
  }
9481
9503
  }
9482
9504
  async function httpReachable(port, fetchImpl, timeoutMs) {
9483
- for (const path59 of ["/session", "/api/session"]) {
9505
+ for (const path60 of ["/session", "/api/session"]) {
9484
9506
  const ac = new AbortController();
9485
9507
  const t = setTimeout(() => ac.abort(), timeoutMs);
9486
9508
  try {
9487
- const res = await fetchImpl(`http://127.0.0.1:${port}${path59}`, { signal: ac.signal });
9509
+ const res = await fetchImpl(`http://127.0.0.1:${port}${path60}`, { signal: ac.signal });
9488
9510
  if (res.ok)
9489
9511
  return true;
9490
9512
  } catch {
@@ -10406,6 +10428,11 @@ 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 sourceCovers(kbRoot, entry, ref) {
10432
+ const abs = path16.resolve(kbRoot, expandHome(entry.path));
10433
+ const file = fileForRef(kbRoot, ref);
10434
+ return /[*?]/.test(entry.path) ? globToRegExp(abs.split(path16.sep).join("/")).test(file.split(path16.sep).join("/")) : abs === file;
10435
+ }
10409
10436
  function detectChange(kbRoot, file) {
10410
10437
  const ref = refFor(kbRoot, file);
10411
10438
  const st = fs15.statSync(file);
@@ -10502,7 +10529,7 @@ async function planIngest(kbRoot, opts) {
10502
10529
  }
10503
10530
  return plan;
10504
10531
  }
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;
10532
+ var execFileAsync, CONVERTED_DIR, DEFAULT_MAX_SECTIONS_PER_PASS, CHARS_PER_TOKEN, PASS_THROUGH, SECTION_TARGET_CHARS, markitdownConverter, expandHome, fileForRef, cacheKey, convertedFile, stampFile, isGatedRef, sha256;
10506
10533
  var init_ingest = __esm({
10507
10534
  "packages/core/dist/knowledge/ingest.js"() {
10508
10535
  execFileAsync = promisify2(execFile2);
@@ -10520,6 +10547,7 @@ var init_ingest = __esm({
10520
10547
  cacheKey = (ref) => `${ref.replace(/[^A-Za-z0-9._-]+/g, "_")}.${crypto3.createHash("sha1").update(ref).digest("hex").slice(0, 6)}`;
10521
10548
  convertedFile = (kbRoot, ref) => path16.join(kbRoot, CONVERTED_DIR, `${cacheKey(ref)}.md`);
10522
10549
  stampFile = (kbRoot, ref) => path16.join(kbRoot, CONVERTED_DIR, `${cacheKey(ref)}.sha`);
10550
+ isGatedRef = (kbRoot, sources, ref) => sources.some((e) => e.review === "required" && sourceCovers(kbRoot, e, ref));
10523
10551
  sha256 = (file) => crypto3.createHash("sha256").update(fs15.readFileSync(file)).digest("hex");
10524
10552
  }
10525
10553
  });
@@ -10712,9 +10740,14 @@ function validateKb(opts) {
10712
10740
  });
10713
10741
  }
10714
10742
  const { hay, convertible } = cache.get(key);
10715
- if (findQuote(hay, src.quote))
10743
+ const span = findQuote(hay, src.quote);
10744
+ if (span) {
10716
10745
  verified++;
10717
- else if (convertible)
10746
+ const active = r.status === "active" && !path17.relative(opts.rulesDir, r.file).split(path17.sep).includes(PROPOSED_DIR);
10747
+ const err = active ? (opts.errata ?? []).find((e) => e.ref === src.ref && ((o) => !!o && spansOverlap(o, span))(findQuote(hay, e.quote))) : void 0;
10748
+ if (err)
10749
+ checks.push({ level: "warn", message: `${r.id}: grounded in erratum ${err.id} (${err.reason}); re-check or retire it (${r.file})` });
10750
+ } else if (convertible)
10718
10751
  notIngested++;
10719
10752
  else
10720
10753
  checks.push({ level: "fail", message: `quote not found in ${file}: ${r.id} sources[${i}] (${r.file})` });
@@ -13919,6 +13952,7 @@ var init_js_yaml = __esm({
13919
13952
  // packages/core/dist/knowledge/sources.js
13920
13953
  import fs19 from "fs";
13921
13954
  import path19 from "path";
13955
+ import matter3 from "gray-matter";
13922
13956
  function loadSources(kbRoot) {
13923
13957
  const file = path19.join(kbRoot, "sources.yaml");
13924
13958
  if (!fs19.existsSync(file))
@@ -13944,20 +13978,77 @@ function loadSources(kbRoot) {
13944
13978
  });
13945
13979
  return { sources, errors };
13946
13980
  }
13981
+ function dropEntryBlock(text, entryPath) {
13982
+ const lines = text.split("\n");
13983
+ const q = entryPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13984
+ const head = new RegExp(`^-\\s+path:\\s*(['"]?)${q}\\1\\s*(#.*)?$`);
13985
+ const i = lines.findIndex((l) => head.test(l));
13986
+ if (i < 0)
13987
+ return null;
13988
+ let j = i + 1;
13989
+ while (j < lines.length && /^\s+\S/.test(lines[j]))
13990
+ j++;
13991
+ return [...lines.slice(0, i), ...lines.slice(j)].join("\n");
13992
+ }
13993
+ function removeSource(kbRoot, rulesDir, entryPath, opts = {}) {
13994
+ const file = path19.join(kbRoot, "sources.yaml");
13995
+ const { sources } = loadSources(kbRoot);
13996
+ const entry = sources.find((s) => s.path === entryPath);
13997
+ if (!entry)
13998
+ throw new Error(`no sources.yaml entry with path '${entryPath}' (give it exactly as listed by \`knowledge sources\`)`);
13999
+ const edited = dropEntryBlock(fs19.readFileSync(file, "utf8"), entryPath);
14000
+ if (edited === null)
14001
+ throw new Error(`could not edit sources.yaml for '${entryPath}' (not a plain block entry); remove it by hand, then rerun to strip the refs`);
14002
+ const res = { dryRun: !!opts.dryRun, entryRemoved: true, stripped: [], stale: [] };
14003
+ const writes = [];
14004
+ for (const rule2 of loadRulesDir(rulesDir, "kb").rules) {
14005
+ const kept = rule2.sources.filter((s) => !sourceCovers(kbRoot, entry, s.ref));
14006
+ if (kept.length === rule2.sources.length)
14007
+ continue;
14008
+ res.stripped.push(rule2.id);
14009
+ const parsed = matter3(fs19.readFileSync(rule2.file, "utf8"), { engines: { javascript: () => {
14010
+ throw new Error("only YAML front matter is allowed");
14011
+ } } });
14012
+ const fm = { ...parsed.data, sources: kept };
14013
+ if (!kept.length) {
14014
+ res.stale.push(rule2.id);
14015
+ if (fm.status === "active")
14016
+ fm.status = "stale";
14017
+ }
14018
+ writes.push({ file: rule2.file, text: matter3.stringify(parsed.content, JSON.parse(JSON.stringify(fm))) });
14019
+ }
14020
+ res.stripped.sort();
14021
+ res.stale.sort();
14022
+ if (opts.dryRun)
14023
+ return res;
14024
+ for (const w of writes)
14025
+ fs19.writeFileSync(w.file, w.text);
14026
+ fs19.writeFileSync(file, edited);
14027
+ const name = opts.kbName ?? path19.basename(kbRoot);
14028
+ writeIndex(kbRoot, compileIndex(name, loadRulesDir(rulesDir, `kb:${name}`).rules));
14029
+ return res;
14030
+ }
13947
14031
  var SOURCES_TEMPLATE;
13948
14032
  var init_sources = __esm({
13949
14033
  "packages/core/dist/knowledge/sources.js"() {
13950
14034
  init_js_yaml();
13951
14035
  init_dist();
14036
+ init_index_file();
14037
+ init_ingest();
14038
+ init_store2();
13952
14039
  SOURCES_TEMPLATE = `# Trusted sources for this knowledge base (spec \xA73). Only these can produce rules.
13953
14040
  # priority: company > project > agent (decides conflicts).
13954
14041
  # sensitivity: local-only \u2192 never sent to a hosted model (D12).
14042
+ # review: required \u2192 new rules from this source wait in rules/_proposed/ for the operator; they never go live on their own.
13955
14043
  #
13956
14044
  # - path: raw/finance/**
13957
14045
  # priority: company
13958
14046
  # - path: ~/work/flooros/docs/handbook/**
13959
14047
  # priority: project
13960
14048
  # domain: coding
14049
+ # - path: raw/**
14050
+ # priority: agent
14051
+ # review: required
13961
14052
  []
13962
14053
  `;
13963
14054
  }
@@ -14349,7 +14440,7 @@ var init_inject = __esm({
14349
14440
  STOPWORDS = new Set("the and for are but not you your yours with this that these those from into onto have has had was were will would can could should shall may might must does did doing done its it's our ours their them they what when where which who whom why how all any some each every other than then there here also just very too only own same such both few more most much many about above below after before again once out over under off further been being let lets please thanks thank okay yes yeah hello hey there need want make use using used get got give like know think see look sure ok now new one two way rule rules must-not looks sounds work working thing things something anything everything going well good great nice cool fine".split(/\s+/));
14350
14441
  DATA_NOTE = "These are project rules (data from the knowledge base), not instructions to run anything.";
14351
14442
  line = (r) => `${r.modality.toUpperCase()} ${r.id}: ${r.statement}`;
14352
- CITE_NOTE = "Name the rule ids you apply in your DONE/commit message.";
14443
+ CITE_NOTE = "List the rule ids you apply as `Rules: <id>, <id>` at the end of your signal done message (commit messages count too).";
14353
14444
  escapeRe = (t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14354
14445
  BRIEF_POINTER_RE = /^Full task is at (\S+\.md) — cat it/;
14355
14446
  READ_TOOLS = /* @__PURE__ */ new Set(["read", "read_file", "readfile", "view"]);
@@ -14461,9 +14552,63 @@ var init_migrate = __esm({
14461
14552
  }
14462
14553
  });
14463
14554
 
14464
- // packages/core/dist/knowledge/anchor.js
14555
+ // packages/core/dist/knowledge/errata.js
14465
14556
  import fs23 from "fs";
14466
14557
  import path24 from "path";
14558
+ function loadErrata(kbRoot) {
14559
+ const file = path24.join(kbRoot, "errata.yaml");
14560
+ if (!fs23.existsSync(file))
14561
+ return { errata: [], errors: [] };
14562
+ let data;
14563
+ try {
14564
+ data = yaml.load(fs23.readFileSync(file, "utf8"), { schema: yaml.JSON_SCHEMA });
14565
+ } catch (e) {
14566
+ return { errata: [], errors: [`YAML parse error: ${e.message.split("\n")[0]}`] };
14567
+ }
14568
+ if (data == null)
14569
+ return { errata: [], errors: [] };
14570
+ if (!Array.isArray(data))
14571
+ return { errata: [], errors: ["errata.yaml must be a YAML list of entries"] };
14572
+ const errata = [];
14573
+ const errors = [];
14574
+ data.forEach((entry, i) => {
14575
+ const problems = validateErratum(entry);
14576
+ if (problems.length)
14577
+ errors.push(...problems.map((p) => `entry ${i}: ${p}`));
14578
+ else
14579
+ errata.push(entry);
14580
+ });
14581
+ return { errata, errors };
14582
+ }
14583
+ function orphanedErrata(kbRoot, errata, refs) {
14584
+ const only = refs ? new Set(refs) : void 0;
14585
+ const hays = /* @__PURE__ */ new Map();
14586
+ const out = [];
14587
+ for (const e of errata) {
14588
+ if (only && !only.has(e.ref))
14589
+ continue;
14590
+ if (!hays.has(e.ref)) {
14591
+ const conv = readState2(kbRoot, e.ref) ? convertedFile(kbRoot, e.ref) : null;
14592
+ hays.set(e.ref, conv && fs23.existsSync(conv) ? normalizedViews(fs23.readFileSync(conv, "utf8")) : null);
14593
+ }
14594
+ const hay = hays.get(e.ref);
14595
+ if (hay && !findQuote(hay, e.quote))
14596
+ out.push({ id: e.id, ref: e.ref });
14597
+ }
14598
+ return out;
14599
+ }
14600
+ var init_errata = __esm({
14601
+ "packages/core/dist/knowledge/errata.js"() {
14602
+ init_js_yaml();
14603
+ init_dist();
14604
+ init_ingest();
14605
+ init_verify();
14606
+ }
14607
+ });
14608
+
14609
+ // packages/core/dist/knowledge/anchor.js
14610
+ import fs24 from "fs";
14611
+ import path25 from "path";
14467
14612
  function* repoFiles(root) {
14468
14613
  let count = 0;
14469
14614
  const stack = [root];
@@ -14471,43 +14616,43 @@ function* repoFiles(root) {
14471
14616
  const dir = stack.pop();
14472
14617
  let ents;
14473
14618
  try {
14474
- ents = fs23.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name < b.name ? 1 : -1);
14619
+ ents = fs24.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name < b.name ? 1 : -1);
14475
14620
  } catch {
14476
14621
  continue;
14477
14622
  }
14478
14623
  for (const ent of ents) {
14479
14624
  if (ent.isDirectory()) {
14480
14625
  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))) {
14626
+ stack.push(path25.join(dir, ent.name));
14627
+ } else if (ent.isFile() && CODE_EXT.has(path25.extname(ent.name))) {
14483
14628
  if (++count > MAX_FILES)
14484
14629
  return;
14485
- yield path24.join(dir, ent.name);
14630
+ yield path25.join(dir, ent.name);
14486
14631
  }
14487
14632
  }
14488
14633
  }
14489
14634
  }
14490
14635
  function computeAnchor(repoRoot, triggers) {
14491
14636
  const terms2 = anchorTerms(triggers);
14492
- if (!terms2.length || !fs23.existsSync(repoRoot))
14637
+ if (!terms2.length || !fs24.existsSync(repoRoot))
14493
14638
  return null;
14494
14639
  const hits = [];
14495
14640
  for (const file of repoFiles(repoRoot)) {
14496
14641
  let text;
14497
14642
  try {
14498
- if (fs23.statSync(file).size > MAX_FILE_BYTES)
14643
+ if (fs24.statSync(file).size > MAX_FILE_BYTES)
14499
14644
  continue;
14500
- text = fs23.readFileSync(file, "utf8");
14645
+ text = fs24.readFileSync(file, "utf8");
14501
14646
  } catch {
14502
14647
  continue;
14503
14648
  }
14504
- const hay = `${path24.relative(repoRoot, file)}
14649
+ const hay = `${path25.relative(repoRoot, file)}
14505
14650
  ${text}`.toLowerCase();
14506
14651
  const score = terms2.filter((t) => hay.includes(t)).length;
14507
14652
  if (!score)
14508
14653
  continue;
14509
14654
  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 });
14655
+ hits.push({ rel: path25.relative(repoRoot, file).split(path25.sep).join("/"), score, symbols: symbols2 });
14511
14656
  }
14512
14657
  if (!hits.length)
14513
14658
  return null;
@@ -14531,11 +14676,11 @@ var init_anchor = __esm({
14531
14676
  });
14532
14677
 
14533
14678
  // packages/core/dist/knowledge/extract.js
14534
- import fs24 from "fs";
14535
- import path25 from "path";
14536
- import matter3 from "gray-matter";
14679
+ import fs25 from "fs";
14680
+ import path26 from "path";
14681
+ import matter4 from "gray-matter";
14537
14682
  function serialize(fm, body) {
14538
- return matter3.stringify(body.trim() + "\n", clean(fm));
14683
+ return matter4.stringify(body.trim() + "\n", clean(fm));
14539
14684
  }
14540
14685
  function validateCandidate(c) {
14541
14686
  const p = [];
@@ -14578,9 +14723,9 @@ function upsertSource(fm, src) {
14578
14723
  }
14579
14724
  function applyCandidates(input, opts) {
14580
14725
  const { kb, kbRoot, rulesDir } = opts;
14581
- const res = { created: [], merged: [], proposed: [], superseded: [], stale: [], rejected: [], doclingFlags: [], idOverrides: [] };
14726
+ const res = { created: [], merged: [], proposed: [], superseded: [], stale: [], rejected: [], doclingFlags: [], idOverrides: [], orphanedErrata: [] };
14582
14727
  const events = [];
14583
- const ev2 = (event, itemId, domain, extra = {}) => events.push({ kb, level: "group", project: "", domain, itemId, event, ...extra });
14728
+ const ev2 = (event, itemId, extra = {}) => events.push({ kb, level: "group", project: "", domain: "rules", itemId, event, ...extra });
14584
14729
  const loaded = loadRulesDir(rulesDir, `kb:${kb}`);
14585
14730
  const work = /* @__PURE__ */ new Map();
14586
14731
  for (const r of loaded.rules) {
@@ -14599,13 +14744,13 @@ function applyCandidates(input, opts) {
14599
14744
  }
14600
14745
  const st = readState2(kbRoot, cand.source);
14601
14746
  const convFile = st ? convertedFile(kbRoot, cand.source) : null;
14602
- if (!st || !fs24.existsSync(convFile)) {
14747
+ if (!st || !fs25.existsSync(convFile)) {
14603
14748
  res.rejected.push({ id: cand.id, source: cand.source, reason: "unknown source (not ingested)" });
14604
14749
  continue;
14605
14750
  }
14606
14751
  let hay = norm.get(cand.source);
14607
14752
  if (!hay) {
14608
- hay = normalizedViews(fs24.readFileSync(convFile, "utf8"));
14753
+ hay = normalizedViews(fs25.readFileSync(convFile, "utf8"));
14609
14754
  norm.set(cand.source, hay);
14610
14755
  }
14611
14756
  const tot = totals.get(cand.source) ?? { total: 0, dropped: 0 };
@@ -14615,7 +14760,14 @@ function applyCandidates(input, opts) {
14615
14760
  if (!offset) {
14616
14761
  tot.dropped++;
14617
14762
  res.rejected.push({ id: cand.id, source: cand.source, reason: "ungrounded" });
14618
- ev2("item.rejected", cand.id, cand.domain, { reason: "ungrounded" });
14763
+ ev2("item.rejected", cand.id, { reason: "ungrounded" });
14764
+ continue;
14765
+ }
14766
+ const hit = (opts.errata ?? []).find((e) => e.ref === cand.source && ((o) => !!o && spansOverlap(o, offset))(findQuote(hay, e.quote)));
14767
+ if (hit) {
14768
+ tot.total--;
14769
+ res.rejected.push({ id: cand.id, source: cand.source, reason: `errata:${hit.id}` });
14770
+ ev2("item.rejected", cand.id, { reason: `errata:${hit.id}` });
14619
14771
  continue;
14620
14772
  }
14621
14773
  let id = cand.id;
@@ -14647,7 +14799,7 @@ ${cand.rationale.trim()}` : "");
14647
14799
  }
14648
14800
  continue;
14649
14801
  }
14650
- if (!target && contradicted.length === 0) {
14802
+ if (!target && contradicted.length === 0 && !isGatedRef(kbRoot, opts.sources ?? [], cand.source)) {
14651
14803
  const fm2 = {
14652
14804
  id,
14653
14805
  domain: cand.domain,
@@ -14660,9 +14812,9 @@ ${cand.rationale.trim()}` : "");
14660
14812
  supersedes: [],
14661
14813
  conflictsWith: []
14662
14814
  };
14663
- work.set(id, { fm: fm2, body: bodyOf(), file: path25.join(rulesDir, cand.domain, `${id}.md`), dirty: true, isNew: true });
14815
+ work.set(id, { fm: fm2, body: bodyOf(), file: path26.join(rulesDir, cand.domain, `${id}.md`), dirty: true, isNew: true });
14664
14816
  res.created.push(id);
14665
- ev2("item.applied", id, cand.domain);
14817
+ ev2("item.applied", id);
14666
14818
  continue;
14667
14819
  }
14668
14820
  const fm = {
@@ -14677,26 +14829,26 @@ ${cand.rationale.trim()}` : "");
14677
14829
  supersedes: [],
14678
14830
  conflictsWith: contradicted
14679
14831
  };
14680
- const dir = path25.join(rulesDir, PROPOSED_DIR);
14681
- const file = path25.join(dir, `${id}@${st.sha.slice(0, SHA_LEN)}.md`);
14832
+ const dir = path26.join(rulesDir, PROPOSED_DIR);
14833
+ const file = path26.join(dir, `${id}@${st.sha.slice(0, SHA_LEN)}.md`);
14682
14834
  const text = serialize(fm, bodyOf());
14683
- if (fs24.existsSync(file) && fs24.readFileSync(file, "utf8") === text)
14835
+ if (fs25.existsSync(file) && fs25.readFileSync(file, "utf8") === text)
14684
14836
  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)
14837
+ fs25.mkdirSync(dir, { recursive: true });
14838
+ for (const other of fs25.readdirSync(dir)) {
14839
+ if (!other.startsWith(`${id}@`) || !other.endsWith(".md") || path26.join(dir, other) === file)
14688
14840
  continue;
14689
- const old = matter3(fs24.readFileSync(path25.join(dir, other), "utf8"));
14841
+ const old = matter4(fs25.readFileSync(path26.join(dir, other), "utf8"));
14690
14842
  if (!old.data.sources?.some((s) => s.ref === cand.source))
14691
14843
  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));
14844
+ fs25.writeFileSync(path26.join(dir, other.replace(/\.md$/, ".superseded")), matter4.stringify(old.content, { ...old.data, supersededBy: path26.basename(file) }));
14845
+ fs25.rmSync(path26.join(dir, other));
14694
14846
  res.superseded.push(other.replace(/\.md$/, ""));
14695
- ev2("item.superseded", id, fm.domain);
14847
+ ev2("item.superseded", id);
14696
14848
  }
14697
- fs24.writeFileSync(file, text);
14849
+ fs25.writeFileSync(file, text);
14698
14850
  res.proposed.push(`${id}@${st.sha.slice(0, SHA_LEN)}`);
14699
- ev2("item.proposed", id, fm.domain);
14851
+ ev2("item.proposed", id);
14700
14852
  }
14701
14853
  for (const [id, w] of work) {
14702
14854
  if (touched.has(id) || w.fm.status !== "active" || w.isNew)
@@ -14722,11 +14874,12 @@ ${cand.rationale.trim()}` : "");
14722
14874
  for (const w of work.values()) {
14723
14875
  if (!w.dirty)
14724
14876
  continue;
14725
- fs24.mkdirSync(path25.dirname(w.file), { recursive: true });
14877
+ fs25.mkdirSync(path26.dirname(w.file), { recursive: true });
14726
14878
  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);
14879
+ if (!fs25.existsSync(w.file) || fs25.readFileSync(w.file, "utf8") !== text)
14880
+ fs25.writeFileSync(w.file, text);
14729
14881
  }
14882
+ res.orphanedErrata = orphanedErrata(kbRoot, opts.errata ?? [], totals.keys());
14730
14883
  for (const [ref, t] of totals)
14731
14884
  if (needsDocling(t.dropped, t.total))
14732
14885
  res.doclingFlags.push({ ref, ...t });
@@ -14743,6 +14896,7 @@ var init_extract = __esm({
14743
14896
  init_anchor();
14744
14897
  init_index_file();
14745
14898
  init_ingest();
14899
+ init_errata();
14746
14900
  init_paths();
14747
14901
  init_store2();
14748
14902
  init_verify();
@@ -14759,20 +14913,20 @@ var init_extract = __esm({
14759
14913
  });
14760
14914
 
14761
14915
  // packages/core/dist/knowledge/schedule.js
14762
- import fs25 from "fs";
14763
- import path26 from "path";
14916
+ import fs26 from "fs";
14917
+ import path27 from "path";
14764
14918
  function readSchedule(stateRoot) {
14765
14919
  try {
14766
- return JSON.parse(fs25.readFileSync(scheduleFile(stateRoot), "utf8"));
14920
+ return JSON.parse(fs26.readFileSync(scheduleFile(stateRoot), "utf8"));
14767
14921
  } catch {
14768
14922
  return {};
14769
14923
  }
14770
14924
  }
14771
14925
  function writeSchedule(stateRoot, s) {
14772
- fs25.mkdirSync(stateRoot, { recursive: true });
14926
+ fs26.mkdirSync(stateRoot, { recursive: true });
14773
14927
  const tmp = `${scheduleFile(stateRoot)}.${process.pid}.tmp`;
14774
- fs25.writeFileSync(tmp, JSON.stringify(s, null, 2) + "\n");
14775
- fs25.renameSync(tmp, scheduleFile(stateRoot));
14928
+ fs26.writeFileSync(tmp, JSON.stringify(s, null, 2) + "\n");
14929
+ fs26.renameSync(tmp, scheduleFile(stateRoot));
14776
14930
  }
14777
14931
  function snapshotSources(kbRoot, sources) {
14778
14932
  const out = {};
@@ -14852,7 +15006,7 @@ async function tickKnowledgeSchedule(d) {
14852
15006
  continue;
14853
15007
  try {
14854
15008
  const root = kbDir(d.cfg, kb);
14855
- if (!fs25.existsSync(root))
15009
+ if (!fs26.existsSync(root))
14856
15010
  continue;
14857
15011
  const { sources } = loadSources(root);
14858
15012
  if (!sources.length)
@@ -14917,7 +15071,7 @@ var init_schedule = __esm({
14917
15071
  init_sources();
14918
15072
  RUNNING_STALE_MS = 6 * 60 * 60 * 1e3;
14919
15073
  DAY_MS = 24 * 60 * 60 * 1e3;
14920
- scheduleFile = (stateRoot) => path26.join(stateRoot, "knowledge-schedule.json");
15074
+ scheduleFile = (stateRoot) => path27.join(stateRoot, "knowledge-schedule.json");
14921
15075
  reconcileConfig = (cfg, kb) => ({ ...DEFAULT_RECONCILE, ...kbConfigs(cfg)[kb]?.reconcile });
14922
15076
  }
14923
15077
  });
@@ -14925,19 +15079,19 @@ var init_schedule = __esm({
14925
15079
  // packages/core/dist/knowledge/review.js
14926
15080
  import crypto4 from "crypto";
14927
15081
  import { execFileSync as execFileSync7 } from "child_process";
14928
- import fs26 from "fs";
14929
- import path27 from "path";
14930
- import matter4 from "gray-matter";
15082
+ import fs27 from "fs";
15083
+ import path28 from "path";
15084
+ import matter5 from "gray-matter";
14931
15085
  function readEscalations(kbRoot) {
14932
15086
  try {
14933
- return JSON.parse(fs26.readFileSync(escalationsFile(kbRoot), "utf8")).items;
15087
+ return JSON.parse(fs27.readFileSync(escalationsFile(kbRoot), "utf8")).items;
14934
15088
  } catch {
14935
15089
  return [];
14936
15090
  }
14937
15091
  }
14938
15092
  function writeEscalations(kbRoot, items) {
14939
- fs26.mkdirSync(kbRoot, { recursive: true });
14940
- fs26.writeFileSync(escalationsFile(kbRoot), JSON.stringify({ items }, null, 2) + "\n");
15093
+ fs27.mkdirSync(kbRoot, { recursive: true });
15094
+ fs27.writeFileSync(escalationsFile(kbRoot), JSON.stringify({ items }, null, 2) + "\n");
14941
15095
  }
14942
15096
  function addEscalation(kbRoot, e) {
14943
15097
  const items = readEscalations(kbRoot);
@@ -14947,8 +15101,8 @@ function addEscalation(kbRoot, e) {
14947
15101
  return true;
14948
15102
  }
14949
15103
  function appendVerdict(kbRoot, v2, now) {
14950
- fs26.mkdirSync(kbRoot, { recursive: true });
14951
- fs26.appendFileSync(verdictsFile(kbRoot), JSON.stringify({ ts: now.toISOString(), ...v2 }) + "\n");
15104
+ fs27.mkdirSync(kbRoot, { recursive: true });
15105
+ fs27.appendFileSync(verdictsFile(kbRoot), JSON.stringify({ ts: now.toISOString(), ...v2 }) + "\n");
14952
15106
  }
14953
15107
  function priorityOfRef(c, ref) {
14954
15108
  if (ref.startsWith("agent:"))
@@ -14969,20 +15123,20 @@ function loadProposals(c) {
14969
15123
  const byId = new Map(live.map((r) => [r.id, r]));
14970
15124
  const proposals = [];
14971
15125
  const pdirs = /* @__PURE__ */ new Set();
14972
- for (const f of fs26.existsSync(dir) ? walkDirs(dir) : [])
14973
- if (path27.basename(f) === PROPOSED_DIR)
15126
+ for (const f of fs27.existsSync(dir) ? walkDirs(dir) : [])
15127
+ if (path28.basename(f) === PROPOSED_DIR)
14974
15128
  pdirs.add(f);
14975
15129
  for (const pd of pdirs) {
14976
15130
  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) });
15131
+ proposals.push({ key: path28.basename(rule2.file).replace(/\.md$/, ""), rule: rule2, live: byId.get(rule2.id) });
14978
15132
  }
14979
15133
  }
14980
15134
  return { proposals, live };
14981
15135
  }
14982
15136
  function walkDirs(dir, out = []) {
14983
- for (const e of fs26.readdirSync(dir, { withFileTypes: true }))
15137
+ for (const e of fs27.readdirSync(dir, { withFileTypes: true }))
14984
15138
  if (e.isDirectory()) {
14985
- const f = path27.join(dir, e.name);
15139
+ const f = path28.join(dir, e.name);
14986
15140
  out.push(f);
14987
15141
  walkDirs(f, out);
14988
15142
  }
@@ -15011,9 +15165,9 @@ function promoteProposal(c, p, by, justification, events) {
15011
15165
  continue;
15012
15166
  const o = readFm(other.file);
15013
15167
  o.fm.status = "retired";
15014
- fs26.writeFileSync(other.file, fileText(o.fm, o.body));
15168
+ fs27.writeFileSync(other.file, fileText(o.fm, o.body));
15015
15169
  retired.push(id);
15016
- events.push(ev(c, "item.superseded", id, o.fm.domain));
15170
+ events.push(ev(c, "item.superseded", id));
15017
15171
  }
15018
15172
  const next = { ...fm, status: "active", approvedBy: by, justification, supersedes: [.../* @__PURE__ */ new Set([...fm.supersedes ?? [], ...retired])], conflictsWith: [] };
15019
15173
  if (p.live?.decision)
@@ -15021,17 +15175,17 @@ function promoteProposal(c, p, by, justification, events) {
15021
15175
  const anchors = anchorsFor(c, { ...next, anchors: p.live?.anchors ?? fm.anchors });
15022
15176
  if (anchors)
15023
15177
  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));
15178
+ const target = p.live?.file ?? path28.join(dir, next.domain, `${next.id}.md`);
15179
+ fs27.mkdirSync(path28.dirname(target), { recursive: true });
15180
+ fs27.writeFileSync(target, fileText(next, body));
15181
+ fs27.rmSync(p.rule.file);
15182
+ events.push(ev(c, "item.approved", next.id), ev(c, "item.applied", next.id));
15029
15183
  if (p.live)
15030
- events.push(ev(c, "item.superseded", next.id, next.domain));
15184
+ events.push(ev(c, "item.superseded", next.id));
15031
15185
  }
15032
15186
  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" }));
15187
+ fs27.rmSync(p.rule.file);
15188
+ events.push(ev(c, "item.rejected", p.rule.id, { reason: "reviewer" }));
15035
15189
  }
15036
15190
  function mergeInto(c, p, targetId, live, events) {
15037
15191
  const t = live.find((r) => r.id === targetId);
@@ -15041,9 +15195,9 @@ function mergeInto(c, p, targetId, live, events) {
15041
15195
  for (const s of p.rule.sources)
15042
15196
  if (!tf.fm.sources.some((x) => x.ref === s.ref))
15043
15197
  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));
15198
+ fs27.writeFileSync(t.file, fileText(tf.fm, tf.body));
15199
+ fs27.rmSync(p.rule.file);
15200
+ events.push(ev(c, "item.superseded", p.rule.id), ev(c, "item.applied", targetId));
15047
15201
  return true;
15048
15202
  }
15049
15203
  function evaluateCodeConflict(c, rule2, cc) {
@@ -15117,14 +15271,15 @@ function applyDecisions(file, c) {
15117
15271
  f.fm.status = "active";
15118
15272
  if (d.loc && f.fm.sources[0])
15119
15273
  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));
15274
+ const noSource = !rule2.sources.length;
15275
+ if (conf >= threshold && !d.flag && !reasons.length && !noSource) {
15276
+ fs27.writeFileSync(rule2.file, fileText(f.fm, f.body));
15122
15277
  res.applied.push(d.item);
15123
- events.push(ev(c, "item.applied", rule2.id, rule2.domain));
15278
+ events.push(ev(c, "item.applied", rule2.id));
15124
15279
  verdict(d, "applied", { confidence: conf });
15125
15280
  continue;
15126
15281
  }
15127
- reasons.push(conf < threshold ? "low-confidence" : "reviewer-flagged");
15282
+ reasons.push(noSource ? "no-source" : conf < threshold ? "low-confidence" : "reviewer-flagged");
15128
15283
  } else if (d.decision === "approve") {
15129
15284
  if (strong(rule2.modality))
15130
15285
  reasons.push("retiring must/must-not");
@@ -15135,9 +15290,9 @@ function applyDecisions(file, c) {
15135
15290
  if (!reasons.length) {
15136
15291
  const f = readFm(rule2.file);
15137
15292
  f.fm.status = "retired";
15138
- fs26.writeFileSync(rule2.file, fileText(f.fm, f.body));
15293
+ fs27.writeFileSync(rule2.file, fileText(f.fm, f.body));
15139
15294
  res.applied.push(d.item);
15140
- events.push(ev(c, "item.archived", rule2.id, rule2.domain));
15295
+ events.push(ev(c, "item.archived", rule2.id));
15141
15296
  verdict(d, "applied", { confidence: conf });
15142
15297
  continue;
15143
15298
  }
@@ -15152,6 +15307,8 @@ function applyDecisions(file, c) {
15152
15307
  reasons.push("reviewer-flagged");
15153
15308
  if (conf < threshold)
15154
15309
  reasons.push("low-confidence");
15310
+ if (p.rule.sources.some((s) => isGatedRef(kbRoot, c.sources, s.ref)))
15311
+ reasons.push("review-required");
15155
15312
  if (d.decision === "approve") {
15156
15313
  const myPri = priorityOfRule(c, p.rule);
15157
15314
  const conflicted = (p.rule.conflictsWith ?? []).map((id) => live.find((r) => r.id === id)).filter((r) => !!r);
@@ -15213,7 +15370,7 @@ function applyPair(pd, c, res, threshold, events, kbRoot, now) {
15213
15370
  for (const [x, y] of [[a, b], [b, a]]) {
15214
15371
  const f = readFm(x.file);
15215
15372
  f.fm.conflictsWith = [.../* @__PURE__ */ new Set([...f.fm.conflictsWith ?? [], y.id])];
15216
- fs26.writeFileSync(x.file, fileText(f.fm, f.body));
15373
+ fs27.writeFileSync(x.file, fileText(f.fm, f.body));
15217
15374
  }
15218
15375
  return;
15219
15376
  }
@@ -15228,11 +15385,11 @@ function applyPair(pd, c, res, threshold, events, kbRoot, now) {
15228
15385
  if (!kf.fm.sources.some((x) => x.ref === s.ref))
15229
15386
  kf.fm.sources.push(s);
15230
15387
  kf.fm.supersedes = [.../* @__PURE__ */ new Set([...kf.fm.supersedes ?? [], drop.id])];
15231
- fs26.writeFileSync(keep.file, fileText(kf.fm, kf.body));
15388
+ fs27.writeFileSync(keep.file, fileText(kf.fm, kf.body));
15232
15389
  const df = readFm(drop.file);
15233
15390
  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));
15391
+ fs27.writeFileSync(drop.file, fileText(df.fm, df.body));
15392
+ events.push(ev(c, "item.superseded", drop.id), ev(c, "item.applied", keep.id));
15236
15393
  res.applied.push(key);
15237
15394
  }
15238
15395
  function resolveEscalation(c, key, r, by, reason) {
@@ -15262,9 +15419,9 @@ function resolveEscalation(c, key, r, by, reason) {
15262
15419
  f.fm.decision = decision;
15263
15420
  if (r.verdict === "supersede-with-code") {
15264
15421
  f.fm.status = "retired";
15265
- events.push(ev(c, "item.superseded", rule2.id, rule2.domain));
15422
+ events.push(ev(c, "item.superseded", rule2.id));
15266
15423
  }
15267
- fs26.writeFileSync(rule2.file, fileText(f.fm, f.body));
15424
+ fs27.writeFileSync(rule2.file, fileText(f.fm, f.body));
15268
15425
  return done(`${rule2.id}: ${r.verdict}`);
15269
15426
  }
15270
15427
  if (esc2.kind === "proposal") {
@@ -15281,23 +15438,26 @@ function resolveEscalation(c, key, r, by, reason) {
15281
15438
  }
15282
15439
  if (esc2.kind === "stale-retire") {
15283
15440
  const rule2 = live.find((x) => x.id === esc2.ruleId);
15441
+ if (rule2 && r.kind !== "approve" && !rule2.sources.length) {
15442
+ throw new Error(`Rule ${rule2.id} has no source, so it cannot be kept active. Add a source to the rule (or re-extract it), or retire it with --approve.`);
15443
+ }
15284
15444
  if (rule2) {
15285
15445
  const f = readFm(rule2.file);
15286
15446
  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));
15447
+ fs27.writeFileSync(rule2.file, fileText(f.fm, f.body));
15448
+ events.push(ev(c, r.kind === "approve" ? "item.archived" : "item.applied", rule2.id));
15289
15449
  }
15290
15450
  return done(`${esc2.ruleId}: ${r.kind === "approve" ? "retired" : "kept"}`);
15291
15451
  }
15292
15452
  if (esc2.kind === "override-base-changed") {
15293
- const ov = esc2.item && fs26.existsSync(esc2.item) ? esc2.item : void 0;
15453
+ const ov = esc2.item && fs27.existsSync(esc2.item) ? esc2.item : void 0;
15294
15454
  if (ov) {
15295
15455
  if (r.kind === "reject")
15296
- fs26.rmSync(ov);
15456
+ fs27.rmSync(ov);
15297
15457
  else {
15298
15458
  const f = readFm(ov);
15299
15459
  f.fm.overridesBase = esc2.evidence?.docRev;
15300
- fs26.writeFileSync(ov, fileText(f.fm, f.body));
15460
+ fs27.writeFileSync(ov, fileText(f.fm, f.body));
15301
15461
  }
15302
15462
  }
15303
15463
  return done(`${esc2.ruleId}: override ${r.kind === "reject" ? "dropped" : "kept"}`);
@@ -15321,11 +15481,11 @@ ${o.evidence}`).digest("hex");
15321
15481
  supersedes: [],
15322
15482
  conflictsWith: []
15323
15483
  };
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);
15484
+ const dir = path28.join(kbRulesDir(c.cfg, c.kb), PROPOSED_DIR);
15485
+ fs27.mkdirSync(dir, { recursive: true });
15486
+ const file = path28.join(dir, `${id}@${sha.slice(0, SHA_LEN2)}.md`);
15487
+ fs27.writeFileSync(file, fileText(fm, o.statement));
15488
+ appendAudit([ev(c, "item.proposed", id)], c.audit);
15329
15489
  return file;
15330
15490
  }
15331
15491
  function buildReviewPacket(c) {
@@ -15365,8 +15525,8 @@ var init_review = __esm({
15365
15525
  PRIORITY_RANK = { company: 3, project: 2, agent: 1 };
15366
15526
  SHA_LEN2 = 12;
15367
15527
  strong = (m) => m === "must" || m === "must-not";
15368
- escalationsFile = (kbRoot) => path27.join(kbRoot, "escalations.json");
15369
- verdictsFile = (kbRoot) => path27.join(kbRoot, "verdicts.jsonl");
15528
+ escalationsFile = (kbRoot) => path28.join(kbRoot, "escalations.json");
15529
+ verdictsFile = (kbRoot) => path28.join(kbRoot, "verdicts.jsonl");
15370
15530
  needsYouCount = (kbRoot) => readEscalations(kbRoot).filter((e) => e.needsYou).length;
15371
15531
  git = (root, args) => execFileSync7("git", ["-C", root, ...args], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
15372
15532
  realGit = {
@@ -15387,34 +15547,34 @@ var init_review = __esm({
15387
15547
  },
15388
15548
  docDate(file) {
15389
15549
  try {
15390
- const d = git(path27.dirname(file), ["log", "-1", "--format=%cI", "--", path27.basename(file)]);
15550
+ const d = git(path28.dirname(file), ["log", "-1", "--format=%cI", "--", path28.basename(file)]);
15391
15551
  if (d)
15392
15552
  return d;
15393
15553
  } catch {
15394
15554
  }
15395
15555
  try {
15396
- return fs26.statSync(file).mtime.toISOString();
15556
+ return fs27.statSync(file).mtime.toISOString();
15397
15557
  } catch {
15398
15558
  return null;
15399
15559
  }
15400
15560
  }
15401
15561
  };
15402
15562
  nowOf = (c) => c.now?.() ?? /* @__PURE__ */ new Date();
15403
- fileText = (fm, body) => matter4.stringify(body.trim() + "\n", JSON.parse(JSON.stringify(fm)));
15563
+ fileText = (fm, body) => matter5.stringify(body.trim() + "\n", JSON.parse(JSON.stringify(fm)));
15404
15564
  readFm = (file) => {
15405
- const m = matter4(fs26.readFileSync(file, "utf8"));
15565
+ const m = matter5(fs27.readFileSync(file, "utf8"));
15406
15566
  return { fm: m.data, body: m.content };
15407
15567
  };
15408
- ev = (c, event, itemId, domain, extra = {}) => ({ kb: c.kb, level: "group", project: "", domain, itemId, event, ...extra });
15568
+ ev = (c, event, itemId, extra = {}) => ({ kb: c.kb, level: "group", project: "", domain: "rules", itemId, event, ...extra });
15409
15569
  docRevOf = (r) => r.sources.map((s) => `${s.ref}@${s.sha.slice(0, SHA_LEN2)}`).join(",");
15410
15570
  }
15411
15571
  });
15412
15572
 
15413
15573
  // packages/core/dist/knowledge/fullpass.js
15414
15574
  import crypto5 from "crypto";
15415
- import fs27 from "fs";
15416
- import path28 from "path";
15417
- import matter5 from "gray-matter";
15575
+ import fs28 from "fs";
15576
+ import path29 from "path";
15577
+ import matter6 from "gray-matter";
15418
15578
  function dedupPairs(rules) {
15419
15579
  const active = rules.filter((r) => r.status === "active").sort((x, y) => x.id.localeCompare(y.id));
15420
15580
  const out = [];
@@ -15430,12 +15590,12 @@ function dedupPairs(rules) {
15430
15590
  return out.slice(0, MAX_DEDUP_PAIRS);
15431
15591
  }
15432
15592
  function readAuditRecords(kb, since, dir = defaultAuditDir()) {
15433
- if (!fs27.existsSync(dir))
15593
+ if (!fs28.existsSync(dir))
15434
15594
  return [];
15435
15595
  const sinceMonth = since.toISOString().slice(0, 7);
15436
15596
  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")) {
15597
+ for (const f of fs28.readdirSync(dir).filter((n) => n.endsWith(".jsonl") && n.slice(0, 7) >= sinceMonth).sort()) {
15598
+ for (const line2 of fs28.readFileSync(path29.join(dir, f), "utf8").split("\n")) {
15439
15599
  if (!line2.trim())
15440
15600
  continue;
15441
15601
  try {
@@ -15498,8 +15658,8 @@ function checkOverrides(cfg, kb) {
15498
15658
  continue;
15499
15659
  const h = baseHash(b);
15500
15660
  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 }));
15661
+ const m = matter6(fs28.readFileSync(o.file, "utf8"));
15662
+ fs28.writeFileSync(o.file, matter6.stringify(m.content, { ...m.data, overridesBase: h }));
15503
15663
  continue;
15504
15664
  }
15505
15665
  out.push({ project, ruleId: o.id, file: o.file, baseHash: h, changed: o.overridesBase !== h });
@@ -15542,8 +15702,8 @@ var init_fullpass = __esm({
15542
15702
  });
15543
15703
 
15544
15704
  // packages/core/dist/knowledge/scores.js
15545
- import fs28 from "fs";
15546
- import path29 from "path";
15705
+ import fs29 from "fs";
15706
+ import path30 from "path";
15547
15707
  function tally(records, into = emptyTally()) {
15548
15708
  for (const e of records) {
15549
15709
  const domain = typeof e.domain === "string" && e.domain ? e.domain : "rules";
@@ -15552,7 +15712,7 @@ function tally(records, into = emptyTally()) {
15552
15712
  d.writes++;
15553
15713
  if (READ_EVENTS.has(e.event ?? ""))
15554
15714
  d.reads++;
15555
- if (!e.itemId)
15715
+ if (!e.itemId || WRITE_EVENTS.has(e.event ?? ""))
15556
15716
  continue;
15557
15717
  const c = into.items[`${domain}|${e.itemId}`] ??= { ...zero(), domain, itemId: e.itemId };
15558
15718
  switch (e.event) {
@@ -15589,7 +15749,7 @@ function tally(records, into = emptyTally()) {
15589
15749
  }
15590
15750
  function parseLines(file) {
15591
15751
  const out = [];
15592
- for (const line2 of fs28.readFileSync(file, "utf8").split("\n")) {
15752
+ for (const line2 of fs29.readFileSync(file, "utf8").split("\n")) {
15593
15753
  if (!line2.trim())
15594
15754
  continue;
15595
15755
  try {
@@ -15601,37 +15761,37 @@ function parseLines(file) {
15601
15761
  }
15602
15762
  function compactAudit(o) {
15603
15763
  const dir = o.dir ?? defaultAuditDir();
15604
- if (!fs28.existsSync(dir))
15764
+ if (!fs29.existsSync(dir))
15605
15765
  return [];
15606
15766
  const cutoffMonth = new Date(o.now.getTime() - SCORE_WINDOW_DAYS * DAY_MS3).toISOString().slice(0, 7);
15607
15767
  const done = [];
15608
- for (const f of fs28.readdirSync(dir).sort()) {
15768
+ for (const f of fs29.readdirSync(dir).sort()) {
15609
15769
  const m = MONTH_FILE_RE.exec(f);
15610
15770
  if (!m || m[1] >= cutoffMonth)
15611
15771
  continue;
15612
15772
  const [, month, machine] = m;
15613
15773
  const byKb = /* @__PURE__ */ new Map();
15614
- for (const r of parseLines(path29.join(dir, f)))
15774
+ for (const r of parseLines(path30.join(dir, f)))
15615
15775
  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;
15776
+ const statsPath = path30.join(dir, statsFileName(month, machine));
15777
+ const prior = fs29.existsSync(statsPath) ? JSON.parse(fs29.readFileSync(statsPath, "utf8")) : null;
15618
15778
  const kbs = prior?.kbs ?? {};
15619
15779
  for (const [kb, recs] of byKb)
15620
15780
  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));
15781
+ fs29.writeFileSync(`${statsPath}.tmp`, JSON.stringify({ month, machine, kbs }));
15782
+ fs29.renameSync(`${statsPath}.tmp`, statsPath);
15783
+ fs29.unlinkSync(path30.join(dir, f));
15624
15784
  done.push(month);
15625
15785
  }
15626
15786
  return done;
15627
15787
  }
15628
15788
  function readCompacted(kb, dir) {
15629
15789
  const t = emptyTally();
15630
- if (!fs28.existsSync(dir))
15790
+ if (!fs29.existsSync(dir))
15631
15791
  return t;
15632
- for (const f of fs28.readdirSync(dir).filter((n) => n.endsWith(".stats.json"))) {
15792
+ for (const f of fs29.readdirSync(dir).filter((n) => n.endsWith(".stats.json"))) {
15633
15793
  try {
15634
- const k = JSON.parse(fs28.readFileSync(path29.join(dir, f), "utf8")).kbs[kb];
15794
+ const k = JSON.parse(fs29.readFileSync(path30.join(dir, f), "utf8")).kbs[kb];
15635
15795
  if (k)
15636
15796
  mergeTally(t, k);
15637
15797
  } catch {
@@ -15681,7 +15841,7 @@ function computeScores(i) {
15681
15841
  noise: w.noise,
15682
15842
  wrong: w.wrong,
15683
15843
  fireRate: w.surfaced,
15684
- pullRate: w.shown,
15844
+ pullRate: w.shown + w.searched,
15685
15845
  useRate: ratio(w.cited, w.surfaced),
15686
15846
  noiseRate: ratio(w.noise, w.surfaced),
15687
15847
  violationRate: ratio(w.violated, w.surfaced),
@@ -15723,21 +15883,21 @@ function computeScores(i) {
15723
15883
  function scoreKb(o) {
15724
15884
  const dir = o.auditDir ?? defaultAuditDir();
15725
15885
  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));
15886
+ if (fs29.existsSync(dir)) {
15887
+ for (const f of fs29.readdirSync(dir).filter((n) => MONTH_FILE_RE.test(n)).sort()) {
15888
+ records.push(...parseLines(path30.join(dir, f)).filter((r) => r.kb === o.kb));
15729
15889
  }
15730
15890
  }
15731
15891
  let verdicts = [];
15732
15892
  try {
15733
- verdicts = fs28.readFileSync(path29.join(o.kbRoot, "verdicts.jsonl"), "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l));
15893
+ verdicts = fs29.readFileSync(path30.join(o.kbRoot, "verdicts.jsonl"), "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l));
15734
15894
  } catch {
15735
15895
  }
15736
15896
  return computeScores({ kb: o.kb, records, now: o.now, known: o.known, compacted: readCompacted(o.kb, dir), verdicts });
15737
15897
  }
15738
15898
  function writeScores(kbRoot, s) {
15739
- fs28.mkdirSync(kbRoot, { recursive: true });
15740
- fs28.writeFileSync(scoresFile(kbRoot), JSON.stringify(s, null, 2) + "\n");
15899
+ fs29.mkdirSync(kbRoot, { recursive: true });
15900
+ fs29.writeFileSync(scoresFile(kbRoot), JSON.stringify(s, null, 2) + "\n");
15741
15901
  }
15742
15902
  function renderScoresSection(s) {
15743
15903
  const pct = (x) => x === null ? "n/a" : `${Math.round(x * 100)}%`;
@@ -15750,7 +15910,7 @@ function renderScoresSection(s) {
15750
15910
  ...used.length ? [
15751
15911
  "| Item | Surfaced | Pulled | Cited | Use | Noise | Violated |",
15752
15912
  "|---|---|---|---|---|---|---|",
15753
- ...used.map((x) => `| ${x.itemId} | ${x.surfaced} | ${x.shown} | ${x.cited} | ${pct(x.useRate)} | ${pct(x.noiseRate)} | ${pct(x.violationRate)} |`)
15913
+ ...used.map((x) => `| ${x.itemId} | ${x.surfaced} | ${x.pullRate} | ${x.cited} | ${pct(x.useRate)} | ${pct(x.noiseRate)} | ${pct(x.violationRate)} |`)
15754
15914
  ] : ["No items surfaced."],
15755
15915
  "",
15756
15916
  `- Never fired (${never.length}): ${never.slice(0, 20).join(", ") || "none"}${never.length > 20 ? ", \u2026" : ""}`,
@@ -15778,13 +15938,13 @@ var init_scores = __esm({
15778
15938
  MONTH_FILE_RE = /^(\d{4}-\d{2})\.(.+)\.jsonl$/;
15779
15939
  statsFileName = (month, machine) => `${month}.${machine}.stats.json`;
15780
15940
  ratio = (n, d) => d ? Math.round(n / d * 1e3) / 1e3 : null;
15781
- scoresFile = (kbRoot) => path29.join(kbRoot, "scores.json");
15941
+ scoresFile = (kbRoot) => path30.join(kbRoot, "scores.json");
15782
15942
  }
15783
15943
  });
15784
15944
 
15785
15945
  // packages/core/dist/knowledge/report.js
15786
- import fs29 from "fs";
15787
- import path30 from "path";
15946
+ import fs30 from "fs";
15947
+ import path31 from "path";
15788
15948
  function renderReport(cfg, kb, o) {
15789
15949
  const { rules } = loadKbRules(cfg, kb, { includeProposed: true });
15790
15950
  const count = (s) => rules.filter((r) => r.status === s).length;
@@ -15806,6 +15966,11 @@ function renderReport(cfg, kb, o) {
15806
15966
  const info = esc2.filter((e) => !e.needsYou);
15807
15967
  if (info.length)
15808
15968
  L.push("", `## For information (${info.length})`, "", ...info.map((e) => `- **${e.key}**: ${e.explanation}`));
15969
+ const { errata, errors: errataErrors } = loadErrata(kbDir(cfg, kb));
15970
+ const orphans = orphanedErrata(kbDir(cfg, kb), errata);
15971
+ if (orphans.length || errataErrors.length) {
15972
+ L.push("", `## Errata to re-check (${orphans.length + errataErrors.length})`, "", ...orphans.map((e) => `- **${e.id}** (${e.ref}): quote no longer found in the source; the raw file changed. Re-check it in errata.yaml.`), ...errataErrors.map((e) => `- errata.yaml: ${e}`));
15973
+ }
15809
15974
  if (o.full) {
15810
15975
  const { usage: usage2, caps } = o.full;
15811
15976
  L.push("", "## Usage (30 days)", "", `- Surfaced: ${Object.values(usage2.surfaced).reduce((a, b) => a + b, 0)} times across ${Object.keys(usage2.surfaced).length} rules.`, `- Never surfaced (re-check triggers): ${usage2.neverSurfaced.join(", ") || "none"}`, `- Noisy (narrow triggers): ${usage2.noisy.join(", ") || "none"}`, `- Violation feedback (reword): ${usage2.violated.join(", ") || "none"}`, `- Domains over cap: ${caps.map((c) => `${c.domain} (${c.count}/${c.cap})`).join(", ") || "none"}`);
@@ -15815,12 +15980,13 @@ function renderReport(cfg, kb, o) {
15815
15980
  return L.join("\n") + "\n";
15816
15981
  }
15817
15982
  function writeReport(cfg, kb, o) {
15818
- const file = path30.join(kbDir(cfg, kb), "REPORT.md");
15819
- fs29.writeFileSync(file, renderReport(cfg, kb, o));
15983
+ const file = path31.join(kbDir(cfg, kb), "REPORT.md");
15984
+ fs30.writeFileSync(file, renderReport(cfg, kb, o));
15820
15985
  return file;
15821
15986
  }
15822
15987
  var init_report = __esm({
15823
15988
  "packages/core/dist/knowledge/report.js"() {
15989
+ init_errata();
15824
15990
  init_layers();
15825
15991
  init_paths();
15826
15992
  init_review();
@@ -15864,44 +16030,63 @@ var init_reconcile = __esm({
15864
16030
 
15865
16031
  // packages/core/dist/knowledge/cite.js
15866
16032
  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);
16033
+ return scanCitations([i], i);
16034
+ }
16035
+ function scanCitations(texts, o) {
16036
+ if (!texts.length)
16037
+ return [];
16038
+ const now = o.sink?.now?.() ?? /* @__PURE__ */ new Date();
16039
+ const lookbackMs = (o.lookbackDays ?? CITE_LOOKBACK_DAYS) * DAY_MS4;
16040
+ const ats = texts.map((t) => (t.at ?? now).getTime());
16041
+ const records = readAuditRecords(void 0, new Date(Math.min(...ats) - lookbackMs), o.sink?.dir ?? defaultAuditDir()).filter((r) => r.itemId && r.project === o.project);
16042
+ const already = new Set(records.filter((r) => r.event === "item.cited").map((r) => `${r.ref}|${r.itemId}`));
16043
+ const deliveries = records.filter((r) => (r.event === "item.surfaced" || r.event === "item.shown" || r.event === "item.searched") && !(o.session && r.session && r.session !== o.session));
16044
+ const events = [];
16045
+ texts.forEach((t, n) => {
16046
+ const at = ats[n];
16047
+ const delivered = /* @__PURE__ */ new Map();
16048
+ for (const r of deliveries) {
16049
+ const ts = Date.parse(r.ts);
16050
+ if (ts <= at && ts >= at - lookbackMs)
16051
+ delivered.set(r.itemId, r);
16052
+ }
16053
+ for (const [itemId, r] of delivered) {
16054
+ if (already.has(`${t.ref}|${itemId}`) || !mentionsId(t.text, itemId))
16055
+ continue;
16056
+ already.add(`${t.ref}|${itemId}`);
16057
+ events.push({
16058
+ kb: String(r.kb ?? ""),
16059
+ level: String(r.level ?? "project"),
16060
+ project: o.project,
16061
+ domain: String(r.domain ?? "rules"),
16062
+ itemId,
16063
+ event: "item.cited",
16064
+ ref: t.ref,
16065
+ ...o.agent ? { agent: o.agent } : {},
16066
+ ...o.session ? { session: o.session } : {}
16067
+ });
16068
+ }
16069
+ });
16070
+ appendAudit(events, o.sink);
16071
+ return events.map((e) => e.itemId);
15896
16072
  }
15897
16073
  function readGitMessages(repo, since, run) {
15898
- const out = run(["-C", repo, "log", `--since=${since.toISOString()}`, "--format=%x1e%H%x1f%cI%x1f%B"]);
16074
+ return parseLog(run(["-C", repo, "log", "--all", `--since=${since.toISOString()}`, LOG_FORMAT]));
16075
+ }
16076
+ function readBranchOnlyMessages(repo, run, max = 200) {
16077
+ const branch = run(["-C", repo, "rev-parse", "--abbrev-ref", "HEAD"]).trim();
16078
+ if (!branch)
16079
+ return [];
16080
+ const own = branch === "HEAD" ? [] : [`--exclude=${branch}`];
16081
+ return parseLog(run(["-C", repo, "log", `--max-count=${max}`, "HEAD", "--not", ...own, "--branches", ...own.length ? [`--exclude=*/${branch}`] : [], "--remotes", LOG_FORMAT]));
16082
+ }
16083
+ function parseLog(out) {
15899
16084
  return out.split("").filter((c) => c.trim()).map((c) => {
15900
16085
  const [sha, at, ...rest] = c.split("");
15901
16086
  return { sha: sha.trim(), at: new Date(at), text: rest.join("") };
15902
16087
  });
15903
16088
  }
15904
- var DAY_MS4, CITE_LOOKBACK_DAYS, escapeRe2, mentionsId;
16089
+ var DAY_MS4, CITE_LOOKBACK_DAYS, escapeRe2, mentionsId, LOG_FORMAT;
15905
16090
  var init_cite = __esm({
15906
16091
  "packages/core/dist/knowledge/cite.js"() {
15907
16092
  init_audit();
@@ -15910,6 +16095,7 @@ var init_cite = __esm({
15910
16095
  CITE_LOOKBACK_DAYS = 7;
15911
16096
  escapeRe2 = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
15912
16097
  mentionsId = (text, id) => new RegExp(`(?<![\\w.-])${escapeRe2(id)}(?![\\w-])`).test(text);
16098
+ LOG_FORMAT = "--format=%x1e%H%x1f%cI%x1f%B";
15913
16099
  }
15914
16100
  });
15915
16101
 
@@ -15927,6 +16113,7 @@ var init_knowledge2 = __esm({
15927
16113
  init_migrate();
15928
16114
  init_verify();
15929
16115
  init_ingest();
16116
+ init_errata();
15930
16117
  init_anchor();
15931
16118
  init_extract();
15932
16119
  init_match();
@@ -16177,6 +16364,7 @@ __export(dist_exports2, {
16177
16364
  isDaemonSocketLive: () => isDaemonSocketLive,
16178
16365
  isGateCacheEnabled: () => isGateCacheEnabled,
16179
16366
  isGateSession: () => isGateSession,
16367
+ isGatedRef: () => isGatedRef,
16180
16368
  isNotifyActive: () => isNotifyActive,
16181
16369
  isOperatorInitiatedCommand: () => isOperatorInitiatedCommand,
16182
16370
  isPendingProposalDup: () => isPendingProposalDup,
@@ -16194,6 +16382,7 @@ __export(dist_exports2, {
16194
16382
  launchOneWorkspace: () => launchOneWorkspace,
16195
16383
  legacyKbDir: () => legacyKbDir,
16196
16384
  listSessions: () => listSessions,
16385
+ loadErrata: () => loadErrata,
16197
16386
  loadKbRules: () => loadKbRules,
16198
16387
  loadPending: () => loadPending,
16199
16388
  loadProposals: () => loadProposals,
@@ -16228,6 +16417,7 @@ __export(dist_exports2, {
16228
16417
  normalizedViews: () => normalizedViews,
16229
16418
  notePaneScreen: () => notePaneScreen,
16230
16419
  notifyToggle: () => notifyToggle,
16420
+ orphanedErrata: () => orphanedErrata,
16231
16421
  parseBearer: () => parseBearer,
16232
16422
  parseClassifierVerdict: () => parseClassifierVerdict,
16233
16423
  parseCommand: () => parseCommand,
@@ -16252,6 +16442,7 @@ __export(dist_exports2, {
16252
16442
  pruneTopics: () => pruneTopics,
16253
16443
  purgeExpiredWorkItems: () => purgeExpiredWorkItems,
16254
16444
  readAuditRecords: () => readAuditRecords,
16445
+ readBranchOnlyMessages: () => readBranchOnlyMessages,
16255
16446
  readCaptainAddress: () => readCaptainAddress,
16256
16447
  readCursor: () => readCursor,
16257
16448
  readEscalations: () => readEscalations,
@@ -16278,6 +16469,7 @@ __export(dist_exports2, {
16278
16469
  refFor: () => refFor,
16279
16470
  releaseDaemonLock: () => releaseDaemonLock,
16280
16471
  removeRunningMarker: () => removeRunningMarker,
16472
+ removeSource: () => removeSource,
16281
16473
  renderEnvAssignments: () => renderEnvAssignments,
16282
16474
  renderPlist: () => renderPlist,
16283
16475
  renderReport: () => renderReport,
@@ -16340,6 +16532,7 @@ __export(dist_exports2, {
16340
16532
  saveSessions: () => saveSessions,
16341
16533
  saveState: () => saveState,
16342
16534
  scanAndLogCitations: () => scanAndLogCitations,
16535
+ scanCitations: () => scanCitations,
16343
16536
  scheduleFile: () => scheduleFile,
16344
16537
  scoreKb: () => scoreKb,
16345
16538
  scoresFile: () => scoresFile,
@@ -16360,6 +16553,7 @@ __export(dist_exports2, {
16360
16553
  sideNextAutoName: () => sideNextAutoName,
16361
16554
  sideTitleFor: () => sideTitleFor,
16362
16555
  snapshotSources: () => snapshotSources,
16556
+ sourceCovers: () => sourceCovers,
16363
16557
  spansOverlap: () => spansOverlap,
16364
16558
  splitSections: () => splitSections,
16365
16559
  splitStatement: () => splitStatement,
@@ -16455,7 +16649,7 @@ var init_dist2 = __esm({
16455
16649
  });
16456
16650
 
16457
16651
  // packages/agents/dist/claude/registry.js
16458
- import fs30 from "fs";
16652
+ import fs31 from "fs";
16459
16653
  import { join as join17 } from "path";
16460
16654
  import { homedir as homedir7 } from "os";
16461
16655
  function parseRegistryDir(files, readFile7) {
@@ -16500,11 +16694,11 @@ function readClaudeStatus(task) {
16500
16694
  return void 0;
16501
16695
  let files;
16502
16696
  try {
16503
- files = fs30.readdirSync(CLAUDE_SESSIONS_DIR);
16697
+ files = fs31.readdirSync(CLAUDE_SESSIONS_DIR);
16504
16698
  } catch {
16505
16699
  return void 0;
16506
16700
  }
16507
- const entries = parseRegistryDir(files, (name) => fs30.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
16701
+ const entries = parseRegistryDir(files, (name) => fs31.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
16508
16702
  let entry;
16509
16703
  if (task.messagingSocketPath) {
16510
16704
  entry = entries.find((e) => e.messagingSocketPath === task.messagingSocketPath);
@@ -16519,11 +16713,11 @@ function readClaudeStatus(task) {
16519
16713
  function readClaudeStatusByCwd(cwd) {
16520
16714
  let files;
16521
16715
  try {
16522
- files = fs30.readdirSync(CLAUDE_SESSIONS_DIR);
16716
+ files = fs31.readdirSync(CLAUDE_SESSIONS_DIR);
16523
16717
  } catch {
16524
16718
  return void 0;
16525
16719
  }
16526
- const entries = parseRegistryDir(files, (name) => fs30.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
16720
+ const entries = parseRegistryDir(files, (name) => fs31.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
16527
16721
  const entry = entries.find((e) => e.cwd === cwd);
16528
16722
  if (!entry)
16529
16723
  return void 0;
@@ -16532,11 +16726,11 @@ function readClaudeStatusByCwd(cwd) {
16532
16726
  function readClaudeStatusBySocketPath(socketPath2) {
16533
16727
  let files;
16534
16728
  try {
16535
- files = fs30.readdirSync(CLAUDE_SESSIONS_DIR);
16729
+ files = fs31.readdirSync(CLAUDE_SESSIONS_DIR);
16536
16730
  } catch {
16537
16731
  return void 0;
16538
16732
  }
16539
- const entries = parseRegistryDir(files, (name) => fs30.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
16733
+ const entries = parseRegistryDir(files, (name) => fs31.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
16540
16734
  const entry = entries.find((e) => e.messagingSocketPath === socketPath2);
16541
16735
  if (!entry)
16542
16736
  return void 0;
@@ -16959,8 +17153,8 @@ var init_registry2 = __esm({
16959
17153
  });
16960
17154
 
16961
17155
  // packages/agents/dist/drivers/launch-cmd.js
16962
- import fs31 from "fs";
16963
- import path31 from "path";
17156
+ import fs32 from "fs";
17157
+ import path32 from "path";
16964
17158
  function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model, templatesDir, messagingSocketPath, sessionName, thinking, captainBoot, settingsPath) {
16965
17159
  const driver = registry.getDriver(agentName);
16966
17160
  if (driver.name === "claude") {
@@ -16987,14 +17181,14 @@ function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model,
16987
17181
  cmd += ` --settings ${settingsPath}`;
16988
17182
  }
16989
17183
  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;
17184
+ const roleFile2 = path32.join(templatesDir, `${role}.claude.md`);
17185
+ const legacyRoleFile = path32.join(templatesDir, `${role}.CLAUDE.md`);
17186
+ const actualRoleFile = fs32.existsSync(roleFile2) ? roleFile2 : fs32.existsSync(legacyRoleFile) ? legacyRoleFile : null;
16993
17187
  if (actualRoleFile) {
16994
17188
  cmd += ` --append-system-prompt-file ${actualRoleFile}`;
16995
17189
  }
16996
- const pluginDir = path31.join(templatesDir, "..", "plugin");
16997
- if (fs31.existsSync(pluginDir)) {
17190
+ const pluginDir = path32.join(templatesDir, "..", "plugin");
17191
+ if (fs32.existsSync(pluginDir)) {
16998
17192
  cmd += ` --plugin-dir ${pluginDir}`;
16999
17193
  }
17000
17194
  }
@@ -17003,14 +17197,14 @@ function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model,
17003
17197
  }
17004
17198
  return cmd;
17005
17199
  }
17006
- const roleFile = templatesDir ? path31.join(templatesDir, `${role}.${driver.templateSuffix}.md`) : void 0;
17200
+ const roleFile = templatesDir ? path32.join(templatesDir, `${role}.${driver.templateSuffix}.md`) : void 0;
17007
17201
  return driver.buildCommand({
17008
17202
  prompt: `You are a squadrant ${role}. Read your instructions from ${roleFile ?? role} and begin.`,
17009
17203
  workdir: process.cwd(),
17010
17204
  role,
17011
17205
  model,
17012
17206
  autoApprove: true,
17013
- promptFile: roleFile && fs31.existsSync(roleFile) ? roleFile : void 0,
17207
+ promptFile: roleFile && fs32.existsSync(roleFile) ? roleFile : void 0,
17014
17208
  ...captainBoot && role === "captain" ? {
17015
17209
  interactive: true,
17016
17210
  ...captainBoot.port ? { port: captainBoot.port } : {},
@@ -17040,7 +17234,7 @@ var init_drivers = __esm({
17040
17234
 
17041
17235
  // packages/agents/dist/projection/cursor.js
17042
17236
  import { mkdir, readFile, writeFile } from "fs/promises";
17043
- import path32 from "path";
17237
+ import path33 from "path";
17044
17238
  import os10 from "os";
17045
17239
  function renderMdc(source) {
17046
17240
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
@@ -17089,7 +17283,7 @@ function createCursorEmitter() {
17089
17283
  if (scope === "user") {
17090
17284
  return [
17091
17285
  {
17092
- path: path32.join(os10.homedir(), ".cursor/rules/squadrant-global.mdc"),
17286
+ path: path33.join(os10.homedir(), ".cursor/rules/squadrant-global.mdc"),
17093
17287
  shared: false,
17094
17288
  format: "mdc"
17095
17289
  }
@@ -17099,7 +17293,7 @@ function createCursorEmitter() {
17099
17293
  return [];
17100
17294
  return [
17101
17295
  {
17102
- path: path32.join(projectRoot, ".cursor/rules/squadrant.mdc"),
17296
+ path: path33.join(projectRoot, ".cursor/rules/squadrant.mdc"),
17103
17297
  shared: false,
17104
17298
  format: "mdc"
17105
17299
  }
@@ -17116,7 +17310,7 @@ function createCursorEmitter() {
17116
17310
  diff: buildDiff(existing, generated)
17117
17311
  };
17118
17312
  }
17119
- await mkdir(path32.dirname(dest.path), { recursive: true });
17313
+ await mkdir(path33.dirname(dest.path), { recursive: true });
17120
17314
  await writeFile(dest.path, generated, "utf-8");
17121
17315
  return {
17122
17316
  written: true,
@@ -17167,7 +17361,7 @@ var init_marker = __esm({
17167
17361
 
17168
17362
  // packages/agents/dist/projection/codex.js
17169
17363
  import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
17170
- import path33 from "path";
17364
+ import path34 from "path";
17171
17365
  import os11 from "os";
17172
17366
  function renderMarkdown(source) {
17173
17367
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
@@ -17192,7 +17386,7 @@ function createCodexEmitter() {
17192
17386
  destinations(scope, projectRoot) {
17193
17387
  if (scope === "user") {
17194
17388
  return [{
17195
- path: path33.join(os11.homedir(), ".codex/AGENTS.md"),
17389
+ path: path34.join(os11.homedir(), ".codex/AGENTS.md"),
17196
17390
  shared: true,
17197
17391
  format: "markdown"
17198
17392
  }];
@@ -17200,7 +17394,7 @@ function createCodexEmitter() {
17200
17394
  if (!projectRoot)
17201
17395
  return [];
17202
17396
  return [{
17203
- path: path33.join(projectRoot, "AGENTS.md"),
17397
+ path: path34.join(projectRoot, "AGENTS.md"),
17204
17398
  shared: true,
17205
17399
  format: "markdown"
17206
17400
  }];
@@ -17221,7 +17415,7 @@ ${existing ?? ""}
17221
17415
  ${generated}`
17222
17416
  };
17223
17417
  }
17224
- await mkdir2(path33.dirname(dest.path), { recursive: true });
17418
+ await mkdir2(path34.dirname(dest.path), { recursive: true });
17225
17419
  await writeFile2(dest.path, generated, "utf-8");
17226
17420
  return {
17227
17421
  written: true,
@@ -17239,7 +17433,7 @@ var init_codex2 = __esm({
17239
17433
 
17240
17434
  // packages/agents/dist/projection/gemini.js
17241
17435
  import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
17242
- import path34 from "path";
17436
+ import path35 from "path";
17243
17437
  import os12 from "os";
17244
17438
  function renderMarkdown2(source) {
17245
17439
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
@@ -17264,7 +17458,7 @@ function createGeminiEmitter() {
17264
17458
  destinations(scope, projectRoot) {
17265
17459
  if (scope === "user") {
17266
17460
  return [{
17267
- path: path34.join(os12.homedir(), ".gemini/GEMINI.md"),
17461
+ path: path35.join(os12.homedir(), ".gemini/GEMINI.md"),
17268
17462
  shared: true,
17269
17463
  format: "markdown"
17270
17464
  }];
@@ -17272,7 +17466,7 @@ function createGeminiEmitter() {
17272
17466
  if (!projectRoot)
17273
17467
  return [];
17274
17468
  return [{
17275
- path: path34.join(projectRoot, "GEMINI.md"),
17469
+ path: path35.join(projectRoot, "GEMINI.md"),
17276
17470
  shared: true,
17277
17471
  format: "markdown"
17278
17472
  }];
@@ -17293,7 +17487,7 @@ ${existing ?? ""}
17293
17487
  ${generated}`
17294
17488
  };
17295
17489
  }
17296
- await mkdir3(path34.dirname(dest.path), { recursive: true });
17490
+ await mkdir3(path35.dirname(dest.path), { recursive: true });
17297
17491
  await writeFile3(dest.path, generated, "utf-8");
17298
17492
  return {
17299
17493
  written: true,
@@ -17311,7 +17505,7 @@ var init_gemini2 = __esm({
17311
17505
 
17312
17506
  // packages/agents/dist/projection/opencode.js
17313
17507
  import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
17314
- import path35 from "path";
17508
+ import path36 from "path";
17315
17509
  import os13 from "os";
17316
17510
  function renderMarkdown3(source) {
17317
17511
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
@@ -17336,7 +17530,7 @@ function createOpencodeEmitter() {
17336
17530
  destinations(scope, projectRoot) {
17337
17531
  if (scope === "user") {
17338
17532
  return [{
17339
- path: path35.join(os13.homedir(), ".config", "opencode", "AGENTS.md"),
17533
+ path: path36.join(os13.homedir(), ".config", "opencode", "AGENTS.md"),
17340
17534
  shared: true,
17341
17535
  format: "markdown"
17342
17536
  }];
@@ -17344,7 +17538,7 @@ function createOpencodeEmitter() {
17344
17538
  if (!projectRoot)
17345
17539
  return [];
17346
17540
  return [{
17347
- path: path35.join(projectRoot, "AGENTS.md"),
17541
+ path: path36.join(projectRoot, "AGENTS.md"),
17348
17542
  shared: true,
17349
17543
  format: "markdown"
17350
17544
  }];
@@ -17365,7 +17559,7 @@ ${existing ?? ""}
17365
17559
  ${generated}`
17366
17560
  };
17367
17561
  }
17368
- await mkdir4(path35.dirname(dest.path), { recursive: true });
17562
+ await mkdir4(path36.dirname(dest.path), { recursive: true });
17369
17563
  await writeFile4(dest.path, generated, "utf-8");
17370
17564
  return {
17371
17565
  written: true,
@@ -17382,12 +17576,12 @@ var init_opencode2 = __esm({
17382
17576
  });
17383
17577
 
17384
17578
  // packages/agents/dist/projection/opencode-skills.js
17385
- import fs32 from "fs";
17579
+ import fs33 from "fs";
17386
17580
  import os14 from "os";
17387
- import path36 from "path";
17581
+ import path37 from "path";
17388
17582
  function defaultOpencodeSkillsRoot(env = process.env) {
17389
- const configHome = env.XDG_CONFIG_HOME || path36.join(os14.homedir(), ".config");
17390
- return path36.join(configHome, "opencode", "skills");
17583
+ const configHome = env.XDG_CONFIG_HOME || path37.join(os14.homedir(), ".config");
17584
+ return path37.join(configHome, "opencode", "skills");
17391
17585
  }
17392
17586
  function parseSkillName(raw) {
17393
17587
  const fm = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
@@ -17401,62 +17595,62 @@ function parseSkillName(raw) {
17401
17595
  }
17402
17596
  function listSourceSkills(sourceSkillsDir) {
17403
17597
  const out = /* @__PURE__ */ new Map();
17404
- if (!fs32.existsSync(sourceSkillsDir))
17598
+ if (!fs33.existsSync(sourceSkillsDir))
17405
17599
  return out;
17406
- for (const entry of fs32.readdirSync(sourceSkillsDir, { withFileTypes: true })) {
17600
+ for (const entry of fs33.readdirSync(sourceSkillsDir, { withFileTypes: true })) {
17407
17601
  if (!entry.isDirectory())
17408
17602
  continue;
17409
- const skillPath = path36.join(sourceSkillsDir, entry.name, "SKILL.md");
17410
- if (!fs32.existsSync(skillPath))
17603
+ const skillPath = path37.join(sourceSkillsDir, entry.name, "SKILL.md");
17604
+ if (!fs33.existsSync(skillPath))
17411
17605
  continue;
17412
- const name = parseSkillName(fs32.readFileSync(skillPath, "utf-8"));
17606
+ const name = parseSkillName(fs33.readFileSync(skillPath, "utf-8"));
17413
17607
  if (name && SAFE_NAME.test(name))
17414
17608
  out.set(name, skillPath);
17415
17609
  }
17416
17610
  return out;
17417
17611
  }
17418
17612
  function hasMarker(dir) {
17419
- return fs32.existsSync(path36.join(dir, OPENCODE_SKILL_MARKER));
17613
+ return fs33.existsSync(path37.join(dir, OPENCODE_SKILL_MARKER));
17420
17614
  }
17421
17615
  function syncOpencodeSkills(opts) {
17422
17616
  const result = { written: [], unchanged: [], removed: [], skipped: [] };
17423
17617
  const wanted = listSourceSkills(opts.sourceSkillsDir);
17424
- if (wanted.size === 0 && !fs32.existsSync(opts.sourceSkillsDir))
17618
+ if (wanted.size === 0 && !fs33.existsSync(opts.sourceSkillsDir))
17425
17619
  return result;
17426
17620
  if (!opts.dryRun)
17427
- fs32.mkdirSync(opts.skillsRoot, { recursive: true });
17621
+ fs33.mkdirSync(opts.skillsRoot, { recursive: true });
17428
17622
  for (const [name, srcPath] of wanted) {
17429
- const targetDir = path36.join(opts.skillsRoot, name);
17430
- const targetSkill = path36.join(targetDir, "SKILL.md");
17623
+ const targetDir = path37.join(opts.skillsRoot, name);
17624
+ const targetSkill = path37.join(targetDir, "SKILL.md");
17431
17625
  const managed = hasMarker(targetDir);
17432
- if (fs32.existsSync(targetSkill) && !managed) {
17626
+ if (fs33.existsSync(targetSkill) && !managed) {
17433
17627
  result.skipped.push({ name, reason: "foreign-collision" });
17434
17628
  continue;
17435
17629
  }
17436
- const current = fs32.existsSync(targetSkill) ? fs32.readFileSync(targetSkill) : null;
17437
- if (current && current.equals(fs32.readFileSync(srcPath))) {
17630
+ const current = fs33.existsSync(targetSkill) ? fs33.readFileSync(targetSkill) : null;
17631
+ if (current && current.equals(fs33.readFileSync(srcPath))) {
17438
17632
  if (!managed && !opts.dryRun) {
17439
- fs32.writeFileSync(path36.join(targetDir, OPENCODE_SKILL_MARKER), MARKER_BODY);
17633
+ fs33.writeFileSync(path37.join(targetDir, OPENCODE_SKILL_MARKER), MARKER_BODY);
17440
17634
  }
17441
17635
  result.unchanged.push(name);
17442
17636
  continue;
17443
17637
  }
17444
17638
  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);
17639
+ fs33.mkdirSync(targetDir, { recursive: true });
17640
+ fs33.copyFileSync(srcPath, targetSkill);
17641
+ fs33.writeFileSync(path37.join(targetDir, OPENCODE_SKILL_MARKER), MARKER_BODY);
17448
17642
  }
17449
17643
  result.written.push(name);
17450
17644
  }
17451
- if (fs32.existsSync(opts.skillsRoot)) {
17452
- for (const entry of fs32.readdirSync(opts.skillsRoot, { withFileTypes: true })) {
17645
+ if (fs33.existsSync(opts.skillsRoot)) {
17646
+ for (const entry of fs33.readdirSync(opts.skillsRoot, { withFileTypes: true })) {
17453
17647
  if (!entry.isDirectory() || wanted.has(entry.name))
17454
17648
  continue;
17455
- const dir = path36.join(opts.skillsRoot, entry.name);
17649
+ const dir = path37.join(opts.skillsRoot, entry.name);
17456
17650
  if (!hasMarker(dir))
17457
17651
  continue;
17458
17652
  if (!opts.dryRun)
17459
- fs32.rmSync(dir, { recursive: true, force: true });
17653
+ fs33.rmSync(dir, { recursive: true, force: true });
17460
17654
  result.removed.push(entry.name);
17461
17655
  }
17462
17656
  }
@@ -17464,7 +17658,7 @@ function syncOpencodeSkills(opts) {
17464
17658
  }
17465
17659
  function syncShippedOpencodeSkills(opts) {
17466
17660
  return syncOpencodeSkills({
17467
- sourceSkillsDir: path36.join(opts.pkgRoot, "plugin", "skills"),
17661
+ sourceSkillsDir: path37.join(opts.pkgRoot, "plugin", "skills"),
17468
17662
  skillsRoot: opts.skillsRoot ?? defaultOpencodeSkillsRoot(),
17469
17663
  dryRun: opts.dryRun
17470
17664
  });
@@ -17480,7 +17674,7 @@ var init_opencode_skills = __esm({
17480
17674
 
17481
17675
  // packages/agents/dist/projection/claude.js
17482
17676
  import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
17483
- import path37 from "path";
17677
+ import path38 from "path";
17484
17678
  import os15 from "os";
17485
17679
  function renderMarkdown4(source) {
17486
17680
  const skillSections = source.skills.map((s) => `## Skill: ${s.name}
@@ -17505,7 +17699,7 @@ function createClaudeEmitter() {
17505
17699
  destinations(scope, projectRoot) {
17506
17700
  if (scope === "user") {
17507
17701
  return [{
17508
- path: path37.join(os15.homedir(), ".claude/CLAUDE.md"),
17702
+ path: path38.join(os15.homedir(), ".claude/CLAUDE.md"),
17509
17703
  shared: true,
17510
17704
  format: "markdown"
17511
17705
  }];
@@ -17513,7 +17707,7 @@ function createClaudeEmitter() {
17513
17707
  if (!projectRoot)
17514
17708
  return [];
17515
17709
  return [{
17516
- path: path37.join(projectRoot, "CLAUDE.md"),
17710
+ path: path38.join(projectRoot, "CLAUDE.md"),
17517
17711
  shared: true,
17518
17712
  format: "markdown"
17519
17713
  }];
@@ -17534,7 +17728,7 @@ ${existing ?? ""}
17534
17728
  ${generated}`
17535
17729
  };
17536
17730
  }
17537
- await mkdir5(path37.dirname(dest.path), { recursive: true });
17731
+ await mkdir5(path38.dirname(dest.path), { recursive: true });
17538
17732
  await writeFile5(dest.path, generated, "utf-8");
17539
17733
  return {
17540
17734
  written: true,
@@ -18569,8 +18763,8 @@ function resolveLastAssistantText(payload) {
18569
18763
  const derived = deriveTranscriptPath(p?.session_id, cwd);
18570
18764
  if (derived)
18571
18765
  candidates.push(derived);
18572
- for (const path59 of candidates) {
18573
- const text = readLastAssistantText(path59);
18766
+ for (const path60 of candidates) {
18767
+ const text = readLastAssistantText(path60);
18574
18768
  if (text != null)
18575
18769
  return text;
18576
18770
  }
@@ -19459,22 +19653,22 @@ var init_receipt_listener = __esm({
19459
19653
  });
19460
19654
 
19461
19655
  // packages/agents/dist/claude/api-key-approval.js
19462
- import fs33 from "fs";
19656
+ import fs34 from "fs";
19463
19657
  import os16 from "os";
19464
- import path38 from "path";
19658
+ import path39 from "path";
19465
19659
  function ensureClaudeApiKeyApproved(apiKey, opts = {}) {
19466
19660
  const suffix = apiKey.slice(-20);
19467
19661
  if (!suffix)
19468
19662
  return { changed: false, reason: "empty api key" };
19469
- const file = opts.claudeJsonPath ?? path38.join(os16.homedir(), ".claude.json");
19663
+ const file = opts.claudeJsonPath ?? path39.join(os16.homedir(), ".claude.json");
19470
19664
  const log = opts.log ?? (() => {
19471
19665
  });
19472
- if (!fs33.existsSync(file)) {
19666
+ if (!fs34.existsSync(file)) {
19473
19667
  return { changed: false, reason: "no ~/.claude.json yet \u2014 skipping routed-key pre-approval" };
19474
19668
  }
19475
19669
  let doc;
19476
19670
  try {
19477
- doc = JSON.parse(fs33.readFileSync(file, "utf8"));
19671
+ doc = JSON.parse(fs34.readFileSync(file, "utf8"));
19478
19672
  } catch {
19479
19673
  return {
19480
19674
  changed: false,
@@ -19496,11 +19690,11 @@ function ensureClaudeApiKeyApproved(apiKey, opts = {}) {
19496
19690
  };
19497
19691
  const tmp = `${file}.squadrant-tmp`;
19498
19692
  try {
19499
- fs33.writeFileSync(tmp, JSON.stringify(doc, null, 2) + "\n", { mode: fs33.statSync(file).mode & 511 });
19500
- fs33.renameSync(tmp, file);
19693
+ fs34.writeFileSync(tmp, JSON.stringify(doc, null, 2) + "\n", { mode: fs34.statSync(file).mode & 511 });
19694
+ fs34.renameSync(tmp, file);
19501
19695
  } catch (e) {
19502
19696
  try {
19503
- fs33.unlinkSync(tmp);
19697
+ fs34.unlinkSync(tmp);
19504
19698
  } catch {
19505
19699
  }
19506
19700
  return {
@@ -19609,10 +19803,10 @@ var init_http_channel = __esm({
19609
19803
  return cached;
19610
19804
  const directory = this.directoryFor?.(taskId);
19611
19805
  const createdAfterMs = this.createdAfterFor?.(taskId);
19612
- for (const path59 of ["/session?", "/api/session?"]) {
19806
+ for (const path60 of ["/session?", "/api/session?"]) {
19613
19807
  let res;
19614
19808
  try {
19615
- res = await this.request(`http://127.0.0.1:${port}${path59}`, { method: "GET" });
19809
+ res = await this.request(`http://127.0.0.1:${port}${path60}`, { method: "GET" });
19616
19810
  } catch {
19617
19811
  return void 0;
19618
19812
  }
@@ -19692,12 +19886,12 @@ var init_fact_adapter = __esm({
19692
19886
  });
19693
19887
 
19694
19888
  // packages/agents/dist/rules-hooks/install.js
19695
- import fs34 from "fs";
19889
+ import fs35 from "fs";
19696
19890
  import os17 from "os";
19697
- import path39 from "path";
19891
+ import path40 from "path";
19698
19892
  function readJson(file) {
19699
19893
  try {
19700
- const v2 = JSON.parse(fs34.readFileSync(file, "utf8"));
19894
+ const v2 = JSON.parse(fs35.readFileSync(file, "utf8"));
19701
19895
  return typeof v2 === "object" && v2 !== null && !Array.isArray(v2) ? v2 : {};
19702
19896
  } catch (err) {
19703
19897
  if (err.code === "ENOENT")
@@ -19719,24 +19913,24 @@ function mergeHooks(root, agent, specs, cli, timeout) {
19719
19913
  function writeIfChanged(file, next, dryRun) {
19720
19914
  let prev = null;
19721
19915
  try {
19722
- prev = fs34.readFileSync(file, "utf8");
19916
+ prev = fs35.readFileSync(file, "utf8");
19723
19917
  } catch {
19724
19918
  }
19725
19919
  const changed = prev !== next;
19726
19920
  if (changed && !dryRun) {
19727
- fs34.mkdirSync(path39.dirname(file), { recursive: true });
19728
- fs34.writeFileSync(file, next);
19921
+ fs35.mkdirSync(path40.dirname(file), { recursive: true });
19922
+ fs35.writeFileSync(file, next);
19729
19923
  }
19730
19924
  return { path: file, changed };
19731
19925
  }
19732
19926
  function installCodexRulesHooks(opts = {}) {
19733
19927
  const env = opts.env ?? process.env;
19734
- const file = opts.target ?? path39.join(env.CODEX_HOME ?? path39.join(os17.homedir(), ".codex"), "hooks.json");
19928
+ const file = opts.target ?? path40.join(env.CODEX_HOME ?? path40.join(os17.homedir(), ".codex"), "hooks.json");
19735
19929
  const merged = mergeHooks(readJson(file), "codex", CODEX_HOOKS, opts.cli ?? "squadrant", 5);
19736
19930
  return writeIfChanged(file, JSON.stringify(merged, null, 2) + "\n", opts.dryRun);
19737
19931
  }
19738
19932
  function installGeminiRulesHooks(opts = {}) {
19739
- const file = opts.target ?? path39.join(os17.homedir(), ".gemini", "settings.json");
19933
+ const file = opts.target ?? path40.join(os17.homedir(), ".gemini", "settings.json");
19740
19934
  const merged = mergeHooks(readJson(file), "gemini", GEMINI_HOOKS, opts.cli ?? "squadrant", 5e3);
19741
19935
  return writeIfChanged(file, JSON.stringify(merged, null, 2) + "\n", opts.dryRun);
19742
19936
  }
@@ -19777,9 +19971,9 @@ export const SquadrantRules = async ({ directory }) => {
19777
19971
  }
19778
19972
  function installOpencodeRulesHooks(opts = {}) {
19779
19973
  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");
19974
+ const file = opts.target ?? path40.join(env.XDG_CONFIG_HOME || path40.join(os17.homedir(), ".config"), "opencode", "plugin", "squadrant-rules.js");
19781
19975
  try {
19782
- if (!fs34.readFileSync(file, "utf8").startsWith(OPENCODE_PLUGIN_MARKER))
19976
+ if (!fs35.readFileSync(file, "utf8").startsWith(OPENCODE_PLUGIN_MARKER))
19783
19977
  return { path: file, changed: false, skipped: "foreign-file" };
19784
19978
  } catch {
19785
19979
  }
@@ -20755,13 +20949,13 @@ var init_notifiers = __esm({
20755
20949
  });
20756
20950
 
20757
20951
  // packages/workspaces/dist/workspaces/obsidian.js
20758
- import fs35 from "fs/promises";
20952
+ import fs36 from "fs/promises";
20759
20953
  import { existsSync as existsSync11 } from "fs";
20760
- import path40 from "path";
20954
+ import path41 from "path";
20761
20955
  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)) {
20956
+ const joined = path41.resolve(root, relative);
20957
+ const normalized2 = path41.resolve(root) + path41.sep;
20958
+ if (joined !== path41.resolve(root) && !joined.startsWith(normalized2)) {
20765
20959
  throw new Error(`Path '${relative}' escapes workspace root`);
20766
20960
  }
20767
20961
  return joined;
@@ -20780,16 +20974,16 @@ function createObsidianDriver(scope) {
20780
20974
  };
20781
20975
  },
20782
20976
  async read(rel) {
20783
- return fs35.readFile(resolveInRoot(root, rel), "utf-8");
20977
+ return fs36.readFile(resolveInRoot(root, rel), "utf-8");
20784
20978
  },
20785
20979
  async write(rel, content) {
20786
20980
  const abs = resolveInRoot(root, rel);
20787
- await fs35.mkdir(path40.dirname(abs), { recursive: true });
20788
- await fs35.writeFile(abs, content);
20981
+ await fs36.mkdir(path41.dirname(abs), { recursive: true });
20982
+ await fs36.writeFile(abs, content);
20789
20983
  },
20790
20984
  async exists(rel) {
20791
20985
  try {
20792
- await fs35.access(resolveInRoot(root, rel));
20986
+ await fs36.access(resolveInRoot(root, rel));
20793
20987
  return true;
20794
20988
  } catch {
20795
20989
  return false;
@@ -20797,13 +20991,13 @@ function createObsidianDriver(scope) {
20797
20991
  },
20798
20992
  async list(rel) {
20799
20993
  try {
20800
- return await fs35.readdir(resolveInRoot(root, rel));
20994
+ return await fs36.readdir(resolveInRoot(root, rel));
20801
20995
  } catch {
20802
20996
  return [];
20803
20997
  }
20804
20998
  },
20805
20999
  async mkdir(rel) {
20806
- await fs35.mkdir(resolveInRoot(root, rel), { recursive: true });
21000
+ await fs36.mkdir(resolveInRoot(root, rel), { recursive: true });
20807
21001
  }
20808
21002
  };
20809
21003
  }
@@ -21217,9 +21411,9 @@ function defaultListFiles(dir) {
21217
21411
  return [];
21218
21412
  }
21219
21413
  }
21220
- function defaultReadFile(path59) {
21414
+ function defaultReadFile(path60) {
21221
21415
  try {
21222
- return readFileSync17(path59, "utf-8");
21416
+ return readFileSync17(path60, "utf-8");
21223
21417
  } catch {
21224
21418
  return void 0;
21225
21419
  }
@@ -21712,16 +21906,16 @@ function extractDetail(sub, payload) {
21712
21906
  }
21713
21907
  return void 0;
21714
21908
  }
21715
- function defaultReadFile2(path59) {
21909
+ function defaultReadFile2(path60) {
21716
21910
  try {
21717
- return readFileSync18(path59, "utf-8");
21911
+ return readFileSync18(path60, "utf-8");
21718
21912
  } catch {
21719
21913
  return void 0;
21720
21914
  }
21721
21915
  }
21722
- function defaultWriteFile(path59, content) {
21723
- mkdirSync8(path59.replace(/\/[^/]+$/, ""), { recursive: true });
21724
- writeFileSync11(path59, content, "utf-8");
21916
+ function defaultWriteFile(path60, content) {
21917
+ mkdirSync8(path60.replace(/\/[^/]+$/, ""), { recursive: true });
21918
+ writeFileSync11(path60, content, "utf-8");
21725
21919
  }
21726
21920
  var CLAUDE_HOOK_EVENTS, DEFAULT_CLI_CMD, FOREIGN_AUTO_GATE_MARKER, SUBAGENT_MODEL_KEY, SMALL_FAST_MODEL_KEY, NativeHookSource;
21727
21921
  var init_native_hook_source = __esm({
@@ -22293,9 +22487,9 @@ init_dist();
22293
22487
  init_dist4();
22294
22488
  import { Command } from "commander";
22295
22489
  import { execSync as execSync7 } from "child_process";
22296
- import fs36 from "fs";
22490
+ import fs37 from "fs";
22297
22491
  import { stat } from "fs/promises";
22298
- import path41 from "path";
22492
+ import path42 from "path";
22299
22493
  import chalk3 from "chalk";
22300
22494
 
22301
22495
  // packages/cli/src/commands/health-view.ts
@@ -22397,7 +22591,7 @@ function settingsHaveAgentTeams() {
22397
22591
  try {
22398
22592
  const home = process.env.HOME || "";
22399
22593
  const settings = JSON.parse(
22400
- fs36.readFileSync(`${home}/.claude/settings.json`, "utf-8")
22594
+ fs37.readFileSync(`${home}/.claude/settings.json`, "utf-8")
22401
22595
  );
22402
22596
  return settings?.env?.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS === "1";
22403
22597
  } catch {
@@ -22408,7 +22602,7 @@ function pluginInstalled(pluginKey) {
22408
22602
  try {
22409
22603
  const home = process.env.HOME || "";
22410
22604
  const plugins = JSON.parse(
22411
- fs36.readFileSync(
22605
+ fs37.readFileSync(
22412
22606
  `${home}/.claude/plugins/installed_plugins.json`,
22413
22607
  "utf-8"
22414
22608
  )
@@ -22432,9 +22626,9 @@ function tryGetVersion(cmd) {
22432
22626
  }
22433
22627
  function candidateGlobalInstalls(roots) {
22434
22628
  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") });
22629
+ if (roots.npm) out.push({ manager: "npm", packageJsonPath: path42.join(roots.npm, "squadrant", "package.json") });
22630
+ if (roots.pnpm) out.push({ manager: "pnpm", packageJsonPath: path42.join(roots.pnpm, "squadrant", "package.json") });
22631
+ if (roots.yarn) out.push({ manager: "yarn", packageJsonPath: path42.join(roots.yarn, "node_modules", "squadrant", "package.json") });
22438
22632
  return out;
22439
22633
  }
22440
22634
  function findInstalledSquadrants(candidates, readVersion) {
@@ -22454,7 +22648,7 @@ ${lines.join("\n")}
22454
22648
  }
22455
22649
  function readPackageVersion(packageJsonPath) {
22456
22650
  try {
22457
- const pkg2 = JSON.parse(fs36.readFileSync(packageJsonPath, "utf-8"));
22651
+ const pkg2 = JSON.parse(fs37.readFileSync(packageJsonPath, "utf-8"));
22458
22652
  return typeof pkg2.version === "string" ? pkg2.version : null;
22459
22653
  } catch {
22460
22654
  return null;
@@ -22537,7 +22731,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
22537
22731
  ));
22538
22732
  checkOptional(
22539
22733
  "Obsidian installed",
22540
- commandExists("obsidian") || fs36.existsSync("/Applications/Obsidian.app"),
22734
+ commandExists("obsidian") || fs37.existsSync("/Applications/Obsidian.app"),
22541
22735
  "viewer for the hub vault; the vault is plain Markdown",
22542
22736
  "Install from: https://obsidian.md"
22543
22737
  );
@@ -22634,7 +22828,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
22634
22828
  const emitter = projectionRegistry.get(name);
22635
22829
  const [userDest] = emitter.destinations("user");
22636
22830
  if (!userDest) continue;
22637
- const dir = path41.dirname(userDest.path);
22831
+ const dir = path42.dirname(userDest.path);
22638
22832
  let status;
22639
22833
  try {
22640
22834
  await stat(dir);
@@ -22655,7 +22849,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
22655
22849
  results.push(
22656
22850
  check(
22657
22851
  "Squadrant config exists",
22658
- fs36.existsSync(
22852
+ fs37.existsSync(
22659
22853
  process.env.SQUADRANT_CONFIG || `${process.env.HOME}/.config/squadrant/config.json`
22660
22854
  ),
22661
22855
  "Run: squadrant init"
@@ -22739,8 +22933,8 @@ init_dist();
22739
22933
  init_dist4();
22740
22934
  init_dist();
22741
22935
  import { Command as Command2 } from "commander";
22742
- import fs37 from "fs";
22743
- import path42 from "path";
22936
+ import fs38 from "fs";
22937
+ import path43 from "path";
22744
22938
  import os18 from "os";
22745
22939
  import readline from "readline";
22746
22940
  import chalk4 from "chalk";
@@ -22991,22 +23185,22 @@ function detectClaudeAuth(probe = {}) {
22991
23185
  // packages/cli/src/commands/init.ts
22992
23186
  init_dist3();
22993
23187
  function findPackageRoot() {
22994
- let dir = path42.dirname(new URL(import.meta.url).pathname);
23188
+ let dir = path43.dirname(new URL(import.meta.url).pathname);
22995
23189
  while (dir !== "/") {
22996
- if (fs37.existsSync(path42.join(dir, "package.json"))) return dir;
22997
- dir = path42.dirname(dir);
23190
+ if (fs38.existsSync(path43.join(dir, "package.json"))) return dir;
23191
+ dir = path43.dirname(dir);
22998
23192
  }
22999
23193
  return process.cwd();
23000
23194
  }
23001
23195
  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);
23196
+ fs38.mkdirSync(dest, { recursive: true });
23197
+ for (const entry of fs38.readdirSync(src, { withFileTypes: true })) {
23198
+ const srcPath = path43.join(src, entry.name);
23199
+ const destPath = path43.join(dest, entry.name);
23006
23200
  if (entry.isDirectory()) {
23007
23201
  copyDirRecursive(srcPath, destPath);
23008
23202
  } else {
23009
- fs37.copyFileSync(srcPath, destPath);
23203
+ fs38.copyFileSync(srcPath, destPath);
23010
23204
  }
23011
23205
  }
23012
23206
  }
@@ -23048,7 +23242,7 @@ function warnIfNoAnthropic(preset, auth) {
23048
23242
  var initCommand = new Command2("init").description("Guided first-time setup: provider, hub vault, agents, plugins, projects (re-run-safe)").option("--hub <path>", "Hub vault path", "~/squadrant-hub").option("--preset <id>", "provider preset: a|b|c (non-interactive; d accepted as a deprecated alias for c)").action(async (opts) => {
23049
23243
  const hubPath = resolveHome(opts.hub);
23050
23244
  const pkgRoot = findPackageRoot();
23051
- const configDir = path42.join(os18.homedir(), ".config", "squadrant");
23245
+ const configDir = path43.join(os18.homedir(), ".config", "squadrant");
23052
23246
  const isTTY = process.stdin.isTTY === true;
23053
23247
  console.log(chalk4.bold("\nSquadrant Init\n"));
23054
23248
  let explicitPreset;
@@ -23086,8 +23280,8 @@ var initCommand = new Command2("init").description("Guided first-time setup: pro
23086
23280
  }
23087
23281
  const wsRegistry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
23088
23282
  try {
23089
- if (fs37.existsSync(DEFAULT_CONFIG_PATH)) {
23090
- const existing = JSON.parse(fs37.readFileSync(DEFAULT_CONFIG_PATH, "utf-8"));
23283
+ if (fs38.existsSync(DEFAULT_CONFIG_PATH)) {
23284
+ const existing = JSON.parse(fs38.readFileSync(DEFAULT_CONFIG_PATH, "utf-8"));
23091
23285
  wsRegistry.get(existing.workspace ?? "obsidian");
23092
23286
  }
23093
23287
  } catch (err) {
@@ -23095,7 +23289,7 @@ var initCommand = new Command2("init").description("Guided first-time setup: pro
23095
23289
  return;
23096
23290
  }
23097
23291
  stepHeader(1, 5, "Hub vault");
23098
- const configExists = fs37.existsSync(DEFAULT_CONFIG_PATH);
23292
+ const configExists = fs38.existsSync(DEFAULT_CONFIG_PATH);
23099
23293
  let config;
23100
23294
  if (configExists) {
23101
23295
  console.log(chalk4.yellow(" \u26A0 Config already exists, skipping creation"));
@@ -23133,37 +23327,37 @@ var initCommand = new Command2("init").description("Guided first-time setup: pro
23133
23327
  saveConfig(config);
23134
23328
  console.log(chalk4.green(` \u2714 Config updated at ${DEFAULT_CONFIG_PATH}`));
23135
23329
  }
23136
- const hubTemplate = path42.join(pkgRoot, "obsidian", "hub");
23137
- if (fs37.existsSync(hubPath)) {
23330
+ const hubTemplate = path43.join(pkgRoot, "obsidian", "hub");
23331
+ if (fs38.existsSync(hubPath)) {
23138
23332
  console.log(chalk4.yellow(` \u26A0 Hub vault already exists at ${hubPath}`));
23139
- } else if (fs37.existsSync(hubTemplate)) {
23333
+ } else if (fs38.existsSync(hubTemplate)) {
23140
23334
  copyDirRecursive(hubTemplate, hubPath);
23141
23335
  console.log(chalk4.green(` \u2714 Hub vault scaffolded at ${hubPath}`));
23142
23336
  } else {
23143
- fs37.mkdirSync(hubPath, { recursive: true });
23337
+ fs38.mkdirSync(hubPath, { recursive: true });
23144
23338
  console.log(chalk4.yellow(` \u26A0 Hub template not found; created empty directory at ${hubPath}`));
23145
23339
  }
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);
23340
+ const hubDashboardSrc = path43.join(pkgRoot, "obsidian", "hub", "dashboard.md");
23341
+ const hubDashboardDest = path43.join(hubPath, "dashboard.md");
23342
+ if (fs38.existsSync(hubDashboardSrc)) {
23343
+ fs38.copyFileSync(hubDashboardSrc, hubDashboardDest);
23150
23344
  console.log(chalk4.green(` \u2714 Dashboard refreshed`));
23151
23345
  }
23152
- fs37.mkdirSync(path42.join(hubPath, "projects"), { recursive: true });
23346
+ fs38.mkdirSync(path43.join(hubPath, "projects"), { recursive: true });
23153
23347
  ensureRuntimeSynced({ sourceRoot: pkgRoot, runtimeRoot: configDir });
23154
23348
  console.log(chalk4.green(` \u2714 Runtime assets synced to ${configDir}`));
23155
23349
  stepHeader(2, 5, "Agent + projection setup");
23156
- const settingsPath = path42.join(os18.homedir(), ".claude", "settings.json");
23350
+ const settingsPath = path43.join(os18.homedir(), ".claude", "settings.json");
23157
23351
  try {
23158
23352
  let settings = {};
23159
- if (fs37.existsSync(settingsPath)) {
23160
- settings = JSON.parse(fs37.readFileSync(settingsPath, "utf-8"));
23353
+ if (fs38.existsSync(settingsPath)) {
23354
+ settings = JSON.parse(fs38.readFileSync(settingsPath, "utf-8"));
23161
23355
  }
23162
23356
  const env = settings.env || {};
23163
23357
  if (env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS !== "1") {
23164
23358
  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");
23359
+ fs38.mkdirSync(path43.dirname(settingsPath), { recursive: true });
23360
+ fs38.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
23167
23361
  console.log(chalk4.green(" \u2714 Agent Teams enabled in ~/.claude/settings.json"));
23168
23362
  } else {
23169
23363
  console.log(chalk4.green(" \u2714 Agent Teams already enabled"));
@@ -23216,7 +23410,7 @@ var initCommand = new Command2("init").description("Guided first-time setup: pro
23216
23410
  chalk4.cyan(" Absolute path to your first project (Enter to skip): ")
23217
23411
  );
23218
23412
  if (projectPath) {
23219
- const projectName = path42.basename(projectPath);
23413
+ const projectName = path43.basename(projectPath);
23220
23414
  console.log(chalk4.bold(`
23221
23415
  Run this to register it:`));
23222
23416
  console.log(chalk4.cyan(` squadrant projects add ${projectName} ${projectPath}
@@ -23244,8 +23438,8 @@ var initCommand = new Command2("init").description("Guided first-time setup: pro
23244
23438
  init_dist();
23245
23439
  init_dist2();
23246
23440
  import { Command as Command3 } from "commander";
23247
- import fs38 from "fs";
23248
- import path43 from "path";
23441
+ import fs39 from "fs";
23442
+ import path44 from "path";
23249
23443
  import chalk5 from "chalk";
23250
23444
  function restartAfterProjectsAdd(opts) {
23251
23445
  const doRestart = opts.doRestart ?? restartDaemonIfRunning;
@@ -23257,22 +23451,22 @@ function restartAfterProjectsAdd(opts) {
23257
23451
  }
23258
23452
  }
23259
23453
  function findPackageRoot2() {
23260
- let dir = path43.dirname(new URL(import.meta.url).pathname);
23454
+ let dir = path44.dirname(new URL(import.meta.url).pathname);
23261
23455
  while (dir !== "/") {
23262
- if (fs38.existsSync(path43.join(dir, "package.json"))) return dir;
23263
- dir = path43.dirname(dir);
23456
+ if (fs39.existsSync(path44.join(dir, "package.json"))) return dir;
23457
+ dir = path44.dirname(dir);
23264
23458
  }
23265
23459
  return process.cwd();
23266
23460
  }
23267
23461
  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);
23462
+ fs39.mkdirSync(dest, { recursive: true });
23463
+ for (const entry of fs39.readdirSync(src, { withFileTypes: true })) {
23464
+ const srcPath = path44.join(src, entry.name);
23465
+ const destPath = path44.join(dest, entry.name);
23272
23466
  if (entry.isDirectory()) {
23273
23467
  copyDirRecursive2(srcPath, destPath);
23274
23468
  } else {
23275
- fs38.copyFileSync(srcPath, destPath);
23469
+ fs39.copyFileSync(srcPath, destPath);
23276
23470
  }
23277
23471
  }
23278
23472
  }
@@ -23308,7 +23502,7 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
23308
23502
  process.exit(1);
23309
23503
  }
23310
23504
  const resolvedPath = resolveHome(projectPath);
23311
- if (!fs38.existsSync(path43.join(resolvedPath, ".git"))) {
23505
+ if (!fs39.existsSync(path44.join(resolvedPath, ".git"))) {
23312
23506
  console.log(chalk5.yellow(`
23313
23507
  \u26A0 No .git found at ${resolvedPath}. Make sure this is the project root, not a parent directory.
23314
23508
  `));
@@ -23361,7 +23555,7 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
23361
23555
  \u26A0 Group '${group}' already has '${primary[0]}' as primary. Overriding.`));
23362
23556
  }
23363
23557
  }
23364
- const spokeVault = opts.spoke ? resolveHome(opts.spoke) : path43.join(config.hubVault, "spokes", name);
23558
+ const spokeVault = opts.spoke ? resolveHome(opts.spoke) : path44.join(config.hubVault, "spokes", name);
23365
23559
  const project = {
23366
23560
  path: resolvedPath,
23367
23561
  captainName,
@@ -23376,20 +23570,20 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
23376
23570
  \u2714 Project '${name}' registered`));
23377
23571
  restartAfterProjectsAdd({ noRestart: opts.restart === false });
23378
23572
  const pkgRoot = findPackageRoot2();
23379
- const spokeTemplate = path43.join(pkgRoot, "obsidian", "spoke");
23380
- if (fs38.existsSync(spokeVault)) {
23573
+ const spokeTemplate = path44.join(pkgRoot, "obsidian", "spoke");
23574
+ if (fs39.existsSync(spokeVault)) {
23381
23575
  console.log(chalk5.yellow(` \u26A0 Spoke vault already exists at ${spokeVault}, skipping scaffold`));
23382
- } else if (fs38.existsSync(spokeTemplate)) {
23576
+ } else if (fs39.existsSync(spokeTemplate)) {
23383
23577
  copyDirRecursive2(spokeTemplate, spokeVault);
23384
- const statusPath = path43.join(spokeVault, "status.md");
23385
- if (fs38.existsSync(statusPath)) {
23386
- const content = fs38.readFileSync(statusPath, "utf-8");
23578
+ const statusPath = path44.join(spokeVault, "status.md");
23579
+ if (fs39.existsSync(statusPath)) {
23580
+ const content = fs39.readFileSync(statusPath, "utf-8");
23387
23581
  const updated = content.replace(/^project: unnamed/m, `project: ${name}`);
23388
- fs38.writeFileSync(statusPath, updated);
23582
+ fs39.writeFileSync(statusPath, updated);
23389
23583
  }
23390
23584
  console.log(chalk5.green(` \u2714 Spoke vault scaffolded at ${spokeVault}`));
23391
23585
  } else {
23392
- fs38.mkdirSync(spokeVault, { recursive: true });
23586
+ fs39.mkdirSync(spokeVault, { recursive: true });
23393
23587
  console.log(chalk5.yellow(` \u26A0 Spoke template not found; created empty dir at ${spokeVault}`));
23394
23588
  }
23395
23589
  console.log("");
@@ -23458,10 +23652,10 @@ init_dist3();
23458
23652
  init_dist();
23459
23653
  import { Command as Command5 } from "commander";
23460
23654
  import { execSync as execSync9 } from "child_process";
23461
- import path44 from "path";
23655
+ import path45 from "path";
23462
23656
  import os19 from "os";
23463
23657
  import chalk7 from "chalk";
23464
- var TEMPLATES_DIR2 = path44.join(os19.homedir(), ".config", "squadrant", "templates");
23658
+ var TEMPLATES_DIR2 = path45.join(os19.homedir(), ".config", "squadrant", "templates");
23465
23659
  var TASK_PROMPTS = {
23466
23660
  briefing: "Run your daily briefing using the squadrant:command-ops skill. Read all spoke handoffs, yesterday's logs, current status; produce a concise cross-project briefing; save to {hubVault}/daily-logs/YYYY-MM-DD.md; then exit.",
23467
23661
  "learnings-review": "Run a learnings review using the squadrant:command-ops skill. Scan {spokeVault}/learnings across all projects, identify cross-project patterns, propose captured-skill or fix actions, and exit when done.",
@@ -23494,7 +23688,7 @@ async function runCommandSpawn(input) {
23494
23688
  if (!agent) {
23495
23689
  throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
23496
23690
  }
23497
- const promptFile = path44.join(TEMPLATES_DIR2, `command.${agent.templateSuffix}.md`);
23691
+ const promptFile = path45.join(TEMPLATES_DIR2, `command.${agent.templateSuffix}.md`);
23498
23692
  const cliCommand = agent.buildCommand({
23499
23693
  prompt,
23500
23694
  workdir: process.cwd(),
@@ -23535,7 +23729,7 @@ init_dist3();
23535
23729
  import { Command as Command9 } from "commander";
23536
23730
  import { createConnection as createConnection4 } from "net";
23537
23731
  import { randomUUID as randomUUID5 } from "crypto";
23538
- import { execFileSync as execFileSync11 } from "child_process";
23732
+ import { execFileSync as execFileSync11, spawnSync } from "child_process";
23539
23733
  import { homedir as homedir15 } from "os";
23540
23734
  import { join as join27 } from "path";
23541
23735
  import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync13 } from "fs";
@@ -24017,6 +24211,31 @@ function defaultWriteResult(id, payload) {
24017
24211
  writeFileSync13(file, payload);
24018
24212
  return file;
24019
24213
  }
24214
+ var CITE_GIT_TIMEOUT_MS = 2e3;
24215
+ var citeGit = (args) => {
24216
+ const r = spawnSync("git", args, { encoding: "utf8", timeout: CITE_GIT_TIMEOUT_MS, stdio: ["ignore", "pipe", "ignore"] });
24217
+ if (r.error) throw r.error;
24218
+ return r.status === 0 ? r.stdout : "";
24219
+ };
24220
+ function logSignalCitations(state, o) {
24221
+ const env = o.env ?? process.env;
24222
+ const project = o.project ?? env.SQUADRANT_CREW_PROJECT;
24223
+ if (state !== "done" && state !== "review" || !project) return [];
24224
+ const texts = o.message ? [{ text: o.message, ref: `crew-${state}:${o.taskId ?? env.SQUADRANT_CREW_TASK_ID}` }] : [];
24225
+ try {
24226
+ texts.push(...readBranchOnlyMessages(o.cwd ?? process.cwd(), o.git ?? citeGit).map((c) => ({ text: c.text, ref: `git:${c.sha}`, at: c.at })));
24227
+ } catch (e) {
24228
+ process.stderr.write(`squadrant: rule citation scan of commits skipped: ${e.message}
24229
+ `);
24230
+ }
24231
+ try {
24232
+ return scanCitations(texts, { project, sink: o.audit });
24233
+ } catch (e) {
24234
+ process.stderr.write(`squadrant: rule citation scan skipped: ${e.message}
24235
+ `);
24236
+ return [];
24237
+ }
24238
+ }
24020
24239
  async function runCrewSignal(signal, o, deps) {
24021
24240
  const taskId = o.taskId ?? process.env.SQUADRANT_CREW_TASK_ID;
24022
24241
  const project = o.project ?? process.env.SQUADRANT_CREW_PROJECT;
@@ -24205,13 +24424,7 @@ function addControlPlaneCrewCommands(crew) {
24205
24424
  ...opts.project !== void 0 ? { project: opts.project } : {},
24206
24425
  writeResult: defaultWriteResult
24207
24426
  }, { 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
- }
24427
+ logSignalCitations(state, opts);
24215
24428
  process.exit(0);
24216
24429
  } catch (e) {
24217
24430
  process.stderr.write(`${e.message}
@@ -24300,7 +24513,7 @@ import { connect as netConnect2 } from "net";
24300
24513
  init_dist3();
24301
24514
  init_dist2();
24302
24515
  import { createServer as createServer4, connect as netConnect } from "net";
24303
- import fs39 from "fs";
24516
+ import fs40 from "fs";
24304
24517
  import { join as join28 } from "path";
24305
24518
  import { randomUUID as randomUUID6 } from "crypto";
24306
24519
  import chalk10 from "chalk";
@@ -24308,15 +24521,15 @@ var shared;
24308
24521
  var registryEntryPath = () => join28(CLAUDE_SESSIONS_DIR, `${process.pid}.json`);
24309
24522
  function unregisterSenderIdentity() {
24310
24523
  try {
24311
- fs39.unlinkSync(registryEntryPath());
24524
+ fs40.unlinkSync(registryEntryPath());
24312
24525
  } catch {
24313
24526
  }
24314
24527
  }
24315
24528
  function registerSenderIdentity(socketPath2) {
24316
24529
  try {
24317
24530
  unregisterSenderIdentity();
24318
- fs39.mkdirSync(CLAUDE_SESSIONS_DIR, { recursive: true });
24319
- fs39.writeFileSync(
24531
+ fs40.mkdirSync(CLAUDE_SESSIONS_DIR, { recursive: true });
24532
+ fs40.writeFileSync(
24320
24533
  registryEntryPath(),
24321
24534
  JSON.stringify({
24322
24535
  pid: process.pid,
@@ -24342,7 +24555,7 @@ async function sharedReceiptListener() {
24342
24555
  // must never be the reason we refuse to start.
24343
24556
  unlinkStale: (p) => {
24344
24557
  try {
24345
- fs39.unlinkSync(p);
24558
+ fs40.unlinkSync(p);
24346
24559
  } catch {
24347
24560
  }
24348
24561
  },
@@ -24367,7 +24580,7 @@ async function buildCaptainChannel() {
24367
24580
  // in /tmp/x registers cwd "/private/tmp/x" on macOS, so cwd matching silently
24368
24581
  // missed and every captain ping reported accepted-unconfirmed.
24369
24582
  statusFor: (taskId) => readClaudeStatusBySocketPath(captainSocketPath(taskId)),
24370
- wire: (p, e) => writeLine(p, e, { connect: (path59) => netConnect(path59) }),
24583
+ wire: (p, e) => writeLine(p, e, { connect: (path60) => netConnect(path60) }),
24371
24584
  receipts,
24372
24585
  newMsgId: () => randomUUID6(),
24373
24586
  sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
@@ -24396,7 +24609,7 @@ async function sendClaudeFirstTurnViaPeer(runtime, o, getTaskRecord) {
24396
24609
  socketPathFor: () => o.messagingSocketPath,
24397
24610
  sessionIdFor: () => readClaudeStatusBySocketPath(o.messagingSocketPath)?.sessionId,
24398
24611
  statusFor: () => readClaudeStatusBySocketPath(o.messagingSocketPath),
24399
- wire: (p, e) => writeLine(p, e, { connect: (path59) => netConnect2(path59) }),
24612
+ wire: (p, e) => writeLine(p, e, { connect: (path60) => netConnect2(path60) }),
24400
24613
  receipts,
24401
24614
  newMsgId: () => randomUUID7(),
24402
24615
  sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
@@ -24540,7 +24753,7 @@ async function runCrewSend2(project, name, message, opts) {
24540
24753
  socketPathFor: (taskId) => tasks.find((t) => t.id === taskId)?.messagingSocketPath,
24541
24754
  sessionIdFor: (taskId) => tasks.find((t) => t.id === taskId)?.sessionId,
24542
24755
  statusFor: (taskId) => readClaudeStatus(tasks.find((t) => t.id === taskId)),
24543
- wire: (p, e) => writeLine(p, e, { connect: (path59) => netConnect3(path59) }),
24756
+ wire: (p, e) => writeLine(p, e, { connect: (path60) => netConnect3(path60) }),
24544
24757
  receipts,
24545
24758
  newMsgId: () => randomUUID8(),
24546
24759
  sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
@@ -24934,11 +25147,11 @@ init_dist4();
24934
25147
  init_dist();
24935
25148
  init_dist2();
24936
25149
  import { Command as Command12 } from "commander";
24937
- import fs40 from "fs";
24938
- import path45 from "path";
25150
+ import fs41 from "fs";
25151
+ import path46 from "path";
24939
25152
  import os20 from "os";
24940
25153
  import chalk13 from "chalk";
24941
- var TEMPLATES_DIR3 = path45.join(os20.homedir(), ".config", "squadrant", "templates");
25154
+ var TEMPLATES_DIR3 = path46.join(os20.homedir(), ".config", "squadrant", "templates");
24942
25155
  async function runSideSpawn2(input) {
24943
25156
  const config = loadConfig();
24944
25157
  const proj = config.projects[input.project];
@@ -24963,7 +25176,7 @@ async function runSideSpawn2(input) {
24963
25176
  }
24964
25177
  const sideModel = sideRole?.model;
24965
25178
  const sideThinking = sideRole?.thinking;
24966
- const promptFile = path45.join(
25179
+ const promptFile = path46.join(
24967
25180
  TEMPLATES_DIR3,
24968
25181
  `side.${input.role}.${agent.templateSuffix}.md`
24969
25182
  );
@@ -24971,7 +25184,7 @@ async function runSideSpawn2(input) {
24971
25184
  prompt: input.topic,
24972
25185
  workdir: spawnCwd,
24973
25186
  role: "side",
24974
- promptFile: fs40.existsSync(promptFile) ? promptFile : void 0,
25187
+ promptFile: fs41.existsSync(promptFile) ? promptFile : void 0,
24975
25188
  interactive: true,
24976
25189
  permissionMode: config.defaults.permissions?.crew ?? "auto",
24977
25190
  ...sideModel ? { model: sideModel } : {},
@@ -25234,8 +25447,8 @@ function renderDashboard(rows, opts) {
25234
25447
 
25235
25448
  // packages/web/dist/sync-hub.js
25236
25449
  init_dist();
25237
- import fs41 from "fs";
25238
- import path46 from "path";
25450
+ import fs42 from "fs";
25451
+ import path47 from "path";
25239
25452
  function buildMirrorMarkdown(s) {
25240
25453
  const fenced = "```";
25241
25454
  return [
@@ -25261,15 +25474,15 @@ function buildMirrorMarkdown(s) {
25261
25474
  function syncHub(deps) {
25262
25475
  if (!deps.config.hubVault)
25263
25476
  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");
25477
+ const writeFile6 = deps.writeFile ?? ((p, c) => fs42.writeFileSync(p, c));
25478
+ const mkdir6 = deps.mkdir ?? ((p) => fs42.mkdirSync(p, { recursive: true }));
25479
+ const projectsDir = path47.join(resolveHome(deps.config.hubVault), "projects");
25267
25480
  mkdir6(projectsDir);
25268
25481
  const out = [];
25269
25482
  for (const s of deps.statuses) {
25270
25483
  if (s.state === "unknown")
25271
25484
  continue;
25272
- const hubPath = path46.join(projectsDir, `${s.project}.md`);
25485
+ const hubPath = path47.join(projectsDir, `${s.project}.md`);
25273
25486
  try {
25274
25487
  writeFile6(hubPath, buildMirrorMarkdown(s));
25275
25488
  out.push({ project: s.project, hubPath });
@@ -26275,18 +26488,18 @@ function renderHtml(snap, opts = {}) {
26275
26488
  import { closeSync as closeSync3, openSync as openSync3, readSync as readSync2, statSync as statSync4 } from "fs";
26276
26489
  var LOG_TAIL_BYTES = 256 * 1024;
26277
26490
  var LOG_RING_SIZE = 1e3;
26278
- function defaultSize(path59) {
26491
+ function defaultSize(path60) {
26279
26492
  try {
26280
- return statSync4(path59).size;
26493
+ return statSync4(path60).size;
26281
26494
  } catch {
26282
26495
  return 0;
26283
26496
  }
26284
26497
  }
26285
- function defaultRead(path59, start, len) {
26498
+ function defaultRead(path60, start, len) {
26286
26499
  if (len <= 0)
26287
26500
  return "";
26288
26501
  try {
26289
- const fd = openSync3(path59, "r");
26502
+ const fd = openSync3(path60, "r");
26290
26503
  try {
26291
26504
  const buf = Buffer.alloc(len);
26292
26505
  const n = readSync2(fd, buf, 0, len, start);
@@ -26306,8 +26519,8 @@ var LogTailer = class {
26306
26519
  ring = [];
26307
26520
  size;
26308
26521
  read;
26309
- constructor(path59, ringSize = LOG_RING_SIZE, deps = {}) {
26310
- this.path = path59;
26522
+ constructor(path60, ringSize = LOG_RING_SIZE, deps = {}) {
26523
+ this.path = path60;
26311
26524
  this.ringSize = ringSize;
26312
26525
  this.size = deps.size ?? defaultSize;
26313
26526
  this.read = deps.read ?? defaultRead;
@@ -26583,8 +26796,8 @@ init_dist4();
26583
26796
  init_dist2();
26584
26797
  import { Command as Command14 } from "commander";
26585
26798
  import { execSync as execSync11, execFileSync as execFileSync13 } from "child_process";
26586
- import fs42 from "fs";
26587
- import path47 from "path";
26799
+ import fs43 from "fs";
26800
+ import path48 from "path";
26588
26801
  import os21 from "os";
26589
26802
  import chalk16 from "chalk";
26590
26803
 
@@ -26677,8 +26890,8 @@ function resolveLaunchBackend(o) {
26677
26890
  // packages/cli/src/commands/launch.ts
26678
26891
  init_dist2();
26679
26892
  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");
26893
+ var TEMPLATES_DIR4 = path48.join(os21.homedir(), ".config", "squadrant", "templates");
26894
+ var SESSIONS_PATH2 = path48.join(os21.homedir(), ".config", "squadrant", "sessions.json");
26682
26895
  function shouldWireCaptainChannel(agentName, config) {
26683
26896
  return agentName === "claude" && resolveCaptainChannelMode(config.defaults) !== "off";
26684
26897
  }
@@ -26775,7 +26988,7 @@ var launchCommand = new Command14("launch").description(
26775
26988
  initialPrompt = "Run your startup checklist: use the squadrant:command-ops skill, complete your daily briefing, then report ready.";
26776
26989
  }
26777
26990
  const runtime = projectName ? runtimes.forProject(projectName, config) : runtimes.global(config);
26778
- const stateRoot = path47.join(os21.homedir(), ".config", "squadrant", "state");
26991
+ const stateRoot = path48.join(os21.homedir(), ".config", "squadrant", "state");
26779
26992
  const isOpencodeCaptain = role === "captain" && agentName === "opencode" && !!projectName;
26780
26993
  if (isOpencodeCaptain && !isOpencodeCaptainDir(cwd)) {
26781
26994
  console.error(chalk16.red(`
@@ -26928,12 +27141,12 @@ var launchCommand = new Command14("launch").description(
26928
27141
  }
26929
27142
  if (opts.all) {
26930
27143
  const hubPath = resolveHome(config.hubVault);
26931
- fs42.mkdirSync(hubPath, { recursive: true });
27144
+ fs43.mkdirSync(hubPath, { recursive: true });
26932
27145
  console.log(chalk16.bold("\nLaunching all captain workspaces\n"));
26933
27146
  for (const [name, proj] of Object.entries(config.projects)) {
26934
27147
  const projPath = resolveHome(proj.path);
26935
27148
  const spokePath = resolveHome(proj.spokeVault);
26936
- if (!fs42.existsSync(spokePath)) {
27149
+ if (!fs43.existsSync(spokePath)) {
26937
27150
  const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
26938
27151
  await ensureSpokeLayout(spokeDriver);
26939
27152
  console.log(chalk16.cyan(` \u2714 Created spoke vault at ${spokePath}`));
@@ -26971,7 +27184,7 @@ Launching ${selected.length} captain workspace(s) in parallel
26971
27184
  const proj = config.projects[name];
26972
27185
  const projPath = resolveHome(proj.path);
26973
27186
  const spokePath = resolveHome(proj.spokeVault);
26974
- if (!fs42.existsSync(spokePath)) {
27187
+ if (!fs43.existsSync(spokePath)) {
26975
27188
  const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
26976
27189
  await ensureSpokeLayout(spokeDriver);
26977
27190
  console.log(chalk16.cyan(` \u2714 Created spoke vault at ${spokePath}`));
@@ -26995,7 +27208,7 @@ Launching ${selected.length} captain workspace(s) in parallel
26995
27208
  const proj = config.projects[project];
26996
27209
  const projPath = resolveHome(proj.path);
26997
27210
  const spokePath = resolveHome(proj.spokeVault);
26998
- if (!fs42.existsSync(spokePath)) {
27211
+ if (!fs43.existsSync(spokePath)) {
26999
27212
  const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(project, config);
27000
27213
  await ensureSpokeLayout(spokeDriver);
27001
27214
  console.log(chalk16.cyan(` \u2714 Created spoke vault at ${spokePath}`));
@@ -27131,24 +27344,24 @@ Shutting down captain workspace for '${project}'...
27131
27344
  // packages/cli/src/commands/feedback.ts
27132
27345
  init_dist();
27133
27346
  import { Command as Command16 } from "commander";
27134
- import fs43 from "fs";
27347
+ import fs44 from "fs";
27135
27348
  import os22 from "os";
27136
- import path48 from "path";
27349
+ import path49 from "path";
27137
27350
  import { fileURLToPath as fileURLToPath3 } from "url";
27138
27351
  import { execSync as execSync12 } from "child_process";
27139
27352
  import chalk18 from "chalk";
27140
27353
  var REPO_URL = "https://github.com/tu11aa/squadrant";
27141
27354
  function readPkgVersion() {
27142
27355
  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";
27356
+ const pkgPath = path49.join(path49.dirname(fileURLToPath3(import.meta.url)), "..", "package.json");
27357
+ return JSON.parse(fs44.readFileSync(pkgPath, "utf-8")).version ?? "unknown";
27145
27358
  } catch {
27146
27359
  return "unknown";
27147
27360
  }
27148
27361
  }
27149
27362
  function readMetrics(metricsPath) {
27150
27363
  try {
27151
- return JSON.parse(fs43.readFileSync(metricsPath, "utf-8"));
27364
+ return JSON.parse(fs44.readFileSync(metricsPath, "utf-8"));
27152
27365
  } catch {
27153
27366
  return {};
27154
27367
  }
@@ -27186,7 +27399,7 @@ function buildIssueUrl(metrics, squadrantVersion) {
27186
27399
  }
27187
27400
  var feedbackCommand = new Command16("feedback").description("Open a pre-filled GitHub issue for feedback or bug reports").action(() => {
27188
27401
  const config = loadConfig();
27189
- const metricsPath = config.metrics?.path || path48.join(os22.homedir(), ".config", "squadrant", "metrics.json");
27402
+ const metricsPath = config.metrics?.path || path49.join(os22.homedir(), ".config", "squadrant", "metrics.json");
27190
27403
  const metrics = readMetrics(metricsPath);
27191
27404
  const version = readStamp(config) ?? readPkgVersion();
27192
27405
  const issueUrl = buildIssueUrl(metrics, version);
@@ -27486,9 +27699,9 @@ workspaceCommand.command("read").description("Print the contents of a scope-rela
27486
27699
  const config = loadConfig();
27487
27700
  const registry = buildRegistry2();
27488
27701
  try {
27489
- const { projectTarget, path: path59 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
27702
+ const { projectTarget, path: path60 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
27490
27703
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
27491
- const content = await driver.read(path59);
27704
+ const content = await driver.read(path60);
27492
27705
  process.stdout.write(content);
27493
27706
  } catch (err) {
27494
27707
  console.error(chalk21.red(err.message));
@@ -27500,26 +27713,26 @@ workspaceCommand.command("write").description("Write content to a scope-relative
27500
27713
  const registry = buildRegistry2();
27501
27714
  try {
27502
27715
  let projectTarget;
27503
- let path59;
27716
+ let path60;
27504
27717
  let rawContent;
27505
27718
  if (opts.hub) {
27506
27719
  if (arg3 !== void 0) {
27507
27720
  throw new Error("With --hub, pass only the path and content");
27508
27721
  }
27509
27722
  projectTarget = void 0;
27510
- path59 = arg1;
27723
+ path60 = arg1;
27511
27724
  rawContent = arg2;
27512
27725
  } else {
27513
27726
  if (arg3 === void 0) {
27514
27727
  throw new Error("Missing content \u2014 usage: <project> <path> <content>");
27515
27728
  }
27516
27729
  projectTarget = arg1;
27517
- path59 = arg2;
27730
+ path60 = arg2;
27518
27731
  rawContent = arg3;
27519
27732
  }
27520
27733
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
27521
27734
  const payload = rawContent === "-" ? await readStdin() : rawContent;
27522
- await driver.write(path59, payload);
27735
+ await driver.write(path60, payload);
27523
27736
  } catch (err) {
27524
27737
  console.error(chalk21.red(err.message));
27525
27738
  process.exit(1);
@@ -27529,9 +27742,9 @@ workspaceCommand.command("list").description("List entries in a scope-relative d
27529
27742
  const config = loadConfig();
27530
27743
  const registry = buildRegistry2();
27531
27744
  try {
27532
- const { projectTarget, path: path59 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
27745
+ const { projectTarget, path: path60 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
27533
27746
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
27534
- const entries = await driver.list(path59);
27747
+ const entries = await driver.list(path60);
27535
27748
  for (const entry of entries) console.log(entry);
27536
27749
  } catch (err) {
27537
27750
  console.error(chalk21.red(err.message));
@@ -27542,9 +27755,9 @@ workspaceCommand.command("exists").description("Exit 0 if path exists, 1 if not"
27542
27755
  const config = loadConfig();
27543
27756
  const registry = buildRegistry2();
27544
27757
  try {
27545
- const { projectTarget, path: path59 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
27758
+ const { projectTarget, path: path60 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
27546
27759
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
27547
- const ok2 = await driver.exists(path59);
27760
+ const ok2 = await driver.exists(path60);
27548
27761
  process.exit(ok2 ? 0 : 1);
27549
27762
  } catch (err) {
27550
27763
  console.error(chalk21.red(err.message));
@@ -27555,9 +27768,9 @@ workspaceCommand.command("mkdir").description("Recursively create a scope-relati
27555
27768
  const config = loadConfig();
27556
27769
  const registry = buildRegistry2();
27557
27770
  try {
27558
- const { projectTarget, path: path59 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
27771
+ const { projectTarget, path: path60 } = resolveTargetAndPath(arg1, arg2, !!opts.hub);
27559
27772
  const driver = resolveDriver(registry, config, projectTarget, !!opts.hub);
27560
- await driver.mkdir(path59);
27773
+ await driver.mkdir(path60);
27561
27774
  } catch (err) {
27562
27775
  console.error(chalk21.red(err.message));
27563
27776
  process.exit(1);
@@ -27597,8 +27810,8 @@ init_dist4();
27597
27810
  init_dist();
27598
27811
  import { Command as Command21 } from "commander";
27599
27812
  import chalk23 from "chalk";
27600
- import fs44 from "fs";
27601
- import path49 from "path";
27813
+ import fs45 from "fs";
27814
+ import path50 from "path";
27602
27815
  import { fileURLToPath as fileURLToPath4 } from "url";
27603
27816
  function parseScope(v2) {
27604
27817
  if (v2 !== "user" && v2 !== "project") {
@@ -27607,10 +27820,10 @@ function parseScope(v2) {
27607
27820
  return v2;
27608
27821
  }
27609
27822
  function findPackageRoot3() {
27610
- let dir = path49.dirname(fileURLToPath4(import.meta.url));
27823
+ let dir = path50.dirname(fileURLToPath4(import.meta.url));
27611
27824
  while (dir !== "/" && dir !== "") {
27612
- if (fs44.existsSync(path49.join(dir, "package.json"))) return dir;
27613
- dir = path49.dirname(dir);
27825
+ if (fs45.existsSync(path50.join(dir, "package.json"))) return dir;
27826
+ dir = path50.dirname(dir);
27614
27827
  }
27615
27828
  return process.cwd();
27616
27829
  }
@@ -27825,12 +28038,12 @@ init_dist();
27825
28038
  init_dist();
27826
28039
  init_dist2();
27827
28040
  import { Command as Command23 } from "commander";
27828
- import fs45 from "fs";
28041
+ import fs46 from "fs";
27829
28042
  import { fileURLToPath as fileURLToPath5 } from "url";
27830
28043
  import { dirname as dirname8, join as join31 } from "path";
27831
28044
  import chalk24 from "chalk";
27832
28045
  function runConfigCheck(opts) {
27833
- const raw = JSON.parse(fs45.readFileSync(opts.configPath, "utf-8"));
28046
+ const raw = JSON.parse(fs46.readFileSync(opts.configPath, "utf-8"));
27834
28047
  const def = getDefaultConfig();
27835
28048
  const items = detectDrift(raw, def);
27836
28049
  let working = raw;
@@ -27918,7 +28131,7 @@ function printItems(items) {
27918
28131
  var configCommand = new Command23("config").description("Inspect and reconcile squadrant config");
27919
28132
  configCommand.command("check").description("Detect config drift vs the current default schema").option("--fix", "Apply the safe tier (add missing, remove deprecated)", false).option("--accept", "Stamp the current version without changing config (dismiss advisories)", false).option("--json", "Output drift items as JSON", false).action((opts) => {
27920
28133
  const pkgVersion = readPkgVersion2();
27921
- if (!fs45.existsSync(DEFAULT_CONFIG_PATH)) {
28134
+ if (!fs46.existsSync(DEFAULT_CONFIG_PATH)) {
27922
28135
  console.log(chalk24.yellow("No config found \u2014 run `squadrant init` first."));
27923
28136
  return;
27924
28137
  }
@@ -27964,7 +28177,7 @@ configCommand.command("set").description("Write a config value by dotted key (e.
27964
28177
  });
27965
28178
  function readPkgVersion2() {
27966
28179
  const pkgPath = join31(dirname8(fileURLToPath5(import.meta.url)), "..", "package.json");
27967
- return JSON.parse(fs45.readFileSync(pkgPath, "utf-8")).version;
28180
+ return JSON.parse(fs46.readFileSync(pkgPath, "utf-8")).version;
27968
28181
  }
27969
28182
 
27970
28183
  // packages/cli/src/commands/heal.ts
@@ -28409,8 +28622,8 @@ var cmuxCommand = new Command28("cmux").description("cmux integration helpers").
28409
28622
  // packages/cli/src/commands/effort.ts
28410
28623
  init_dist();
28411
28624
  init_dist2();
28412
- import fs46 from "fs";
28413
- import path50 from "path";
28625
+ import fs47 from "fs";
28626
+ import path51 from "path";
28414
28627
  import { Command as Command29 } from "commander";
28415
28628
  import chalk30 from "chalk";
28416
28629
  var VALID_EFFORTS = ["max", "balance", "low"];
@@ -28453,9 +28666,9 @@ function effortScopeLabel(projectName) {
28453
28666
  }
28454
28667
  function canonical(p) {
28455
28668
  try {
28456
- return fs46.realpathSync(p);
28669
+ return fs47.realpathSync(p);
28457
28670
  } catch {
28458
- return path50.resolve(p);
28671
+ return path51.resolve(p);
28459
28672
  }
28460
28673
  }
28461
28674
  async function notifyCaptainsOfEffort(effort, config, driver, cwd = process.cwd(), append, scopeProject, projectConfigRoot) {
@@ -28505,7 +28718,7 @@ var effortCommand = new Command29("effort").description("Get or set the crew tok
28505
28718
  const config = loadConfig();
28506
28719
  const registry = new RuntimeRegistry2({ cmux: createCmuxDriver2() });
28507
28720
  const driver = registry.global(config);
28508
- const stateRoot = path50.join(path50.dirname(DEFAULT_CONFIG_PATH), "state");
28721
+ const stateRoot = path51.join(path51.dirname(DEFAULT_CONFIG_PATH), "state");
28509
28722
  const append = (project, text) => appendCaptainMessage({ stateRoot, project, text, source: "daemon" });
28510
28723
  await notifyCaptainsOfEffort(effort, config, driver, process.cwd(), append, options.project);
28511
28724
  } catch {
@@ -28515,13 +28728,13 @@ var effortCommand = new Command29("effort").description("Get or set the crew tok
28515
28728
 
28516
28729
  // packages/cli/src/commands/tokens.ts
28517
28730
  init_dist();
28518
- import fs47 from "fs";
28519
- import path51 from "path";
28731
+ import fs48 from "fs";
28732
+ import path52 from "path";
28520
28733
  import os23 from "os";
28521
28734
  import readline3 from "readline";
28522
28735
  import { Command as Command30 } from "commander";
28523
28736
  import chalk31 from "chalk";
28524
- var CLAUDE_PROJECTS_DIR = path51.join(os23.homedir(), ".claude", "projects");
28737
+ var CLAUDE_PROJECTS_DIR = path52.join(os23.homedir(), ".claude", "projects");
28525
28738
  function parseTranscriptLine(rawLine) {
28526
28739
  const line2 = rawLine.trim();
28527
28740
  if (!line2) return { timestamp: null, usage: null };
@@ -28571,7 +28784,7 @@ function foldTranscriptLine(agg, rawLine, state) {
28571
28784
  async function aggregateTranscriptFile(filePath) {
28572
28785
  const agg = emptySessionAggregate();
28573
28786
  const state = { lastCacheRead: null };
28574
- const rl = readline3.createInterface({ input: fs47.createReadStream(filePath), crlfDelay: Infinity });
28787
+ const rl = readline3.createInterface({ input: fs48.createReadStream(filePath), crlfDelay: Infinity });
28575
28788
  for await (const line2 of rl) {
28576
28789
  foldTranscriptLine(agg, line2, state);
28577
28790
  }
@@ -28639,22 +28852,22 @@ function buildRoleReport(role, sessions) {
28639
28852
  }
28640
28853
  async function readdirSafe(dir) {
28641
28854
  try {
28642
- return await fs47.promises.readdir(dir);
28855
+ return await fs48.promises.readdir(dir);
28643
28856
  } catch {
28644
28857
  return [];
28645
28858
  }
28646
28859
  }
28647
28860
  async function listJsonlFiles(dir) {
28648
28861
  const entries = await readdirSafe(dir);
28649
- return entries.filter((e) => e.endsWith(".jsonl")).map((e) => path51.join(dir, e));
28862
+ return entries.filter((e) => e.endsWith(".jsonl")).map((e) => path52.join(dir, e));
28650
28863
  }
28651
28864
  async function findTranscriptDirs(claudeProjectsDir, captainSlug) {
28652
28865
  const entries = await readdirSafe(claudeProjectsDir);
28653
28866
  const captainDirs = [];
28654
28867
  const crewDirs = [];
28655
28868
  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));
28869
+ if (entry === captainSlug) captainDirs.push(path52.join(claudeProjectsDir, entry));
28870
+ else if (isCrewDirName(entry, captainSlug)) crewDirs.push(path52.join(claudeProjectsDir, entry));
28658
28871
  }
28659
28872
  return { captainDirs, crewDirs };
28660
28873
  }
@@ -29141,8 +29354,8 @@ import { join as join36 } from "path";
29141
29354
  import { homedir as homedir20 } from "os";
29142
29355
 
29143
29356
  // packages/cli/src/lib/captain-session-registry.ts
29144
- import fs48 from "fs";
29145
- import path52 from "path";
29357
+ import fs49 from "fs";
29358
+ import path53 from "path";
29146
29359
 
29147
29360
  // packages/cli/src/lib/handoff-facts.ts
29148
29361
  var STALE_FETCH_WARNING_MS = 24 * 60 * 60 * 1e3;
@@ -29193,15 +29406,15 @@ function assembleHandoffFacts(live, claudeMem, gapSessions, checkpoint, now, ext
29193
29406
  // packages/cli/src/lib/captain-session-registry.ts
29194
29407
  var CAPTAIN_SESSION_REGISTRY_FILE = "captain-sessions.jsonl";
29195
29408
  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");
29409
+ fs49.mkdirSync(spokeVault, { recursive: true });
29410
+ const file = path53.join(spokeVault, CAPTAIN_SESSION_REGISTRY_FILE);
29411
+ fs49.appendFileSync(file, JSON.stringify(record) + "\n");
29199
29412
  }
29200
29413
  function readCaptainSessionRegistry(spokeVault) {
29201
- const file = path52.join(spokeVault, CAPTAIN_SESSION_REGISTRY_FILE);
29202
- if (!fs48.existsSync(file)) return [];
29414
+ const file = path53.join(spokeVault, CAPTAIN_SESSION_REGISTRY_FILE);
29415
+ if (!fs49.existsSync(file)) return [];
29203
29416
  const records = [];
29204
- for (const line2 of fs48.readFileSync(file, "utf-8").split("\n")) {
29417
+ for (const line2 of fs49.readFileSync(file, "utf-8").split("\n")) {
29205
29418
  if (!line2.trim()) continue;
29206
29419
  try {
29207
29420
  records.push(JSON.parse(line2));
@@ -29651,13 +29864,13 @@ function hooksCommand() {
29651
29864
  // packages/cli/src/commands/work.ts
29652
29865
  init_dist();
29653
29866
  init_dist2();
29654
- import path53 from "path";
29867
+ import path54 from "path";
29655
29868
  import { Command as Command34 } from "commander";
29656
29869
  import chalk34 from "chalk";
29657
29870
  function detectCurrentProject(config, cwd = process.cwd()) {
29658
29871
  for (const [name, proj] of Object.entries(config.projects)) {
29659
29872
  const projPath = resolveHome(proj.path);
29660
- if (cwd === projPath || cwd.startsWith(projPath + path53.sep)) return name;
29873
+ if (cwd === projPath || cwd.startsWith(projPath + path54.sep)) return name;
29661
29874
  }
29662
29875
  return void 0;
29663
29876
  }
@@ -29789,14 +30002,14 @@ var workCommand = new Command34("work").description("Track your own in-flight wo
29789
30002
  // packages/cli/src/commands/handoff.ts
29790
30003
  init_dist();
29791
30004
  import { Command as Command35 } from "commander";
29792
- import path56 from "path";
30005
+ import path57 from "path";
29793
30006
  import os24 from "os";
29794
30007
 
29795
30008
  // packages/cli/src/lib/handoff-live-repo.ts
29796
30009
  init_dist();
29797
30010
  import { execFileSync as execFileSync15 } from "child_process";
29798
- import fs49 from "fs";
29799
- import path54 from "path";
30011
+ import fs50 from "fs";
30012
+ import path55 from "path";
29800
30013
 
29801
30014
  // packages/cli/src/lib/handoff-branch-state.ts
29802
30015
  function tryRun(runner, cmd, args, cwd) {
@@ -29935,7 +30148,7 @@ function localAheadOfBase(runner, projectPath, base) {
29935
30148
  }
29936
30149
  function readFetchAgeMs(projectPath, now) {
29937
30150
  try {
29938
- const stat2 = fs49.statSync(path54.join(projectPath, ".git", "FETCH_HEAD"));
30151
+ const stat2 = fs50.statSync(path55.join(projectPath, ".git", "FETCH_HEAD"));
29939
30152
  return Math.max(0, now - stat2.mtime.getTime());
29940
30153
  } catch {
29941
30154
  return null;
@@ -30006,7 +30219,7 @@ function gatherLiveRepoState(projectPath, fallbackBaseBranch, tasks, runner = de
30006
30219
 
30007
30220
  // packages/cli/src/lib/handoff-claude-mem.ts
30008
30221
  import { createRequire } from "module";
30009
- import fs50 from "fs";
30222
+ import fs51 from "fs";
30010
30223
  var { DatabaseSync } = createRequire(import.meta.url)("node:sqlite");
30011
30224
  var CLAUDE_MEM_RECENCY_LIMIT = 20;
30012
30225
  function decisionText(row) {
@@ -30020,7 +30233,7 @@ function decisionText(row) {
30020
30233
  return row.narrative ?? "";
30021
30234
  }
30022
30235
  function queryClaudeMem(dbPath, project) {
30023
- if (!fs50.existsSync(dbPath)) return null;
30236
+ if (!fs51.existsSync(dbPath)) return null;
30024
30237
  let db;
30025
30238
  try {
30026
30239
  db = new DatabaseSync(dbPath, { readOnly: true });
@@ -30063,7 +30276,7 @@ function queryClaudeMem(dbPath, project) {
30063
30276
  }
30064
30277
 
30065
30278
  // packages/cli/src/lib/handoff-transcript.ts
30066
- import fs51 from "fs";
30279
+ import fs52 from "fs";
30067
30280
  var TRANSCRIPT_BYTE_CAP = 2e5;
30068
30281
  var DIGEST_BYTE_CAP = 8 * 1024;
30069
30282
  var PROMPT_TRUNCATE_CHARS = 300;
@@ -30172,27 +30385,27 @@ function extractMessages(tailText) {
30172
30385
  return { lastUserMessage, lastAssistantText, digest };
30173
30386
  }
30174
30387
  function extractTranscriptTail(transcriptPath, byteCap = TRANSCRIPT_BYTE_CAP) {
30175
- if (!fs51.existsSync(transcriptPath)) return null;
30176
- const content = fs51.readFileSync(transcriptPath, "utf-8");
30388
+ if (!fs52.existsSync(transcriptPath)) return null;
30389
+ const content = fs52.readFileSync(transcriptPath, "utf-8");
30177
30390
  const { lastUserMessage, lastAssistantText, digest } = extractMessages(tailOf(content, byteCap));
30178
- const mtimeIso = fs51.statSync(transcriptPath).mtime.toISOString();
30391
+ const mtimeIso = fs52.statSync(transcriptPath).mtime.toISOString();
30179
30392
  return { path: transcriptPath, mtimeIso, lastUserMessage, lastAssistantText, digest };
30180
30393
  }
30181
30394
 
30182
30395
  // packages/cli/src/lib/handoff-archive.ts
30183
- import fs52 from "fs";
30184
- import path55 from "path";
30396
+ import fs53 from "fs";
30397
+ import path56 from "path";
30185
30398
  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 };
30399
+ const dir = path56.join(spokeVault, "handoffs");
30400
+ if (!fs53.existsSync(dir)) return null;
30401
+ const candidates = fs53.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".json")).map((e) => {
30402
+ const full = path56.join(dir, e.name);
30403
+ return { name: e.name, full, mtime: fs53.statSync(full).mtime };
30191
30404
  }).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
30192
30405
  for (const candidate of candidates) {
30193
30406
  let content;
30194
30407
  try {
30195
- content = JSON.parse(fs52.readFileSync(candidate.full, "utf-8"));
30408
+ content = JSON.parse(fs53.readFileSync(candidate.full, "utf-8"));
30196
30409
  } catch {
30197
30410
  continue;
30198
30411
  }
@@ -30202,7 +30415,7 @@ function readNewestArchivedHandoff(spokeVault, now) {
30202
30415
  }
30203
30416
 
30204
30417
  // packages/cli/src/commands/handoff.ts
30205
- var CLAUDE_MEM_DB_PATH = path56.join(os24.homedir(), ".claude-mem", "claude-mem.db");
30418
+ var CLAUDE_MEM_DB_PATH = path57.join(os24.homedir(), ".claude-mem", "claude-mem.db");
30206
30419
  async function defaultFetchTasks(project) {
30207
30420
  return await squadrantdCall({ kind: "list", project });
30208
30421
  }
@@ -30270,9 +30483,9 @@ function filterSessions(rows, f) {
30270
30483
  if (f.agent && r.agent !== f.agent) return false;
30271
30484
  if (f.liveOnly && r.status === "stale") return false;
30272
30485
  if (f.project) {
30273
- const path59 = f.projects?.[f.project];
30486
+ const path60 = f.projects?.[f.project];
30274
30487
  const byRecord = r.project === f.project;
30275
- const byCwd = !!path59 && !!r.cwd && (r.cwd === normalized(path59) || r.cwd.startsWith(normalized(path59) + "/"));
30488
+ const byCwd = !!path60 && !!r.cwd && (r.cwd === normalized(path60) || r.cwd.startsWith(normalized(path60) + "/"));
30276
30489
  if (!byRecord && !byCwd) return false;
30277
30490
  }
30278
30491
  return true;
@@ -30363,7 +30576,7 @@ init_dist();
30363
30576
  init_dist2();
30364
30577
  init_dist3();
30365
30578
  import os25 from "os";
30366
- import path57 from "path";
30579
+ import path58 from "path";
30367
30580
  import { Command as Command37 } from "commander";
30368
30581
  import chalk36 from "chalk";
30369
30582
 
@@ -30481,7 +30694,7 @@ function buildWhoamiDeps(opts = {}) {
30481
30694
  const projects = Object.fromEntries(
30482
30695
  Object.entries(config.projects).map(([n, p]) => [n, resolveHome(p.path)])
30483
30696
  );
30484
- const root = stateRoot ?? path57.join(os25.homedir(), ".config", "squadrant", "state");
30697
+ const root = stateRoot ?? path58.join(os25.homedir(), ".config", "squadrant", "state");
30485
30698
  const store = createStore(root);
30486
30699
  const merged = {
30487
30700
  env: process.env,
@@ -30533,11 +30746,11 @@ var whoamiCommand = new Command37("whoami").description("Show the agent session
30533
30746
  // packages/cli/src/commands/knowledge.ts
30534
30747
  init_dist();
30535
30748
  init_dist2();
30536
- import fs53 from "fs";
30749
+ import fs54 from "fs";
30537
30750
  import os26 from "os";
30538
- import path58 from "path";
30751
+ import path59 from "path";
30539
30752
  import readline4 from "readline/promises";
30540
- import { spawnSync } from "child_process";
30753
+ import { spawnSync as spawnSync2 } from "child_process";
30541
30754
  import { Command as Command39 } from "commander";
30542
30755
  import chalk38 from "chalk";
30543
30756
 
@@ -30602,7 +30815,8 @@ function runRulesSearch(query, opts, configPath = DEFAULT_CONFIG_PATH) {
30602
30815
  const rules = loadFor(opts, configPath);
30603
30816
  const hits = searchRules(rules, query, { statuses: opts.all ? [...RULE_STATUSES] : DEFAULT_SEARCH_STATUSES });
30604
30817
  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);
30818
+ const shown = hits.slice(0, parseLimit(opts.limit));
30819
+ appendAudit(shown.length ? shown.map((h) => ({ ...ruleAuditScope(h.rule), project, domain: "rules", itemId: h.rule.id, event: "item.searched", query, score: h.score })) : [{ kb: "", level: "project", project, domain: "rules", itemId: "", event: "item.searched", query }], opts.audit);
30606
30820
  return hits;
30607
30821
  }
30608
30822
  function runRulesList(opts, configPath = DEFAULT_CONFIG_PATH) {
@@ -30685,18 +30899,18 @@ function runKnowledgeInit(kb, configPath = DEFAULT_CONFIG_PATH) {
30685
30899
  const cfg = loadConfig(configPath);
30686
30900
  const dir = kbDir(cfg, kb);
30687
30901
  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));
30902
+ for (const d of [path59.join(dir, "raw"), kbRulesDir(cfg, kb), path59.join(kbRulesDir(cfg, kb), PROPOSED_DIR)]) {
30903
+ if (!fs54.existsSync(d)) {
30904
+ fs54.mkdirSync(d, { recursive: true });
30905
+ created.push(path59.relative(dir, d));
30692
30906
  }
30693
30907
  }
30694
- const sourcesFile = path58.join(dir, "sources.yaml");
30695
- if (!fs53.existsSync(sourcesFile)) {
30696
- fs53.writeFileSync(sourcesFile, SOURCES_TEMPLATE);
30908
+ const sourcesFile = path59.join(dir, "sources.yaml");
30909
+ if (!fs54.existsSync(sourcesFile)) {
30910
+ fs54.writeFileSync(sourcesFile, SOURCES_TEMPLATE);
30697
30911
  created.push("sources.yaml");
30698
30912
  }
30699
- if (!fs53.existsSync(path58.join(dir, "index.json"))) {
30913
+ if (!fs54.existsSync(path59.join(dir, "index.json"))) {
30700
30914
  writeIndex(dir, compileIndex(kb, []));
30701
30915
  created.push("index.json");
30702
30916
  }
@@ -30713,7 +30927,7 @@ function runKnowledgeSubscribe(kb, project, configPath = DEFAULT_CONFIG_PATH) {
30713
30927
  const cfg = loadConfig(configPath);
30714
30928
  const pc = cfg.projects[project];
30715
30929
  if (!pc) throw new Error(`Unknown project '${project}'`);
30716
- if (!fs53.existsSync(kbDir(cfg, kb))) {
30930
+ if (!fs54.existsSync(kbDir(cfg, kb))) {
30717
30931
  throw new Error(`Knowledge base '${kb}' does not exist. Run: squadrant knowledge init ${kb}`);
30718
30932
  }
30719
30933
  const list = pc.knowledge ?? [];
@@ -30728,6 +30942,12 @@ function runKnowledgeSources(kb, configPath = DEFAULT_CONFIG_PATH) {
30728
30942
  const cfg = loadConfig(configPath);
30729
30943
  return loadSources(kbDir(cfg, kb));
30730
30944
  }
30945
+ function runKnowledgeSourcesRemove(kb, entryPath, opts = {}, configPath = DEFAULT_CONFIG_PATH) {
30946
+ assertKbName(kb);
30947
+ const cfg = loadConfig(configPath);
30948
+ if (!fs54.existsSync(kbDir(cfg, kb))) throw new Error(`Knowledge base '${kb}' does not exist. Run: squadrant knowledge init ${kb}`);
30949
+ return removeSource(kbDir(cfg, kb), kbRulesDir(cfg, kb), entryPath, { ...opts, kbName: kb });
30950
+ }
30731
30951
  var knowledgeCommand = new Command39("knowledge").description("Manage rules knowledge bases (#893)");
30732
30952
  knowledgeCommand.command("init <kb>").description("Create a knowledge base (its own git repo) under ~/squadrant/kb/<kb>/").action((kb) => {
30733
30953
  const { dir, created } = runKnowledgeInit(kb);
@@ -30739,11 +30959,20 @@ knowledgeCommand.command("subscribe <kb>").description("Subscribe a project to a
30739
30959
  const list = runKnowledgeSubscribe(kb, opts.project);
30740
30960
  console.log(`${opts.project} knowledge: ${list.join(", ")}`);
30741
30961
  });
30742
- knowledgeCommand.command("sources <kb>").description("List the trusted sources of a knowledge base").action((kb) => {
30962
+ knowledgeCommand.command("sources <kb>").description("List the trusted sources of a knowledge base; --remove drops one and strips its refs from rules").option("--remove <path>", "remove the sources.yaml entry with this exact path; rules left with no source become stale").option("--dry-run", "with --remove: print what would change, write nothing").action((kb, o) => {
30963
+ if (o.dryRun && !o.remove) throw new Error("--dry-run only applies to --remove");
30964
+ if (o.remove) {
30965
+ const r = runKnowledgeSourcesRemove(kb, o.remove, { dryRun: o.dryRun });
30966
+ const stripped = r.stripped.length - r.stale.length;
30967
+ console.log(`${r.dryRun ? "would remove" : "removed"} ${o.remove}: ${r.stripped.length} rules lose a ref, ${r.stale.length} left with no source (stale), ${stripped} keep another source`);
30968
+ for (const id of r.stale) console.log(chalk38.yellow(` stale: ${id}`));
30969
+ if (r.dryRun) console.log(chalk38.dim("dry run: nothing written"));
30970
+ return;
30971
+ }
30743
30972
  const { sources, errors } = runKnowledgeSources(kb);
30744
30973
  if (!sources.length && !errors.length) console.log("(no sources \u2014 edit sources.yaml)");
30745
30974
  for (const s of sources) {
30746
- console.log(` ${s.priority.padEnd(8)} ${s.path}${s.domain ? ` [${s.domain}]` : ""}${s.sensitivity ? chalk38.yellow(" local-only") : ""}`);
30975
+ console.log(` ${s.priority.padEnd(8)} ${s.path}${s.domain ? ` [${s.domain}]` : ""}${s.sensitivity ? chalk38.yellow(" local-only") : ""}${s.review ? chalk38.cyan(" review: required") : ""}`);
30747
30976
  }
30748
30977
  for (const e of errors) console.log(chalk38.red(` \u2718 ${e}`));
30749
30978
  if (errors.length) process.exitCode = 1;
@@ -30751,9 +30980,9 @@ knowledgeCommand.command("sources <kb>").description("List the trusted sources o
30751
30980
  function runKnowledgeReindex(kb, configPath = DEFAULT_CONFIG_PATH) {
30752
30981
  assertKbName(kb);
30753
30982
  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}`);
30983
+ if (!fs54.existsSync(kbDir(cfg, kb))) throw new Error(`Knowledge base '${kb}' does not exist. Run: squadrant knowledge init ${kb}`);
30755
30984
  const { rules: all, errors: allErrors } = loadKbRules(cfg, kb, { includeProposed: true });
30756
- const proposedRoot = path58.join(kbRulesDir(cfg, kb), PROPOSED_DIR) + path58.sep;
30985
+ const proposedRoot = path59.join(kbRulesDir(cfg, kb), PROPOSED_DIR) + path59.sep;
30757
30986
  const live = all.filter((r) => !r.file.startsWith(proposedRoot));
30758
30987
  const pendingDups = allErrors.filter((e) => isPendingProposalDup(e, kbRulesDir(cfg, kb)));
30759
30988
  const errors = allErrors.filter((e) => !pendingDups.includes(e));
@@ -30763,11 +30992,15 @@ function runKnowledgeReindex(kb, configPath = DEFAULT_CONFIG_PATH) {
30763
30992
  function runKnowledgeValidate(kb, configPath = DEFAULT_CONFIG_PATH) {
30764
30993
  assertKbName(kb);
30765
30994
  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}`);
30995
+ if (!fs54.existsSync(kbDir(cfg, kb))) throw new Error(`Knowledge base '${kb}' does not exist. Run: squadrant knowledge init ${kb}`);
30767
30996
  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 };
30997
+ const { errata, errors: errataErrors } = loadErrata(kbDir(cfg, kb));
30998
+ const res = validateKb({ rulesDir: kbRulesDir(cfg, kb), kbRoot: kbDir(cfg, kb), sources, errata, domains: kbConfigs(cfg)[kb]?.domains });
30999
+ const srcChecks = [
31000
+ ...errors.map((e) => ({ level: "fail", message: `sources.yaml: ${e}` })),
31001
+ ...errataErrors.map((e) => ({ level: "fail", message: `errata.yaml: ${e}` }))
31002
+ ];
31003
+ return { checks: [...srcChecks, ...res.checks], ok: res.ok && !srcChecks.length };
30771
31004
  }
30772
31005
  knowledgeCommand.command("reindex <kb>").description("Recompile index.json from the KB's rule files").action((kb) => {
30773
31006
  const r = runKnowledgeReindex(kb);
@@ -30793,7 +31026,7 @@ knowledgeCommand.command("migrate").description("Move <hubVault>/knowledge/<kb>/
30793
31026
  function requireKb(kb, configPath) {
30794
31027
  assertKbName(kb);
30795
31028
  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}`);
31029
+ if (!fs54.existsSync(kbDir(cfg, kb))) throw new Error(`Knowledge base '${kb}' does not exist. Run: squadrant knowledge init ${kb}`);
30797
31030
  return cfg;
30798
31031
  }
30799
31032
  async function runKnowledgeIngest(kb, opts = {}, configPath = DEFAULT_CONFIG_PATH) {
@@ -30812,10 +31045,14 @@ async function runKnowledgeIngest(kb, opts = {}, configPath = DEFAULT_CONFIG_PAT
30812
31045
  }
30813
31046
  function runKnowledgeApply(kb, candidatesFile, configPath = DEFAULT_CONFIG_PATH) {
30814
31047
  const cfg = requireKb(kb, configPath);
30815
- const input = JSON.parse(fs53.readFileSync(candidatesFile, "utf8"));
31048
+ const input = JSON.parse(fs54.readFileSync(candidatesFile, "utf8"));
30816
31049
  if (!input || !Array.isArray(input.candidates)) throw new Error("candidates file must be { complete?: string[], candidates: [...] }");
30817
31050
  const projects = Object.entries(cfg.projects).filter(([name, pc]) => pc.path && subscribedKbs(cfg, name).includes(kb)).map(([name, pc]) => ({ name, root: resolveHome(pc.path) }));
30818
- return applyCandidates(input, { kb, kbRoot: kbDir(cfg, kb), rulesDir: kbRulesDir(cfg, kb), projects });
31051
+ const { errata, errors } = loadErrata(kbDir(cfg, kb));
31052
+ if (errors.length) throw new Error(`errata.yaml: ${errors.join("; ")}`);
31053
+ const { sources, errors: sourceErrors } = loadSources(kbDir(cfg, kb));
31054
+ if (sourceErrors.length) throw new Error(`sources.yaml: ${sourceErrors.join("; ")}`);
31055
+ return applyCandidates(input, { kb, kbRoot: kbDir(cfg, kb), rulesDir: kbRulesDir(cfg, kb), projects, sources, errata });
30819
31056
  }
30820
31057
  knowledgeCommand.command("ingest <kb>").description("Detect changed sources, convert them (markitdown) into .converted/, and print the section plan for the knowledge-extract skill").option("--dry-run", "estimate tokens only; write nothing").option("--json", "print the full plan as JSON").option("--local-model", "a local model is available, so sensitivity: local-only sources are planned instead of skipped").option("--max-sections <n>", "override maxSectionsPerPass", (v2) => parseInt(v2, 10)).action(async (kb, opts) => {
30821
31058
  const plan = await runKnowledgeIngest(kb, opts);
@@ -30834,16 +31071,19 @@ knowledgeCommand.command("apply <kb> <candidates>").description("Verify extracte
30834
31071
  console.log(`created ${r.created.length}, merged ${r.merged.length}, proposed ${r.proposed.length}, superseded ${r.superseded.length}, stale ${r.stale.length}, rejected ${r.rejected.length}`);
30835
31072
  for (const x of r.rejected) console.log(chalk38.red(` \u2718 ${x.id} (${x.source}): ${x.reason}`));
30836
31073
  for (const x of r.idOverrides) console.log(chalk38.dim(` id ${x.from} \u2192 ${x.to} (matched by quote offset)`));
31074
+ for (const e of r.orphanedErrata) console.log(chalk38.yellow(` errata ${e.id} (${e.ref}): quote no longer found; the raw file changed, re-check it`));
30837
31075
  for (const d of r.doclingFlags) console.log(chalk38.yellow(` ${d.ref}: ${d.dropped}/${d.total} candidates dropped; conversion likely poor, try docling`));
30838
31076
  });
30839
- var defaultStateRoot2 = () => path58.join(path58.dirname(DEFAULT_CONFIG_PATH), "state");
31077
+ var defaultStateRoot2 = () => path59.join(path59.dirname(DEFAULT_CONFIG_PATH), "state");
30840
31078
  function reviewContext(kb, configPath) {
30841
31079
  const cfg = requireKb(kb, configPath);
30842
31080
  const projects = Object.entries(cfg.projects).filter(([name, pc]) => pc.path && subscribedKbs(cfg, name).includes(kb)).map(([name, pc]) => ({ name, root: resolveHome(pc.path) }));
30843
- return { cfg, kb, sources: loadSources(kbDir(cfg, kb)).sources, projects };
31081
+ const { sources, errors } = loadSources(kbDir(cfg, kb));
31082
+ if (errors.length) throw new Error(`sources.yaml: ${errors.join("; ")}`);
31083
+ return { cfg, kb, sources, projects };
30844
31084
  }
30845
31085
  function runKnowledgeReviewApply(kb, file, configPath = DEFAULT_CONFIG_PATH) {
30846
- return applyDecisions(JSON.parse(fs53.readFileSync(file, "utf8")), reviewContext(kb, configPath));
31086
+ return applyDecisions(JSON.parse(fs54.readFileSync(file, "utf8")), reviewContext(kb, configPath));
30847
31087
  }
30848
31088
  function runKnowledgeResolve(kb, key, r, by, reason, configPath = DEFAULT_CONFIG_PATH) {
30849
31089
  return resolveEscalation(reviewContext(kb, configPath), key, r, by, reason);
@@ -30891,8 +31131,8 @@ ${e.explanation}`);
30891
31131
  else if (ans === "r") r = { kind: "reject" };
30892
31132
  else if (ans === "e" && e.kind === "proposal") {
30893
31133
  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" });
31134
+ const file = path59.join(kbRulesDir(ctx.cfg, kb), PROPOSED_DIR, `${e.item}.md`);
31135
+ spawnSync2(process.env.EDITOR ?? "vi", [file], { stdio: "inherit" });
30896
31136
  r = { kind: "approve" };
30897
31137
  }
30898
31138
  if (r) console.log(chalk38.green(runKnowledgeResolve(kb, e.key, r, by, reason)));
@@ -30963,7 +31203,7 @@ function renderStats(all) {
30963
31203
  const pct = (x) => x === null ? "-" : `${Math.round(x * 100)}%`;
30964
31204
  return all.flatMap((s) => [
30965
31205
  `${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" : ""}`),
31206
+ ...s.items.map((x) => ` ${x.itemId} surfaced ${x.surfaced} pulled ${x.pullRate} cited ${x.cited} use ${pct(x.useRate)} noise ${pct(x.noiseRate)} violated ${pct(x.violationRate)}${x.neverFired30d ? " NEVER FIRED" : ""}`),
30967
31207
  ...s.domains.map((d) => ` domain ${d.domain}: writes ${d.writesPerMonth}/mo reads ${d.readsPerMonth}/mo${d.deadDomain ? " DEAD DOMAIN" : ""}`),
30968
31208
  ` agents: ${Object.entries(s.perAgentCoverage).map(([k, v2]) => `${k} ${v2}`).join(", ") || "none"}`
30969
31209
  ]);
@@ -30974,10 +31214,10 @@ knowledgeCommand.command("stats").description("Scores computed from the audit lo
30974
31214
  else for (const l of renderStats(all)) console.log(l);
30975
31215
  });
30976
31216
  function runKnowledgeCiteScan(o) {
30977
- const run = o.git_run ?? ((args) => spawnSync("git", args, { encoding: "utf8" }).stdout ?? "");
31217
+ const run = o.git_run ?? ((args) => spawnSync2("git", args, { encoding: "utf8" }).stdout ?? "");
30978
31218
  if (o.git) {
30979
31219
  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 }));
31220
+ return scanCitations(readGitMessages(o.git, since, run).map((c) => ({ text: c.text, ref: `git:${c.sha}`, at: c.at })), { project: o.project, sink: o.audit });
30981
31221
  }
30982
31222
  if (!o.text) throw new Error("Pass --text <message> or --git <repo>");
30983
31223
  return scanAndLogCitations({ text: o.text, ref: o.ref ?? "manual", project: o.project, sink: o.audit });