squadrant 0.26.3 → 0.27.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.
@@ -273,6 +273,7 @@ var init_effort = __esm({
273
273
  });
274
274
 
275
275
  // packages/shared/dist/knowledge.js
276
+ import path4 from "path";
276
277
  function validateRuleFrontmatter(fm) {
277
278
  if (!isObj(fm))
278
279
  return ["frontmatter must be an object"];
@@ -340,19 +341,58 @@ function validateSourceEntry(e) {
340
341
  p.push("sensitivity must be 'local-only' when set");
341
342
  return p;
342
343
  }
344
+ function kbConfigs(cfg) {
345
+ const out = { ...cfg.knowledge };
346
+ for (const [kb, c] of Object.entries(cfg.knowledgeBases ?? {}))
347
+ out[kb] = { ...out[kb], ...c };
348
+ return out;
349
+ }
350
+ function kbPath(cfg, kb) {
351
+ return resolveHome(kbConfigs(cfg)[kb]?.path ?? `${DEFAULT_KB_ROOT}/${kb}`);
352
+ }
353
+ function homeKb(cfg, project) {
354
+ const pc = cfg.projects[project];
355
+ if (pc?.knowledgeHome?.startsWith("kb:"))
356
+ return pc.knowledgeHome.slice(3);
357
+ if (pc?.knowledgeHome)
358
+ return void 0;
359
+ return pc?.group ? cfg.groups?.[pc.group]?.kb : void 0;
360
+ }
343
361
  function subscribedKbs(cfg, project) {
344
- return cfg.projects[project]?.knowledge ?? [];
362
+ const pc = cfg.projects[project];
363
+ const group = pc?.group ? cfg.groups?.[pc.group]?.kb : void 0;
364
+ return [...new Set([group, homeKb(cfg, project), ...pc?.knowledge ?? []].filter((k) => !!k))];
345
365
  }
346
- var RULE_MODALITIES, RULE_STATUSES, SOURCE_PRIORITIES, RULE_APPROVERS, RULE_ID_RE, isObj, isStrArr;
366
+ function projectRulesHome(cfg, project) {
367
+ const pc = cfg.projects[project];
368
+ if (pc?.knowledgeHome === "repo:docs")
369
+ return path4.join(resolveHome(pc.path), "docs", "rules");
370
+ const kb = homeKb(cfg, project);
371
+ return kb ? path4.join(kbPath(cfg, kb), "projects", project, "rules") : void 0;
372
+ }
373
+ function resolveKbConfig(cfg, kb) {
374
+ const explicit = kbConfigs(cfg)[kb] ?? {};
375
+ const firstSubscriber = Object.keys(cfg.projects).find((p) => subscribedKbs(cfg, p).includes(kb));
376
+ return {
377
+ homeProject: explicit.homeProject ?? firstSubscriber,
378
+ domainCap: explicit.domainCap ?? DEFAULT_DOMAIN_CAP
379
+ };
380
+ }
381
+ var RULE_MODALITIES, RULE_STATUSES, SOURCE_PRIORITIES, RULE_APPROVERS, RULE_ID_RE, KB_NAME_RE, DEFAULT_DOMAIN_CAP, DEFAULT_RECONCILE, isObj, isStrArr, DEFAULT_KB_ROOT;
347
382
  var init_knowledge = __esm({
348
383
  "packages/shared/dist/knowledge.js"() {
384
+ init_config();
349
385
  RULE_MODALITIES = ["must", "must-not", "should", "may"];
350
386
  RULE_STATUSES = ["proposed", "active", "stale", "retired"];
351
387
  SOURCE_PRIORITIES = ["company", "project", "agent"];
352
388
  RULE_APPROVERS = ["auto", "reviewer-agent", "human"];
353
389
  RULE_ID_RE = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;
390
+ KB_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
391
+ DEFAULT_DOMAIN_CAP = 150;
392
+ DEFAULT_RECONCILE = { incrementalAfterDays: 7, fullEveryDays: 30, autoApproveConfidence: 0.8 };
354
393
  isObj = (v2) => typeof v2 === "object" && v2 !== null && !Array.isArray(v2);
355
394
  isStrArr = (v2) => Array.isArray(v2) && v2.every((x) => typeof x === "string");
395
+ DEFAULT_KB_ROOT = "~/squadrant/kb";
356
396
  }
357
397
  });
358
398
 
@@ -423,22 +463,22 @@ function defaultCmuxConfigPath() {
423
463
  return join(homedir(), ".config", "cmux", "cmux.json");
424
464
  }
425
465
  function ensureSocketAutomation(opts = {}) {
426
- const path29 = opts.path ?? defaultCmuxConfigPath();
427
- if (!existsSync(path29)) {
428
- mkdirSync(dirname(path29), { recursive: true });
429
- writeFileSync(path29, MINIMAL_TEMPLATE);
430
- return { path: path29, changed: true, alreadySet: false };
466
+ const path42 = opts.path ?? defaultCmuxConfigPath();
467
+ if (!existsSync(path42)) {
468
+ mkdirSync(dirname(path42), { recursive: true });
469
+ writeFileSync(path42, MINIMAL_TEMPLATE);
470
+ return { path: path42, changed: true, alreadySet: false };
431
471
  }
432
- const text = readFileSync(path29, "utf-8");
472
+ const text = readFileSync(path42, "utf-8");
433
473
  const current = parse(text)?.automation?.socketControlMode;
434
474
  if (current === AUTOMATION_MODE) {
435
- return { path: path29, changed: false, alreadySet: true };
475
+ return { path: path42, changed: false, alreadySet: true };
436
476
  }
437
477
  const edits = modify(text, [...SOCKET_CONTROL_MODE_PATH], AUTOMATION_MODE, {
438
478
  formattingOptions: { insertSpaces: true, tabSize: 2 }
439
479
  });
440
- writeFileSync(path29, applyEdits(text, edits));
441
- return { path: path29, changed: true, alreadySet: false };
480
+ writeFileSync(path42, applyEdits(text, edits));
481
+ return { path: path42, changed: true, alreadySet: false };
442
482
  }
443
483
  var SOCKET_CONTROL_MODE_PATH, AUTOMATION_MODE, MINIMAL_TEMPLATE;
444
484
  var init_cmux_config = __esm({
@@ -588,9 +628,9 @@ import { join as join4 } from "path";
588
628
  function defaultStatePath() {
589
629
  return join4(homedir3(), ".config", "squadrant", "state", "cmux-autoconfig.json");
590
630
  }
591
- function readState(path29) {
631
+ function readState(path42) {
592
632
  try {
593
- return JSON.parse(readConfigFileSync(path29));
633
+ return JSON.parse(readConfigFileSync(path42));
594
634
  } catch {
595
635
  return {};
596
636
  }
@@ -663,13 +703,13 @@ var init_config_version = __esm({
663
703
 
664
704
  // packages/shared/dist/lib/update-check.js
665
705
  import fs2 from "fs";
666
- import path4 from "path";
706
+ import path5 from "path";
667
707
  import os3 from "os";
668
708
  import https from "https";
669
709
  var UPDATE_CHECK_STATE_PATH, CHECK_INTERVAL_MS, FAILURE_RETRY_MS;
670
710
  var init_update_check = __esm({
671
711
  "packages/shared/dist/lib/update-check.js"() {
672
- UPDATE_CHECK_STATE_PATH = path4.join(os3.homedir(), ".config", "squadrant", "update-check.json");
712
+ UPDATE_CHECK_STATE_PATH = path5.join(os3.homedir(), ".config", "squadrant", "update-check.json");
673
713
  CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
674
714
  FAILURE_RETRY_MS = 60 * 60 * 1e3;
675
715
  }
@@ -678,9 +718,9 @@ var init_update_check = __esm({
678
718
  // packages/shared/dist/lib/git-worktree.js
679
719
  import { execFileSync as execFileSync2 } from "child_process";
680
720
  import fs3 from "fs";
681
- import path5 from "path";
721
+ import path6 from "path";
682
722
  function worktreePath(repoRoot, worktreeDir, project, name) {
683
- return path5.resolve(repoRoot, worktreeDir, `${project}-${name}`);
723
+ return path6.resolve(repoRoot, worktreeDir, `${project}-${name}`);
684
724
  }
685
725
  function crewBranch(name) {
686
726
  return `crew/${name}`;
@@ -689,9 +729,9 @@ function ensureSpotlightExcluded(repoRoot, worktreeDir) {
689
729
  if (process.platform !== "darwin")
690
730
  return;
691
731
  try {
692
- const dir = path5.resolve(repoRoot, worktreeDir);
732
+ const dir = path6.resolve(repoRoot, worktreeDir);
693
733
  fs3.mkdirSync(dir, { recursive: true });
694
- const marker = path5.join(dir, ".metadata_never_index");
734
+ const marker = path6.join(dir, ".metadata_never_index");
695
735
  if (!fs3.existsSync(marker))
696
736
  fs3.writeFileSync(marker, "");
697
737
  } catch {
@@ -755,15 +795,15 @@ function addWorktree(spec) {
755
795
  return wt;
756
796
  }
757
797
  function installWorktreeDependencies(wt) {
758
- if (!fs3.existsSync(path5.join(wt, "package.json")))
798
+ if (!fs3.existsSync(path6.join(wt, "package.json")))
759
799
  return;
760
- if (fs3.existsSync(path5.join(wt, "pnpm-lock.yaml"))) {
800
+ if (fs3.existsSync(path6.join(wt, "pnpm-lock.yaml"))) {
761
801
  execFileSync2("pnpm", ["-C", wt, "install", "--frozen-lockfile"], { stdio: "pipe" });
762
- } else if (fs3.existsSync(path5.join(wt, "yarn.lock"))) {
802
+ } else if (fs3.existsSync(path6.join(wt, "yarn.lock"))) {
763
803
  execFileSync2("yarn", ["install", "--frozen-lockfile"], { cwd: wt, stdio: "pipe" });
764
- } else if (fs3.existsSync(path5.join(wt, "package-lock.json"))) {
804
+ } else if (fs3.existsSync(path6.join(wt, "package-lock.json"))) {
765
805
  execFileSync2("npm", ["ci"], { cwd: wt, stdio: "pipe" });
766
- } else if (fs3.existsSync(path5.join(wt, "bun.lockb"))) {
806
+ } else if (fs3.existsSync(path6.join(wt, "bun.lockb"))) {
767
807
  execFileSync2("bun", ["install", "--frozen-lockfile"], { cwd: wt, stdio: "pipe" });
768
808
  } else {
769
809
  process.stderr.write(`worktree ${wt}: package.json present but no lockfile \u2014 dependencies not installed; local typechecks/tests may resolve against the main checkout instead of this worktree.
@@ -782,7 +822,7 @@ function worktreeDirtyFiles(wtPath) {
782
822
  }
783
823
  function removeGeneratedWorktreeFiles(wtPath) {
784
824
  for (const [, file] of worktreeStatus(wtPath).filter(isGeneratedUntracked)) {
785
- fs3.rmSync(path5.join(wtPath, file), { force: true });
825
+ fs3.rmSync(path6.join(wtPath, file), { force: true });
786
826
  }
787
827
  }
788
828
  function removeWorktree(repoRoot, wtPath, opts) {
@@ -806,7 +846,7 @@ var init_resolve_text_input = __esm({
806
846
 
807
847
  // packages/shared/dist/lib/runtime-sync.js
808
848
  import fs5 from "fs";
809
- import path6 from "path";
849
+ import path7 from "path";
810
850
  var init_runtime_sync = __esm({
811
851
  "packages/shared/dist/lib/runtime-sync.js"() {
812
852
  }
@@ -849,7 +889,7 @@ var init_tool_compat = __esm({
849
889
 
850
890
  // packages/shared/dist/lib/canonical-source.js
851
891
  import fs6 from "fs";
852
- import path7 from "path";
892
+ import path8 from "path";
853
893
  var init_canonical_source = __esm({
854
894
  "packages/shared/dist/lib/canonical-source.js"() {
855
895
  }
@@ -858,7 +898,7 @@ var init_canonical_source = __esm({
858
898
  // packages/shared/dist/lib/daily-logs.js
859
899
  import { execSync } from "child_process";
860
900
  import fs7 from "fs";
861
- import path8 from "path";
901
+ import path9 from "path";
862
902
  import matter from "gray-matter";
863
903
  var init_daily_logs = __esm({
864
904
  "packages/shared/dist/lib/daily-logs.js"() {
@@ -941,47 +981,47 @@ function stampAttempt(rec, patch, now) {
941
981
  function isStickyAttention(state) {
942
982
  return state === "blocked" || state === "review";
943
983
  }
944
- function nextPendingTool(current, ev, now) {
945
- if (ev.note === "agent.hook.PreToolUse")
946
- return { name: ev.tool ?? "tool", since: now };
947
- if (ev.note === "posttooluse" || ev.note === "agent.hook.PostToolUse" || ev.note === "agent.hook.UserPromptSubmit") {
984
+ function nextPendingTool(current, ev2, now) {
985
+ if (ev2.note === "agent.hook.PreToolUse")
986
+ return { name: ev2.tool ?? "tool", since: now };
987
+ if (ev2.note === "posttooluse" || ev2.note === "agent.hook.PostToolUse" || ev2.note === "agent.hook.UserPromptSubmit") {
948
988
  return void 0;
949
989
  }
950
990
  return current;
951
991
  }
952
- function nextPendingMonitor(current, ev, now) {
953
- if (ev.note === "agent.hook.PreToolUse" && ev.tool === "Monitor")
992
+ function nextPendingMonitor(current, ev2, now) {
993
+ if (ev2.note === "agent.hook.PreToolUse" && ev2.tool === "Monitor")
954
994
  return { since: now };
955
995
  return current;
956
996
  }
957
- function reduce(rec, ev, now) {
958
- if (ev.type === "task.reopened") {
959
- return { ...rec, state: "working", question: void 0, error: void 0, lastHeartbeat: now, lastEvent: ev.type, workingStretchStartedAt: now };
997
+ function reduce(rec, ev2, now) {
998
+ if (ev2.type === "task.reopened") {
999
+ return { ...rec, state: "working", question: void 0, error: void 0, lastHeartbeat: now, lastEvent: ev2.type, workingStretchStartedAt: now };
960
1000
  }
961
- if (ev.type === "crew.takeover.started") {
1001
+ if (ev2.type === "crew.takeover.started") {
962
1002
  if (rec.operatorHold)
963
- return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
1003
+ return { ...rec, lastHeartbeat: now, lastEvent: ev2.type };
964
1004
  return {
965
1005
  ...rec,
966
- operatorHold: { since: now, ...ev.note !== void 0 ? { note: ev.note } : {} },
1006
+ operatorHold: { since: now, ...ev2.note !== void 0 ? { note: ev2.note } : {} },
967
1007
  lastHeartbeat: now,
968
- lastEvent: ev.type
1008
+ lastEvent: ev2.type
969
1009
  };
970
1010
  }
971
- if (ev.type === "crew.takeover.ended") {
1011
+ if (ev2.type === "crew.takeover.ended") {
972
1012
  const { operatorHold: _dropped, ...rest } = rec;
973
- return { ...rest, lastHeartbeat: now, lastEvent: ev.type };
1013
+ return { ...rest, lastHeartbeat: now, lastEvent: ev2.type };
974
1014
  }
975
1015
  if (TERMINAL_STATES.has(rec.state))
976
1016
  return rec;
977
- const base = { ...rec, lastHeartbeat: now, lastEvent: ev.type };
978
- switch (ev.type) {
1017
+ const base = { ...rec, lastHeartbeat: now, lastEvent: ev2.type };
1018
+ switch (ev2.type) {
979
1019
  case "task.started":
980
1020
  return {
981
- ...stampAttempt(base, { pid: ev.pid }, now),
1021
+ ...stampAttempt(base, { pid: ev2.pid }, now),
982
1022
  state: "working",
983
- pid: ev.pid ?? rec.pid,
984
- sessionId: ev.sessionId ?? rec.sessionId,
1023
+ pid: ev2.pid ?? rec.pid,
1024
+ sessionId: ev2.sessionId ?? rec.sessionId,
985
1025
  question: void 0,
986
1026
  // resuming after a blocked→reply clears the question
987
1027
  pendingTool: void 0,
@@ -991,10 +1031,10 @@ function reduce(rec, ev, now) {
991
1031
  workingStretchStartedAt: now
992
1032
  };
993
1033
  case "task.progress": {
994
- const pendingTool = nextPendingTool(rec.pendingTool, ev, now);
995
- const pendingMonitor = nextPendingMonitor(rec.pendingMonitor, ev, now);
1034
+ const pendingTool = nextPendingTool(rec.pendingTool, ev2, now);
1035
+ const pendingMonitor = nextPendingMonitor(rec.pendingMonitor, ev2, now);
996
1036
  if (isStickyAttention(rec.state))
997
- return { ...rec, lastHeartbeat: now, lastEvent: ev.type, pendingTool, pendingMonitor };
1037
+ return { ...rec, lastHeartbeat: now, lastEvent: ev2.type, pendingTool, pendingMonitor };
998
1038
  const b = { ...base, pendingTool, pendingMonitor };
999
1039
  if (rec.state === "awaiting-input" || rec.state === "stalled")
1000
1040
  return { ...stampAttempt(b, {}, now), state: "working" };
@@ -1002,29 +1042,29 @@ function reduce(rec, ev, now) {
1002
1042
  }
1003
1043
  case "heartbeat":
1004
1044
  if (isStickyAttention(rec.state))
1005
- return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
1045
+ return { ...rec, lastHeartbeat: now, lastEvent: ev2.type };
1006
1046
  if (rec.state === "awaiting-input")
1007
1047
  return { ...base, state: "working" };
1008
1048
  return base;
1009
1049
  case "task.blocked":
1010
1050
  if (rec.state === "blocked")
1011
- return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
1012
- return { ...base, state: "blocked", question: ev.question, pendingTool: void 0, pendingMonitor: void 0 };
1051
+ return { ...rec, lastHeartbeat: now, lastEvent: ev2.type };
1052
+ return { ...base, state: "blocked", question: ev2.question, pendingTool: void 0, pendingMonitor: void 0 };
1013
1053
  case "task.review":
1014
- return { ...base, state: "review", reviewNote: ev.message, pendingTool: void 0, pendingMonitor: void 0 };
1054
+ return { ...base, state: "review", reviewNote: ev2.message, pendingTool: void 0, pendingMonitor: void 0 };
1015
1055
  case "task.done":
1016
- if (rec.state === "review" && ev.source !== "approve") {
1017
- return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
1056
+ if (rec.state === "review" && ev2.source !== "approve") {
1057
+ return { ...rec, lastHeartbeat: now, lastEvent: ev2.type };
1018
1058
  }
1019
- return { ...base, state: "done", resultRef: ev.resultRef, parseWarning: ev.parseWarning };
1059
+ return { ...base, state: "done", resultRef: ev2.resultRef, parseWarning: ev2.parseWarning };
1020
1060
  case "task.failed":
1021
- return { ...base, state: "failed", error: ev.error, exitCode: ev.exitCode };
1061
+ return { ...base, state: "failed", error: ev2.error, exitCode: ev2.exitCode };
1022
1062
  case "task.cancelled":
1023
1063
  return { ...base, state: "cancelled" };
1024
1064
  case "task.session.ended":
1025
1065
  return { ...base, state: "cancelled" };
1026
1066
  case "task.session":
1027
- return stampAttempt(base, { resumeRef: ev.resumeRef }, now);
1067
+ return stampAttempt(base, { resumeRef: ev2.resumeRef }, now);
1028
1068
  case "task.turn.started":
1029
1069
  return { ...stampAttempt(base, {}, now), state: "working", pendingTool: void 0, pendingMonitor: void 0 };
1030
1070
  case "task.turn.failed":
@@ -1034,7 +1074,7 @@ function reduce(rec, ev, now) {
1034
1074
  // only the notify message (reduce.ts formatMessage) differs.
1035
1075
  case "task.turn.completed":
1036
1076
  if (isStickyAttention(rec.state))
1037
- return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
1077
+ return { ...rec, lastHeartbeat: now, lastEvent: ev2.type };
1038
1078
  if (rec.pendingTool || rec.pendingMonitor)
1039
1079
  return stampAttempt(base, {}, now);
1040
1080
  return { ...stampAttempt(base, {}, now), state: "awaiting-input", pendingTool: void 0, pendingMonitor: void 0 };
@@ -1043,14 +1083,14 @@ function reduce(rec, ev, now) {
1043
1083
  // heartbeat-only
1044
1084
  case "task.input.requested":
1045
1085
  case "task.approval.requested":
1046
- return { ...stampAttempt(base, {}, now), state: "blocked", question: ev.question, pendingTool: void 0, pendingMonitor: void 0 };
1086
+ return { ...stampAttempt(base, {}, now), state: "blocked", question: ev2.question, pendingTool: void 0, pendingMonitor: void 0 };
1047
1087
  case "task.reattached":
1048
1088
  return stampAttempt(base, {}, now);
1049
1089
  case "task.first-turn.confirmed":
1050
1090
  if (rec.firstTurnConfirmedAt) {
1051
1091
  return { ...rec, lastEvent: "task.progress" };
1052
1092
  }
1053
- return { ...rec, firstTurnConfirmedAt: now, lastEvent: ev.type };
1093
+ return { ...rec, firstTurnConfirmedAt: now, lastEvent: ev2.type };
1054
1094
  case "task.stalled":
1055
1095
  case "task.idle":
1056
1096
  case "task.quiet":
@@ -2537,10 +2577,10 @@ function detectForeignInstall(parsed, thisEntry, registeredEntryExists) {
2537
2577
  isUpgrade: isVersionUpgrade(parsed.daemonEntry, thisEntry)
2538
2578
  };
2539
2579
  }
2540
- function sanitizePathForPlist(path29) {
2580
+ function sanitizePathForPlist(path42) {
2541
2581
  const seen = /* @__PURE__ */ new Set();
2542
2582
  const stable = [];
2543
- for (const p of path29.split(":")) {
2583
+ for (const p of path42.split(":")) {
2544
2584
  if (!p)
2545
2585
  continue;
2546
2586
  if (p.includes("/.claude/plugins/"))
@@ -3639,7 +3679,7 @@ function coalesceBacklog(entries, opts) {
3639
3679
  }
3640
3680
  return decisions;
3641
3681
  }
3642
- function decide(entry, later, opts) {
3682
+ function decide(entry, later2, opts) {
3643
3683
  if (entry.kind === "captain.message") {
3644
3684
  if (entry.payload?.source !== "daemon" || !entry.message)
3645
3685
  return { action: "deliver", entry };
@@ -3648,10 +3688,10 @@ function decide(entry, later, opts) {
3648
3688
  const prefix = stalePrefix(Date.parse(entry.ts), opts.now);
3649
3689
  return { action: "deliver", entry: prefix ? { ...entry, message: `${prefix}${entry.message}` } : entry };
3650
3690
  }
3651
- if (later) {
3691
+ if (later2) {
3652
3692
  if (TRANSIENT_KINDS.has(entry.kind))
3653
3693
  return { action: "skip", entry, reason: "superseded" };
3654
- if (entry.kind === "task.review" && (later.has("task.review") || Object.keys(TERMINAL_EVENT_STATE).some((k) => later.has(k)))) {
3694
+ if (entry.kind === "task.review" && (later2.has("task.review") || Object.keys(TERMINAL_EVENT_STATE).some((k) => later2.has(k)))) {
3655
3695
  return { action: "skip", entry, reason: "superseded" };
3656
3696
  }
3657
3697
  }
@@ -4297,11 +4337,11 @@ var init_auto_gate = __esm({
4297
4337
  // packages/core/dist/first-turn-spill.js
4298
4338
  import fs9 from "fs";
4299
4339
  import os4 from "os";
4300
- import path9 from "path";
4340
+ import path10 from "path";
4301
4341
  function spillOversizedFirstTurn(text, id) {
4302
4342
  if (Buffer.byteLength(text, "utf8") <= FIRST_TURN_INLINE_MAX_BYTES)
4303
4343
  return text;
4304
- const spillFile = path9.join(os4.tmpdir(), `squadrant-task-${id}.md`);
4344
+ const spillFile = path10.join(os4.tmpdir(), `squadrant-task-${id}.md`);
4305
4345
  fs9.writeFileSync(spillFile, text, "utf8");
4306
4346
  return `Full task is at ${spillFile} \u2014 cat it and follow it exactly.`;
4307
4347
  }
@@ -4411,7 +4451,7 @@ var init_crew_lifecycle = __esm({
4411
4451
  // packages/core/dist/crew-spawn.js
4412
4452
  import fs10 from "fs";
4413
4453
  import os5 from "os";
4414
- import path10 from "path";
4454
+ import path11 from "path";
4415
4455
  import { randomUUID as randomUUID3 } from "crypto";
4416
4456
  function ensureSocksDir(dir = CC_SOCKS_DIR) {
4417
4457
  fs10.mkdirSync(dir, { recursive: true, mode: 448 });
@@ -4549,9 +4589,9 @@ async function runCrewSpawn(input, config, deps) {
4549
4589
  }) : proj.path;
4550
4590
  let firstTurnTask = input.task;
4551
4591
  if (input.taskFile && input.taskFile !== "-" && !input.shared) {
4552
- const absTaskFile = path10.resolve(input.taskFile);
4553
- const basename = path10.basename(absTaskFile);
4554
- fs10.copyFileSync(absTaskFile, path10.join(spawnCwd, basename));
4592
+ const absTaskFile = path11.resolve(input.taskFile);
4593
+ const basename = path11.basename(absTaskFile);
4594
+ fs10.copyFileSync(absTaskFile, path11.join(spawnCwd, basename));
4555
4595
  firstTurnTask = `Read ./${basename} to get your task brief, then execute it.`;
4556
4596
  }
4557
4597
  const agentName = requestedAgent;
@@ -4565,7 +4605,7 @@ async function runCrewSpawn(input, config, deps) {
4565
4605
  assertBackendUsable({ backend, agent: agent.name, router: config.defaults.router });
4566
4606
  deps.onBackendResolved?.({ backend });
4567
4607
  if (agentName === "codex") {
4568
- const codexRoleFile = path10.join(TEMPLATES_DIR, `crew.${agent.templateSuffix}.md`);
4608
+ const codexRoleFile = path11.join(TEMPLATES_DIR, `crew.${agent.templateSuffix}.md`);
4569
4609
  const roleInstructions = fs10.existsSync(codexRoleFile) ? fs10.readFileSync(codexRoleFile, "utf8") : void 0;
4570
4610
  return runCodexInteractiveSpawn({
4571
4611
  project: input.project,
@@ -4582,7 +4622,7 @@ async function runCrewSpawn(input, config, deps) {
4582
4622
  sendCodexFirstTurn: deps.sendCodexFirstTurn
4583
4623
  });
4584
4624
  }
4585
- const promptFile = path10.join(TEMPLATES_DIR, `crew.${agent.templateSuffix}.md`);
4625
+ const promptFile = path11.join(TEMPLATES_DIR, `crew.${agent.templateSuffix}.md`);
4586
4626
  const interactive = agent.name === "claude" || agent.name === "opencode";
4587
4627
  const crewRole = config.defaults.roles?.crew;
4588
4628
  const configModel = crewRole && crewRole.agent === agent.name ? crewRole.model : void 0;
@@ -4608,7 +4648,7 @@ async function runCrewSpawn(input, config, deps) {
4608
4648
  }
4609
4649
  }
4610
4650
  ensureSocksDir();
4611
- const messagingSocketPath = path10.join(CC_SOCKS_DIR, `squadrant-${randomUUID3()}.sock`);
4651
+ const messagingSocketPath = path11.join(CC_SOCKS_DIR, `squadrant-${randomUUID3()}.sock`);
4612
4652
  const rec = await deps.dispatchCrew({
4613
4653
  provider: "claude",
4614
4654
  mode: "interactive",
@@ -4821,7 +4861,7 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps, o
4821
4861
  }
4822
4862
  } catch {
4823
4863
  }
4824
- const deliver = deps.sendToPane ?? ((pane, msg) => runtime.sendToPane(pane, msg).then(() => ({ delivered: true })));
4864
+ const deliver2 = deps.sendToPane ?? ((pane, msg) => runtime.sendToPane(pane, msg).then(() => ({ delivered: true })));
4825
4865
  const agent = task?.provider;
4826
4866
  const channel = agent ? deps.controlChannels?.find((c) => c.agent === agent) : void 0;
4827
4867
  const mode = channel && agent && deps.controlChannelMode ? deps.controlChannelMode(agent) : "off";
@@ -4851,7 +4891,7 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps, o
4851
4891
  channelLog(`crew send ${name}: threw, falling back to pane \u2014 ${e.message}`);
4852
4892
  probe = { status: "gone" };
4853
4893
  }
4854
- const { delivered: paneOk, blockedByModal: paneModal } = await deliver(crew, message);
4894
+ const { delivered: paneOk, blockedByModal: paneModal } = await deliver2(crew, message);
4855
4895
  const channelWouldSay = probe.status === "reachable" ? "deliverable" : probe.status;
4856
4896
  if (paneOk !== (probe.status === "reachable")) {
4857
4897
  channelLog(`crew send ${name}: DISAGREEMENT \u2014 pane=${paneOk ? "delivered" : "not delivered"}, channel=${channelWouldSay}`);
@@ -4865,7 +4905,7 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps, o
4865
4905
  }
4866
4906
  return { reopened };
4867
4907
  }
4868
- const { delivered, blockedByModal } = await deliver(crew, message);
4908
+ const { delivered, blockedByModal } = await deliver2(crew, message);
4869
4909
  if (blockedByModal) {
4870
4910
  throw new Error(blockedByModalMessage());
4871
4911
  }
@@ -4886,7 +4926,7 @@ function buildRecoveryHint(sessId, provider, worktreeCwd) {
4886
4926
  return "";
4887
4927
  if (provider === "claude") {
4888
4928
  const escaped = worktreeCwd.replace(/[^a-zA-Z0-9]/g, "-");
4889
- const transcriptPath = path10.join(os5.homedir(), ".claude", "projects", escaped, `${sessId}.jsonl`);
4929
+ const transcriptPath = path11.join(os5.homedir(), ".claude", "projects", escaped, `${sessId}.jsonl`);
4890
4930
  return `
4891
4931
  transcript: ${transcriptPath}
4892
4932
  resume: claude --resume ${sessId} (run from the worktree path above)
@@ -4995,8 +5035,8 @@ var init_crew_spawn = __esm({
4995
5035
  init_crew_protocol();
4996
5036
  init_crew_lifecycle();
4997
5037
  CC_SOCKS_DIR = "/tmp/cc-socks";
4998
- TEMPLATES_DIR = path10.join(os5.homedir(), ".config", "squadrant", "templates");
4999
- STATE_ROOT = path10.join(os5.homedir(), ".config", "squadrant", "state");
5038
+ TEMPLATES_DIR = path11.join(os5.homedir(), ".config", "squadrant", "templates");
5039
+ STATE_ROOT = path11.join(os5.homedir(), ".config", "squadrant", "state");
5000
5040
  FIRST_TURN_HOOK_CONFIRM_GRACE_MS = 15e3;
5001
5041
  FIRST_TURN_HOOK_CONFIRM_MAX_MS = 18e4;
5002
5042
  FIRST_TURN_HOOK_POLL_INTERVAL_MS = 2e3;
@@ -5080,11 +5120,11 @@ var init_captain_record = __esm({
5080
5120
  // packages/core/dist/opencode-session.js
5081
5121
  import { execFileSync as execFileSync4 } from "child_process";
5082
5122
  async function listSessions(port, fetchImpl = fetch, timeoutMs = 5e3) {
5083
- for (const path29 of ["/session", "/api/session"]) {
5123
+ for (const path42 of ["/session", "/api/session"]) {
5084
5124
  const ac = new AbortController();
5085
5125
  const t = setTimeout(() => ac.abort(), timeoutMs);
5086
5126
  try {
5087
- const res = await fetchImpl(`http://127.0.0.1:${port}${path29}`, { signal: ac.signal });
5127
+ const res = await fetchImpl(`http://127.0.0.1:${port}${path42}`, { signal: ac.signal });
5088
5128
  if (!res.ok)
5089
5129
  continue;
5090
5130
  const rows = await res.json();
@@ -5777,10 +5817,10 @@ function distBuiltAt() {
5777
5817
  return 0;
5778
5818
  }
5779
5819
  }
5780
- function gatherLogStats(path29, now, windowMs) {
5820
+ function gatherLogStats(path42, now, windowMs) {
5781
5821
  let sizeBytes = 0;
5782
5822
  try {
5783
- sizeBytes = statSync3(path29).size;
5823
+ sizeBytes = statSync3(path42).size;
5784
5824
  } catch {
5785
5825
  return { errorCount: 0, sizeBytes: 0, windowMs };
5786
5826
  }
@@ -5791,7 +5831,7 @@ function gatherLogStats(path29, now, windowMs) {
5791
5831
  const len = sizeBytes - start;
5792
5832
  let text = "";
5793
5833
  try {
5794
- const fd = openSync2(path29, "r");
5834
+ const fd = openSync2(path42, "r");
5795
5835
  try {
5796
5836
  const buf = Buffer.alloc(len);
5797
5837
  readSync(fd, buf, 0, len, start);
@@ -6122,6 +6162,7 @@ function startDaemon(ctx, opts, pkgVersion) {
6122
6162
  const projects = new Set(entries.filter((e) => e.endsWith(".log")).map((e) => e.slice(0, -".log".length)));
6123
6163
  for (const project of projects)
6124
6164
  await rotateIfNeeded({ stateRoot, project, ...mboxCfg });
6165
+ await opts.knowledgeTick?.();
6125
6166
  } catch (e) {
6126
6167
  log(`rotation timer error: ${e.message}`);
6127
6168
  } finally {
@@ -6199,7 +6240,7 @@ var init_start = __esm({
6199
6240
  // packages/core/dist/session-freshness.js
6200
6241
  import crypto from "crypto";
6201
6242
  import fs11 from "fs";
6202
- import path11 from "path";
6243
+ import path12 from "path";
6203
6244
  function loadSessions(sessionsPath) {
6204
6245
  try {
6205
6246
  return JSON.parse(readConfigFileSync(sessionsPath));
@@ -6212,17 +6253,17 @@ function saveSessions(sessionsPath, sessions) {
6212
6253
  }
6213
6254
  function computeTemplateHash(role, templatesDir) {
6214
6255
  const hash = crypto.createHash("sha256");
6215
- const roleFile = path11.join(templatesDir, `${role}.claude.md`);
6216
- const legacyRoleFile = path11.join(templatesDir, `${role}.CLAUDE.md`);
6256
+ const roleFile = path12.join(templatesDir, `${role}.claude.md`);
6257
+ const legacyRoleFile = path12.join(templatesDir, `${role}.CLAUDE.md`);
6217
6258
  if (fs11.existsSync(roleFile)) {
6218
6259
  hash.update(fs11.readFileSync(roleFile, "utf-8"));
6219
6260
  } else if (fs11.existsSync(legacyRoleFile)) {
6220
6261
  hash.update(fs11.readFileSync(legacyRoleFile, "utf-8"));
6221
6262
  }
6222
- const pluginSkillsDir = path11.join(templatesDir, "..", "plugin", "skills");
6263
+ const pluginSkillsDir = path12.join(templatesDir, "..", "plugin", "skills");
6223
6264
  if (fs11.existsSync(pluginSkillsDir)) {
6224
6265
  for (const skill of fs11.readdirSync(pluginSkillsDir).sort()) {
6225
- const skillFile = path11.join(pluginSkillsDir, skill, "SKILL.md");
6266
+ const skillFile = path12.join(pluginSkillsDir, skill, "SKILL.md");
6226
6267
  if (fs11.existsSync(skillFile)) {
6227
6268
  hash.update(fs11.readFileSync(skillFile, "utf-8"));
6228
6269
  }
@@ -6529,32 +6570,32 @@ var init_ensure_captain = __esm({
6529
6570
  function topicName(project) {
6530
6571
  return project;
6531
6572
  }
6532
- function formatLifecycle(project, ev) {
6533
- switch (ev.type) {
6573
+ function formatLifecycle(project, ev2) {
6574
+ switch (ev2.type) {
6534
6575
  case "task.done":
6535
- return `\u2705 [${project}] CREW DONE \xB7 ${ev.id}` + (ev.message ? `
6536
- ${ev.message}` : "");
6576
+ return `\u2705 [${project}] CREW DONE \xB7 ${ev2.id}` + (ev2.message ? `
6577
+ ${ev2.message}` : "");
6537
6578
  case "task.blocked":
6538
- return `\u{1F6A7} [${project}] CREW BLOCKED \xB7 ${ev.id}
6539
- ${ev.question}`;
6579
+ return `\u{1F6A7} [${project}] CREW BLOCKED \xB7 ${ev2.id}
6580
+ ${ev2.question}`;
6540
6581
  case "task.review":
6541
- return `\u{1F440} [${project}] CREW REVIEW \xB7 ${ev.id}` + (ev.message ? `
6542
- ${ev.message}` : "");
6582
+ return `\u{1F440} [${project}] CREW REVIEW \xB7 ${ev2.id}` + (ev2.message ? `
6583
+ ${ev2.message}` : "");
6543
6584
  case "task.idle":
6544
- return `\u{1F4A4} [${project}] CREW IDLE \xB7 ${ev.id}`;
6585
+ return `\u{1F4A4} [${project}] CREW IDLE \xB7 ${ev2.id}`;
6545
6586
  case "task.failed":
6546
- return `\u274C [${project}] CREW FAILED \xB7 ${ev.id}
6547
- ${ev.error}`;
6587
+ return `\u274C [${project}] CREW FAILED \xB7 ${ev2.id}
6588
+ ${ev2.error}`;
6548
6589
  case "task.approval.requested":
6549
- return `\u{1F510} [${project}] APPROVAL NEEDED \xB7 ${ev.id}
6550
- ${ev.question}`;
6590
+ return `\u{1F510} [${project}] APPROVAL NEEDED \xB7 ${ev2.id}
6591
+ ${ev2.question}`;
6551
6592
  case "task.input.requested":
6552
- return `\u2753 [${project}] INPUT NEEDED \xB7 ${ev.id}
6553
- ${ev.question}`;
6593
+ return `\u2753 [${project}] INPUT NEEDED \xB7 ${ev2.id}
6594
+ ${ev2.question}`;
6554
6595
  case "task.timeout":
6555
- return `\u23F1\uFE0F [${project}] CREW TIMEOUT \xB7 ${ev.id}`;
6596
+ return `\u23F1\uFE0F [${project}] CREW TIMEOUT \xB7 ${ev2.id}`;
6556
6597
  default:
6557
- return `\u2139\uFE0F [${project}] ${ev.type} \xB7 ${ev.id}`;
6598
+ return `\u2139\uFE0F [${project}] ${ev2.type} \xB7 ${ev2.id}`;
6558
6599
  }
6559
6600
  }
6560
6601
  function formatInbound(text) {
@@ -6608,9 +6649,9 @@ var init_format = __esm({
6608
6649
  });
6609
6650
 
6610
6651
  // packages/core/dist/telegram/state.js
6611
- import path12 from "path";
6652
+ import path13 from "path";
6612
6653
  function statePath(stateRoot) {
6613
- return path12.join(stateRoot, "telegram-state.json");
6654
+ return path13.join(stateRoot, "telegram-state.json");
6614
6655
  }
6615
6656
  function topicKey(project, scope = "project") {
6616
6657
  return `${project}::${scope}`;
@@ -7104,7 +7145,7 @@ var init_tiers = __esm({
7104
7145
  // packages/core/dist/telegram/bridge.js
7105
7146
  import fs12 from "fs";
7106
7147
  import os6 from "os";
7107
- import path13 from "path";
7148
+ import path14 from "path";
7108
7149
  import crypto2 from "crypto";
7109
7150
  function formatInboundReceipt(project, outcome) {
7110
7151
  if (!outcome)
@@ -7123,7 +7164,7 @@ function formatInboundReceipt(project, outcome) {
7123
7164
  }
7124
7165
  function telegramPollLockPath(token) {
7125
7166
  const hash = crypto2.createHash("sha256").update(token).digest("hex").slice(0, 16);
7126
- return path13.join(os6.tmpdir(), `squadrant-telegram-poll-${hash}.lock`);
7167
+ return path14.join(os6.tmpdir(), `squadrant-telegram-poll-${hash}.lock`);
7127
7168
  }
7128
7169
  function pidAlive(pid) {
7129
7170
  try {
@@ -7227,7 +7268,7 @@ function notifyToggle(text) {
7227
7268
  function createTelegramBridge(opts) {
7228
7269
  const { cfg, stateRoot, client, appendCaptainMessage: appendCaptainMessage2, log, ensureCaptainAlive, runCommand, sendReply } = opts;
7229
7270
  const lifecycle = opts.lifecycle;
7230
- const configRoot = opts.configRoot ?? path13.join(os6.homedir(), ".config", "squadrant");
7271
+ const configRoot = opts.configRoot ?? path14.join(os6.homedir(), ".config", "squadrant");
7231
7272
  const pollMs = cfg.pollMs ?? 1e3;
7232
7273
  const schedule = opts.schedule ?? defaultSchedule2;
7233
7274
  const lockToken = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
@@ -7249,16 +7290,16 @@ function createTelegramBridge(opts) {
7249
7290
  const { topicId } = await runTelegramLink({ project, cfg, client, stateRoot });
7250
7291
  await client.sendMessage(cfg.supergroupId, topicId, text);
7251
7292
  }
7252
- async function deliverOutbound(project, ev) {
7293
+ async function deliverOutbound(project, ev2) {
7253
7294
  const resolved = resolveNotify(cfg.notify, loadProjectOverride(project, configRoot));
7254
7295
  const live = loadState(stateRoot).notify[project];
7255
7296
  const active = live ?? resolved.active;
7256
7297
  if (!active)
7257
7298
  return;
7258
- if (!tierIncludes(resolved.crew, ev.type))
7299
+ if (!tierIncludes(resolved.crew, ev2.type))
7259
7300
  return;
7260
- let text = formatLifecycle(project, ev);
7261
- if ((ev.type === "task.done" || ev.type === "task.failed") && opts.usageFor) {
7301
+ let text = formatLifecycle(project, ev2);
7302
+ if ((ev2.type === "task.done" || ev2.type === "task.failed") && opts.usageFor) {
7262
7303
  const usage2 = opts.usageFor(project);
7263
7304
  if (usage2 && usage2.requests > 0)
7264
7305
  text += `
@@ -7368,14 +7409,14 @@ ${formatUsageLine(usage2)}`;
7368
7409
  }
7369
7410
  function currentEffort() {
7370
7411
  try {
7371
- return loadConfig(path13.join(configRoot, "config.json")).defaults.effort ?? "balance";
7412
+ return loadConfig(path14.join(configRoot, "config.json")).defaults.effort ?? "balance";
7372
7413
  } catch {
7373
7414
  return "balance";
7374
7415
  }
7375
7416
  }
7376
7417
  function projectNames() {
7377
7418
  try {
7378
- return Object.keys(loadConfig(path13.join(configRoot, "config.json")).projects);
7419
+ return Object.keys(loadConfig(path14.join(configRoot, "config.json")).projects);
7379
7420
  } catch {
7380
7421
  return [];
7381
7422
  }
@@ -7677,8 +7718,8 @@ ${formatUsageLine(usage2)}`;
7677
7718
  releasePollLock?.();
7678
7719
  releasePollLock = null;
7679
7720
  },
7680
- pushLifecycle(project, ev) {
7681
- void deliverOutbound(project, ev).catch((e) => {
7721
+ pushLifecycle(project, ev2) {
7722
+ void deliverOutbound(project, ev2).catch((e) => {
7682
7723
  log(`telegram outbound failed project=${project}: ${e.message}`);
7683
7724
  });
7684
7725
  },
@@ -8036,8 +8077,8 @@ var init_stream = __esm({
8036
8077
  // packages/core/dist/router/shim.js
8037
8078
  import { createServer as createServer3 } from "http";
8038
8079
  import { Readable } from "stream";
8039
- function joinUrl(base, path29) {
8040
- return base.replace(/\/+$/, "") + path29;
8080
+ function joinUrl(base, path42) {
8081
+ return base.replace(/\/+$/, "") + path42;
8041
8082
  }
8042
8083
  async function readBody(req) {
8043
8084
  const chunks = [];
@@ -8213,8 +8254,8 @@ function createRouterShim(opts) {
8213
8254
  writeJson(res, 200, { input_tokens: Math.max(1, Math.ceil(text.length / 4)) });
8214
8255
  }
8215
8256
  function handler(req, res) {
8216
- const path29 = pathnameOf(req.url);
8217
- if (req.method === "POST" && path29 === "/v1/messages") {
8257
+ const path42 = pathnameOf(req.url);
8258
+ if (req.method === "POST" && path42 === "/v1/messages") {
8218
8259
  void handleMessages(req, res).catch((err) => {
8219
8260
  log(`router internal error: ${err instanceof Error ? err.message : String(err)}`);
8220
8261
  if (!res.headersSent) {
@@ -8226,18 +8267,18 @@ function createRouterShim(opts) {
8226
8267
  });
8227
8268
  return;
8228
8269
  }
8229
- if (req.method === "POST" && path29 === "/v1/messages/count_tokens") {
8270
+ if (req.method === "POST" && path42 === "/v1/messages/count_tokens") {
8230
8271
  void handleCountTokens(req, res).catch(() => {
8231
8272
  if (!res.headersSent)
8232
8273
  writeJson(res, 502, anthropicError(502, "api_error", "router internal error").body);
8233
8274
  });
8234
8275
  return;
8235
8276
  }
8236
- if (req.method === "GET" && path29 === "/v1/models") {
8277
+ if (req.method === "GET" && path42 === "/v1/models") {
8237
8278
  writeJson(res, 200, modelsList([...advertisedModels]));
8238
8279
  return;
8239
8280
  }
8240
- if (req.method === "GET" && path29 === "/healthz") {
8281
+ if (req.method === "GET" && path42 === "/healthz") {
8241
8282
  writeJson(res, 200, { ready: server?.listening === true, upstreamReachable, lastError });
8242
8283
  return;
8243
8284
  }
@@ -8506,11 +8547,11 @@ async function probeCaptainChannel(project, opts = {}) {
8506
8547
  }
8507
8548
  }
8508
8549
  async function httpReachable(port, fetchImpl, timeoutMs) {
8509
- for (const path29 of ["/session", "/api/session"]) {
8550
+ for (const path42 of ["/session", "/api/session"]) {
8510
8551
  const ac = new AbortController();
8511
8552
  const t = setTimeout(() => ac.abort(), timeoutMs);
8512
8553
  try {
8513
- const res = await fetchImpl(`http://127.0.0.1:${port}${path29}`, { signal: ac.signal });
8554
+ const res = await fetchImpl(`http://127.0.0.1:${port}${path42}`, { signal: ac.signal });
8514
8555
  if (res.ok)
8515
8556
  return true;
8516
8557
  } catch {
@@ -9281,8 +9322,8 @@ function createEventsSource(opts) {
9281
9322
  for (const v2 of checkFact(traceFor(taskId), fact, opts.check ?? {})) {
9282
9323
  opts.onViolation(v2);
9283
9324
  }
9284
- for (const ev of toControlEvent(fact))
9285
- opts.emit(ev);
9325
+ for (const ev2 of toControlEvent(fact))
9326
+ opts.emit(ev2);
9286
9327
  }
9287
9328
  }
9288
9329
  };
@@ -9349,19 +9390,20 @@ var init_peer_first_turn = __esm({
9349
9390
  });
9350
9391
 
9351
9392
  // packages/core/dist/knowledge/paths.js
9352
- import path14 from "path";
9353
- function kbDir(hubVault, kb) {
9354
- return path14.join(hubVault, "knowledge", kb);
9393
+ import path15 from "path";
9394
+ function kbDir(cfg, kb) {
9395
+ return kbPath(cfg, kb);
9355
9396
  }
9356
- function kbRulesDir(hubVault, kb) {
9357
- return path14.join(kbDir(hubVault, kb), "rules");
9397
+ function kbRulesDir(cfg, kb) {
9398
+ return path15.join(kbDir(cfg, kb), "shared", "rules");
9358
9399
  }
9359
- function projectRulesDir(spokeVault) {
9360
- return path14.join(spokeVault, "knowledge", "rules");
9400
+ function legacyKbDir(hubVault, kb) {
9401
+ return path15.join(hubVault, "knowledge", kb);
9361
9402
  }
9362
9403
  var PROPOSED_DIR;
9363
9404
  var init_paths = __esm({
9364
9405
  "packages/core/dist/knowledge/paths.js"() {
9406
+ init_dist();
9365
9407
  PROPOSED_DIR = "_proposed";
9366
9408
  }
9367
9409
  });
@@ -9369,11 +9411,14 @@ var init_paths = __esm({
9369
9411
  // packages/core/dist/knowledge/store.js
9370
9412
  import fs15 from "fs";
9371
9413
  import os7 from "os";
9372
- import path15 from "path";
9414
+ import path16 from "path";
9373
9415
  import matter2 from "gray-matter";
9374
9416
  function duplicateIdProblem(id, ignored, used) {
9375
9417
  return `duplicate id ${id}: ${ignored} is ignored, ${used} is used (live files win over _proposed, then path order). Fix: rename the id in one file, or delete or move one of them.`;
9376
9418
  }
9419
+ function isPendingProposalDup(e, rulesDir) {
9420
+ return e.problems.every((p) => p.startsWith("duplicate id")) && path16.relative(rulesDir, e.file).split(path16.sep).includes(PROPOSED_DIR);
9421
+ }
9377
9422
  function splitStatement(body) {
9378
9423
  const first = body.trim().split(/\n\s*\n/)[0] ?? "";
9379
9424
  return first.replace(/\s+/g, " ").trim();
@@ -9382,7 +9427,7 @@ function listMarkdown(dir, includeProposed) {
9382
9427
  const out = [];
9383
9428
  const walk = (d) => {
9384
9429
  for (const ent of fs15.readdirSync(d, { withFileTypes: true })) {
9385
- const full = path15.join(d, ent.name);
9430
+ const full = path16.join(d, ent.name);
9386
9431
  if (ent.isDirectory()) {
9387
9432
  if (ent.name === PROPOSED_DIR && !includeProposed)
9388
9433
  continue;
@@ -9393,7 +9438,7 @@ function listMarkdown(dir, includeProposed) {
9393
9438
  }
9394
9439
  };
9395
9440
  walk(dir);
9396
- const isProposed = (f) => path15.relative(dir, f).split(path15.sep).includes(PROPOSED_DIR);
9441
+ const isProposed = (f) => path16.relative(dir, f).split(path16.sep).includes(PROPOSED_DIR);
9397
9442
  return out.sort((a, b) => Number(isProposed(a)) - Number(isProposed(b)) || (a < b ? -1 : a > b ? 1 : 0));
9398
9443
  }
9399
9444
  function loadRulesDir(dir, layer, opts = {}) {
@@ -9429,22 +9474,23 @@ function loadRulesDir(dir, layer, opts = {}) {
9429
9474
  function resolveSourceRef(ref, kbRoot, sources) {
9430
9475
  const bases = /* @__PURE__ */ new Set([kbRoot]);
9431
9476
  for (const s of sources) {
9432
- let d = path15.resolve(kbRoot, expandHome(s.path.split(/[*?[{]/)[0]));
9433
- for (let i = 0; i < 6 && d !== path15.dirname(d); i++) {
9434
- d = path15.dirname(d);
9477
+ let d = path16.resolve(kbRoot, expandHome(s.path.split(/[*?[{]/)[0]));
9478
+ for (let i = 0; i < 6 && d !== path16.dirname(d); i++) {
9479
+ d = path16.dirname(d);
9435
9480
  bases.add(d);
9436
9481
  }
9437
9482
  }
9438
- const cands = [path15.resolve(kbRoot, expandHome(ref)), ...[...bases].map((b) => path15.join(b, ref))];
9483
+ const cands = [path16.resolve(kbRoot, expandHome(ref)), ...[...bases].map((b) => path16.join(b, ref))];
9439
9484
  return cands.find((c) => fs15.existsSync(c) && fs15.statSync(c).isFile());
9440
9485
  }
9441
9486
  function validateKb(opts) {
9442
9487
  const { rules, errors } = loadRulesDir(opts.rulesDir, "kb", { includeProposed: true });
9443
9488
  const checks = [];
9444
- const dups = errors.filter((e) => e.problems.some((p) => p.startsWith("duplicate id")));
9445
- const invalid = errors.filter((e) => !dups.includes(e));
9446
- const proposed = rules.filter((r) => path15.relative(opts.rulesDir, r.file).split(path15.sep).includes(PROPOSED_DIR)).length;
9447
- checks.push({ level: "ok", message: `${rules.length + errors.length} rule files read (${rules.length - proposed} live, ${proposed} proposed parsed OK)` });
9489
+ const dups = errors.filter((e) => e.problems.some((p) => p.startsWith("duplicate id")) && !isPendingProposalDup(e, opts.rulesDir));
9490
+ const pendingDups = errors.filter((e) => isPendingProposalDup(e, opts.rulesDir)).length;
9491
+ const invalid = errors.filter((e) => !dups.includes(e) && !isPendingProposalDup(e, opts.rulesDir));
9492
+ const proposed = rules.filter((r) => path16.relative(opts.rulesDir, r.file).split(path16.sep).includes(PROPOSED_DIR)).length;
9493
+ checks.push({ level: "ok", message: `${rules.length + errors.length} rule files read (${rules.length - proposed} live, ${proposed + pendingDups} proposed parsed OK)` });
9448
9494
  if (invalid.length)
9449
9495
  for (const e of invalid)
9450
9496
  checks.push({ level: "fail", message: `invalid ${e.file}: ${e.problems.join("; ")}` });
@@ -9494,14 +9540,14 @@ var init_store2 = __esm({
9494
9540
  throw new Error("only YAML front matter is allowed");
9495
9541
  } } }
9496
9542
  };
9497
- expandHome = (p) => p === "~" || p.startsWith("~/") ? path15.join(os7.homedir(), p.slice(1)) : p;
9543
+ expandHome = (p) => p === "~" || p.startsWith("~/") ? path16.join(os7.homedir(), p.slice(1)) : p;
9498
9544
  }
9499
9545
  });
9500
9546
 
9501
9547
  // packages/core/dist/knowledge/layers.js
9502
9548
  import fs16 from "fs";
9503
9549
  function loadKbRules(cfg, kb, opts = {}) {
9504
- return loadRulesDir(kbRulesDir(resolveHome(cfg.hubVault), kb), `kb:${kb}`, opts);
9550
+ return loadRulesDir(kbRulesDir(cfg, kb), `kb:${kb}`, opts);
9505
9551
  }
9506
9552
  function resolveProjectRules(cfg, project, opts = {}) {
9507
9553
  const pc = cfg.projects[project];
@@ -9511,7 +9557,7 @@ function resolveProjectRules(cfg, project, opts = {}) {
9511
9557
  const errors = [];
9512
9558
  const warnings = [];
9513
9559
  for (const kb of subscribedKbs(cfg, project)) {
9514
- if (!fs16.existsSync(kbDir(resolveHome(cfg.hubVault), kb))) {
9560
+ if (!fs16.existsSync(kbDir(cfg, kb))) {
9515
9561
  warnings.push(`knowledge base '${kb}' not found (run: squadrant knowledge init ${kb})`);
9516
9562
  continue;
9517
9563
  }
@@ -9526,10 +9572,13 @@ function resolveProjectRules(cfg, project, opts = {}) {
9526
9572
  byId.set(r.id, r);
9527
9573
  }
9528
9574
  }
9529
- const proj = loadRulesDir(projectRulesDir(resolveHome(pc.spokeVault)), `project:${project}`, opts);
9530
- errors.push(...proj.errors);
9531
- for (const r of proj.rules)
9532
- byId.set(r.id, r);
9575
+ const projDir = projectRulesHome(cfg, project);
9576
+ if (projDir) {
9577
+ const proj = loadRulesDir(projDir, `project:${project}`, opts);
9578
+ errors.push(...proj.errors);
9579
+ for (const r of proj.rules)
9580
+ byId.set(r.id, r);
9581
+ }
9533
9582
  for (const id of pc.rulesDisabled ?? []) {
9534
9583
  if (!byId.delete(id))
9535
9584
  warnings.push(`rulesDisabled: unknown rule id '${id}'`);
@@ -9547,7 +9596,7 @@ var init_layers = __esm({
9547
9596
 
9548
9597
  // packages/core/dist/knowledge/index-file.js
9549
9598
  import fs17 from "fs";
9550
- import path16 from "path";
9599
+ import path17 from "path";
9551
9600
  function compileIndex(kb, rules, now = /* @__PURE__ */ new Date()) {
9552
9601
  return {
9553
9602
  kb,
@@ -9555,15 +9604,15 @@ function compileIndex(kb, rules, now = /* @__PURE__ */ new Date()) {
9555
9604
  rules: rules.map(({ id, domain, modality, status, statement, triggers, anchors, file }) => ({ id, domain, modality, status, statement, triggers, anchors, file }))
9556
9605
  };
9557
9606
  }
9558
- function writeIndex(hubVault, kb, index) {
9559
- const file = path16.join(kbDir(hubVault, kb), "index.json");
9607
+ function writeIndex(kbRoot, index) {
9608
+ const file = path17.join(kbRoot, "index.json");
9560
9609
  const tmp = `${file}.tmp-${process.pid}`;
9561
9610
  fs17.writeFileSync(tmp, JSON.stringify(index, null, 2) + "\n");
9562
9611
  fs17.renameSync(tmp, file);
9563
9612
  return file;
9564
9613
  }
9565
- function readIndex(hubVault, kb) {
9566
- const file = path16.join(kbDir(hubVault, kb), "index.json");
9614
+ function readIndex(kbRoot, kb) {
9615
+ const file = path17.join(kbRoot, "index.json");
9567
9616
  if (!fs17.existsSync(file))
9568
9617
  return { index: null, problem: `index.json missing for kb '${kb}'` };
9569
9618
  try {
@@ -9577,14 +9626,13 @@ function readIndex(hubVault, kb) {
9577
9626
  }
9578
9627
  var init_index_file = __esm({
9579
9628
  "packages/core/dist/knowledge/index-file.js"() {
9580
- init_paths();
9581
9629
  }
9582
9630
  });
9583
9631
 
9584
9632
  // packages/core/dist/knowledge/search.js
9585
9633
  function searchRules(rules, query, opts = {}) {
9586
- const terms = [...new Set(query.toLowerCase().split(/\s+/).filter(Boolean))];
9587
- if (terms.length === 0)
9634
+ const terms2 = [...new Set(query.toLowerCase().split(/\s+/).filter(Boolean))];
9635
+ if (terms2.length === 0)
9588
9636
  return [];
9589
9637
  const statuses = opts.statuses ?? DEFAULT_SEARCH_STATUSES;
9590
9638
  const hits = [];
@@ -9600,7 +9648,7 @@ function searchRules(rules, query, opts = {}) {
9600
9648
  let score = 0;
9601
9649
  let matched = 0;
9602
9650
  let curated = false;
9603
- for (const t of terms) {
9651
+ for (const t of terms2) {
9604
9652
  let s = 0;
9605
9653
  if (id.includes(t))
9606
9654
  s += 3;
@@ -9624,8 +9672,8 @@ function searchRules(rules, query, opts = {}) {
9624
9672
  if (score > 0)
9625
9673
  hits.push({ rule, score, matched, curated });
9626
9674
  }
9627
- const strong = terms.length >= 2 ? hits.filter((h) => h.matched >= 2 || h.curated) : hits;
9628
- const out = strong.length ? strong : hits;
9675
+ const strong2 = terms2.length >= 2 ? hits.filter((h) => h.matched >= 2 || h.curated) : hits;
9676
+ const out = strong2.length ? strong2 : hits;
9629
9677
  return out.sort((a, b) => b.score - a.score || a.rule.id.localeCompare(b.rule.id));
9630
9678
  }
9631
9679
  var DEFAULT_SEARCH_STATUSES, lower, rationaleOf;
@@ -12664,9 +12712,9 @@ var init_js_yaml = __esm({
12664
12712
 
12665
12713
  // packages/core/dist/knowledge/sources.js
12666
12714
  import fs18 from "fs";
12667
- import path17 from "path";
12668
- function loadSources(hubVault, kb) {
12669
- const file = path17.join(kbDir(hubVault, kb), "sources.yaml");
12715
+ import path18 from "path";
12716
+ function loadSources(kbRoot) {
12717
+ const file = path18.join(kbRoot, "sources.yaml");
12670
12718
  if (!fs18.existsSync(file))
12671
12719
  return { sources: [], errors: [] };
12672
12720
  let data;
@@ -12695,7 +12743,6 @@ var init_sources = __esm({
12695
12743
  "packages/core/dist/knowledge/sources.js"() {
12696
12744
  init_js_yaml();
12697
12745
  init_dist();
12698
- init_paths();
12699
12746
  SOURCES_TEMPLATE = `# Trusted sources for this knowledge base (spec \xA73). Only these can produce rules.
12700
12747
  # priority: company > project > agent (decides conflicts).
12701
12748
  # sensitivity: local-only \u2192 never sent to a hosted model (D12).
@@ -12710,9 +12757,296 @@ var init_sources = __esm({
12710
12757
  }
12711
12758
  });
12712
12759
 
12713
- // packages/core/dist/knowledge/inject.js
12760
+ // packages/core/dist/knowledge/ingest.js
12761
+ import crypto3 from "crypto";
12714
12762
  import fs19 from "fs";
12715
- import path18 from "path";
12763
+ import os8 from "os";
12764
+ import path19 from "path";
12765
+ import { execFile as execFile2 } from "child_process";
12766
+ import { promisify as promisify2 } from "util";
12767
+ function refFor(kbRoot, file) {
12768
+ const rel = path19.relative(kbRoot, file);
12769
+ if (rel && !rel.startsWith("..") && !path19.isAbsolute(rel))
12770
+ return rel.split(path19.sep).join("/");
12771
+ const home = os8.homedir();
12772
+ return file.startsWith(home + path19.sep) ? `~/${path19.relative(home, file).split(path19.sep).join("/")}` : file;
12773
+ }
12774
+ function readState2(kbRoot, ref) {
12775
+ try {
12776
+ return JSON.parse(fs19.readFileSync(stampFile(kbRoot, ref), "utf8"));
12777
+ } catch {
12778
+ return null;
12779
+ }
12780
+ }
12781
+ function writeState(kbRoot, st) {
12782
+ fs19.mkdirSync(path19.join(kbRoot, CONVERTED_DIR), { recursive: true });
12783
+ fs19.writeFileSync(stampFile(kbRoot, st.ref), JSON.stringify(st) + "\n");
12784
+ }
12785
+ function globToRegExp(glob) {
12786
+ let re = "";
12787
+ for (let i = 0; i < glob.length; i++) {
12788
+ const c = glob[i];
12789
+ if (c === "*") {
12790
+ if (glob[i + 1] === "*") {
12791
+ re += glob[i + 2] === "/" ? "(?:.*/)?" : ".*";
12792
+ i += glob[i + 2] === "/" ? 2 : 1;
12793
+ } else
12794
+ re += "[^/]*";
12795
+ } else if (c === "?")
12796
+ re += "[^/]";
12797
+ else
12798
+ re += c.replace(/[.+^${}()|[\]\\]/g, "\\$&");
12799
+ }
12800
+ return new RegExp(`^${re}$`);
12801
+ }
12802
+ function walkFiles(dir, out = []) {
12803
+ if (!fs19.existsSync(dir))
12804
+ return out;
12805
+ for (const ent of fs19.readdirSync(dir, { withFileTypes: true })) {
12806
+ if (ent.name === ".git" || ent.name === CONVERTED_DIR || ent.name === "node_modules")
12807
+ continue;
12808
+ const full = path19.join(dir, ent.name);
12809
+ if (ent.isDirectory())
12810
+ walkFiles(full, out);
12811
+ else if (ent.isFile())
12812
+ out.push(full);
12813
+ }
12814
+ return out;
12815
+ }
12816
+ function expandSource(kbRoot, entry) {
12817
+ const abs = path19.resolve(kbRoot, expandHome2(entry.path));
12818
+ if (!/[*?]/.test(entry.path))
12819
+ return fs19.existsSync(abs) && fs19.statSync(abs).isFile() ? [abs] : [];
12820
+ const base = abs.split(/[*?]/)[0];
12821
+ const root = base.endsWith(path19.sep) ? base : path19.dirname(base);
12822
+ const re = globToRegExp(abs.split(path19.sep).join("/"));
12823
+ return walkFiles(root).filter((f) => re.test(f.split(path19.sep).join("/"))).sort();
12824
+ }
12825
+ function detectChange(kbRoot, file) {
12826
+ const ref = refFor(kbRoot, file);
12827
+ const st = fs19.statSync(file);
12828
+ const prior = readState2(kbRoot, ref);
12829
+ if (prior && prior.size === st.size && prior.mtimeMs === st.mtimeMs) {
12830
+ return { ref, changed: false, stamp: { sha: prior.sha, size: st.size, mtimeMs: st.mtimeMs }, prior };
12831
+ }
12832
+ const sha = sha256(file);
12833
+ return { ref, changed: !prior || prior.sha !== sha, stamp: { sha, size: st.size, mtimeMs: st.mtimeMs }, prior };
12834
+ }
12835
+ function splitSections(text) {
12836
+ const cuts = [];
12837
+ const re = /^#{1,6}[ \t]+(.+)$/gm;
12838
+ for (let m = re.exec(text); m; m = re.exec(text))
12839
+ cuts.push({ at: m.index, heading: m[1].trim() });
12840
+ if (!cuts.length || cuts[0].at > 0)
12841
+ cuts.unshift({ at: 0 });
12842
+ const spans = [];
12843
+ cuts.forEach((c, i) => {
12844
+ const end = i + 1 < cuts.length ? cuts[i + 1].at : text.length;
12845
+ let start = c.at;
12846
+ while (end - start > SECTION_TARGET_CHARS * 1.5) {
12847
+ const brk = text.indexOf("\n\n", start + SECTION_TARGET_CHARS);
12848
+ if (brk < 0 || brk >= end)
12849
+ break;
12850
+ spans.push({ index: 0, start, end: brk, heading: c.heading });
12851
+ start = brk + 2;
12852
+ }
12853
+ if (text.slice(start, end).trim())
12854
+ spans.push({ index: 0, start, end, heading: c.heading });
12855
+ });
12856
+ return spans.map((s, i) => ({ ...s, index: i }));
12857
+ }
12858
+ async function planIngest(kbRoot, opts) {
12859
+ const converter = opts.converter ?? markitdownConverter;
12860
+ const cap = opts.maxSectionsPerPass ?? DEFAULT_MAX_SECTIONS_PER_PASS;
12861
+ const plan = { sources: [], skipped: [], failed: [], carriedSections: 0, sectionCount: 0, estimatedTokens: 0, existing: opts.existing ?? [] };
12862
+ let budget = cap;
12863
+ const seen = /* @__PURE__ */ new Set();
12864
+ for (const entry of opts.sources) {
12865
+ for (const file of expandSource(kbRoot, entry)) {
12866
+ const { ref, changed, stamp, prior } = detectChange(kbRoot, file);
12867
+ if (seen.has(ref))
12868
+ continue;
12869
+ seen.add(ref);
12870
+ const pending = !!prior && prior.cursor < prior.sections;
12871
+ if (!changed && !pending)
12872
+ continue;
12873
+ if (entry.sensitivity === "local-only" && !opts.localModel) {
12874
+ plan.skipped.push({ ref, reason: "sensitivity: local-only and no local model available" });
12875
+ continue;
12876
+ }
12877
+ if (budget <= 0) {
12878
+ plan.carriedSections += changed ? 1 : prior.sections - prior.cursor;
12879
+ continue;
12880
+ }
12881
+ let text;
12882
+ const conv = convertedFile(kbRoot, ref);
12883
+ try {
12884
+ if (!changed && fs19.existsSync(conv))
12885
+ text = fs19.readFileSync(conv, "utf8");
12886
+ else {
12887
+ text = PASS_THROUGH.has(path19.extname(file).toLowerCase()) ? fs19.readFileSync(file, "utf8") : await converter(file);
12888
+ if (!opts.dryRun) {
12889
+ fs19.mkdirSync(path19.dirname(conv), { recursive: true });
12890
+ fs19.writeFileSync(conv, text);
12891
+ }
12892
+ }
12893
+ } catch (e) {
12894
+ plan.failed.push({ ref, error: e.message.split("\n")[0] });
12895
+ continue;
12896
+ }
12897
+ const all = splitSections(text);
12898
+ const from = changed ? 0 : prior.cursor;
12899
+ const take = all.slice(from, from + budget);
12900
+ budget -= take.length;
12901
+ plan.carriedSections += all.length - from - take.length;
12902
+ plan.sectionCount += take.length;
12903
+ plan.estimatedTokens += Math.ceil(take.reduce((n, s) => n + (s.end - s.start), 0) / CHARS_PER_TOKEN);
12904
+ plan.sources.push({
12905
+ ref,
12906
+ file,
12907
+ converted: conv,
12908
+ sha: stamp.sha,
12909
+ priority: entry.priority,
12910
+ domain: entry.domain,
12911
+ sensitivity: entry.sensitivity,
12912
+ sections: take,
12913
+ totalSections: all.length
12914
+ });
12915
+ if (!opts.dryRun)
12916
+ writeState(kbRoot, { ref, ...stamp, sections: all.length, cursor: from + take.length, priority: entry.priority });
12917
+ }
12918
+ }
12919
+ return plan;
12920
+ }
12921
+ var execFileAsync, CONVERTED_DIR, DEFAULT_MAX_SECTIONS_PER_PASS, CHARS_PER_TOKEN, PASS_THROUGH, SECTION_TARGET_CHARS, markitdownConverter, expandHome2, fileForRef, cacheKey, convertedFile, stampFile, sha256;
12922
+ var init_ingest = __esm({
12923
+ "packages/core/dist/knowledge/ingest.js"() {
12924
+ execFileAsync = promisify2(execFile2);
12925
+ CONVERTED_DIR = ".converted";
12926
+ DEFAULT_MAX_SECTIONS_PER_PASS = 40;
12927
+ CHARS_PER_TOKEN = 4;
12928
+ PASS_THROUGH = /* @__PURE__ */ new Set([".md", ".markdown", ".txt"]);
12929
+ SECTION_TARGET_CHARS = 4e3;
12930
+ markitdownConverter = async (file) => {
12931
+ const { stdout } = await execFileAsync("markitdown", [file], { maxBuffer: 64 * 1024 * 1024 });
12932
+ return stdout;
12933
+ };
12934
+ expandHome2 = (p) => p === "~" || p.startsWith("~/") ? path19.join(os8.homedir(), p.slice(1)) : p;
12935
+ fileForRef = (kbRoot, ref) => path19.resolve(kbRoot, expandHome2(ref));
12936
+ cacheKey = (ref) => `${ref.replace(/[^A-Za-z0-9._-]+/g, "_")}.${crypto3.createHash("sha1").update(ref).digest("hex").slice(0, 6)}`;
12937
+ convertedFile = (kbRoot, ref) => path19.join(kbRoot, CONVERTED_DIR, `${cacheKey(ref)}.md`);
12938
+ stampFile = (kbRoot, ref) => path19.join(kbRoot, CONVERTED_DIR, `${cacheKey(ref)}.sha`);
12939
+ sha256 = (file) => crypto3.createHash("sha256").update(fs19.readFileSync(file)).digest("hex");
12940
+ }
12941
+ });
12942
+
12943
+ // packages/core/dist/knowledge/match.js
12944
+ import path20 from "path";
12945
+ function globMatches(glob, relPath) {
12946
+ const g = glob.replace(/^\.\//, "");
12947
+ if (globToRegExp(g).test(relPath))
12948
+ return true;
12949
+ return !g.includes("/") && globToRegExp(g).test(path20.posix.basename(relPath));
12950
+ }
12951
+ function repoRelativePaths(file, cwd, projectRoot) {
12952
+ const out = /* @__PURE__ */ new Set();
12953
+ const add = (base) => {
12954
+ const rel = path20.relative(base, file).split(path20.sep).join("/");
12955
+ if (!rel || rel.startsWith("..") || path20.isAbsolute(rel))
12956
+ return;
12957
+ out.add(rel);
12958
+ const wt = /^\.worktrees\/[^/]+\/(.+)$/.exec(rel);
12959
+ if (wt)
12960
+ out.add(wt[1]);
12961
+ };
12962
+ if (path20.isAbsolute(file)) {
12963
+ add(cwd);
12964
+ add(projectRoot);
12965
+ } else
12966
+ out.add(file.replace(/^\.\//, ""));
12967
+ return [...out];
12968
+ }
12969
+ function matchPaths(rules, project, relPaths) {
12970
+ const hits = [];
12971
+ for (const rule of rules) {
12972
+ if (rule.status !== "active")
12973
+ continue;
12974
+ if (rule.triggers?.globs?.some((g) => relPaths.some((p) => globMatches(g, p))))
12975
+ hits.push({ rule, via: "glob" });
12976
+ else if (rule.anchors?.[project]?.paths?.some((a) => relPaths.includes(a)))
12977
+ hits.push({ rule, via: "anchor" });
12978
+ }
12979
+ return hits;
12980
+ }
12981
+ function matchSymbols(rules, project, text) {
12982
+ const idents = new Set(text.match(/[A-Za-z_$][\w$]{2,}/g) ?? []);
12983
+ return rules.filter((r) => r.status === "active" && r.anchors?.[project]?.symbols?.some((s) => idents.has(s))).map((rule) => ({ rule, via: "anchor" }));
12984
+ }
12985
+ function mentionedPaths(text) {
12986
+ const found = text.match(/[\w@.\-/]*[\w-]\.[A-Za-z]{1,5}\b|[\w.-]+\/[\w./-]+/g) ?? [];
12987
+ return [...new Set(found.map((f) => f.replace(/^\.\//, "")))].slice(0, 20);
12988
+ }
12989
+ function applyBudget(items, lineLen, maxRules = INJECT_MAX_RULES, maxChars = INJECT_MAX_CHARS) {
12990
+ const shown = [];
12991
+ let chars = 0;
12992
+ for (const it of items) {
12993
+ const n = lineLen(it.rule);
12994
+ if (shown.length >= maxRules || shown.length > 0 && chars + n > maxChars)
12995
+ break;
12996
+ shown.push(it);
12997
+ chars += n;
12998
+ }
12999
+ return { shown, overflow: items.slice(shown.length) };
13000
+ }
13001
+ var INJECT_MAX_RULES, INJECT_MAX_CHARS;
13002
+ var init_match = __esm({
13003
+ "packages/core/dist/knowledge/match.js"() {
13004
+ init_ingest();
13005
+ INJECT_MAX_RULES = 5;
13006
+ INJECT_MAX_CHARS = 3200;
13007
+ }
13008
+ });
13009
+
13010
+ // packages/core/dist/knowledge/audit.js
13011
+ import fs20 from "fs";
13012
+ import os9 from "os";
13013
+ import path21 from "path";
13014
+ function appendAudit(events, sink = {}) {
13015
+ if (!events.length)
13016
+ return;
13017
+ try {
13018
+ const ts = (sink.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
13019
+ const dir = sink.dir ?? defaultAuditDir();
13020
+ const file = path21.join(dir, `${ts.slice(0, 7)}.${sink.machineId ?? auditMachineId()}.jsonl`);
13021
+ fs20.mkdirSync(dir, { recursive: true });
13022
+ const fd = fs20.openSync(file, "a");
13023
+ try {
13024
+ for (const e of events) {
13025
+ const q = e.query === void 0 ? {} : { query: e.query.slice(0, AUDIT_QUERY_CHARS) };
13026
+ fs20.writeSync(fd, JSON.stringify({ ts, ...e, ...q }) + "\n");
13027
+ }
13028
+ } finally {
13029
+ fs20.closeSync(fd);
13030
+ }
13031
+ } catch (err) {
13032
+ console.error(`squadrant audit: log write failed: ${err.message}`);
13033
+ }
13034
+ }
13035
+ function ruleAuditScope(rule) {
13036
+ return rule.layer.startsWith("kb:") ? { kb: rule.layer.slice(3), level: "group" } : { kb: "", level: "project" };
13037
+ }
13038
+ var AUDIT_QUERY_CHARS, defaultAuditDir, auditMachineId;
13039
+ var init_audit = __esm({
13040
+ "packages/core/dist/knowledge/audit.js"() {
13041
+ AUDIT_QUERY_CHARS = 200;
13042
+ defaultAuditDir = () => process.env.SQUADRANT_AUDIT_DIR || path21.join(os9.homedir(), ".local", "state", "squadrant", "audit");
13043
+ auditMachineId = () => os9.hostname().replace(/[^A-Za-z0-9_-]/g, "_") || "host";
13044
+ }
13045
+ });
13046
+
13047
+ // packages/core/dist/knowledge/inject.js
13048
+ import fs21 from "fs";
13049
+ import path22 from "path";
12716
13050
  function sessionOrder(a, b) {
12717
13051
  const proj = (r) => r.layer.startsWith("project:") ? 0 : 1;
12718
13052
  const mod = (r) => r.modality === "must-not" ? 0 : 1;
@@ -12720,7 +13054,7 @@ function sessionOrder(a, b) {
12720
13054
  }
12721
13055
  function formatSessionContext(kbs, rules, cap = SESSION_RULE_CAP) {
12722
13056
  const hard = rules.filter((r) => r.status === "active" && (r.modality === "must" || r.modality === "must-not")).sort(sessionOrder);
12723
- const shown = hard.slice(0, cap);
13057
+ const shown = applyBudget(hard.map((rule) => ({ rule })), (r) => line(r).length, cap, INJECT_MAX_CHARS).shown.map((x) => x.rule);
12724
13058
  const lines = [
12725
13059
  `<squadrant-project-rules kb="${kbs.join(",")}">`,
12726
13060
  `Rules KB: ${kbs.join(", ")}. ${DATA_NOTE}`,
@@ -12729,16 +13063,17 @@ function formatSessionContext(kbs, rules, cap = SESSION_RULE_CAP) {
12729
13063
  if (hard.length > shown.length)
12730
13064
  lines.push(`+${hard.length - shown.length} more: squadrant rules list --brief`);
12731
13065
  lines.push("Look up rules for a task with `squadrant rules search <terms>` / `squadrant rules show <id>` (rules-ops skill).");
13066
+ lines.push(CITE_NOTE);
12732
13067
  lines.push("</squadrant-project-rules>");
12733
- return { text: lines.join("\n"), ids: shown.map((r) => r.id) };
13068
+ return { text: lines.join("\n"), ids: shown.map((r) => r.id), hiddenIds: hard.slice(shown.length).map((r) => r.id) };
12734
13069
  }
12735
13070
  function cleanPromptQuery(prompt) {
12736
13071
  const text = prompt.replace(/```[\s\S]*?(```|$)/g, " ").split("\n").filter((l) => l.length <= MAX_LINE_CHARS).join("\n").slice(0, MAX_PROMPT_CHARS).toLowerCase().replace(/https?:\/\/\S+/g, " ");
12737
- const terms = [];
13072
+ const terms2 = [];
12738
13073
  const add = (t) => {
12739
- if (t.length < 3 && !SHORT_TERMS.has(t) || /^\d+$/.test(t) || STOPWORDS.has(t) || terms.includes(t))
13074
+ if (t.length < 3 && !SHORT_TERMS.has(t) || /^\d+$/.test(t) || STOPWORDS.has(t) || terms2.includes(t))
12740
13075
  return;
12741
- terms.push(t);
13076
+ terms2.push(t);
12742
13077
  };
12743
13078
  for (const raw of text.split(/[^a-z0-9._-]+/)) {
12744
13079
  const t = raw.replace(/^[._-]+|[._-]+$/g, "");
@@ -12748,12 +13083,12 @@ function cleanPromptQuery(prompt) {
12748
13083
  const parts = t.split(/[._-]+/);
12749
13084
  if (parts.length > 1 && parts.every((x) => x.length >= 3 && !PREFIXES.has(x)))
12750
13085
  parts.forEach(add);
12751
- if (terms.length >= MAX_TERMS)
13086
+ if (terms2.length >= MAX_TERMS)
12752
13087
  break;
12753
13088
  }
12754
- return terms.slice(0, MAX_TERMS).join(" ");
13089
+ return terms2.slice(0, MAX_TERMS).join(" ");
12755
13090
  }
12756
- function wholeWordTerms(rule, terms) {
13091
+ function wholeWordTerms(rule, terms2) {
12757
13092
  const text = [
12758
13093
  rule.id,
12759
13094
  ...rule.triggers?.keywords ?? [],
@@ -12761,18 +13096,18 @@ function wholeWordTerms(rule, terms) {
12761
13096
  rule.triggers?.when ?? "",
12762
13097
  rule.statement
12763
13098
  ].join("\n").toLowerCase();
12764
- return terms.filter((t) => {
13099
+ return terms2.filter((t) => {
12765
13100
  const base = t.length > 4 ? t.replace(/(es|s)$/, "") : t;
12766
13101
  return new RegExp(`(^|[^a-z0-9])(?:${escapeRe(t)}|${escapeRe(base)}(?:s|es)?)($|[^a-z0-9])`).test(text);
12767
13102
  });
12768
13103
  }
12769
13104
  function selectPromptHits(hits, query, seen, opts = {}) {
12770
13105
  const minScore = opts.minScore ?? PROMPT_MIN_SCORE;
12771
- const terms = query.split(" ").filter(Boolean);
13106
+ const terms2 = query.split(" ").filter(Boolean);
12772
13107
  return hits.filter((h) => {
12773
13108
  if (seen.has(h.rule.id))
12774
13109
  return false;
12775
- const n = wholeWordTerms(h.rule, terms).length;
13110
+ const n = wholeWordTerms(h.rule, terms2).length;
12776
13111
  return n >= 2 && h.score >= minScore || n === 1 && h.score >= PROMPT_SINGLE_TERM_MIN_SCORE;
12777
13112
  }).slice(0, opts.cap ?? PROMPT_RULE_CAP);
12778
13113
  }
@@ -12785,25 +13120,56 @@ function promptText(prompt) {
12785
13120
  if (!m)
12786
13121
  return prompt;
12787
13122
  try {
12788
- return fs19.readFileSync(m[1], "utf8").slice(0, MAX_PROMPT_CHARS * 2);
13123
+ return fs21.readFileSync(m[1], "utf8").slice(0, MAX_PROMPT_CHARS * 2);
12789
13124
  } catch {
12790
13125
  return prompt;
12791
13126
  }
12792
13127
  }
12793
- function formatPromptContext(hits) {
13128
+ function formatPromptContext(hits, match = "prompt", overflowIds = []) {
12794
13129
  return [
12795
- '<squadrant-project-rules match="prompt">',
12796
- `Rules that may apply to this prompt. ${DATA_NOTE}`,
13130
+ `<squadrant-project-rules match="${match}">`,
13131
+ `Rules that may apply to this ${match === "read" ? "file" : "prompt"}. ${DATA_NOTE}`,
12797
13132
  ...hits.map((h) => line(h.rule)),
13133
+ ...overflowIds.length ? [`+${overflowIds.length} more: squadrant rules show ${overflowIds.join(" ")}`] : [],
13134
+ CITE_NOTE,
12798
13135
  "</squadrant-project-rules>"
12799
13136
  ].join("\n");
12800
13137
  }
13138
+ function readTargetPath(payload) {
13139
+ const q = typeof payload === "object" && payload !== null ? payload : {};
13140
+ const tool = typeof q.tool_name === "string" ? q.tool_name.toLowerCase() : "";
13141
+ const input = typeof q.tool_input === "object" && q.tool_input !== null ? q.tool_input : {};
13142
+ if (READ_TOOLS.has(tool)) {
13143
+ for (const k of ["file_path", "absolute_path", "filePath", "path"]) {
13144
+ if (typeof input[k] === "string" && input[k])
13145
+ return input[k];
13146
+ }
13147
+ return null;
13148
+ }
13149
+ const cmd = SHELL_TOOLS.has(tool) ? input.command ?? input.cmd : void 0;
13150
+ const text = Array.isArray(cmd) ? cmd.filter((x) => typeof x === "string").join(" ") : cmd;
13151
+ if (typeof text !== "string" || SHELL_META.test(text))
13152
+ return null;
13153
+ const words = text.trim().split(/\s+/);
13154
+ if (words.length < 2 || !/^(cat|head|tail|nl|bat|sed)$/.test(words[0]))
13155
+ return null;
13156
+ const last = words[words.length - 1].replace(/^["']|["']$/g, "");
13157
+ return last && !last.startsWith("-") ? last : null;
13158
+ }
13159
+ function rulesFallbackPointer(kbs) {
13160
+ return [
13161
+ "## Project rules (squadrant)",
13162
+ "",
13163
+ `This project subscribes to the rules KB: ${kbs.join(", ")}. ${DATA_NOTE}`,
13164
+ "Before changing code, look up the rules that apply with `squadrant rules search <terms>`, then `squadrant rules show <id>`."
13165
+ ].join("\n");
13166
+ }
12801
13167
  function injectStateDir(stateRoot) {
12802
- return path18.join(stateRoot, "rules-inject");
13168
+ return path22.join(stateRoot, "rules-inject");
12803
13169
  }
12804
13170
  function readSeenRules(stateRoot, sessionId) {
12805
13171
  try {
12806
- const ids = JSON.parse(fs19.readFileSync(sessionFile(stateRoot, sessionId), "utf8"));
13172
+ const ids = JSON.parse(fs21.readFileSync(sessionFile(stateRoot, sessionId), "utf8"));
12807
13173
  return new Set(Array.isArray(ids) ? ids.filter((x) => typeof x === "string") : []);
12808
13174
  } catch {
12809
13175
  return /* @__PURE__ */ new Set();
@@ -12815,22 +13181,22 @@ function recordSeenRules(stateRoot, sessionId, ids) {
12815
13181
  const seen = readSeenRules(stateRoot, sessionId);
12816
13182
  for (const id of ids)
12817
13183
  seen.add(id);
12818
- fs19.mkdirSync(injectStateDir(stateRoot), { recursive: true });
12819
- fs19.writeFileSync(sessionFile(stateRoot, sessionId), JSON.stringify([...seen]));
13184
+ fs21.mkdirSync(injectStateDir(stateRoot), { recursive: true });
13185
+ fs21.writeFileSync(sessionFile(stateRoot, sessionId), JSON.stringify([...seen]));
12820
13186
  }
12821
13187
  function pruneSeenRules(stateRoot, now = Date.now()) {
12822
13188
  const dir = injectStateDir(stateRoot);
12823
13189
  let names;
12824
13190
  try {
12825
- names = fs19.readdirSync(dir);
13191
+ names = fs21.readdirSync(dir);
12826
13192
  } catch {
12827
13193
  return;
12828
13194
  }
12829
13195
  for (const n of names) {
12830
- const f = path18.join(dir, n);
13196
+ const f = path22.join(dir, n);
12831
13197
  try {
12832
- if (now - fs19.statSync(f).mtimeMs > SEEN_TTL_MS)
12833
- fs19.unlinkSync(f);
13198
+ if (now - fs21.statSync(f).mtimeMs > SEEN_TTL_MS)
13199
+ fs21.unlinkSync(f);
12834
13200
  } catch {
12835
13201
  }
12836
13202
  }
@@ -12858,13 +13224,27 @@ function resolveInjectProject(cfg, cwd, env, project) {
12858
13224
  let bestLen = -1;
12859
13225
  for (const [name, pc] of Object.entries(cfg.projects)) {
12860
13226
  const root = resolveHome(pc.path);
12861
- if ((cwd === root || cwd.startsWith(root + path18.sep)) && root.length > bestLen) {
13227
+ if ((cwd === root || cwd.startsWith(root + path22.sep)) && root.length > bestLen) {
12862
13228
  best = name;
12863
13229
  bestLen = root.length;
12864
13230
  }
12865
13231
  }
12866
13232
  return best;
12867
13233
  }
13234
+ function audit(i, project, session, trigger, items) {
13235
+ appendAudit(items.map(({ rule, event, score }) => ({
13236
+ ...ruleAuditScope(rule),
13237
+ project,
13238
+ domain: "rules",
13239
+ itemId: rule.id,
13240
+ event,
13241
+ trigger,
13242
+ ...score === void 0 ? {} : { score },
13243
+ chars: line(rule).length,
13244
+ agent: i.agent ?? "claude",
13245
+ ...session ? { session } : {}
13246
+ })), i.audit);
13247
+ }
12868
13248
  function computeRulesInjection(i) {
12869
13249
  if (i.env.SQUADRANT_RULES_INJECT === "0")
12870
13250
  return null;
@@ -12878,32 +13258,60 @@ function computeRulesInjection(i) {
12878
13258
  return null;
12879
13259
  const sessionId = typeof p.session_id === "string" && p.session_id ? p.session_id : null;
12880
13260
  if (i.event === "session-start") {
12881
- const { text, ids } = formatSessionContext(kbs, resolveProjectRules(i.cfg, project).rules);
13261
+ const rules2 = resolveProjectRules(i.cfg, project).rules;
13262
+ const { text: text2, ids, hiddenIds } = formatSessionContext(kbs, rules2);
13263
+ const byId = new Map(rules2.map((r) => [r.id, r]));
13264
+ audit(i, project, sessionId, "session", [
13265
+ ...ids.map((id) => ({ id, event: "item.surfaced" })),
13266
+ ...hiddenIds.map((id) => ({ id, event: "item.suppressed" }))
13267
+ ].map((e) => ({ rule: byId.get(e.id), event: e.event, score: void 0 })));
12882
13268
  if (sessionId) {
12883
13269
  pruneSeenRules(i.stateRoot);
12884
13270
  recordSeenRules(i.stateRoot, sessionId, ids);
12885
13271
  }
12886
- return text;
13272
+ return text2;
13273
+ }
13274
+ const rules = resolveProjectRules(i.cfg, project).rules;
13275
+ const seen = sessionId ? readSeenRules(i.stateRoot, sessionId) : /* @__PURE__ */ new Set();
13276
+ if (i.event === "post-read") {
13277
+ const file = readTargetPath(p);
13278
+ if (!file)
13279
+ return null;
13280
+ const rels = repoRelativePaths(file, cwd, resolveHome(i.cfg.projects[project].path));
13281
+ const hits = matchPaths(rules, project, rels).map((h) => ({ rule: h.rule, score: h.via === "glob" ? 100 : 90, matched: 1, curated: true }));
13282
+ return deliver(i, project, sessionId, "tool", "read", hits, seen, []);
12887
13283
  }
12888
13284
  if (typeof p.prompt !== "string" || isSystemPrompt(p.prompt))
12889
13285
  return null;
12890
- const query = cleanPromptQuery(promptText(p.prompt));
12891
- if (!query)
12892
- return null;
12893
- const seen = sessionId ? readSeenRules(i.stateRoot, sessionId) : /* @__PURE__ */ new Set();
12894
- const picked = selectPromptHits(searchRules(resolveProjectRules(i.cfg, project).rules, query), query, seen);
12895
- if (!picked.length)
13286
+ const text = promptText(p.prompt);
13287
+ const query = cleanPromptQuery(text);
13288
+ const structural = [...matchPaths(rules, project, mentionedPaths(text)), ...matchSymbols(rules, project, text)].map((h) => ({ rule: h.rule, score: 100, matched: 1, curated: true }));
13289
+ const lexical = query ? searchRules(rules, query) : [];
13290
+ const picked = selectPromptHits(lexical, query, seen);
13291
+ const all = [...new Map([...structural, ...picked].map((h) => [h.rule.id, h])).values()];
13292
+ return deliver(i, project, sessionId, "prompt", "prompt", all, seen, lexical.filter((h) => !picked.includes(h)));
13293
+ }
13294
+ function deliver(i, project, sessionId, trigger, match, candidates, seen, belowBar) {
13295
+ const fresh = candidates.filter((h) => !seen.has(h.rule.id));
13296
+ const { shown, overflow } = applyBudget(fresh, (r) => line(r).length);
13297
+ audit(i, project, sessionId, trigger, [
13298
+ ...shown.map((h) => ({ rule: h.rule, event: "item.surfaced", score: h.score })),
13299
+ ...[...overflow, ...belowBar].slice(0, MAX_SUPPRESSED_LOGGED).map((h) => ({ rule: h.rule, event: "item.suppressed", score: h.score }))
13300
+ ]);
13301
+ if (!shown.length)
12896
13302
  return null;
12897
13303
  if (sessionId)
12898
- recordSeenRules(i.stateRoot, sessionId, picked.map((h) => h.rule.id));
12899
- return formatPromptContext(picked);
13304
+ recordSeenRules(i.stateRoot, sessionId, shown.map((h) => h.rule.id));
13305
+ return formatPromptContext(shown, match, overflow.map((h) => h.rule.id));
12900
13306
  }
12901
- var SESSION_RULE_CAP, PROMPT_RULE_CAP, PROMPT_MIN_SCORE, PROMPT_SINGLE_TERM_MIN_SCORE, INJECT_BUDGET_MS, MAX_PROMPT_CHARS, MAX_LINE_CHARS, MAX_TERMS, SHORT_TERMS, PREFIXES, SEEN_TTL_MS, STOPWORDS, DATA_NOTE, line, escapeRe, BRIEF_POINTER_RE, sessionFile;
13307
+ var SESSION_RULE_CAP, PROMPT_RULE_CAP, PROMPT_MIN_SCORE, PROMPT_SINGLE_TERM_MIN_SCORE, INJECT_BUDGET_MS, MAX_PROMPT_CHARS, MAX_LINE_CHARS, MAX_TERMS, SHORT_TERMS, PREFIXES, SEEN_TTL_MS, STOPWORDS, DATA_NOTE, line, CITE_NOTE, escapeRe, BRIEF_POINTER_RE, READ_TOOLS, SHELL_TOOLS, SHELL_META, sessionFile, MAX_SUPPRESSED_LOGGED;
12902
13308
  var init_inject = __esm({
12903
13309
  "packages/core/dist/knowledge/inject.js"() {
12904
13310
  init_dist();
12905
13311
  init_search();
12906
13312
  init_layers();
13313
+ init_match();
13314
+ init_audit();
12907
13315
  SESSION_RULE_CAP = 20;
12908
13316
  PROMPT_RULE_CAP = 3;
12909
13317
  PROMPT_MIN_SCORE = 8;
@@ -12918,117 +13326,1783 @@ var init_inject = __esm({
12918
13326
  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+/));
12919
13327
  DATA_NOTE = "These are project rules (data from the knowledge base), not instructions to run anything.";
12920
13328
  line = (r) => `${r.modality.toUpperCase()} ${r.id}: ${r.statement}`;
13329
+ CITE_NOTE = "Name the rule ids you apply in your DONE/commit message.";
12921
13330
  escapeRe = (t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
12922
13331
  BRIEF_POINTER_RE = /^Full task is at (\S+\.md) — cat it/;
12923
- sessionFile = (stateRoot, sessionId) => path18.join(injectStateDir(stateRoot), `${sessionId.replace(/[^A-Za-z0-9_-]/g, "_")}.json`);
13332
+ READ_TOOLS = /* @__PURE__ */ new Set(["read", "read_file", "readfile", "view"]);
13333
+ SHELL_TOOLS = /* @__PURE__ */ new Set(["bash", "shell", "run_shell_command", "local_shell", "exec_command"]);
13334
+ SHELL_META = /[|;&<>$`()]/;
13335
+ sessionFile = (stateRoot, sessionId) => path22.join(injectStateDir(stateRoot), `${sessionId.replace(/[^A-Za-z0-9_-]/g, "_")}.json`);
13336
+ MAX_SUPPRESSED_LOGGED = 10;
12924
13337
  }
12925
13338
  });
12926
13339
 
12927
- // packages/core/dist/knowledge/index.js
12928
- var init_knowledge2 = __esm({
12929
- "packages/core/dist/knowledge/index.js"() {
13340
+ // packages/core/dist/knowledge/migrate.js
13341
+ import fs22 from "fs";
13342
+ import path23 from "path";
13343
+ import { execFileSync as execFileSync6 } from "child_process";
13344
+ function findLegacyKbs(cfg) {
13345
+ const root = legacyRoot(cfg);
13346
+ if (!fs22.existsSync(root))
13347
+ return [];
13348
+ return fs22.readdirSync(root, { withFileTypes: true }).filter((e) => e.isDirectory() && KB_NAME_RE.test(e.name)).map((e) => e.name).sort();
13349
+ }
13350
+ function ensureGitRepo(dir) {
13351
+ if (fs22.existsSync(path23.join(dir, ".git")))
13352
+ return false;
13353
+ try {
13354
+ execFileSync6("git", ["init", "-q", dir], { stdio: "ignore" });
13355
+ return true;
13356
+ } catch {
13357
+ return false;
13358
+ }
13359
+ }
13360
+ function findLegacyOverlays(cfg) {
13361
+ return Object.keys(cfg.projects).filter((p) => fs22.existsSync(legacyOverlay(cfg, p))).sort();
13362
+ }
13363
+ function moveLegacyOverlays(cfg, opts = {}) {
13364
+ return findLegacyOverlays(cfg).map((project) => {
13365
+ const from = legacyOverlay(cfg, project);
13366
+ const to = projectRulesHome(cfg, project);
13367
+ const kb = `overlay:${project}`;
13368
+ if (!to)
13369
+ return { kb, from, to: "", action: "skipped", note: "no group KB or knowledgeHome for this project; set one, then re-run" };
13370
+ if (fs22.existsSync(to))
13371
+ return { kb, from, to, action: "skipped", note: "target already exists; nothing moved" };
13372
+ if (opts.dryRun)
13373
+ return { kb, from, to, action: "would-move" };
13374
+ fs22.mkdirSync(path23.dirname(to), { recursive: true });
13375
+ try {
13376
+ fs22.renameSync(from, to);
13377
+ return { kb, from, to, action: "moved" };
13378
+ } catch (e) {
13379
+ if (e.code !== "EXDEV")
13380
+ throw e;
13381
+ fs22.cpSync(from, to, { recursive: true });
13382
+ return { kb, from, to, action: "copied", note: "cross-device: original left in place, delete it yourself once verified" };
13383
+ }
13384
+ });
13385
+ }
13386
+ function moveLegacyKbs(cfg, opts = {}) {
13387
+ const hub = resolveHome(cfg.hubVault);
13388
+ return findLegacyKbs(cfg).map((kb) => {
13389
+ const from = legacyKbDir(hub, kb);
13390
+ const to = kbDir(cfg, kb);
13391
+ if (fs22.existsSync(to))
13392
+ return { kb, from, to, action: "skipped", note: "target already exists; nothing moved" };
13393
+ if (opts.dryRun)
13394
+ return { kb, from, to, action: "would-move" };
13395
+ fs22.mkdirSync(path23.dirname(to), { recursive: true });
13396
+ let action = "moved";
13397
+ try {
13398
+ fs22.renameSync(from, to);
13399
+ } catch (e) {
13400
+ if (e.code !== "EXDEV")
13401
+ throw e;
13402
+ fs22.cpSync(from, to, { recursive: true });
13403
+ action = "copied";
13404
+ }
13405
+ const notes = [];
13406
+ const oldRules = path23.join(to, "rules"), newRules = path23.join(to, "shared", "rules");
13407
+ if (fs22.existsSync(oldRules) && !fs22.existsSync(newRules)) {
13408
+ fs22.mkdirSync(path23.dirname(newRules), { recursive: true });
13409
+ fs22.renameSync(oldRules, newRules);
13410
+ notes.push("rules/ -> shared/rules/");
13411
+ }
13412
+ if (ensureGitRepo(to))
13413
+ notes.push("git init");
13414
+ if (fs22.existsSync(path23.join(to, "index.json")))
13415
+ notes.push(`index.json holds old paths: run squadrant knowledge reindex ${kb}`);
13416
+ if (action === "copied")
13417
+ notes.push("cross-device: original left in place, delete it yourself once verified");
13418
+ return { kb, from, to, action, ...notes.length ? { note: notes.join("; ") } : {} };
13419
+ });
13420
+ }
13421
+ var legacyRoot, legacyOverlay;
13422
+ var init_migrate = __esm({
13423
+ "packages/core/dist/knowledge/migrate.js"() {
13424
+ init_dist();
12930
13425
  init_paths();
12931
- init_store2();
12932
- init_layers();
12933
- init_index_file();
12934
- init_search();
12935
- init_sources();
12936
- init_inject();
13426
+ legacyRoot = (cfg) => path23.join(resolveHome(cfg.hubVault), "knowledge");
13427
+ legacyOverlay = (cfg, project) => path23.join(resolveHome(cfg.projects[project].spokeVault), "knowledge", "rules");
12937
13428
  }
12938
13429
  });
12939
13430
 
12940
- // packages/core/dist/index.js
12941
- var dist_exports = {};
12942
- __export(dist_exports, {
12943
- AGENT_BINS: () => AGENT_BINS,
12944
- ANTHROPIC_ONLY_REQUEST_FIELDS: () => ANTHROPIC_ONLY_REQUEST_FIELDS,
12945
- BOT_COMMANDS: () => BOT_COMMANDS,
12946
- CAPTAIN_RECEIVED_ACK: () => CAPTAIN_RECEIVED_ACK,
12947
- CC_SOCKS_DIR: () => CC_SOCKS_DIR,
12948
- CMUX_PRESERVE_CLAUDE_AUTH_ENV: () => CMUX_PRESERVE_CLAUDE_AUTH_ENV,
12949
- CONFLICT_409_SELF_HEAL_MS: () => CONFLICT_409_SELF_HEAL_MS,
12950
- CREW_GONE_MS: () => CREW_GONE_MS,
12951
- CREW_STALE_MS: () => CREW_STALE_MS,
12952
- CaptainDelivery: () => CaptainDelivery,
12953
- DEFAULT_BLOCKED_RENUDGE_MS: () => DEFAULT_BLOCKED_RENUDGE_MS,
12954
- DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS: () => DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS,
12955
- DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS: () => DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS,
12956
- DEFAULT_GATE_DENY_RULES: () => DEFAULT_GATE_DENY_RULES,
12957
- DEFAULT_GATE_TOOLS: () => DEFAULT_GATE_TOOLS,
12958
- DEFAULT_SEARCH_STATUSES: () => DEFAULT_SEARCH_STATUSES,
12959
- DEFAULT_TASK_TIMEOUT_MS: () => DEFAULT_TASK_TIMEOUT_MS,
12960
- DeferDelivery: () => DeferDelivery,
12961
- FIRST_TURN_INLINE_MAX_BYTES: () => FIRST_TURN_INLINE_MAX_BYTES,
12962
- FactLog: () => FactLog,
12963
- GROUP_DISPATCH_WARMUP_POLL_MS: () => GROUP_DISPATCH_WARMUP_POLL_MS,
12964
- GROUP_DISPATCH_WARMUP_TIMEOUT_MS: () => GROUP_DISPATCH_WARMUP_TIMEOUT_MS,
12965
- GateDecisionCache: () => GateDecisionCache,
12966
- IDLE_DEBOUNCE_MS: () => IDLE_DEBOUNCE_MS,
12967
- INJECT_BUDGET_MS: () => INJECT_BUDGET_MS,
12968
- LABEL: () => LABEL,
12969
- LivenessRegistry: () => LivenessRegistry,
12970
- MONITOR_STALL_BUDGET_MS: () => MONITOR_STALL_BUDGET_MS,
12971
- OPERATOR_INITIATED_COMMANDS: () => OPERATOR_INITIATED_COMMANDS,
12972
- PANE_IDLE_AFTER_MS: () => PANE_IDLE_AFTER_MS,
12973
- PROBE_QUIET_MS: () => PROBE_QUIET_MS,
12974
- PROMPT_MIN_SCORE: () => PROMPT_MIN_SCORE,
12975
- PROMPT_RULE_CAP: () => PROMPT_RULE_CAP,
12976
- PROMPT_SINGLE_TERM_MIN_SCORE: () => PROMPT_SINGLE_TERM_MIN_SCORE,
12977
- PROPOSED_DIR: () => PROPOSED_DIR,
12978
- PROTOCOL_VERSION: () => PROTOCOL_VERSION,
12979
- READ_ONLY_CREW_SUBCOMMANDS: () => READ_ONLY_CREW_SUBCOMMANDS,
12980
- READ_ONLY_TOP_LEVEL_COMMANDS: () => READ_ONLY_TOP_LEVEL_COMMANDS,
12981
- ROUTER_READY_ATTEMPTS: () => ROUTER_READY_ATTEMPTS,
12982
- ROUTER_READY_DELAY_MS: () => ROUTER_READY_DELAY_MS,
12983
- SERVER_TOOL_TYPE_RE: () => SERVER_TOOL_TYPE_RE,
12984
- SESSION_RULE_CAP: () => SESSION_RULE_CAP,
12985
- SIDE_SESSION_ENV: () => SIDE_SESSION_ENV,
12986
- SOURCES_TEMPLATE: () => SOURCES_TEMPLATE,
12987
- STALE_THRESHOLD_MS: () => STALE_THRESHOLD_MS,
12988
- STUCK_ALERT_TEXT: () => STUCK_ALERT_TEXT,
12989
- TERMINAL_RECORD_KEEP_PER_PROJECT: () => TERMINAL_RECORD_KEEP_PER_PROJECT,
12990
- TERMINAL_RECORD_TTL_MS: () => TERMINAL_RECORD_TTL_MS,
12991
- TOOL_STALL_BUDGET_MS: () => TOOL_STALL_BUDGET_MS,
12992
- TYPING_KEEPALIVE_MS: () => TYPING_KEEPALIVE_MS,
12993
- TelegramApiError: () => TelegramApiError,
12994
- WATCHDOG_GRACE_MS: () => WATCHDOG_GRACE_MS,
12995
- WATCHDOG_MS: () => WATCHDOG_MS,
12996
- WORK_ITEM_TTL_MS: () => WORK_ITEM_TTL_MS,
12997
- WRITABLE_CONFIG_KEYS: () => WRITABLE_CONFIG_KEYS,
12998
- _resetRestartInFlightForTest: () => _resetRestartInFlightForTest,
12999
- ageText: () => ageText,
13000
- anthropicError: () => anthropicError,
13001
- appendCaptainMessage: () => appendCaptainMessage,
13002
- appendToMailbox: () => appendToMailbox,
13003
- assembleDaemonSnapshot: () => assembleDaemonSnapshot,
13004
- assertBackendUsable: () => assertBackendUsable,
13005
- bootWorkspace: () => bootWorkspace,
13006
- buildClassifierInput: () => buildClassifierInput,
13007
- buildCompletionProtocol: () => buildCompletionProtocol,
13008
- buildContext: () => buildContext,
13009
- buildDaemonPath: () => buildDaemonPath,
13010
- buildFreshness: () => buildFreshness,
13011
- buildOpencodeGateRunCommand: () => buildOpencodeGateRunCommand,
13012
- buildRouterCredentialsRequest: () => buildRouterCredentialsRequest,
13013
- buildRouterEnv: () => buildRouterEnv,
13014
- buildSideFirstTurn: () => buildSideFirstTurn,
13015
- callClassifier: () => callClassifier,
13016
- capAllowed: () => capAllowed,
13017
- capOutput: () => capOutput,
13018
- captainAliveStateFromHealth: () => captainAliveStateFromHealth,
13019
- captainRecordPath: () => captainRecordPath,
13020
- captainSocketPath: () => captainSocketPath,
13021
- checkFact: () => checkFact,
13022
- classifyCaptainPane: () => classifyCaptainPane,
13023
- classifyHealth: () => classifyHealth,
13024
- claudeEnvShadowsRouter: () => claudeEnvShadowsRouter,
13431
+ // packages/core/dist/knowledge/verify.js
13432
+ function normalizeWithMap(src, keepBreakHyphen = false) {
13433
+ const out = [];
13434
+ const map2 = [];
13435
+ const push = (s, at) => {
13436
+ for (const ch of s) {
13437
+ out.push(ch);
13438
+ map2.push(at);
13439
+ }
13440
+ };
13441
+ let i = 0;
13442
+ while (i < src.length) {
13443
+ const ch = src[i];
13444
+ if (ch === "-" && /^-[ \t]*\r?\n[ \t]*[a-z]/.test(src.slice(i, i + 40)) && i > 0 && /[A-Za-z]/.test(src[i - 1])) {
13445
+ if (keepBreakHyphen)
13446
+ push("-", i);
13447
+ i++;
13448
+ while (/\s/.test(src[i]))
13449
+ i++;
13450
+ continue;
13451
+ }
13452
+ if (/\s/.test(ch)) {
13453
+ const start = i;
13454
+ while (i < src.length && /\s/.test(src[i]))
13455
+ i++;
13456
+ if (out.length && out[out.length - 1] !== " ")
13457
+ push(" ", start);
13458
+ continue;
13459
+ }
13460
+ if (LIGATURES[ch])
13461
+ push(LIGATURES[ch], i);
13462
+ else if (QUOTES[ch])
13463
+ push(QUOTES[ch], i);
13464
+ else if (DASHES.has(ch))
13465
+ push("-", i);
13466
+ else
13467
+ push(ch, i);
13468
+ i++;
13469
+ }
13470
+ while (out.length && out[out.length - 1] === " ") {
13471
+ out.pop();
13472
+ map2.pop();
13473
+ }
13474
+ return { text: out.join(""), map: map2 };
13475
+ }
13476
+ function findQuote(source, quote) {
13477
+ const needle = normalizeText(quote);
13478
+ if (!needle)
13479
+ return null;
13480
+ const views = typeof source === "string" ? normalizedViews(source) : source;
13481
+ for (const hay of views) {
13482
+ const at = hay.text.indexOf(needle);
13483
+ if (at >= 0)
13484
+ return [hay.map[at], hay.map[at + needle.length - 1] + 1];
13485
+ }
13486
+ return null;
13487
+ }
13488
+ var LIGATURES, QUOTES, DASHES, normalizeText, normalizedViews, DOCLING_DROP_THRESHOLD, needsDocling, spansOverlap;
13489
+ var init_verify = __esm({
13490
+ "packages/core/dist/knowledge/verify.js"() {
13491
+ LIGATURES = { "\uFB01": "fi", "\uFB02": "fl", "\uFB00": "ff", "\uFB03": "ffi", "\uFB04": "ffl", "\uFB05": "st", "\uFB06": "st" };
13492
+ QUOTES = { "\u2018": "'", "\u2019": "'", "\u201A": "'", "\u201B": "'", "\u201C": '"', "\u201D": '"', "\u201E": '"', "\u201F": '"' };
13493
+ DASHES = /* @__PURE__ */ new Set(["\u2010", "\u2011", "\u2012", "\u2013", "\u2014", "\u2015", "\u2212"]);
13494
+ normalizeText = (s) => normalizeWithMap(s).text;
13495
+ normalizedViews = (src) => [normalizeWithMap(src), normalizeWithMap(src, true)];
13496
+ DOCLING_DROP_THRESHOLD = 0.2;
13497
+ needsDocling = (dropped, total) => total > 0 && dropped / total > DOCLING_DROP_THRESHOLD;
13498
+ spansOverlap = (a, b) => a[0] < b[1] && b[0] < a[1];
13499
+ }
13500
+ });
13501
+
13502
+ // packages/core/dist/knowledge/anchor.js
13503
+ import fs23 from "fs";
13504
+ import path24 from "path";
13505
+ function* repoFiles(root) {
13506
+ let count = 0;
13507
+ const stack = [root];
13508
+ while (stack.length) {
13509
+ const dir = stack.pop();
13510
+ let ents;
13511
+ try {
13512
+ ents = fs23.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name < b.name ? 1 : -1);
13513
+ } catch {
13514
+ continue;
13515
+ }
13516
+ for (const ent of ents) {
13517
+ if (ent.isDirectory()) {
13518
+ if (!SKIP_DIRS.has(ent.name))
13519
+ stack.push(path24.join(dir, ent.name));
13520
+ } else if (ent.isFile() && CODE_EXT.has(path24.extname(ent.name))) {
13521
+ if (++count > MAX_FILES)
13522
+ return;
13523
+ yield path24.join(dir, ent.name);
13524
+ }
13525
+ }
13526
+ }
13527
+ }
13528
+ function computeAnchor(repoRoot, triggers) {
13529
+ const terms2 = anchorTerms(triggers);
13530
+ if (!terms2.length || !fs23.existsSync(repoRoot))
13531
+ return null;
13532
+ const hits = [];
13533
+ for (const file of repoFiles(repoRoot)) {
13534
+ let text;
13535
+ try {
13536
+ if (fs23.statSync(file).size > MAX_FILE_BYTES)
13537
+ continue;
13538
+ text = fs23.readFileSync(file, "utf8");
13539
+ } catch {
13540
+ continue;
13541
+ }
13542
+ const hay = `${path24.relative(repoRoot, file)}
13543
+ ${text}`.toLowerCase();
13544
+ const score = terms2.filter((t) => hay.includes(t)).length;
13545
+ if (!score)
13546
+ continue;
13547
+ const symbols2 = [...text.matchAll(SYMBOL_RE)].map((m) => m[1]).filter((s) => terms2.some((t) => s.toLowerCase().includes(t)));
13548
+ hits.push({ rel: path24.relative(repoRoot, file).split(path24.sep).join("/"), score, symbols: symbols2 });
13549
+ }
13550
+ if (!hits.length)
13551
+ return null;
13552
+ hits.sort((a, b) => b.score - a.score || (a.rel < b.rel ? -1 : 1));
13553
+ const top = hits.slice(0, MAX_PATHS);
13554
+ const symbols = [...new Set(top.flatMap((h) => h.symbols))].sort().slice(0, MAX_SYMBOLS);
13555
+ return { paths: top.map((h) => h.rel).sort(), ...symbols.length ? { symbols } : {} };
13556
+ }
13557
+ var SKIP_DIRS, CODE_EXT, MAX_FILES, MAX_FILE_BYTES, MAX_PATHS, MAX_SYMBOLS, SYMBOL_RE, anchorTerms;
13558
+ var init_anchor = __esm({
13559
+ "packages/core/dist/knowledge/anchor.js"() {
13560
+ SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", "coverage", ".next", ".worktrees", ".planning", ".converted"]);
13561
+ CODE_EXT = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs", ".java", ".kt", ".swift", ".rb", ".php", ".cs", ".sol", ".sql", ".md"]);
13562
+ MAX_FILES = 5e3;
13563
+ MAX_FILE_BYTES = 256 * 1024;
13564
+ MAX_PATHS = 5;
13565
+ MAX_SYMBOLS = 10;
13566
+ SYMBOL_RE = /\b(?:function|class|interface|type|const|let|var|enum|def|fn|struct)\s+([A-Za-z_]\w*)/g;
13567
+ anchorTerms = (t) => [...new Set([...t?.keywords ?? [], ...t?.expanded ?? []].map((s) => s.toLowerCase().trim()).filter((s) => s.length >= 3))].sort();
13568
+ }
13569
+ });
13570
+
13571
+ // packages/core/dist/knowledge/extract.js
13572
+ import fs24 from "fs";
13573
+ import path25 from "path";
13574
+ import matter3 from "gray-matter";
13575
+ function serialize(fm, body) {
13576
+ return matter3.stringify(body.trim() + "\n", clean(fm));
13577
+ }
13578
+ function validateCandidate(c) {
13579
+ const p = [];
13580
+ if (typeof c.source !== "string" || !c.source)
13581
+ p.push("source must be a non-empty string");
13582
+ if (typeof c.id !== "string" || !RULE_ID_RE.test(c.id))
13583
+ p.push(`id must match ${RULE_ID_RE}`);
13584
+ if (typeof c.domain !== "string" || !/^[a-z][a-z0-9-]*$/.test(c.domain))
13585
+ p.push("domain must be a lowercase slug");
13586
+ if (!RULE_MODALITIES.includes(c.modality))
13587
+ p.push(`modality must be one of ${RULE_MODALITIES.join("|")}`);
13588
+ if (typeof c.statement !== "string" || !c.statement.trim())
13589
+ p.push("statement must be non-empty");
13590
+ if (typeof c.quote !== "string" || !c.quote.trim())
13591
+ p.push("quote must be non-empty");
13592
+ const t = c.triggers;
13593
+ if (t !== void 0) {
13594
+ for (const k of ["globs", "keywords", "expanded"]) {
13595
+ const v2 = t[k];
13596
+ if (v2 !== void 0 && !(Array.isArray(v2) && v2.every((x) => typeof x === "string")))
13597
+ p.push(`triggers.${k} must be a string array`);
13598
+ }
13599
+ for (const g of t.globs ?? []) {
13600
+ if (typeof g === "string" && /^\s*$/.test(g))
13601
+ p.push("triggers.globs entries must be non-empty");
13602
+ }
13603
+ }
13604
+ return p;
13605
+ }
13606
+ function upsertSource(fm, src) {
13607
+ const i = fm.sources.findIndex((s) => s.ref === src.ref);
13608
+ if (i < 0) {
13609
+ fm.sources.push(src);
13610
+ return true;
13611
+ }
13612
+ if (JSON.stringify(fm.sources[i]) === JSON.stringify(src))
13613
+ return false;
13614
+ fm.sources[i] = src;
13615
+ return true;
13616
+ }
13617
+ function applyCandidates(input, opts) {
13618
+ const { kb, kbRoot, rulesDir } = opts;
13619
+ const res = { created: [], merged: [], proposed: [], superseded: [], stale: [], rejected: [], doclingFlags: [], idOverrides: [] };
13620
+ const events = [];
13621
+ const ev2 = (event, itemId, domain, extra = {}) => events.push({ kb, level: "group", project: "", domain, itemId, event, ...extra });
13622
+ const loaded = loadRulesDir(rulesDir, `kb:${kb}`);
13623
+ const work = /* @__PURE__ */ new Map();
13624
+ for (const r of loaded.rules) {
13625
+ const { statement: _s, body, file, layer: _l, ...fm } = r;
13626
+ work.set(r.id, { fm, body, file, dirty: false, isNew: false });
13627
+ }
13628
+ const norm = /* @__PURE__ */ new Map();
13629
+ const totals = /* @__PURE__ */ new Map();
13630
+ const touched = /* @__PURE__ */ new Set();
13631
+ const complete = new Set(input.complete ?? []);
13632
+ for (const cand of input.candidates) {
13633
+ const problems = validateCandidate(cand);
13634
+ if (problems.length) {
13635
+ res.rejected.push({ id: String(cand.id), source: String(cand.source), reason: `invalid: ${problems.join("; ")}` });
13636
+ continue;
13637
+ }
13638
+ const st = readState2(kbRoot, cand.source);
13639
+ const convFile = st ? convertedFile(kbRoot, cand.source) : null;
13640
+ if (!st || !fs24.existsSync(convFile)) {
13641
+ res.rejected.push({ id: cand.id, source: cand.source, reason: "unknown source (not ingested)" });
13642
+ continue;
13643
+ }
13644
+ let hay = norm.get(cand.source);
13645
+ if (!hay) {
13646
+ hay = normalizedViews(fs24.readFileSync(convFile, "utf8"));
13647
+ norm.set(cand.source, hay);
13648
+ }
13649
+ const tot = totals.get(cand.source) ?? { total: 0, dropped: 0 };
13650
+ tot.total++;
13651
+ totals.set(cand.source, tot);
13652
+ const offset = findQuote(hay, cand.quote);
13653
+ if (!offset) {
13654
+ tot.dropped++;
13655
+ res.rejected.push({ id: cand.id, source: cand.source, reason: "ungrounded" });
13656
+ ev2("item.rejected", cand.id, cand.domain, { reason: "ungrounded" });
13657
+ continue;
13658
+ }
13659
+ let id = cand.id;
13660
+ for (const [wid, w] of work) {
13661
+ const span = (s) => s.sha === st.sha ? s.offset : findQuote(hay, s.quote) ?? s.offset;
13662
+ if (w.fm.sources.some((s) => s.ref === cand.source && s.offset && ((o) => !!o && spansOverlap(o, offset))(span(s)))) {
13663
+ id = wid;
13664
+ break;
13665
+ }
13666
+ }
13667
+ if (id !== cand.id)
13668
+ res.idOverrides.push({ from: cand.id, to: id });
13669
+ touched.add(id);
13670
+ const src = { ref: cand.source, sha: st.sha, ...cand.loc ? { loc: cand.loc } : {}, offset, quote: cand.quote.trim() };
13671
+ const target = work.get(id);
13672
+ const contradicted = (cand.contradicts ?? []).filter((x) => work.has(x) && x !== id);
13673
+ const bodyOf = () => cand.statement.trim() + (cand.rationale?.trim() ? `
13674
+
13675
+ ${cand.rationale.trim()}` : "");
13676
+ if (target && contradicted.length === 0 && sameMeaning(target, cand)) {
13677
+ let changed = upsertSource(target.fm, src);
13678
+ if (target.fm.status === "stale") {
13679
+ target.fm.status = "active";
13680
+ changed = true;
13681
+ }
13682
+ if (changed) {
13683
+ target.dirty = true;
13684
+ res.merged.push(id);
13685
+ }
13686
+ continue;
13687
+ }
13688
+ if (!target && contradicted.length === 0) {
13689
+ const fm2 = {
13690
+ id,
13691
+ domain: cand.domain,
13692
+ modality: cand.modality,
13693
+ status: "active",
13694
+ ...cand.triggers ? { triggers: cand.triggers } : {},
13695
+ sources: [src],
13696
+ approvedBy: "auto",
13697
+ justification: "",
13698
+ supersedes: [],
13699
+ conflictsWith: []
13700
+ };
13701
+ work.set(id, { fm: fm2, body: bodyOf(), file: path25.join(rulesDir, cand.domain, `${id}.md`), dirty: true, isNew: true });
13702
+ res.created.push(id);
13703
+ ev2("item.applied", id, cand.domain);
13704
+ continue;
13705
+ }
13706
+ const fm = {
13707
+ id,
13708
+ domain: target?.fm.domain ?? cand.domain,
13709
+ modality: cand.modality,
13710
+ status: "proposed",
13711
+ ...cand.triggers ?? target?.fm.triggers ? { triggers: cand.triggers ?? target?.fm.triggers } : {},
13712
+ sources: [src],
13713
+ approvedBy: "reviewer-agent",
13714
+ justification: "",
13715
+ supersedes: [],
13716
+ conflictsWith: contradicted
13717
+ };
13718
+ const dir = path25.join(rulesDir, PROPOSED_DIR);
13719
+ const file = path25.join(dir, `${id}@${st.sha.slice(0, SHA_LEN)}.md`);
13720
+ const text = serialize(fm, bodyOf());
13721
+ if (fs24.existsSync(file) && fs24.readFileSync(file, "utf8") === text)
13722
+ continue;
13723
+ fs24.mkdirSync(dir, { recursive: true });
13724
+ for (const other of fs24.readdirSync(dir)) {
13725
+ if (!other.startsWith(`${id}@`) || !other.endsWith(".md") || path25.join(dir, other) === file)
13726
+ continue;
13727
+ const old = matter3(fs24.readFileSync(path25.join(dir, other), "utf8"));
13728
+ if (!old.data.sources?.some((s) => s.ref === cand.source))
13729
+ continue;
13730
+ fs24.writeFileSync(path25.join(dir, other.replace(/\.md$/, ".superseded")), matter3.stringify(old.content, { ...old.data, supersededBy: path25.basename(file) }));
13731
+ fs24.rmSync(path25.join(dir, other));
13732
+ res.superseded.push(other.replace(/\.md$/, ""));
13733
+ ev2("item.superseded", id, fm.domain);
13734
+ }
13735
+ fs24.writeFileSync(file, text);
13736
+ res.proposed.push(`${id}@${st.sha.slice(0, SHA_LEN)}`);
13737
+ ev2("item.proposed", id, fm.domain);
13738
+ }
13739
+ for (const [id, w] of work) {
13740
+ if (touched.has(id) || w.fm.status !== "active" || w.isNew)
13741
+ continue;
13742
+ if (w.fm.sources.length && w.fm.sources.every((s) => complete.has(s.ref))) {
13743
+ w.fm.status = "stale";
13744
+ w.dirty = true;
13745
+ res.stale.push(id);
13746
+ }
13747
+ }
13748
+ for (const w of work.values()) {
13749
+ if (!w.dirty || !opts.projects?.length)
13750
+ continue;
13751
+ const anchors = { ...w.fm.anchors ?? {} };
13752
+ for (const p of opts.projects) {
13753
+ const a = computeAnchor(p.root, w.fm.triggers);
13754
+ if (a)
13755
+ anchors[p.name] = a;
13756
+ }
13757
+ if (Object.keys(anchors).length)
13758
+ w.fm.anchors = anchors;
13759
+ }
13760
+ for (const w of work.values()) {
13761
+ if (!w.dirty)
13762
+ continue;
13763
+ fs24.mkdirSync(path25.dirname(w.file), { recursive: true });
13764
+ const text = serialize(w.fm, w.body);
13765
+ if (!fs24.existsSync(w.file) || fs24.readFileSync(w.file, "utf8") !== text)
13766
+ fs24.writeFileSync(w.file, text);
13767
+ }
13768
+ for (const [ref, t] of totals)
13769
+ if (needsDocling(t.dropped, t.total))
13770
+ res.doclingFlags.push({ ref, ...t });
13771
+ const live = loadRulesDir(rulesDir, `kb:${kb}`).rules;
13772
+ writeIndex(kbRoot, compileIndex(kb, live, opts.now?.()));
13773
+ appendAudit(events, opts.audit);
13774
+ return res;
13775
+ }
13776
+ var SHA_LEN, clean, sameMeaning;
13777
+ var init_extract = __esm({
13778
+ "packages/core/dist/knowledge/extract.js"() {
13779
+ init_dist();
13780
+ init_audit();
13781
+ init_anchor();
13782
+ init_index_file();
13783
+ init_ingest();
13784
+ init_paths();
13785
+ init_store2();
13786
+ init_verify();
13787
+ SHA_LEN = 12;
13788
+ clean = (v2) => JSON.parse(JSON.stringify(v2));
13789
+ sameMeaning = (existing, c) => {
13790
+ if (existing.fm.modality !== c.modality)
13791
+ return false;
13792
+ if (normalizeText(existing.body.trim().split(/\n\s*\n/)[0] ?? "") === normalizeText(c.statement))
13793
+ return true;
13794
+ return c.meaning === "same";
13795
+ };
13796
+ }
13797
+ });
13798
+
13799
+ // packages/core/dist/knowledge/schedule.js
13800
+ import fs25 from "fs";
13801
+ import path26 from "path";
13802
+ function readSchedule(stateRoot) {
13803
+ try {
13804
+ return JSON.parse(fs25.readFileSync(scheduleFile(stateRoot), "utf8"));
13805
+ } catch {
13806
+ return {};
13807
+ }
13808
+ }
13809
+ function writeSchedule(stateRoot, s) {
13810
+ fs25.mkdirSync(stateRoot, { recursive: true });
13811
+ const tmp = `${scheduleFile(stateRoot)}.${process.pid}.tmp`;
13812
+ fs25.writeFileSync(tmp, JSON.stringify(s, null, 2) + "\n");
13813
+ fs25.renameSync(tmp, scheduleFile(stateRoot));
13814
+ }
13815
+ function snapshotSources(kbRoot, sources) {
13816
+ const out = {};
13817
+ for (const e of sources)
13818
+ for (const f of expandSource(kbRoot, e)) {
13819
+ const d = detectChange(kbRoot, f);
13820
+ out[d.ref] = d.stamp.sha;
13821
+ }
13822
+ return out;
13823
+ }
13824
+ function changedRefs(kbRoot, sources) {
13825
+ const out = [];
13826
+ for (const e of sources) {
13827
+ for (const f of expandSource(kbRoot, e)) {
13828
+ const d = detectChange(kbRoot, f);
13829
+ const st = readState2(kbRoot, d.ref);
13830
+ if (d.changed || st && st.cursor < st.sections)
13831
+ out.push(d.ref);
13832
+ }
13833
+ }
13834
+ return out;
13835
+ }
13836
+ function duePass(entry, o) {
13837
+ const now = o.now.getTime();
13838
+ if (o.empty && !entry.lastFullAt)
13839
+ return "full";
13840
+ const fullBase = entry.lastFullAt ?? entry.createdAt;
13841
+ if (fullBase && now >= Date.parse(fullBase) + o.rc.fullEveryDays * DAY_MS)
13842
+ return "full";
13843
+ if (entry.firstChangeAt && entry.incrementalDueAt && now >= Date.parse(entry.incrementalDueAt))
13844
+ return "incremental";
13845
+ return null;
13846
+ }
13847
+ function acquirePass(entry, pass, now, snapshot, log) {
13848
+ if (entry.running) {
13849
+ if (now.getTime() - Date.parse(entry.running.startedAt) <= RUNNING_STALE_MS)
13850
+ return false;
13851
+ log?.(`knowledge: clearing stale ${entry.running.pass} pass started ${entry.running.startedAt}`);
13852
+ }
13853
+ entry.running = { pass, startedAt: now.toISOString(), sourceSnapshot: snapshot };
13854
+ return true;
13855
+ }
13856
+ function requestText(kb, pass) {
13857
+ const absorbed = pass === "full" ? " (absorbs any pending incremental pass)" : "";
13858
+ return `KB ${kb}: ${pass} reconcile due${absorbed}. Spawn a crew with the knowledge-review skill: it extracts changed sources, reviews proposals, then runs \`squadrant knowledge reconcile ${kb}${pass === "full" ? " --full" : ""} --finish\`.`;
13859
+ }
13860
+ async function requestPass(kb, pass, d, opts = {}) {
13861
+ const now = d.now?.() ?? /* @__PURE__ */ new Date();
13862
+ const home = resolveKbConfig(d.cfg, kb).homeProject;
13863
+ if (!home)
13864
+ return { kb, pass, requested: false, reason: "no homeProject configured" };
13865
+ const schedule = readSchedule(d.stateRoot);
13866
+ const entry = schedule[kb] ??= { createdAt: now.toISOString() };
13867
+ const root = kbDir(d.cfg, kb);
13868
+ if (opts.dryRun) {
13869
+ const busy = entry.running && now.getTime() - Date.parse(entry.running.startedAt) <= RUNNING_STALE_MS;
13870
+ return { kb, pass, requested: false, reason: busy ? `a ${entry.running.pass} pass is already running` : `dry run: would enqueue to ${home}` };
13871
+ }
13872
+ const snapshot = snapshotSources(root, loadSources(root).sources);
13873
+ if (!acquirePass(entry, pass, now, snapshot, d.log))
13874
+ return { kb, pass, requested: false, reason: `a ${entry.running.pass} pass is already running` };
13875
+ writeSchedule(d.stateRoot, schedule);
13876
+ try {
13877
+ await d.enqueue(home, requestText(kb, pass));
13878
+ } catch (e) {
13879
+ delete entry.running;
13880
+ writeSchedule(d.stateRoot, schedule);
13881
+ throw e;
13882
+ }
13883
+ return { kb, pass, requested: true };
13884
+ }
13885
+ async function tickKnowledgeSchedule(d) {
13886
+ const out = [];
13887
+ const now = d.now?.() ?? /* @__PURE__ */ new Date();
13888
+ for (const kb of Object.keys(kbConfigs(d.cfg))) {
13889
+ if (!resolveKbConfig(d.cfg, kb).homeProject)
13890
+ continue;
13891
+ try {
13892
+ const root = kbDir(d.cfg, kb);
13893
+ if (!fs25.existsSync(root))
13894
+ continue;
13895
+ const { sources } = loadSources(root);
13896
+ if (!sources.length)
13897
+ continue;
13898
+ const schedule = readSchedule(d.stateRoot);
13899
+ const entry = schedule[kb] ??= { createdAt: now.toISOString() };
13900
+ const rc = reconcileConfig(d.cfg, kb);
13901
+ let dirty = false;
13902
+ if (entry.running && now.getTime() - Date.parse(entry.running.startedAt) > RUNNING_STALE_MS) {
13903
+ d.log?.(`knowledge: ${kb} pass stale, cleared`);
13904
+ delete entry.running;
13905
+ dirty = true;
13906
+ }
13907
+ if (!entry.running && !entry.firstChangeAt && changedRefs(root, sources).length) {
13908
+ entry.firstChangeAt = now.toISOString();
13909
+ entry.incrementalDueAt = new Date(now.getTime() + rc.incrementalAfterDays * DAY_MS).toISOString();
13910
+ dirty = true;
13911
+ }
13912
+ if (dirty)
13913
+ writeSchedule(d.stateRoot, schedule);
13914
+ if (entry.running)
13915
+ continue;
13916
+ const empty = loadKbRules(d.cfg, kb, { includeProposed: true }).rules.length === 0;
13917
+ const pass = duePass(entry, { now, rc, empty });
13918
+ if (pass)
13919
+ out.push(await requestPass(kb, pass, d));
13920
+ } catch (e) {
13921
+ d.log?.(`knowledge schedule error (${kb}): ${e.message}`);
13922
+ }
13923
+ }
13924
+ return out;
13925
+ }
13926
+ function completePass(kb, d) {
13927
+ const now = d.now?.() ?? /* @__PURE__ */ new Date();
13928
+ const schedule = readSchedule(d.stateRoot);
13929
+ const entry = schedule[kb] ??= { createdAt: now.toISOString() };
13930
+ const pass = d.pass ?? entry.running?.pass ?? "incremental";
13931
+ const snap = entry.running?.sourceSnapshot;
13932
+ const root = kbDir(d.cfg, kb);
13933
+ delete entry.running;
13934
+ if (pass === "full")
13935
+ entry.lastFullAt = now.toISOString();
13936
+ delete entry.firstChangeAt;
13937
+ delete entry.incrementalDueAt;
13938
+ if (snap) {
13939
+ const nowSnap = snapshotSources(root, loadSources(root).sources);
13940
+ if (Object.entries(nowSnap).some(([ref, sha]) => snap[ref] !== sha)) {
13941
+ entry.firstChangeAt = now.toISOString();
13942
+ entry.incrementalDueAt = new Date(now.getTime() + reconcileConfig(d.cfg, kb).incrementalAfterDays * DAY_MS).toISOString();
13943
+ }
13944
+ }
13945
+ writeSchedule(d.stateRoot, schedule);
13946
+ return pass;
13947
+ }
13948
+ var RUNNING_STALE_MS, DAY_MS, scheduleFile, reconcileConfig;
13949
+ var init_schedule = __esm({
13950
+ "packages/core/dist/knowledge/schedule.js"() {
13951
+ init_dist();
13952
+ init_ingest();
13953
+ init_layers();
13954
+ init_paths();
13955
+ init_sources();
13956
+ RUNNING_STALE_MS = 6 * 60 * 60 * 1e3;
13957
+ DAY_MS = 24 * 60 * 60 * 1e3;
13958
+ scheduleFile = (stateRoot) => path26.join(stateRoot, "knowledge-schedule.json");
13959
+ reconcileConfig = (cfg, kb) => ({ ...DEFAULT_RECONCILE, ...kbConfigs(cfg)[kb]?.reconcile });
13960
+ }
13961
+ });
13962
+
13963
+ // packages/core/dist/knowledge/review.js
13964
+ import crypto4 from "crypto";
13965
+ import { execFileSync as execFileSync7 } from "child_process";
13966
+ import fs26 from "fs";
13967
+ import path27 from "path";
13968
+ import matter4 from "gray-matter";
13969
+ function readEscalations(kbRoot) {
13970
+ try {
13971
+ return JSON.parse(fs26.readFileSync(escalationsFile(kbRoot), "utf8")).items;
13972
+ } catch {
13973
+ return [];
13974
+ }
13975
+ }
13976
+ function writeEscalations(kbRoot, items) {
13977
+ fs26.mkdirSync(kbRoot, { recursive: true });
13978
+ fs26.writeFileSync(escalationsFile(kbRoot), JSON.stringify({ items }, null, 2) + "\n");
13979
+ }
13980
+ function addEscalation(kbRoot, e) {
13981
+ const items = readEscalations(kbRoot);
13982
+ if (items.some((x) => x.key === e.key))
13983
+ return false;
13984
+ writeEscalations(kbRoot, [...items, e]);
13985
+ return true;
13986
+ }
13987
+ function appendVerdict(kbRoot, v2, now) {
13988
+ fs26.mkdirSync(kbRoot, { recursive: true });
13989
+ fs26.appendFileSync(verdictsFile(kbRoot), JSON.stringify({ ts: now.toISOString(), ...v2 }) + "\n");
13990
+ }
13991
+ function priorityOfRef(c, ref) {
13992
+ if (ref.startsWith("agent:"))
13993
+ return "agent";
13994
+ const root = kbDir(c.cfg, c.kb);
13995
+ for (const e of c.sources)
13996
+ for (const f of expandSource(root, e))
13997
+ if (refFor(root, f) === ref)
13998
+ return e.priority;
13999
+ return "project";
14000
+ }
14001
+ function priorityOfRule(c, r) {
14002
+ return r.sources.map((s) => priorityOfRef(c, s.ref)).sort((a, b) => PRIORITY_RANK[b] - PRIORITY_RANK[a])[0] ?? "project";
14003
+ }
14004
+ function loadProposals(c) {
14005
+ const dir = kbRulesDir(c.cfg, c.kb);
14006
+ const live = loadRulesDir(dir, `kb:${c.kb}`).rules;
14007
+ const byId = new Map(live.map((r) => [r.id, r]));
14008
+ const proposals = [];
14009
+ const pdirs = /* @__PURE__ */ new Set();
14010
+ for (const f of fs26.existsSync(dir) ? walkDirs(dir) : [])
14011
+ if (path27.basename(f) === PROPOSED_DIR)
14012
+ pdirs.add(f);
14013
+ for (const pd of pdirs) {
14014
+ for (const rule of loadRulesDir(pd, `kb:${c.kb}`, { includeProposed: true }).rules) {
14015
+ proposals.push({ key: path27.basename(rule.file).replace(/\.md$/, ""), rule, live: byId.get(rule.id) });
14016
+ }
14017
+ }
14018
+ return { proposals, live };
14019
+ }
14020
+ function walkDirs(dir, out = []) {
14021
+ for (const e of fs26.readdirSync(dir, { withFileTypes: true }))
14022
+ if (e.isDirectory()) {
14023
+ const f = path27.join(dir, e.name);
14024
+ out.push(f);
14025
+ walkDirs(f, out);
14026
+ }
14027
+ return out;
14028
+ }
14029
+ function reindex(c) {
14030
+ const live = loadRulesDir(kbRulesDir(c.cfg, c.kb), `kb:${c.kb}`).rules;
14031
+ writeIndex(kbDir(c.cfg, c.kb), compileIndex(c.kb, live, nowOf(c)));
14032
+ }
14033
+ function anchorsFor(c, fm) {
14034
+ const anchors = { ...fm.anchors ?? {} };
14035
+ for (const p of c.projects ?? []) {
14036
+ const a = computeAnchor(p.root, fm.triggers);
14037
+ if (a)
14038
+ anchors[p.name] = a;
14039
+ }
14040
+ return Object.keys(anchors).length ? anchors : void 0;
14041
+ }
14042
+ function promoteProposal(c, p, by, justification, events) {
14043
+ const dir = kbRulesDir(c.cfg, c.kb);
14044
+ const { fm, body } = readFm(p.rule.file);
14045
+ const retired = [];
14046
+ for (const id of fm.conflictsWith ?? []) {
14047
+ const other = loadRulesDir(dir, `kb:${c.kb}`).rules.find((r) => r.id === id);
14048
+ if (!other || other.id === fm.id)
14049
+ continue;
14050
+ const o = readFm(other.file);
14051
+ o.fm.status = "retired";
14052
+ fs26.writeFileSync(other.file, fileText(o.fm, o.body));
14053
+ retired.push(id);
14054
+ events.push(ev(c, "item.superseded", id, o.fm.domain));
14055
+ }
14056
+ const next = { ...fm, status: "active", approvedBy: by, justification, supersedes: [.../* @__PURE__ */ new Set([...fm.supersedes ?? [], ...retired])], conflictsWith: [] };
14057
+ if (p.live?.decision)
14058
+ next.decision = p.live.decision;
14059
+ const anchors = anchorsFor(c, { ...next, anchors: p.live?.anchors ?? fm.anchors });
14060
+ if (anchors)
14061
+ next.anchors = anchors;
14062
+ const target = p.live?.file ?? path27.join(dir, next.domain, `${next.id}.md`);
14063
+ fs26.mkdirSync(path27.dirname(target), { recursive: true });
14064
+ fs26.writeFileSync(target, fileText(next, body));
14065
+ fs26.rmSync(p.rule.file);
14066
+ events.push(ev(c, "item.approved", next.id, next.domain), ev(c, "item.applied", next.id, next.domain));
14067
+ if (p.live)
14068
+ events.push(ev(c, "item.superseded", next.id, next.domain));
14069
+ }
14070
+ function dropProposal(c, p, events) {
14071
+ fs26.rmSync(p.rule.file);
14072
+ events.push(ev(c, "item.rejected", p.rule.id, p.rule.domain, { reason: "reviewer" }));
14073
+ }
14074
+ function mergeInto(c, p, targetId, live, events) {
14075
+ const t = live.find((r) => r.id === targetId);
14076
+ if (!t)
14077
+ return false;
14078
+ const tf = readFm(t.file);
14079
+ for (const s of p.rule.sources)
14080
+ if (!tf.fm.sources.some((x) => x.ref === s.ref))
14081
+ tf.fm.sources.push(s);
14082
+ fs26.writeFileSync(t.file, fileText(tf.fm, tf.body));
14083
+ fs26.rmSync(p.rule.file);
14084
+ events.push(ev(c, "item.superseded", p.rule.id, p.rule.domain), ev(c, "item.applied", targetId, t.domain));
14085
+ return true;
14086
+ }
14087
+ function evaluateCodeConflict(c, rule, cc) {
14088
+ const g = c.git ?? realGit;
14089
+ const proj = (c.projects ?? []).find((p) => p.name === cc.project);
14090
+ const root = proj?.root;
14091
+ const code = root ? g.lastCommit(root, cc.files) : null;
14092
+ const kbRoot = kbDir(c.cfg, c.kb);
14093
+ const docDates = rule.sources.map((s) => g.docDate(fileForRef(kbRoot, s.ref))).filter((d2) => !!d2).sort();
14094
+ const docDate = docDates[docDates.length - 1];
14095
+ const docRev = docRevOf(rule);
14096
+ const evidence = { docRev, codeRev: code?.rev ?? "unknown", files: cc.files, project: cc.project };
14097
+ const doc = rule.sources.map((s) => `${s.ref} (${docDate ?? "date unknown"})`).join(", ");
14098
+ if (!code || !docDate) {
14099
+ return { action: "escalate", evidence, explanation: `Rule ${rule.id} ("${rule.statement}") may conflict with code in ${cc.project} (${cc.files.join(", ")}): ${cc.summary}. Dates could not be verified (doc ${doc}; code ${code?.date ?? "unknown"}), so a human must judge it.` };
14100
+ }
14101
+ if (Date.parse(docDate) >= Date.parse(code.date)) {
14102
+ return { action: "flag-violation", explanation: `Code in ${cc.project} (${cc.files.join(", ")}, last commit ${code.date}) contradicts rule ${rule.id}, but the doc (${doc}) is newer: treat the code as the violation. ${cc.summary}` };
14103
+ }
14104
+ const d = rule.decision;
14105
+ if (d && d.docRev === docRev && !g.changedSince(root, cc.files, d.at))
14106
+ return { action: "skip-decided" };
14107
+ const reopened = d ? ` An earlier decision (${d.verdict}, ${d.at}) is re-opened because ${d.docRev !== docRev ? "the doc has a new revision" : "the anchored code changed after it"}.` : "";
14108
+ return {
14109
+ action: "escalate",
14110
+ evidence,
14111
+ explanation: `Rule ${rule.id} ("${rule.statement}") from ${doc} is contradicted by newer code: ${cc.project} ${cc.files.join(", ")} (last commit ${code.date}). ${cc.summary} Keeping the doc leaves the code in violation; superseding with the code retires this rule and any code-derived replacement inherits its priority unless you say otherwise.${reopened}`
14112
+ };
14113
+ }
14114
+ function applyDecisions(file, c) {
14115
+ const now = nowOf(c);
14116
+ const kbRoot = kbDir(c.cfg, c.kb);
14117
+ const threshold = reconcileConfig(c.cfg, c.kb).autoApproveConfidence;
14118
+ const res = { applied: [], escalated: [], violations: [], skipped: [] };
14119
+ const events = [];
14120
+ let { proposals, live } = loadProposals(c);
14121
+ const escalate = (e) => {
14122
+ addEscalation(kbRoot, { createdAt: now.toISOString(), needsYou: true, ...e });
14123
+ res.escalated.push({ key: e.key, reasons: e.reasons });
14124
+ };
14125
+ const verdict = (d, outcome, extra = {}) => appendVerdict(kbRoot, { kb: c.kb, kind: "review", item: d.item, outcome, ...extra }, now);
14126
+ for (const d of file.decisions ?? []) {
14127
+ const conf = Number.isFinite(d.confidence) ? d.confidence : 0;
14128
+ const stale = d.item.startsWith("stale:");
14129
+ const prop = stale ? void 0 : proposals.find((p2) => p2.key === d.item);
14130
+ const rule = stale ? live.find((r) => r.id === d.item.slice(6)) : prop?.rule ?? live.find((r) => r.id === d.item);
14131
+ if (!rule) {
14132
+ res.skipped.push({ item: d.item, reason: "no such proposal or rule" });
14133
+ continue;
14134
+ }
14135
+ const reasons = [];
14136
+ if (d.codeConflict) {
14137
+ const target = prop?.live ?? live.find((r) => r.id === rule.id) ?? rule;
14138
+ const v2 = evaluateCodeConflict(c, target, d.codeConflict);
14139
+ if (v2.action === "flag-violation") {
14140
+ escalate({ key: `violation:${target.id}`, kind: "code-violation", ruleId: target.id, reasons: ["doc newer than code"], explanation: v2.explanation, needsYou: false });
14141
+ res.violations.push(target.id);
14142
+ } else if (v2.action === "escalate") {
14143
+ escalate({ key: `code-vs-doc:${target.id}`, kind: "code-vs-doc", ruleId: target.id, reasons: ["code-vs-doc"], explanation: v2.explanation, evidence: v2.evidence, confidence: conf });
14144
+ verdict(d, "escalated", { reasons: ["code-vs-doc"], confidence: conf });
14145
+ }
14146
+ if (!prop && !stale)
14147
+ continue;
14148
+ if (v2.action === "escalate") {
14149
+ reasons.push("code-vs-doc");
14150
+ }
14151
+ }
14152
+ if (stale) {
14153
+ if (d.decision === "reject") {
14154
+ const f = readFm(rule.file);
14155
+ f.fm.status = "active";
14156
+ if (d.loc && f.fm.sources[0])
14157
+ f.fm.sources[0].loc = d.loc;
14158
+ if (conf >= threshold && !d.flag && !reasons.length) {
14159
+ fs26.writeFileSync(rule.file, fileText(f.fm, f.body));
14160
+ res.applied.push(d.item);
14161
+ events.push(ev(c, "item.applied", rule.id, rule.domain));
14162
+ verdict(d, "applied", { confidence: conf });
14163
+ continue;
14164
+ }
14165
+ reasons.push(conf < threshold ? "low-confidence" : "reviewer-flagged");
14166
+ } else if (d.decision === "approve") {
14167
+ if (strong(rule.modality))
14168
+ reasons.push("retiring must/must-not");
14169
+ if (conf < threshold)
14170
+ reasons.push("low-confidence");
14171
+ if (d.flag)
14172
+ reasons.push("reviewer-flagged");
14173
+ if (!reasons.length) {
14174
+ const f = readFm(rule.file);
14175
+ f.fm.status = "retired";
14176
+ fs26.writeFileSync(rule.file, fileText(f.fm, f.body));
14177
+ res.applied.push(d.item);
14178
+ events.push(ev(c, "item.archived", rule.id, rule.domain));
14179
+ verdict(d, "applied", { confidence: conf });
14180
+ continue;
14181
+ }
14182
+ } else
14183
+ reasons.push("reviewer-flagged");
14184
+ escalate({ key: d.item, kind: "stale-retire", ruleId: rule.id, item: d.item, reasons: [...new Set(reasons)], explanation: `Rule ${rule.id} ("${rule.statement}") lost its source support. Reviewer: ${d.justification}`, confidence: conf });
14185
+ verdict(d, "escalated", { reasons, confidence: conf });
14186
+ continue;
14187
+ }
14188
+ const p = prop;
14189
+ if (d.decision === "escalate" || d.flag)
14190
+ reasons.push("reviewer-flagged");
14191
+ if (conf < threshold)
14192
+ reasons.push("low-confidence");
14193
+ if (d.decision === "approve") {
14194
+ const myPri = priorityOfRule(c, p.rule);
14195
+ const conflicted = (p.rule.conflictsWith ?? []).map((id) => live.find((r) => r.id === id)).filter((r) => !!r);
14196
+ if (myPri === "company" && conflicted.some((r) => priorityOfRule(c, r) === "company"))
14197
+ reasons.push("company-vs-company conflict");
14198
+ const retired = [...conflicted, ...p.live && p.live.modality !== p.rule.modality ? [p.live] : []];
14199
+ if (retired.some((r) => strong(r.modality)))
14200
+ reasons.push("retiring must/must-not");
14201
+ }
14202
+ if (reasons.length) {
14203
+ escalate({ key: p.key, kind: "proposal", ruleId: p.rule.id, item: p.key, reasons: [...new Set(reasons)], explanation: `Proposal ${p.key}: "${p.rule.statement}"${p.live ? ` (replaces live "${p.live.statement}")` : ""}. Reviewer (${d.decision}, ${conf}): ${d.justification}`, confidence: conf });
14204
+ verdict(d, "escalated", { reasons: [...new Set(reasons)], confidence: conf, decision: d.decision });
14205
+ continue;
14206
+ }
14207
+ if (d.decision === "approve")
14208
+ promoteProposal(c, p, "reviewer-agent", d.justification, events);
14209
+ else if (d.decision === "reject")
14210
+ dropProposal(c, p, events);
14211
+ else if (d.decision.startsWith("merge-into:")) {
14212
+ if (!mergeInto(c, p, d.decision.slice("merge-into:".length), live, events)) {
14213
+ res.skipped.push({ item: d.item, reason: `merge target ${d.decision.slice(11)} not found` });
14214
+ continue;
14215
+ }
14216
+ } else {
14217
+ res.skipped.push({ item: d.item, reason: `unknown decision '${d.decision}'` });
14218
+ continue;
14219
+ }
14220
+ res.applied.push(d.item);
14221
+ verdict(d, "applied", { decision: d.decision, confidence: conf, justification: d.justification });
14222
+ ({ proposals, live } = loadProposals(c));
14223
+ }
14224
+ for (const pd of file.pairs ?? [])
14225
+ applyPair(pd, c, res, threshold, events, kbRoot, now);
14226
+ reindex(c);
14227
+ appendAudit(events, c.audit);
14228
+ return res;
14229
+ }
14230
+ function applyPair(pd, c, res, threshold, events, kbRoot, now) {
14231
+ const { live } = loadProposals(c);
14232
+ const a = live.find((r) => r.id === pd.a), b = live.find((r) => r.id === pd.b);
14233
+ const key = `pair:${[pd.a, pd.b].sort().join("+")}`;
14234
+ if (!a || !b) {
14235
+ res.skipped.push({ item: key, reason: "rule not found" });
14236
+ return;
14237
+ }
14238
+ const esc = (reasons2) => {
14239
+ addEscalation(kbRoot, { key, kind: "pair", reasons: reasons2, explanation: `${a.id} ("${a.statement}") vs ${b.id} ("${b.statement}"): ${pd.relation}. ${pd.justification}`, confidence: pd.confidence, createdAt: now.toISOString(), needsYou: true });
14240
+ res.escalated.push({ key, reasons: reasons2 });
14241
+ };
14242
+ appendVerdict(kbRoot, { kb: c.kb, kind: "pair", item: key, relation: pd.relation, confidence: pd.confidence }, now);
14243
+ if (pd.relation === "unrelated" || pd.relation === "refines")
14244
+ return;
14245
+ if (pd.relation === "conflicts") {
14246
+ const reasons2 = priorityOfRule(c, a) === "company" && priorityOfRule(c, b) === "company" ? ["company-vs-company conflict"] : pd.confidence < threshold ? ["low-confidence"] : [];
14247
+ if (reasons2.length) {
14248
+ esc(reasons2);
14249
+ return;
14250
+ }
14251
+ for (const [x, y] of [[a, b], [b, a]]) {
14252
+ const f = readFm(x.file);
14253
+ f.fm.conflictsWith = [.../* @__PURE__ */ new Set([...f.fm.conflictsWith ?? [], y.id])];
14254
+ fs26.writeFileSync(x.file, fileText(f.fm, f.body));
14255
+ }
14256
+ return;
14257
+ }
14258
+ const keep = pd.keep === b.id ? b : a, drop = keep === a ? b : a;
14259
+ const reasons = [...pd.confidence < threshold ? ["low-confidence"] : [], ...strong(drop.modality) && drop.modality !== keep.modality ? ["retiring must/must-not"] : []];
14260
+ if (reasons.length) {
14261
+ esc(reasons);
14262
+ return;
14263
+ }
14264
+ const kf = readFm(keep.file);
14265
+ for (const s of drop.sources)
14266
+ if (!kf.fm.sources.some((x) => x.ref === s.ref))
14267
+ kf.fm.sources.push(s);
14268
+ kf.fm.supersedes = [.../* @__PURE__ */ new Set([...kf.fm.supersedes ?? [], drop.id])];
14269
+ fs26.writeFileSync(keep.file, fileText(kf.fm, kf.body));
14270
+ const df = readFm(drop.file);
14271
+ df.fm.status = "retired";
14272
+ fs26.writeFileSync(drop.file, fileText(df.fm, df.body));
14273
+ events.push(ev(c, "item.superseded", drop.id, drop.domain), ev(c, "item.applied", keep.id, keep.domain));
14274
+ res.applied.push(key);
14275
+ }
14276
+ function resolveEscalation(c, key, r, by, reason) {
14277
+ const now = nowOf(c);
14278
+ const kbRoot = kbDir(c.cfg, c.kb);
14279
+ const items = readEscalations(kbRoot);
14280
+ const esc = items.find((e) => e.key === key);
14281
+ if (!esc)
14282
+ throw new Error(`No open escalation '${key}'`);
14283
+ const events = [];
14284
+ const { proposals, live } = loadProposals(c);
14285
+ const done = (msg) => {
14286
+ writeEscalations(kbRoot, items.filter((e) => e.key !== key));
14287
+ appendVerdict(kbRoot, { kb: c.kb, kind: "operator", item: key, resolution: r, by, reason }, now);
14288
+ reindex(c);
14289
+ appendAudit(events, c.audit);
14290
+ return msg;
14291
+ };
14292
+ if (esc.kind === "code-vs-doc") {
14293
+ if (r.kind !== "verdict")
14294
+ throw new Error("code-vs-doc needs --keep-doc or --supersede-with-code");
14295
+ const rule = live.find((x) => x.id === esc.ruleId);
14296
+ if (!rule || !esc.evidence)
14297
+ throw new Error(`rule ${esc.ruleId} or its evidence is gone`);
14298
+ const f = readFm(rule.file);
14299
+ const decision = { by, at: now.toISOString(), verdict: r.verdict, docRev: esc.evidence.docRev, codeRev: esc.evidence.codeRev, files: esc.evidence.files, reason };
14300
+ f.fm.decision = decision;
14301
+ if (r.verdict === "supersede-with-code") {
14302
+ f.fm.status = "retired";
14303
+ events.push(ev(c, "item.superseded", rule.id, rule.domain));
14304
+ }
14305
+ fs26.writeFileSync(rule.file, fileText(f.fm, f.body));
14306
+ return done(`${rule.id}: ${r.verdict}`);
14307
+ }
14308
+ if (esc.kind === "proposal") {
14309
+ const p = proposals.find((x) => x.key === esc.item);
14310
+ if (!p)
14311
+ throw new Error(`proposal ${esc.item} no longer exists`);
14312
+ if (r.kind === "approve")
14313
+ promoteProposal(c, p, "human", reason || "approved by operator", events);
14314
+ else if (r.kind === "reject")
14315
+ dropProposal(c, p, events);
14316
+ else
14317
+ throw new Error("proposal needs approve or reject");
14318
+ return done(`${p.key}: ${r.kind}`);
14319
+ }
14320
+ if (esc.kind === "stale-retire") {
14321
+ const rule = live.find((x) => x.id === esc.ruleId);
14322
+ if (rule) {
14323
+ const f = readFm(rule.file);
14324
+ f.fm.status = r.kind === "approve" ? "retired" : "active";
14325
+ fs26.writeFileSync(rule.file, fileText(f.fm, f.body));
14326
+ events.push(ev(c, r.kind === "approve" ? "item.archived" : "item.applied", rule.id, rule.domain));
14327
+ }
14328
+ return done(`${esc.ruleId}: ${r.kind === "approve" ? "retired" : "kept"}`);
14329
+ }
14330
+ if (esc.kind === "override-base-changed") {
14331
+ const ov = esc.item && fs26.existsSync(esc.item) ? esc.item : void 0;
14332
+ if (ov) {
14333
+ if (r.kind === "reject")
14334
+ fs26.rmSync(ov);
14335
+ else {
14336
+ const f = readFm(ov);
14337
+ f.fm.overridesBase = esc.evidence?.docRev;
14338
+ fs26.writeFileSync(ov, fileText(f.fm, f.body));
14339
+ }
14340
+ }
14341
+ return done(`${esc.ruleId}: override ${r.kind === "reject" ? "dropped" : "kept"}`);
14342
+ }
14343
+ return done(`${key}: acknowledged`);
14344
+ }
14345
+ function proposeAgentRule(c, o) {
14346
+ const sha = crypto4.createHash("sha256").update(`${o.statement}
14347
+ ${o.evidence}`).digest("hex");
14348
+ const slug = o.statement.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").split("-").slice(0, 6).join("-") || "proposal";
14349
+ const id = o.id ?? `agent.${slug}`;
14350
+ const domain = o.domain ?? "general";
14351
+ const fm = {
14352
+ id,
14353
+ domain,
14354
+ modality: o.modality ?? "should",
14355
+ status: "proposed",
14356
+ sources: [{ ref: `agent:${o.agent ?? "unknown"}`, sha, quote: o.evidence.trim() }],
14357
+ approvedBy: "reviewer-agent",
14358
+ justification: "",
14359
+ supersedes: [],
14360
+ conflictsWith: []
14361
+ };
14362
+ const dir = path27.join(kbRulesDir(c.cfg, c.kb), PROPOSED_DIR);
14363
+ fs26.mkdirSync(dir, { recursive: true });
14364
+ const file = path27.join(dir, `${id}@${sha.slice(0, SHA_LEN2)}.md`);
14365
+ fs26.writeFileSync(file, fileText(fm, o.statement));
14366
+ appendAudit([ev(c, "item.proposed", id, domain)], c.audit);
14367
+ return file;
14368
+ }
14369
+ function buildReviewPacket(c) {
14370
+ const { proposals, live } = loadProposals(c);
14371
+ return {
14372
+ kb: c.kb,
14373
+ autoApproveConfidence: reconcileConfig(c.cfg, c.kb).autoApproveConfidence,
14374
+ proposals: proposals.map((p) => ({
14375
+ item: p.key,
14376
+ id: p.rule.id,
14377
+ domain: p.rule.domain,
14378
+ modality: p.rule.modality,
14379
+ statement: p.rule.statement,
14380
+ priority: priorityOfRule(c, p.rule),
14381
+ sources: p.rule.sources,
14382
+ ...p.live ? { replaces: { statement: p.live.statement, modality: p.live.modality } } : {},
14383
+ conflictsWith: (p.rule.conflictsWith ?? []).flatMap((id) => {
14384
+ const r = live.find((x) => x.id === id);
14385
+ return r ? [{ id, statement: r.statement, modality: r.modality, priority: priorityOfRule(c, r) }] : [];
14386
+ })
14387
+ })),
14388
+ stale: live.filter((r) => r.status === "stale").map((r) => ({ item: `stale:${r.id}`, id: r.id, modality: r.modality, statement: r.statement, sources: r.sources })),
14389
+ decided: live.filter((r) => r.decision).map((r) => ({ id: r.id, verdict: r.decision.verdict, at: r.decision.at })),
14390
+ open: readEscalations(kbDir(c.cfg, c.kb))
14391
+ };
14392
+ }
14393
+ var PRIORITY_RANK, SHA_LEN2, strong, escalationsFile, verdictsFile, needsYouCount, git, realGit, nowOf, fileText, readFm, ev, docRevOf;
14394
+ var init_review = __esm({
14395
+ "packages/core/dist/knowledge/review.js"() {
14396
+ init_audit();
14397
+ init_anchor();
14398
+ init_index_file();
14399
+ init_ingest();
14400
+ init_paths();
14401
+ init_schedule();
14402
+ init_store2();
14403
+ PRIORITY_RANK = { company: 3, project: 2, agent: 1 };
14404
+ SHA_LEN2 = 12;
14405
+ strong = (m) => m === "must" || m === "must-not";
14406
+ escalationsFile = (kbRoot) => path27.join(kbRoot, "escalations.json");
14407
+ verdictsFile = (kbRoot) => path27.join(kbRoot, "verdicts.jsonl");
14408
+ needsYouCount = (kbRoot) => readEscalations(kbRoot).filter((e) => e.needsYou).length;
14409
+ git = (root, args) => execFileSync7("git", ["-C", root, ...args], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
14410
+ realGit = {
14411
+ lastCommit(root, files) {
14412
+ try {
14413
+ const [rev, date] = git(root, ["log", "-1", "--format=%H%x09%cI", "--", ...files]).split(" ");
14414
+ return rev && date ? { rev, date } : null;
14415
+ } catch {
14416
+ return null;
14417
+ }
14418
+ },
14419
+ changedSince(root, files, since) {
14420
+ try {
14421
+ return git(root, ["log", "--since", since, "--format=%H", "--", ...files]) !== "";
14422
+ } catch {
14423
+ return false;
14424
+ }
14425
+ },
14426
+ docDate(file) {
14427
+ try {
14428
+ const d = git(path27.dirname(file), ["log", "-1", "--format=%cI", "--", path27.basename(file)]);
14429
+ if (d)
14430
+ return d;
14431
+ } catch {
14432
+ }
14433
+ try {
14434
+ return fs26.statSync(file).mtime.toISOString();
14435
+ } catch {
14436
+ return null;
14437
+ }
14438
+ }
14439
+ };
14440
+ nowOf = (c) => c.now?.() ?? /* @__PURE__ */ new Date();
14441
+ fileText = (fm, body) => matter4.stringify(body.trim() + "\n", JSON.parse(JSON.stringify(fm)));
14442
+ readFm = (file) => {
14443
+ const m = matter4(fs26.readFileSync(file, "utf8"));
14444
+ return { fm: m.data, body: m.content };
14445
+ };
14446
+ ev = (c, event, itemId, domain, extra = {}) => ({ kb: c.kb, level: "group", project: "", domain, itemId, event, ...extra });
14447
+ docRevOf = (r) => r.sources.map((s) => `${s.ref}@${s.sha.slice(0, SHA_LEN2)}`).join(",");
14448
+ }
14449
+ });
14450
+
14451
+ // packages/core/dist/knowledge/fullpass.js
14452
+ import crypto5 from "crypto";
14453
+ import fs27 from "fs";
14454
+ import path28 from "path";
14455
+ import matter5 from "gray-matter";
14456
+ function dedupPairs(rules) {
14457
+ const active = rules.filter((r) => r.status === "active").sort((x, y) => x.id.localeCompare(y.id));
14458
+ const out = [];
14459
+ for (let i = 0; i < active.length; i++) {
14460
+ for (let j = i + 1; j < active.length; j++) {
14461
+ const ta = terms(active[i]), shared2 = [...terms(active[j])].filter((t) => ta.has(t));
14462
+ const pa = new Set(Object.values(active[i].anchors ?? {}).flatMap((a) => a.paths ?? []));
14463
+ const sharedPaths = Object.values(active[j].anchors ?? {}).flatMap((a) => a.paths ?? []).filter((p) => pa.has(p));
14464
+ if (shared2.length >= 2 || sharedPaths.length)
14465
+ out.push({ a: active[i].id, b: active[j].id, shared: [...shared2, ...sharedPaths] });
14466
+ }
14467
+ }
14468
+ return out.slice(0, MAX_DEDUP_PAIRS);
14469
+ }
14470
+ function readAuditRecords(kb, since, dir = defaultAuditDir()) {
14471
+ if (!fs27.existsSync(dir))
14472
+ return [];
14473
+ const sinceMonth = since.toISOString().slice(0, 7);
14474
+ const out = [];
14475
+ for (const f of fs27.readdirSync(dir).filter((n) => n.endsWith(".jsonl") && n.slice(0, 7) >= sinceMonth).sort()) {
14476
+ for (const line2 of fs27.readFileSync(path28.join(dir, f), "utf8").split("\n")) {
14477
+ if (!line2.trim())
14478
+ continue;
14479
+ try {
14480
+ const e = JSON.parse(line2);
14481
+ if ((kb === void 0 || e.kb === kb) && Date.parse(e.ts) >= since.getTime())
14482
+ out.push(e);
14483
+ } catch {
14484
+ }
14485
+ }
14486
+ }
14487
+ return out;
14488
+ }
14489
+ function usageReview(rules, events) {
14490
+ const surfaced = {};
14491
+ const noise = {};
14492
+ const violated = /* @__PURE__ */ new Set();
14493
+ for (const e of events) {
14494
+ if (!e.itemId)
14495
+ continue;
14496
+ if (e.event === "item.surfaced")
14497
+ surfaced[e.itemId] = (surfaced[e.itemId] ?? 0) + 1;
14498
+ if (e.event === "item.outcome") {
14499
+ if (e.outcome === "noise")
14500
+ noise[e.itemId] = (noise[e.itemId] ?? 0) + 1;
14501
+ if (e.outcome === "violated")
14502
+ violated.add(e.itemId);
14503
+ }
14504
+ }
14505
+ const active = rules.filter((r) => r.status === "active");
14506
+ return {
14507
+ surfaced,
14508
+ neverSurfaced: active.filter((r) => !surfaced[r.id]).map((r) => r.id).sort(),
14509
+ noisy: Object.keys(noise).filter((id) => (surfaced[id] ?? 0) >= NOISY_MIN_SURFACED && noise[id] / surfaced[id] >= NOISY_RATE).sort(),
14510
+ violated: [...violated].sort()
14511
+ };
14512
+ }
14513
+ function domainCapExcess(rules, cap, surfaced) {
14514
+ const byDomain = /* @__PURE__ */ new Map();
14515
+ for (const r of rules.filter((x) => x.status === "active"))
14516
+ byDomain.set(r.domain, [...byDomain.get(r.domain) ?? [], r]);
14517
+ return [...byDomain].filter(([, rs]) => rs.length > cap).map(([domain, rs]) => ({
14518
+ domain,
14519
+ count: rs.length,
14520
+ cap,
14521
+ candidates: [...rs].sort((a, b) => (surfaced[a.id] ?? 0) - (surfaced[b.id] ?? 0) || a.id.localeCompare(b.id)).slice(0, rs.length - cap).map((r) => r.id)
14522
+ }));
14523
+ }
14524
+ function checkOverrides(cfg, kb) {
14525
+ const base = new Map(loadKbRules(cfg, kb).rules.map((r) => [r.id, r]));
14526
+ const out = [];
14527
+ for (const project of Object.keys(cfg.projects)) {
14528
+ if (!subscribedKbs(cfg, project).includes(kb))
14529
+ continue;
14530
+ const dir = projectRulesHome(cfg, project);
14531
+ if (!dir)
14532
+ continue;
14533
+ for (const o of loadRulesDir(dir, `project:${project}`).rules) {
14534
+ const b = base.get(o.id);
14535
+ if (!b)
14536
+ continue;
14537
+ const h = baseHash(b);
14538
+ if (!o.overridesBase) {
14539
+ const m = matter5(fs27.readFileSync(o.file, "utf8"));
14540
+ fs27.writeFileSync(o.file, matter5.stringify(m.content, { ...m.data, overridesBase: h }));
14541
+ continue;
14542
+ }
14543
+ out.push({ project, ruleId: o.id, file: o.file, baseHash: h, changed: o.overridesBase !== h });
14544
+ }
14545
+ }
14546
+ return out;
14547
+ }
14548
+ function runFullChecks(cfg, kb, o) {
14549
+ const rules = loadKbRules(cfg, kb).rules;
14550
+ const kbRoot = kbDir(cfg, kb);
14551
+ const usage2 = usageReview(rules, readAuditRecords(kb, new Date(o.now.getTime() - NEVER_SURFACED_DAYS * DAY_MS2), o.auditDir));
14552
+ const caps = domainCapExcess(rules, resolveKbConfig(cfg, kb).domainCap, usage2.surfaced);
14553
+ const overrides = checkOverrides(cfg, kb).filter((f) => f.changed);
14554
+ const base = { createdAt: o.now.toISOString(), needsYou: true };
14555
+ for (const c of caps) {
14556
+ addEscalation(kbRoot, { ...base, key: `domain-cap:${c.domain}`, kind: "domain-cap", reasons: ["domain cap"], explanation: `Domain '${c.domain}' has ${c.count} active rules (cap ${c.cap}). Archive candidates (least surfaced): ${c.candidates.join(", ")}.` });
14557
+ }
14558
+ for (const f of overrides) {
14559
+ addEscalation(kbRoot, { ...base, key: `override:${f.project}:${f.ruleId}`, kind: "override-base-changed", ruleId: f.ruleId, item: f.file, reasons: ["override.base-changed"], explanation: `Project ${f.project} overrides ${f.ruleId}, but the KB rule changed since the override was confirmed. Keep the override (approve) or drop it (reject).`, evidence: { docRev: f.baseHash, codeRev: "", files: [], project: f.project } });
14560
+ }
14561
+ return { usage: usage2, caps, overrides };
14562
+ }
14563
+ var DAY_MS2, NEVER_SURFACED_DAYS, NOISY_MIN_SURFACED, NOISY_RATE, MAX_DEDUP_PAIRS, terms, baseHash;
14564
+ var init_fullpass = __esm({
14565
+ "packages/core/dist/knowledge/fullpass.js"() {
14566
+ init_dist();
14567
+ init_audit();
14568
+ init_layers();
14569
+ init_review();
14570
+ init_paths();
14571
+ init_store2();
14572
+ DAY_MS2 = 24 * 60 * 60 * 1e3;
14573
+ NEVER_SURFACED_DAYS = 30;
14574
+ NOISY_MIN_SURFACED = 5;
14575
+ NOISY_RATE = 0.5;
14576
+ MAX_DEDUP_PAIRS = 50;
14577
+ terms = (r) => new Set([...r.triggers?.keywords ?? [], ...r.triggers?.expanded ?? []].map((s) => s.toLowerCase()));
14578
+ baseHash = (r) => crypto5.createHash("sha256").update(JSON.stringify([r.modality, r.statement, r.sources.map((s) => s.sha)])).digest("hex").slice(0, 12);
14579
+ }
14580
+ });
14581
+
14582
+ // packages/core/dist/knowledge/scores.js
14583
+ import fs28 from "fs";
14584
+ import path29 from "path";
14585
+ function tally(records, into = emptyTally()) {
14586
+ for (const e of records) {
14587
+ const domain = typeof e.domain === "string" && e.domain ? e.domain : "rules";
14588
+ const d = into.domains[domain] ??= { writes: 0, reads: 0 };
14589
+ if (WRITE_EVENTS.has(e.event ?? ""))
14590
+ d.writes++;
14591
+ if (READ_EVENTS.has(e.event ?? ""))
14592
+ d.reads++;
14593
+ if (!e.itemId)
14594
+ continue;
14595
+ const c = into.items[`${domain}|${e.itemId}`] ??= { ...zero(), domain, itemId: e.itemId };
14596
+ switch (e.event) {
14597
+ case "item.surfaced": {
14598
+ c.surfaced++;
14599
+ c.chars += Number(e.chars) || 0;
14600
+ c.lastSurfaced = later(c.lastSurfaced, e.ts);
14601
+ const agent = typeof e.agent === "string" ? e.agent : "unknown";
14602
+ into.agents[agent] = (into.agents[agent] ?? 0) + 1;
14603
+ const tier = typeof e.tier === "string" ? e.tier : typeof e.trigger === "string" ? e.trigger : "unknown";
14604
+ into.tiers[tier] = (into.tiers[tier] ?? 0) + 1;
14605
+ break;
14606
+ }
14607
+ case "item.suppressed":
14608
+ c.suppressed++;
14609
+ break;
14610
+ case "item.searched":
14611
+ c.searched++;
14612
+ break;
14613
+ case "item.shown":
14614
+ c.shown++;
14615
+ break;
14616
+ case "item.cited":
14617
+ c.cited++;
14618
+ c.lastUsed = later(c.lastUsed, e.ts);
14619
+ break;
14620
+ case "item.outcome":
14621
+ if (OUTCOMES.has(String(e.outcome)))
14622
+ c[e.outcome]++;
14623
+ break;
14624
+ }
14625
+ }
14626
+ return into;
14627
+ }
14628
+ function parseLines(file) {
14629
+ const out = [];
14630
+ for (const line2 of fs28.readFileSync(file, "utf8").split("\n")) {
14631
+ if (!line2.trim())
14632
+ continue;
14633
+ try {
14634
+ out.push(JSON.parse(line2));
14635
+ } catch {
14636
+ }
14637
+ }
14638
+ return out;
14639
+ }
14640
+ function compactAudit(o) {
14641
+ const dir = o.dir ?? defaultAuditDir();
14642
+ if (!fs28.existsSync(dir))
14643
+ return [];
14644
+ const cutoffMonth = new Date(o.now.getTime() - SCORE_WINDOW_DAYS * DAY_MS3).toISOString().slice(0, 7);
14645
+ const done = [];
14646
+ for (const f of fs28.readdirSync(dir).sort()) {
14647
+ const m = MONTH_FILE_RE.exec(f);
14648
+ if (!m || m[1] >= cutoffMonth)
14649
+ continue;
14650
+ const [, month, machine] = m;
14651
+ const byKb = /* @__PURE__ */ new Map();
14652
+ for (const r of parseLines(path29.join(dir, f)))
14653
+ byKb.set(r.kb ?? "", [...byKb.get(r.kb ?? "") ?? [], r]);
14654
+ const statsPath = path29.join(dir, statsFileName(month, machine));
14655
+ const prior = fs28.existsSync(statsPath) ? JSON.parse(fs28.readFileSync(statsPath, "utf8")) : null;
14656
+ const kbs = prior?.kbs ?? {};
14657
+ for (const [kb, recs] of byKb)
14658
+ kbs[kb] = tally(recs, kbs[kb]);
14659
+ fs28.writeFileSync(`${statsPath}.tmp`, JSON.stringify({ month, machine, kbs }));
14660
+ fs28.renameSync(`${statsPath}.tmp`, statsPath);
14661
+ fs28.unlinkSync(path29.join(dir, f));
14662
+ done.push(month);
14663
+ }
14664
+ return done;
14665
+ }
14666
+ function readCompacted(kb, dir) {
14667
+ const t = emptyTally();
14668
+ if (!fs28.existsSync(dir))
14669
+ return t;
14670
+ for (const f of fs28.readdirSync(dir).filter((n) => n.endsWith(".stats.json"))) {
14671
+ try {
14672
+ const k = JSON.parse(fs28.readFileSync(path29.join(dir, f), "utf8")).kbs[kb];
14673
+ if (k)
14674
+ mergeTally(t, k);
14675
+ } catch {
14676
+ }
14677
+ }
14678
+ return t;
14679
+ }
14680
+ function mergeTally(into, from) {
14681
+ for (const [k, c] of Object.entries(from.items)) {
14682
+ const t = into.items[k] ??= { ...zero(), domain: c.domain, itemId: c.itemId };
14683
+ t.lastSurfaced = later(t.lastSurfaced, c.lastSurfaced);
14684
+ t.lastUsed = later(t.lastUsed, c.lastUsed);
14685
+ for (const f of Object.keys(zero()))
14686
+ if (typeof c[f] === "number")
14687
+ t[f] += c[f];
14688
+ }
14689
+ for (const [k, v2] of Object.entries(from.domains)) {
14690
+ const d = into.domains[k] ??= { writes: 0, reads: 0 };
14691
+ d.writes += v2.writes;
14692
+ d.reads += v2.reads;
14693
+ }
14694
+ for (const [k, v2] of Object.entries(from.agents))
14695
+ into.agents[k] = (into.agents[k] ?? 0) + v2;
14696
+ for (const [k, v2] of Object.entries(from.tiers))
14697
+ into.tiers[k] = (into.tiers[k] ?? 0) + v2;
14698
+ }
14699
+ function computeScores(i) {
14700
+ const since = i.now.getTime() - SCORE_WINDOW_DAYS * DAY_MS3;
14701
+ const win = tally(i.records.filter((r) => Date.parse(r.ts) >= since));
14702
+ const life = tally(i.records, i.compacted ? structuredClone(i.compacted) : void 0);
14703
+ const knownActive = new Set((i.known ?? []).filter((k) => k.status === "active").map((k) => `${k.domain}|${k.id}`));
14704
+ const keys = /* @__PURE__ */ new Set([...Object.keys(win.items), ...Object.keys(life.items), ...knownActive]);
14705
+ const items = [...keys].sort().map((key) => {
14706
+ const [domain, itemId] = [key.slice(0, key.indexOf("|")), key.slice(key.indexOf("|") + 1)];
14707
+ const w = win.items[key] ?? { ...zero(), domain, itemId };
14708
+ const l = life.items[key];
14709
+ return {
14710
+ domain,
14711
+ itemId,
14712
+ surfaced: w.surfaced,
14713
+ suppressed: w.suppressed,
14714
+ searched: w.searched,
14715
+ shown: w.shown,
14716
+ cited: w.cited,
14717
+ followed: w.followed,
14718
+ violated: w.violated,
14719
+ noise: w.noise,
14720
+ wrong: w.wrong,
14721
+ fireRate: w.surfaced,
14722
+ pullRate: w.shown,
14723
+ useRate: ratio(w.cited, w.surfaced),
14724
+ noiseRate: ratio(w.noise, w.surfaced),
14725
+ violationRate: ratio(w.violated, w.surfaced),
14726
+ lastSurfaced: l?.lastSurfaced,
14727
+ lastUsed: l?.lastUsed,
14728
+ neverFired30d: w.surfaced === 0 && (knownActive.has(key) || (l?.surfaced ?? 0) > 0),
14729
+ lifetimeSurfaced: l?.surfaced ?? 0
14730
+ };
14731
+ });
14732
+ const domainNames = /* @__PURE__ */ new Set([...Object.keys(win.domains), ...items.map((x) => x.domain)]);
14733
+ const domains = [...domainNames].sort().map((domain) => {
14734
+ const d = win.domains[domain] ?? { writes: 0, reads: 0 };
14735
+ const rows = items.filter((x) => x.domain === domain);
14736
+ const chars = Object.values(win.items).filter((x) => x.domain === domain).reduce((a, x) => a + x.chars, 0);
14737
+ const cited = rows.reduce((a, x) => a + x.cited, 0);
14738
+ return {
14739
+ domain,
14740
+ writesPerMonth: d.writes,
14741
+ readsPerMonth: d.reads,
14742
+ deadDomain: d.writes === 0 && d.reads === 0,
14743
+ charsPerCitation: cited ? Math.round(chars / cited) : null,
14744
+ surfaced: rows.reduce((a, x) => a + x.surfaced, 0),
14745
+ cited
14746
+ };
14747
+ });
14748
+ const operator = (i.verdicts ?? []).filter((v2) => v2.kind === "operator");
14749
+ const approved = operator.filter((v2) => v2.resolution?.kind === "approve").length;
14750
+ return {
14751
+ kb: i.kb,
14752
+ generatedAt: i.now.toISOString(),
14753
+ windowDays: SCORE_WINDOW_DAYS,
14754
+ items,
14755
+ domains,
14756
+ perAgentCoverage: win.agents,
14757
+ tierContribution: win.tiers,
14758
+ reviewerAgreement: { resolved: operator.length, approved, rate: ratio(approved, operator.length) }
14759
+ };
14760
+ }
14761
+ function scoreKb(o) {
14762
+ const dir = o.auditDir ?? defaultAuditDir();
14763
+ const records = [];
14764
+ if (fs28.existsSync(dir)) {
14765
+ for (const f of fs28.readdirSync(dir).filter((n) => MONTH_FILE_RE.test(n)).sort()) {
14766
+ records.push(...parseLines(path29.join(dir, f)).filter((r) => r.kb === o.kb));
14767
+ }
14768
+ }
14769
+ let verdicts = [];
14770
+ try {
14771
+ verdicts = fs28.readFileSync(path29.join(o.kbRoot, "verdicts.jsonl"), "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l));
14772
+ } catch {
14773
+ }
14774
+ return computeScores({ kb: o.kb, records, now: o.now, known: o.known, compacted: readCompacted(o.kb, dir), verdicts });
14775
+ }
14776
+ function writeScores(kbRoot, s) {
14777
+ fs28.mkdirSync(kbRoot, { recursive: true });
14778
+ fs28.writeFileSync(scoresFile(kbRoot), JSON.stringify(s, null, 2) + "\n");
14779
+ }
14780
+ function renderScoresSection(s) {
14781
+ const pct = (x) => x === null ? "n/a" : `${Math.round(x * 100)}%`;
14782
+ const used = s.items.filter((x) => x.surfaced).sort((a, b) => b.surfaced - a.surfaced || a.itemId.localeCompare(b.itemId)).slice(0, 10);
14783
+ const never = s.items.filter((x) => x.neverFired30d).map((x) => x.itemId);
14784
+ return [
14785
+ "",
14786
+ `## Scores (${s.windowDays} days)`,
14787
+ "",
14788
+ ...used.length ? [
14789
+ "| Item | Surfaced | Pulled | Cited | Use | Noise | Violated |",
14790
+ "|---|---|---|---|---|---|---|",
14791
+ ...used.map((x) => `| ${x.itemId} | ${x.surfaced} | ${x.shown} | ${x.cited} | ${pct(x.useRate)} | ${pct(x.noiseRate)} | ${pct(x.violationRate)} |`)
14792
+ ] : ["No items surfaced."],
14793
+ "",
14794
+ `- Never fired (${never.length}): ${never.slice(0, 20).join(", ") || "none"}${never.length > 20 ? ", \u2026" : ""}`,
14795
+ `- Tier contribution: ${Object.entries(s.tierContribution).map(([k, v2]) => `${k} ${v2}`).join(", ") || "none"}`,
14796
+ `- Per-agent coverage (surfaced): ${Object.entries(s.perAgentCoverage).map(([k, v2]) => `${k} ${v2}`).join(", ") || "none"}`,
14797
+ `- Reviewer agreement: ${pct(s.reviewerAgreement.rate)} (${s.reviewerAgreement.approved}/${s.reviewerAgreement.resolved} operator resolutions approved)`,
14798
+ "",
14799
+ "| Domain | Writes/mo | Reads/mo | Chars per citation | Alarm |",
14800
+ "|---|---|---|---|---|",
14801
+ ...s.domains.map((d) => `| ${d.domain} | ${d.writesPerMonth} | ${d.readsPerMonth} | ${d.charsPerCitation ?? "n/a"} | ${d.deadDomain ? "DEAD (no activity 30d)" : ""} |`)
14802
+ ];
14803
+ }
14804
+ var DAY_MS3, SCORE_WINDOW_DAYS, WRITE_EVENTS, READ_EVENTS, OUTCOMES, zero, later, emptyTally, MONTH_FILE_RE, statsFileName, ratio, scoresFile;
14805
+ var init_scores = __esm({
14806
+ "packages/core/dist/knowledge/scores.js"() {
14807
+ init_audit();
14808
+ DAY_MS3 = 24 * 60 * 60 * 1e3;
14809
+ SCORE_WINDOW_DAYS = 30;
14810
+ WRITE_EVENTS = /* @__PURE__ */ new Set(["item.proposed", "item.approved", "item.rejected", "item.applied", "item.promoted", "item.superseded", "item.archived"]);
14811
+ READ_EVENTS = /* @__PURE__ */ new Set(["item.surfaced", "item.shown", "item.searched"]);
14812
+ OUTCOMES = /* @__PURE__ */ new Set(["followed", "violated", "noise", "wrong"]);
14813
+ zero = () => ({ surfaced: 0, suppressed: 0, searched: 0, shown: 0, cited: 0, followed: 0, violated: 0, noise: 0, wrong: 0, chars: 0 });
14814
+ later = (a, b) => a && b ? a > b ? a : b : a ?? b;
14815
+ emptyTally = () => ({ items: {}, domains: {}, agents: {}, tiers: {} });
14816
+ MONTH_FILE_RE = /^(\d{4}-\d{2})\.(.+)\.jsonl$/;
14817
+ statsFileName = (month, machine) => `${month}.${machine}.stats.json`;
14818
+ ratio = (n, d) => d ? Math.round(n / d * 1e3) / 1e3 : null;
14819
+ scoresFile = (kbRoot) => path29.join(kbRoot, "scores.json");
14820
+ }
14821
+ });
14822
+
14823
+ // packages/core/dist/knowledge/report.js
14824
+ import fs29 from "fs";
14825
+ import path30 from "path";
14826
+ function renderReport(cfg, kb, o) {
14827
+ const { rules } = loadKbRules(cfg, kb, { includeProposed: true });
14828
+ const count = (s) => rules.filter((r) => r.status === s).length;
14829
+ const esc = readEscalations(kbDir(cfg, kb));
14830
+ const need = esc.filter((e) => e.needsYou);
14831
+ const L = [
14832
+ `# ${kb} knowledge base: reconcile report`,
14833
+ "",
14834
+ `${o.pass} pass, ${o.now.toISOString()}`,
14835
+ "",
14836
+ `Rules: ${count("active")} active, ${count("proposed")} proposed, ${count("stale")} stale, ${count("retired")} retired.`,
14837
+ "",
14838
+ `## Needs you (${need.length})`,
14839
+ "",
14840
+ ...need.length ? need.map((e) => `- **${e.key}** [${e.kind}] ${e.reasons.join(", ")}: ${e.explanation}`) : ["Nothing."],
14841
+ "",
14842
+ "Answer with `squadrant knowledge review " + kb + "`."
14843
+ ];
14844
+ const info = esc.filter((e) => !e.needsYou);
14845
+ if (info.length)
14846
+ L.push("", `## For information (${info.length})`, "", ...info.map((e) => `- **${e.key}**: ${e.explanation}`));
14847
+ if (o.full) {
14848
+ const { usage: usage2, caps } = o.full;
14849
+ 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"}`);
14850
+ }
14851
+ if (o.scores)
14852
+ L.push(...renderScoresSection(o.scores));
14853
+ return L.join("\n") + "\n";
14854
+ }
14855
+ function writeReport(cfg, kb, o) {
14856
+ const file = path30.join(kbDir(cfg, kb), "REPORT.md");
14857
+ fs29.writeFileSync(file, renderReport(cfg, kb, o));
14858
+ return file;
14859
+ }
14860
+ var init_report = __esm({
14861
+ "packages/core/dist/knowledge/report.js"() {
14862
+ init_layers();
14863
+ init_paths();
14864
+ init_review();
14865
+ init_scores();
14866
+ }
14867
+ });
14868
+
14869
+ // packages/core/dist/knowledge/reconcile.js
14870
+ function finishMessage(kb, needsYou) {
14871
+ return needsYou ? `${kb} KB: ${needsYou} item${needsYou === 1 ? "" : "s"} need you (squadrant knowledge review ${kb})` : `${kb} KB: reconcile finished, nothing needs you`;
14872
+ }
14873
+ async function finishReconcile(kb, d) {
14874
+ const now = d.now?.() ?? /* @__PURE__ */ new Date();
14875
+ const pass = completePass(kb, { ...d, now: () => now });
14876
+ let full;
14877
+ if (pass === "full")
14878
+ full = runFullChecks(d.cfg, kb, { now, auditDir: d.auditDir });
14879
+ if (pass === "full")
14880
+ compactAudit({ now, dir: d.auditDir });
14881
+ const scores = scoreKb({ kb, now, auditDir: d.auditDir, kbRoot: kbDir(d.cfg, kb), known: loadKbRules(d.cfg, kb).rules.map((r) => ({ id: r.id, domain: "rules", status: r.status })) });
14882
+ writeScores(kbDir(d.cfg, kb), scores);
14883
+ const report = writeReport(d.cfg, kb, { pass, now, full, scores });
14884
+ const needsYou = needsYouCount(kbDir(d.cfg, kb));
14885
+ const home = resolveKbConfig(d.cfg, kb).homeProject;
14886
+ if (home)
14887
+ await d.enqueue(home, finishMessage(kb, needsYou));
14888
+ return { pass, report, needsYou };
14889
+ }
14890
+ var init_reconcile = __esm({
14891
+ "packages/core/dist/knowledge/reconcile.js"() {
14892
+ init_dist();
14893
+ init_scores();
14894
+ init_layers();
14895
+ init_fullpass();
14896
+ init_paths();
14897
+ init_report();
14898
+ init_review();
14899
+ init_schedule();
14900
+ }
14901
+ });
14902
+
14903
+ // packages/core/dist/knowledge/cite.js
14904
+ function scanAndLogCitations(i) {
14905
+ const at = i.at ?? i.sink?.now?.() ?? /* @__PURE__ */ new Date();
14906
+ const since = new Date(at.getTime() - (i.lookbackDays ?? CITE_LOOKBACK_DAYS) * DAY_MS4);
14907
+ const dir = i.sink?.dir ?? defaultAuditDir();
14908
+ const delivered = /* @__PURE__ */ new Map();
14909
+ const already = /* @__PURE__ */ new Set();
14910
+ for (const r of readAuditRecords(void 0, since, dir)) {
14911
+ if (!r.itemId || r.project !== i.project)
14912
+ continue;
14913
+ if (r.event === "item.cited" && r.ref === i.ref)
14914
+ already.add(r.itemId);
14915
+ if (!(r.event === "item.surfaced" || r.event === "item.shown") || Date.parse(r.ts) > at.getTime())
14916
+ continue;
14917
+ if (i.session && r.session && r.session !== i.session)
14918
+ continue;
14919
+ delivered.set(r.itemId, r);
14920
+ }
14921
+ const hits = [...delivered].filter(([id]) => !already.has(id) && mentionsId(i.text, id));
14922
+ appendAudit(hits.map(([itemId, r]) => ({
14923
+ kb: String(r.kb ?? ""),
14924
+ level: String(r.level ?? "project"),
14925
+ project: i.project,
14926
+ domain: String(r.domain ?? "rules"),
14927
+ itemId,
14928
+ event: "item.cited",
14929
+ ref: i.ref,
14930
+ ...i.agent ? { agent: i.agent } : {},
14931
+ ...i.session ? { session: i.session } : {}
14932
+ })), i.sink);
14933
+ return hits.map(([id]) => id);
14934
+ }
14935
+ function readGitMessages(repo, since, run) {
14936
+ const out = run(["-C", repo, "log", `--since=${since.toISOString()}`, "--format=%x1e%H%x1f%cI%x1f%B"]);
14937
+ return out.split("").filter((c) => c.trim()).map((c) => {
14938
+ const [sha, at, ...rest] = c.split("");
14939
+ return { sha: sha.trim(), at: new Date(at), text: rest.join("") };
14940
+ });
14941
+ }
14942
+ var DAY_MS4, CITE_LOOKBACK_DAYS, escapeRe2, mentionsId;
14943
+ var init_cite = __esm({
14944
+ "packages/core/dist/knowledge/cite.js"() {
14945
+ init_audit();
14946
+ init_fullpass();
14947
+ DAY_MS4 = 24 * 60 * 60 * 1e3;
14948
+ CITE_LOOKBACK_DAYS = 7;
14949
+ escapeRe2 = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14950
+ mentionsId = (text, id) => new RegExp(`(?<![\\w.-])${escapeRe2(id)}(?![\\w-])`).test(text);
14951
+ }
14952
+ });
14953
+
14954
+ // packages/core/dist/knowledge/index.js
14955
+ var init_knowledge2 = __esm({
14956
+ "packages/core/dist/knowledge/index.js"() {
14957
+ init_paths();
14958
+ init_store2();
14959
+ init_layers();
14960
+ init_index_file();
14961
+ init_search();
14962
+ init_sources();
14963
+ init_inject();
14964
+ init_audit();
14965
+ init_migrate();
14966
+ init_verify();
14967
+ init_ingest();
14968
+ init_anchor();
14969
+ init_extract();
14970
+ init_match();
14971
+ init_schedule();
14972
+ init_review();
14973
+ init_fullpass();
14974
+ init_report();
14975
+ init_reconcile();
14976
+ init_scores();
14977
+ init_cite();
14978
+ }
14979
+ });
14980
+
14981
+ // packages/core/dist/index.js
14982
+ var dist_exports = {};
14983
+ __export(dist_exports, {
14984
+ AGENT_BINS: () => AGENT_BINS,
14985
+ ANTHROPIC_ONLY_REQUEST_FIELDS: () => ANTHROPIC_ONLY_REQUEST_FIELDS,
14986
+ AUDIT_QUERY_CHARS: () => AUDIT_QUERY_CHARS,
14987
+ BOT_COMMANDS: () => BOT_COMMANDS,
14988
+ CAPTAIN_RECEIVED_ACK: () => CAPTAIN_RECEIVED_ACK,
14989
+ CC_SOCKS_DIR: () => CC_SOCKS_DIR,
14990
+ CHARS_PER_TOKEN: () => CHARS_PER_TOKEN,
14991
+ CITE_LOOKBACK_DAYS: () => CITE_LOOKBACK_DAYS,
14992
+ CITE_NOTE: () => CITE_NOTE,
14993
+ CMUX_PRESERVE_CLAUDE_AUTH_ENV: () => CMUX_PRESERVE_CLAUDE_AUTH_ENV,
14994
+ CONFLICT_409_SELF_HEAL_MS: () => CONFLICT_409_SELF_HEAL_MS,
14995
+ CONVERTED_DIR: () => CONVERTED_DIR,
14996
+ CREW_GONE_MS: () => CREW_GONE_MS,
14997
+ CREW_STALE_MS: () => CREW_STALE_MS,
14998
+ CaptainDelivery: () => CaptainDelivery,
14999
+ DEFAULT_BLOCKED_RENUDGE_MS: () => DEFAULT_BLOCKED_RENUDGE_MS,
15000
+ DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS: () => DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS,
15001
+ DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS: () => DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS,
15002
+ DEFAULT_GATE_DENY_RULES: () => DEFAULT_GATE_DENY_RULES,
15003
+ DEFAULT_GATE_TOOLS: () => DEFAULT_GATE_TOOLS,
15004
+ DEFAULT_MAX_SECTIONS_PER_PASS: () => DEFAULT_MAX_SECTIONS_PER_PASS,
15005
+ DEFAULT_SEARCH_STATUSES: () => DEFAULT_SEARCH_STATUSES,
15006
+ DEFAULT_TASK_TIMEOUT_MS: () => DEFAULT_TASK_TIMEOUT_MS,
15007
+ DOCLING_DROP_THRESHOLD: () => DOCLING_DROP_THRESHOLD,
15008
+ DeferDelivery: () => DeferDelivery,
15009
+ FIRST_TURN_INLINE_MAX_BYTES: () => FIRST_TURN_INLINE_MAX_BYTES,
15010
+ FactLog: () => FactLog,
15011
+ GROUP_DISPATCH_WARMUP_POLL_MS: () => GROUP_DISPATCH_WARMUP_POLL_MS,
15012
+ GROUP_DISPATCH_WARMUP_TIMEOUT_MS: () => GROUP_DISPATCH_WARMUP_TIMEOUT_MS,
15013
+ GateDecisionCache: () => GateDecisionCache,
15014
+ IDLE_DEBOUNCE_MS: () => IDLE_DEBOUNCE_MS,
15015
+ INJECT_BUDGET_MS: () => INJECT_BUDGET_MS,
15016
+ INJECT_MAX_CHARS: () => INJECT_MAX_CHARS,
15017
+ INJECT_MAX_RULES: () => INJECT_MAX_RULES,
15018
+ LABEL: () => LABEL,
15019
+ LivenessRegistry: () => LivenessRegistry,
15020
+ MAX_DEDUP_PAIRS: () => MAX_DEDUP_PAIRS,
15021
+ MONITOR_STALL_BUDGET_MS: () => MONITOR_STALL_BUDGET_MS,
15022
+ NEVER_SURFACED_DAYS: () => NEVER_SURFACED_DAYS,
15023
+ NOISY_MIN_SURFACED: () => NOISY_MIN_SURFACED,
15024
+ NOISY_RATE: () => NOISY_RATE,
15025
+ OPERATOR_INITIATED_COMMANDS: () => OPERATOR_INITIATED_COMMANDS,
15026
+ PANE_IDLE_AFTER_MS: () => PANE_IDLE_AFTER_MS,
15027
+ PROBE_QUIET_MS: () => PROBE_QUIET_MS,
15028
+ PROMPT_MIN_SCORE: () => PROMPT_MIN_SCORE,
15029
+ PROMPT_RULE_CAP: () => PROMPT_RULE_CAP,
15030
+ PROMPT_SINGLE_TERM_MIN_SCORE: () => PROMPT_SINGLE_TERM_MIN_SCORE,
15031
+ PROPOSED_DIR: () => PROPOSED_DIR,
15032
+ PROTOCOL_VERSION: () => PROTOCOL_VERSION,
15033
+ READ_ONLY_CREW_SUBCOMMANDS: () => READ_ONLY_CREW_SUBCOMMANDS,
15034
+ READ_ONLY_TOP_LEVEL_COMMANDS: () => READ_ONLY_TOP_LEVEL_COMMANDS,
15035
+ ROUTER_READY_ATTEMPTS: () => ROUTER_READY_ATTEMPTS,
15036
+ ROUTER_READY_DELAY_MS: () => ROUTER_READY_DELAY_MS,
15037
+ RUNNING_STALE_MS: () => RUNNING_STALE_MS,
15038
+ SCORE_WINDOW_DAYS: () => SCORE_WINDOW_DAYS,
15039
+ SERVER_TOOL_TYPE_RE: () => SERVER_TOOL_TYPE_RE,
15040
+ SESSION_RULE_CAP: () => SESSION_RULE_CAP,
15041
+ SIDE_SESSION_ENV: () => SIDE_SESSION_ENV,
15042
+ SOURCES_TEMPLATE: () => SOURCES_TEMPLATE,
15043
+ STALE_THRESHOLD_MS: () => STALE_THRESHOLD_MS,
15044
+ STUCK_ALERT_TEXT: () => STUCK_ALERT_TEXT,
15045
+ TERMINAL_RECORD_KEEP_PER_PROJECT: () => TERMINAL_RECORD_KEEP_PER_PROJECT,
15046
+ TERMINAL_RECORD_TTL_MS: () => TERMINAL_RECORD_TTL_MS,
15047
+ TOOL_STALL_BUDGET_MS: () => TOOL_STALL_BUDGET_MS,
15048
+ TYPING_KEEPALIVE_MS: () => TYPING_KEEPALIVE_MS,
15049
+ TelegramApiError: () => TelegramApiError,
15050
+ WATCHDOG_GRACE_MS: () => WATCHDOG_GRACE_MS,
15051
+ WATCHDOG_MS: () => WATCHDOG_MS,
15052
+ WORK_ITEM_TTL_MS: () => WORK_ITEM_TTL_MS,
15053
+ WRITABLE_CONFIG_KEYS: () => WRITABLE_CONFIG_KEYS,
15054
+ _resetRestartInFlightForTest: () => _resetRestartInFlightForTest,
15055
+ acquirePass: () => acquirePass,
15056
+ addEscalation: () => addEscalation,
15057
+ ageText: () => ageText,
15058
+ anchorTerms: () => anchorTerms,
15059
+ anthropicError: () => anthropicError,
15060
+ appendAudit: () => appendAudit,
15061
+ appendCaptainMessage: () => appendCaptainMessage,
15062
+ appendToMailbox: () => appendToMailbox,
15063
+ appendVerdict: () => appendVerdict,
15064
+ applyBudget: () => applyBudget,
15065
+ applyCandidates: () => applyCandidates,
15066
+ applyDecisions: () => applyDecisions,
15067
+ assembleDaemonSnapshot: () => assembleDaemonSnapshot,
15068
+ assertBackendUsable: () => assertBackendUsable,
15069
+ auditMachineId: () => auditMachineId,
15070
+ baseHash: () => baseHash,
15071
+ bootWorkspace: () => bootWorkspace,
15072
+ buildClassifierInput: () => buildClassifierInput,
15073
+ buildCompletionProtocol: () => buildCompletionProtocol,
15074
+ buildContext: () => buildContext,
15075
+ buildDaemonPath: () => buildDaemonPath,
15076
+ buildFreshness: () => buildFreshness,
15077
+ buildOpencodeGateRunCommand: () => buildOpencodeGateRunCommand,
15078
+ buildReviewPacket: () => buildReviewPacket,
15079
+ buildRouterCredentialsRequest: () => buildRouterCredentialsRequest,
15080
+ buildRouterEnv: () => buildRouterEnv,
15081
+ buildSideFirstTurn: () => buildSideFirstTurn,
15082
+ callClassifier: () => callClassifier,
15083
+ capAllowed: () => capAllowed,
15084
+ capOutput: () => capOutput,
15085
+ captainAliveStateFromHealth: () => captainAliveStateFromHealth,
15086
+ captainRecordPath: () => captainRecordPath,
15087
+ captainSocketPath: () => captainSocketPath,
15088
+ changedRefs: () => changedRefs,
15089
+ checkFact: () => checkFact,
15090
+ checkOverrides: () => checkOverrides,
15091
+ classifyCaptainPane: () => classifyCaptainPane,
15092
+ classifyHealth: () => classifyHealth,
15093
+ claudeEnvShadowsRouter: () => claudeEnvShadowsRouter,
13025
15094
  cleanPromptQuery: () => cleanPromptQuery,
13026
15095
  clearPending: () => clearPending,
13027
15096
  closeWorkItem: () => closeWorkItem,
15097
+ compactAudit: () => compactAudit,
13028
15098
  compileIndex: () => compileIndex,
15099
+ completePass: () => completePass,
15100
+ computeAnchor: () => computeAnchor,
13029
15101
  computeRulesInjection: () => computeRulesInjection,
15102
+ computeScores: () => computeScores,
13030
15103
  computeTemplateHash: () => computeTemplateHash,
13031
15104
  consumeExitMarker: () => consumeExitMarker,
15105
+ convertedFile: () => convertedFile,
13032
15106
  createAttach: () => createAttach,
13033
15107
  createCaptainPaneReader: () => createCaptainPaneReader,
13034
15108
  createCrewPaneReader: () => createCrewPaneReader,
@@ -13062,6 +15136,8 @@ __export(dist_exports, {
13062
15136
  decideForeignInstall: () => decideForeignInstall,
13063
15137
  decideOpencodeGateWrap: () => decideOpencodeGateWrap,
13064
15138
  decodeFrames: () => decodeFrames,
15139
+ dedupPairs: () => dedupPairs,
15140
+ defaultAuditDir: () => defaultAuditDir,
13065
15141
  defaultGateCachePath: () => defaultGateCachePath,
13066
15142
  defaultGateDenyRules: () => defaultGateDenyRules,
13067
15143
  defaultIsPidAlive: () => defaultIsPidAlive,
@@ -13073,27 +15149,40 @@ __export(dist_exports, {
13073
15149
  deliverable: () => deliverable,
13074
15150
  deriveCaptainState: () => deriveCaptainState,
13075
15151
  describeOutcome: () => describeOutcome,
15152
+ detectChange: () => detectChange,
13076
15153
  detectForeignInstall: () => detectForeignInstall,
13077
15154
  detectGroupAndUser: () => detectGroupAndUser,
13078
15155
  detectGroupId: () => detectGroupId,
13079
15156
  discoverCaptainSurface: () => discoverCaptainSurface,
13080
15157
  discoverLiveOpencodeServer: () => discoverLiveOpencodeServer,
13081
15158
  dispatchToSibling: () => dispatchToSibling,
15159
+ domainCapExcess: () => domainCapExcess,
15160
+ duePass: () => duePass,
13082
15161
  duplicateIdProblem: () => duplicateIdProblem,
13083
15162
  encodeFrame: () => encodeFrame,
13084
15163
  encodeMsg: () => encodeMsg,
13085
15164
  ensureDaemon: () => ensureDaemon,
15165
+ ensureGitRepo: () => ensureGitRepo,
13086
15166
  ensureSocksDir: () => ensureSocksDir,
15167
+ escalationsFile: () => escalationsFile,
15168
+ evaluateCodeConflict: () => evaluateCodeConflict,
13087
15169
  evaluatePermissionRequest: () => evaluatePermissionRequest,
13088
15170
  evaluateStall: () => evaluateStall,
13089
15171
  exitMarkerPath: () => exitMarkerPath,
15172
+ expandSource: () => expandSource,
13090
15173
  extractToolPayload: () => extractToolPayload,
13091
15174
  extractUserIntentFromTranscript: () => extractUserIntentFromTranscript,
13092
15175
  fallsBackToPane: () => fallsBackToPane,
15176
+ fileForRef: () => fileForRef,
13093
15177
  findCrewPane: () => findCrewPane,
15178
+ findLegacyKbs: () => findLegacyKbs,
15179
+ findLegacyOverlays: () => findLegacyOverlays,
13094
15180
  findOpenChildren: () => findOpenChildren,
13095
15181
  findProjectByThread: () => findProjectByThread,
15182
+ findQuote: () => findQuote,
13096
15183
  findWorkItemById: () => findWorkItemById,
15184
+ finishMessage: () => finishMessage,
15185
+ finishReconcile: () => finishReconcile,
13097
15186
  forceKickstartAndVerify: () => forceKickstartAndVerify,
13098
15187
  formatCustomHeaders: () => formatCustomHeaders,
13099
15188
  formatInbound: () => formatInbound,
@@ -13108,6 +15197,8 @@ __export(dist_exports, {
13108
15197
  freshTrace: () => freshTrace,
13109
15198
  gateCacheKey: () => gateCacheKey,
13110
15199
  getDaemonPid: () => getDaemonPid,
15200
+ globMatches: () => globMatches,
15201
+ globToRegExp: () => globToRegExp,
13111
15202
  hasAutoGateCredential: () => hasAutoGateCredential,
13112
15203
  hasModalOptionList: () => hasModalOptionList,
13113
15204
  healCmdFor: () => healCmdFor,
@@ -13124,6 +15215,7 @@ __export(dist_exports, {
13124
15215
  isGateSession: () => isGateSession,
13125
15216
  isNotifyActive: () => isNotifyActive,
13126
15217
  isOperatorInitiatedCommand: () => isOperatorInitiatedCommand,
15218
+ isPendingProposalDup: () => isPendingProposalDup,
13127
15219
  isReadOnlyCrewCommand: () => isReadOnlyCrewCommand,
13128
15220
  isReadOnlyTopLevelCommand: () => isReadOnlyTopLevelCommand,
13129
15221
  isSideTitle: () => isSideTitle,
@@ -13136,9 +15228,11 @@ __export(dist_exports, {
13136
15228
  kbRulesDir: () => kbRulesDir,
13137
15229
  kickstartArgv: () => kickstartArgv,
13138
15230
  launchOneWorkspace: () => launchOneWorkspace,
15231
+ legacyKbDir: () => legacyKbDir,
13139
15232
  listSessions: () => listSessions,
13140
15233
  loadKbRules: () => loadKbRules,
13141
15234
  loadPending: () => loadPending,
15235
+ loadProposals: () => loadProposals,
13142
15236
  loadRulesDir: () => loadRulesDir,
13143
15237
  loadSessions: () => loadSessions,
13144
15238
  loadSources: () => loadSources,
@@ -13146,16 +15240,28 @@ __export(dist_exports, {
13146
15240
  mailboxStats: () => mailboxStats,
13147
15241
  makeGate: () => makeGate,
13148
15242
  markPendingWarned: () => markPendingWarned,
15243
+ markitdownConverter: () => markitdownConverter,
13149
15244
  maskToken: () => maskToken,
15245
+ matchPaths: () => matchPaths,
15246
+ matchSymbols: () => matchSymbols,
13150
15247
  matchTier1Deny: () => matchTier1Deny,
13151
15248
  mediaKind: () => mediaKind,
13152
15249
  mediaMarker: () => mediaMarker,
15250
+ mentionedPaths: () => mentionedPaths,
15251
+ mentionsId: () => mentionsId,
13153
15252
  mergeClaudeEnvRouterSettings: () => mergeClaudeEnvRouterSettings,
13154
15253
  mintToken: () => mintToken,
15254
+ moveLegacyKbs: () => moveLegacyKbs,
15255
+ moveLegacyOverlays: () => moveLegacyOverlays,
13155
15256
  nameFromTitle: () => nameFromTitle,
15257
+ needsDocling: () => needsDocling,
15258
+ needsYouCount: () => needsYouCount,
13156
15259
  newestSessionInDirectory: () => newestSessionInDirectory,
13157
15260
  nextAutoName: () => nextAutoName,
13158
15261
  niceCrewCommand: () => niceCrewCommand,
15262
+ normalizeText: () => normalizeText,
15263
+ normalizeWithMap: () => normalizeWithMap,
15264
+ normalizedViews: () => normalizedViews,
13159
15265
  notePaneScreen: () => notePaneScreen,
13160
15266
  notifyToggle: () => notifyToggle,
13161
15267
  parseBearer: () => parseBearer,
@@ -13164,39 +15270,57 @@ __export(dist_exports, {
13164
15270
  parseLiveOpencodeServers: () => parseLiveOpencodeServers,
13165
15271
  parseNotifyPref: () => parseNotifyPref,
13166
15272
  parseProgramArgs: () => parseProgramArgs,
15273
+ planIngest: () => planIngest,
13167
15274
  plistPath: () => plistPath,
13168
15275
  pollBackoffMs: () => pollBackoffMs,
13169
15276
  pollNewestSessionInDirectory: () => pollNewestSessionInDirectory,
13170
15277
  prepareCaptainRoute: () => prepareCaptainRoute,
13171
15278
  printForeignInstallError: () => printForeignInstallError,
13172
15279
  printVersionUpgradeNotice: () => printVersionUpgradeNotice,
15280
+ priorityOfRef: () => priorityOfRef,
15281
+ priorityOfRule: () => priorityOfRule,
13173
15282
  probeCaptainChannel: () => probeCaptainChannel,
13174
15283
  programArgsBlock: () => programArgsBlock,
13175
15284
  projectHealth: () => projectHealth,
13176
- projectRulesDir: () => projectRulesDir,
15285
+ promoteProposal: () => promoteProposal,
15286
+ proposeAgentRule: () => proposeAgentRule,
13177
15287
  pruneSeenRules: () => pruneSeenRules,
13178
15288
  pruneTopics: () => pruneTopics,
13179
15289
  purgeExpiredWorkItems: () => purgeExpiredWorkItems,
15290
+ readAuditRecords: () => readAuditRecords,
13180
15291
  readCaptainAddress: () => readCaptainAddress,
13181
15292
  readCursor: () => readCursor,
15293
+ readEscalations: () => readEscalations,
13182
15294
  readFromCursor: () => readFromCursor,
15295
+ readGitMessages: () => readGitMessages,
13183
15296
  readIndex: () => readIndex,
13184
15297
  readRunningMarker: () => readRunningMarker,
15298
+ readSchedule: () => readSchedule,
13185
15299
  readSeenRules: () => readSeenRules,
15300
+ readState: () => readState2,
15301
+ readTargetPath: () => readTargetPath,
13186
15302
  readUserIntent: () => readUserIntent,
15303
+ realGit: () => realGit,
13187
15304
  realpathOrSelf: () => realpathOrSelf,
13188
15305
  reapCrewChildren: () => reapCrewChildren,
13189
15306
  reapOrphanedCrews: () => reapOrphanedCrews,
15307
+ reconcileConfig: () => reconcileConfig,
13190
15308
  reconcileLiveness: () => reconcileLiveness,
13191
15309
  recordSeenRules: () => recordSeenRules,
13192
15310
  recordSession: () => recordSession,
13193
15311
  recoverStall: () => recoverStall,
13194
15312
  reduce: () => reduce,
13195
15313
  reduceLifecycle: () => reduceLifecycle,
15314
+ refFor: () => refFor,
13196
15315
  releaseDaemonLock: () => releaseDaemonLock,
13197
15316
  removeRunningMarker: () => removeRunningMarker,
13198
15317
  renderEnvAssignments: () => renderEnvAssignments,
13199
15318
  renderPlist: () => renderPlist,
15319
+ renderReport: () => renderReport,
15320
+ renderScoresSection: () => renderScoresSection,
15321
+ repoRelativePaths: () => repoRelativePaths,
15322
+ requestPass: () => requestPass,
15323
+ requestText: () => requestText,
13200
15324
  reregisterDaemon: () => reregisterDaemon,
13201
15325
  resolveAgentBinDirs: () => resolveAgentBinDirs,
13202
15326
  resolveAndPersistOpencodeCaptain: () => resolveAndPersistOpencodeCaptain,
@@ -13204,6 +15328,7 @@ __export(dist_exports, {
13204
15328
  resolveClassifierModel: () => resolveClassifierModel,
13205
15329
  resolveCrewRoute: () => resolveCrewRoute,
13206
15330
  resolveCurrentProject: () => resolveCurrentProject,
15331
+ resolveEscalation: () => resolveEscalation,
13207
15332
  resolveGate: () => resolveGate,
13208
15333
  resolveGateDenyRules: () => resolveGateDenyRules,
13209
15334
  resolveGateEngine: () => resolveGateEngine,
@@ -13220,6 +15345,8 @@ __export(dist_exports, {
13220
15345
  resolveSetupUserId: () => resolveSetupUserId,
13221
15346
  restartDaemonIfRunning: () => restartDaemonIfRunning,
13222
15347
  rotateIfNeeded: () => rotateIfNeeded,
15348
+ ruleAuditScope: () => ruleAuditScope,
15349
+ rulesFallbackPointer: () => rulesFallbackPointer,
13223
15350
  runAdapterConformance: () => runAdapterConformance,
13224
15351
  runCrewAnswer: () => runCrewAnswer,
13225
15352
  runCrewClose: () => runCrewClose,
@@ -13227,6 +15354,7 @@ __export(dist_exports, {
13227
15354
  runCrewRead: () => runCrewRead,
13228
15355
  runCrewSend: () => runCrewSend,
13229
15356
  runCrewSpawn: () => runCrewSpawn,
15357
+ runFullChecks: () => runFullChecks,
13230
15358
  runLivenessTick: () => runLivenessTick,
13231
15359
  runNotifyConfirmation: () => runNotifyConfirmation,
13232
15360
  runRegisterCommands: () => runRegisterCommands,
@@ -13247,6 +15375,10 @@ __export(dist_exports, {
13247
15375
  sanitizeRequest: () => sanitizeRequest,
13248
15376
  saveSessions: () => saveSessions,
13249
15377
  saveState: () => saveState,
15378
+ scanAndLogCitations: () => scanAndLogCitations,
15379
+ scheduleFile: () => scheduleFile,
15380
+ scoreKb: () => scoreKb,
15381
+ scoresFile: () => scoresFile,
13250
15382
  screenHasSplashMarker: () => screenHasSplashMarker,
13251
15383
  screenHash: () => screenHash,
13252
15384
  screenShowsTrustPanel: () => screenShowsTrustPanel,
@@ -13263,13 +15395,18 @@ __export(dist_exports, {
13263
15395
  sideNameFromTitle: () => sideNameFromTitle,
13264
15396
  sideNextAutoName: () => sideNextAutoName,
13265
15397
  sideTitleFor: () => sideTitleFor,
15398
+ snapshotSources: () => snapshotSources,
15399
+ spansOverlap: () => spansOverlap,
15400
+ splitSections: () => splitSections,
13266
15401
  splitStatement: () => splitStatement,
13267
15402
  stampFact: () => stampFact,
13268
15403
  startDaemon: () => startDaemon,
13269
15404
  startServer: () => startServer,
13270
15405
  stripBotMention: () => stripBotMention,
13271
15406
  surfaceVerdict: () => surfaceVerdict,
15407
+ tally: () => tally,
13272
15408
  telegramPollLockPath: () => telegramPollLockPath,
15409
+ tickKnowledgeSchedule: () => tickKnowledgeSchedule,
13273
15410
  timeoutGate: () => timeoutGate,
13274
15411
  titleFor: () => titleFor,
13275
15412
  toControlEvent: () => toControlEvent,
@@ -13278,16 +15415,23 @@ __export(dist_exports, {
13278
15415
  trustPanelAcceptKeys: () => trustPanelAcceptKeys,
13279
15416
  tryAcquireDaemonLock: () => tryAcquireDaemonLock,
13280
15417
  usageFromJson: () => usageFromJson,
15418
+ usageReview: () => usageReview,
15419
+ validateCandidate: () => validateCandidate,
13281
15420
  validateKb: () => validateKb,
15421
+ verdictsFile: () => verdictsFile,
13282
15422
  waitForCaptainDelivery: () => waitForCaptainDelivery,
13283
15423
  waitForWarmup: () => waitForWarmup,
13284
15424
  wholeWordTerms: () => wholeWordTerms,
13285
15425
  withBudget: () => withBudget,
13286
15426
  writeCaptainAddress: () => writeCaptainAddress,
13287
15427
  writeCursor: () => writeCursor,
15428
+ writeEscalations: () => writeEscalations,
13288
15429
  writeExitMarker: () => writeExitMarker,
13289
15430
  writeIndex: () => writeIndex,
15431
+ writeReport: () => writeReport,
13290
15432
  writeRunningMarker: () => writeRunningMarker,
15433
+ writeSchedule: () => writeSchedule,
15434
+ writeScores: () => writeScores,
13291
15435
  writeTelegramConfig: () => writeTelegramConfig
13292
15436
  });
13293
15437
  var init_dist2 = __esm({
@@ -13386,7 +15530,7 @@ import { readdirSync as readdirSync4, readFileSync as readFileSync12 } from "fs"
13386
15530
  import { join as join18 } from "path";
13387
15531
 
13388
15532
  // packages/agents/dist/claude/registry.js
13389
- import fs20 from "fs";
15533
+ import fs30 from "fs";
13390
15534
  import { join as join17 } from "path";
13391
15535
  import { homedir as homedir7 } from "os";
13392
15536
  var CLAUDE_SESSIONS_DIR = join17(homedir7(), ".claude", "sessions");
@@ -13431,11 +15575,11 @@ function toLifecycleSnapshot(entry, taskId, alive, now) {
13431
15575
  function readClaudeStatusBySocketPath(socketPath) {
13432
15576
  let files;
13433
15577
  try {
13434
- files = fs20.readdirSync(CLAUDE_SESSIONS_DIR);
15578
+ files = fs30.readdirSync(CLAUDE_SESSIONS_DIR);
13435
15579
  } catch {
13436
15580
  return void 0;
13437
15581
  }
13438
- const entries = parseRegistryDir(files, (name) => fs20.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
15582
+ const entries = parseRegistryDir(files, (name) => fs30.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
13439
15583
  const entry = entries.find((e) => e.messagingSocketPath === socketPath);
13440
15584
  if (!entry)
13441
15585
  return void 0;
@@ -13458,38 +15602,38 @@ import { homedir as homedir8 } from "os";
13458
15602
  var SQUADRANT_STATE_DIR = join19(homedir8(), ".config", "squadrant", "state");
13459
15603
 
13460
15604
  // packages/agents/dist/drivers/launch-cmd.js
13461
- import fs21 from "fs";
13462
- import path19 from "path";
15605
+ import fs31 from "fs";
15606
+ import path31 from "path";
13463
15607
 
13464
15608
  // packages/agents/dist/projection/cursor.js
13465
15609
  import { mkdir, readFile, writeFile } from "fs/promises";
13466
- import path20 from "path";
13467
- import os8 from "os";
15610
+ import path32 from "path";
15611
+ import os10 from "os";
13468
15612
 
13469
15613
  // packages/agents/dist/projection/codex.js
13470
15614
  import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
13471
- import path21 from "path";
13472
- import os9 from "os";
15615
+ import path33 from "path";
15616
+ import os11 from "os";
13473
15617
 
13474
15618
  // packages/agents/dist/projection/gemini.js
13475
15619
  import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
13476
- import path22 from "path";
13477
- import os10 from "os";
15620
+ import path34 from "path";
15621
+ import os12 from "os";
13478
15622
 
13479
15623
  // packages/agents/dist/projection/opencode.js
13480
15624
  import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
13481
- import path23 from "path";
13482
- import os11 from "os";
15625
+ import path35 from "path";
15626
+ import os13 from "os";
13483
15627
 
13484
15628
  // packages/agents/dist/projection/opencode-skills.js
13485
- import fs22 from "fs";
13486
- import os12 from "os";
13487
- import path24 from "path";
15629
+ import fs32 from "fs";
15630
+ import os14 from "os";
15631
+ import path36 from "path";
13488
15632
  var OPENCODE_SKILL_MARKER = ".squadrant-managed";
13489
15633
  var MARKER_BODY = "squadrant\n";
13490
15634
  function defaultOpencodeSkillsRoot(env = process.env) {
13491
- const configHome = env.XDG_CONFIG_HOME || path24.join(os12.homedir(), ".config");
13492
- return path24.join(configHome, "opencode", "skills");
15635
+ const configHome = env.XDG_CONFIG_HOME || path36.join(os14.homedir(), ".config");
15636
+ return path36.join(configHome, "opencode", "skills");
13493
15637
  }
13494
15638
  var SAFE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
13495
15639
  function parseSkillName(raw) {
@@ -13504,62 +15648,62 @@ function parseSkillName(raw) {
13504
15648
  }
13505
15649
  function listSourceSkills(sourceSkillsDir) {
13506
15650
  const out = /* @__PURE__ */ new Map();
13507
- if (!fs22.existsSync(sourceSkillsDir))
15651
+ if (!fs32.existsSync(sourceSkillsDir))
13508
15652
  return out;
13509
- for (const entry of fs22.readdirSync(sourceSkillsDir, { withFileTypes: true })) {
15653
+ for (const entry of fs32.readdirSync(sourceSkillsDir, { withFileTypes: true })) {
13510
15654
  if (!entry.isDirectory())
13511
15655
  continue;
13512
- const skillPath = path24.join(sourceSkillsDir, entry.name, "SKILL.md");
13513
- if (!fs22.existsSync(skillPath))
15656
+ const skillPath = path36.join(sourceSkillsDir, entry.name, "SKILL.md");
15657
+ if (!fs32.existsSync(skillPath))
13514
15658
  continue;
13515
- const name = parseSkillName(fs22.readFileSync(skillPath, "utf-8"));
15659
+ const name = parseSkillName(fs32.readFileSync(skillPath, "utf-8"));
13516
15660
  if (name && SAFE_NAME.test(name))
13517
15661
  out.set(name, skillPath);
13518
15662
  }
13519
15663
  return out;
13520
15664
  }
13521
15665
  function hasMarker(dir) {
13522
- return fs22.existsSync(path24.join(dir, OPENCODE_SKILL_MARKER));
15666
+ return fs32.existsSync(path36.join(dir, OPENCODE_SKILL_MARKER));
13523
15667
  }
13524
15668
  function syncOpencodeSkills(opts) {
13525
15669
  const result = { written: [], unchanged: [], removed: [], skipped: [] };
13526
15670
  const wanted = listSourceSkills(opts.sourceSkillsDir);
13527
- if (wanted.size === 0 && !fs22.existsSync(opts.sourceSkillsDir))
15671
+ if (wanted.size === 0 && !fs32.existsSync(opts.sourceSkillsDir))
13528
15672
  return result;
13529
15673
  if (!opts.dryRun)
13530
- fs22.mkdirSync(opts.skillsRoot, { recursive: true });
15674
+ fs32.mkdirSync(opts.skillsRoot, { recursive: true });
13531
15675
  for (const [name, srcPath] of wanted) {
13532
- const targetDir = path24.join(opts.skillsRoot, name);
13533
- const targetSkill = path24.join(targetDir, "SKILL.md");
15676
+ const targetDir = path36.join(opts.skillsRoot, name);
15677
+ const targetSkill = path36.join(targetDir, "SKILL.md");
13534
15678
  const managed = hasMarker(targetDir);
13535
- if (fs22.existsSync(targetSkill) && !managed) {
15679
+ if (fs32.existsSync(targetSkill) && !managed) {
13536
15680
  result.skipped.push({ name, reason: "foreign-collision" });
13537
15681
  continue;
13538
15682
  }
13539
- const current = fs22.existsSync(targetSkill) ? fs22.readFileSync(targetSkill) : null;
13540
- if (current && current.equals(fs22.readFileSync(srcPath))) {
15683
+ const current = fs32.existsSync(targetSkill) ? fs32.readFileSync(targetSkill) : null;
15684
+ if (current && current.equals(fs32.readFileSync(srcPath))) {
13541
15685
  if (!managed && !opts.dryRun) {
13542
- fs22.writeFileSync(path24.join(targetDir, OPENCODE_SKILL_MARKER), MARKER_BODY);
15686
+ fs32.writeFileSync(path36.join(targetDir, OPENCODE_SKILL_MARKER), MARKER_BODY);
13543
15687
  }
13544
15688
  result.unchanged.push(name);
13545
15689
  continue;
13546
15690
  }
13547
15691
  if (!opts.dryRun) {
13548
- fs22.mkdirSync(targetDir, { recursive: true });
13549
- fs22.copyFileSync(srcPath, targetSkill);
13550
- fs22.writeFileSync(path24.join(targetDir, OPENCODE_SKILL_MARKER), MARKER_BODY);
15692
+ fs32.mkdirSync(targetDir, { recursive: true });
15693
+ fs32.copyFileSync(srcPath, targetSkill);
15694
+ fs32.writeFileSync(path36.join(targetDir, OPENCODE_SKILL_MARKER), MARKER_BODY);
13551
15695
  }
13552
15696
  result.written.push(name);
13553
15697
  }
13554
- if (fs22.existsSync(opts.skillsRoot)) {
13555
- for (const entry of fs22.readdirSync(opts.skillsRoot, { withFileTypes: true })) {
15698
+ if (fs32.existsSync(opts.skillsRoot)) {
15699
+ for (const entry of fs32.readdirSync(opts.skillsRoot, { withFileTypes: true })) {
13556
15700
  if (!entry.isDirectory() || wanted.has(entry.name))
13557
15701
  continue;
13558
- const dir = path24.join(opts.skillsRoot, entry.name);
15702
+ const dir = path36.join(opts.skillsRoot, entry.name);
13559
15703
  if (!hasMarker(dir))
13560
15704
  continue;
13561
15705
  if (!opts.dryRun)
13562
- fs22.rmSync(dir, { recursive: true, force: true });
15706
+ fs32.rmSync(dir, { recursive: true, force: true });
13563
15707
  result.removed.push(entry.name);
13564
15708
  }
13565
15709
  }
@@ -13567,7 +15711,7 @@ function syncOpencodeSkills(opts) {
13567
15711
  }
13568
15712
  function syncShippedOpencodeSkills(opts) {
13569
15713
  return syncOpencodeSkills({
13570
- sourceSkillsDir: path24.join(opts.pkgRoot, "plugin", "skills"),
15714
+ sourceSkillsDir: path36.join(opts.pkgRoot, "plugin", "skills"),
13571
15715
  skillsRoot: opts.skillsRoot ?? defaultOpencodeSkillsRoot(),
13572
15716
  dryRun: opts.dryRun
13573
15717
  });
@@ -13575,8 +15719,8 @@ function syncShippedOpencodeSkills(opts) {
13575
15719
 
13576
15720
  // packages/agents/dist/projection/claude.js
13577
15721
  import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
13578
- import path25 from "path";
13579
- import os13 from "os";
15722
+ import path37 from "path";
15723
+ import os15 from "os";
13580
15724
 
13581
15725
  // packages/agents/dist/codex/app-server-client.js
13582
15726
  import { EventEmitter } from "events";
@@ -13794,51 +15938,51 @@ var CodexAppServerSource = class {
13794
15938
  * are ignored — terminal state still comes exclusively from `squadrant crew signal`
13795
15939
  * (anti-#2576 invariant).
13796
15940
  */
13797
- observe(ev) {
13798
- const snap = toSnapshot(ev);
15941
+ observe(ev2) {
15942
+ const snap = toSnapshot(ev2);
13799
15943
  if (!snap || !this.deps)
13800
15944
  return;
13801
15945
  this.cache.set(snap.taskId, snap);
13802
15946
  this.deps.report(snap);
13803
15947
  }
13804
15948
  };
13805
- function toSnapshot(ev) {
15949
+ function toSnapshot(ev2) {
13806
15950
  const now = Date.now();
13807
- switch (ev.type) {
15951
+ switch (ev2.type) {
13808
15952
  // ── running: a turn is live ──────────────────────────────────────────────
13809
15953
  case "task.started":
13810
15954
  case "task.reattached":
13811
15955
  case "task.turn.started":
13812
15956
  case "task.delta":
13813
15957
  case "task.progress":
13814
- return { taskId: ev.id, state: "running", alive: true, origin: "agent", at: now };
15958
+ return { taskId: ev2.id, state: "running", alive: true, origin: "agent", at: now };
13815
15959
  // ── idle: turn ended, crew alive, awaiting next input ────────────────────
13816
15960
  // task.failed: the turn ended with an error, but the crew process is alive.
13817
15961
  // task.session.ended: process is gone (alive:false) — signals liveness loss.
13818
15962
  case "task.turn.completed":
13819
- return { taskId: ev.id, state: "idle", alive: true, origin: "agent", at: now };
15963
+ return { taskId: ev2.id, state: "idle", alive: true, origin: "agent", at: now };
13820
15964
  case "task.failed":
13821
- return { taskId: ev.id, state: "idle", alive: true, origin: "agent", at: now };
15965
+ return { taskId: ev2.id, state: "idle", alive: true, origin: "agent", at: now };
13822
15966
  case "task.session.ended":
13823
- return { taskId: ev.id, state: "idle", alive: false, origin: "agent", at: now };
15967
+ return { taskId: ev2.id, state: "idle", alive: false, origin: "agent", at: now };
13824
15968
  // ── needsInput: crew is blocked on a human ───────────────────────────────
13825
15969
  case "task.approval.requested":
13826
15970
  return {
13827
- taskId: ev.id,
15971
+ taskId: ev2.id,
13828
15972
  state: "needsInput",
13829
15973
  alive: true,
13830
15974
  origin: "agent",
13831
15975
  at: now,
13832
- detail: { note: ev.question, reason: ev.kind }
15976
+ detail: { note: ev2.question, reason: ev2.kind }
13833
15977
  };
13834
15978
  case "task.input.requested":
13835
15979
  return {
13836
- taskId: ev.id,
15980
+ taskId: ev2.id,
13837
15981
  state: "needsInput",
13838
15982
  alive: true,
13839
15983
  origin: "agent",
13840
15984
  at: now,
13841
- detail: { note: ev.question }
15985
+ detail: { note: ev2.question }
13842
15986
  };
13843
15987
  // ── terminal / notify-only — ignored ────────────────────────────────────
13844
15988
  // task.done, task.blocked, task.cancelled: terminal state from crew signal only.
@@ -14123,9 +16267,9 @@ var CodexInteractiveDriver = class {
14123
16267
  const taskId = tid ? this.taskByThread.get(tid) : void 0;
14124
16268
  if (!taskId)
14125
16269
  return;
14126
- const ev = normalizeAppServerNotification(taskId, n);
14127
- if (ev)
14128
- this.deps.emit(ev);
16270
+ const ev2 = normalizeAppServerNotification(taskId, n);
16271
+ if (ev2)
16272
+ this.deps.emit(ev2);
14129
16273
  }
14130
16274
  onServerRequest(r) {
14131
16275
  const tid = r.params?.threadId ?? r.params?.thread_id;
@@ -14920,9 +17064,9 @@ var ClaudeReceiptListener = class {
14920
17064
  };
14921
17065
 
14922
17066
  // packages/agents/dist/claude/api-key-approval.js
14923
- import fs23 from "fs";
14924
- import os14 from "os";
14925
- import path26 from "path";
17067
+ import fs33 from "fs";
17068
+ import os16 from "os";
17069
+ import path38 from "path";
14926
17070
 
14927
17071
  // packages/agents/dist/opencode/http-channel.js
14928
17072
  init_dist2();
@@ -15014,10 +17158,10 @@ var OpencodeHttpChannel = class {
15014
17158
  return cached;
15015
17159
  const directory = this.directoryFor?.(taskId);
15016
17160
  const createdAfterMs = this.createdAfterFor?.(taskId);
15017
- for (const path29 of ["/session?", "/api/session?"]) {
17161
+ for (const path42 of ["/session?", "/api/session?"]) {
15018
17162
  let res;
15019
17163
  try {
15020
- res = await this.request(`http://127.0.0.1:${port}${path29}`, { method: "GET" });
17164
+ res = await this.request(`http://127.0.0.1:${port}${path42}`, { method: "GET" });
15021
17165
  } catch {
15022
17166
  return void 0;
15023
17167
  }
@@ -15090,13 +17234,18 @@ function createOpencodeFactAdapter(deps) {
15090
17234
  };
15091
17235
  }
15092
17236
 
17237
+ // packages/agents/dist/rules-hooks/install.js
17238
+ import fs34 from "fs";
17239
+ import os17 from "os";
17240
+ import path39 from "path";
17241
+
15093
17242
  // packages/workspaces/dist/runtimes/cmux.js
15094
17243
  init_dist();
15095
17244
  init_dist();
15096
17245
  init_dist();
15097
17246
  init_dist2();
15098
17247
  init_dist2();
15099
- import { execFile as execFile2, execFileSync as execFileSync6 } from "child_process";
17248
+ import { execFile as execFile3, execFileSync as execFileSync8 } from "child_process";
15100
17249
  var CMUX_TIMEOUT = 15e3;
15101
17250
  var CmuxTimeoutError = class extends Error {
15102
17251
  constructor(cmd) {
@@ -15106,7 +17255,7 @@ var CmuxTimeoutError = class extends Error {
15106
17255
  };
15107
17256
  function cmux(args) {
15108
17257
  return new Promise((resolve4, reject) => {
15109
- execFile2(
17258
+ execFile3(
15110
17259
  resolveCmuxBin(),
15111
17260
  args,
15112
17261
  // CMUX_QUIET=1 silences cmux 0.64's one-time deprecation hints (e.g. the
@@ -15126,7 +17275,7 @@ function cmux(args) {
15126
17275
  }
15127
17276
  function cmuxStdin(args, input) {
15128
17277
  return new Promise((resolve4, reject) => {
15129
- const child = execFile2(resolveCmuxBin(), args, { encoding: "utf-8", timeout: CMUX_TIMEOUT, env: { ...process.env, CMUX_QUIET: "1" } }, (err, stdout) => {
17278
+ const child = execFile3(resolveCmuxBin(), args, { encoding: "utf-8", timeout: CMUX_TIMEOUT, env: { ...process.env, CMUX_QUIET: "1" } }, (err, stdout) => {
15130
17279
  if (err) {
15131
17280
  reject(err.code === "ETIMEDOUT" ? new CmuxTimeoutError(args.join(" ")) : err);
15132
17281
  return;
@@ -15549,7 +17698,7 @@ function createCmuxDriver() {
15549
17698
  async sendToSurface(surface, text, opts) {
15550
17699
  const ws = surface.workspaceId;
15551
17700
  const sf = surface.surfaceId;
15552
- const deliver = async () => {
17701
+ const deliver2 = async () => {
15553
17702
  const dbg = sendDebugEnabled();
15554
17703
  let preBox = null;
15555
17704
  if (dbg) {
@@ -15579,7 +17728,7 @@ function createCmuxDriver() {
15579
17728
  if (state === "dialog")
15580
17729
  throw new DeferDelivery(null, "modal");
15581
17730
  if (state === "empty") {
15582
- await deliver();
17731
+ await deliver2();
15583
17732
  return;
15584
17733
  }
15585
17734
  }
@@ -15597,7 +17746,7 @@ function createCmuxDriver() {
15597
17746
  if (hasModalOptionList2(screen))
15598
17747
  throw new DeferDelivery(null, "modal");
15599
17748
  if (draft === "") {
15600
- await deliver();
17749
+ await deliver2();
15601
17750
  return;
15602
17751
  }
15603
17752
  if (!opts?.probe)
@@ -15625,7 +17774,7 @@ function createCmuxDriver() {
15625
17774
  throw new DeferDelivery(draft);
15626
17775
  }
15627
17776
  if (liveness === "no-draft") {
15628
- await deliver();
17777
+ await deliver2();
15629
17778
  return;
15630
17779
  }
15631
17780
  if (rawBefore !== null && rawAfter !== null) {
@@ -15635,7 +17784,7 @@ function createCmuxDriver() {
15635
17784
  await restoreGrapheme(lastGrapheme);
15636
17785
  throw new DeferDelivery(draft);
15637
17786
  }
15638
- await deliver();
17787
+ await deliver2();
15639
17788
  return;
15640
17789
  }
15641
17790
  throw new DeferDelivery(draft);
@@ -15770,15 +17919,15 @@ var RuntimeRegistry = class {
15770
17919
 
15771
17920
  // packages/workspaces/dist/notifiers/cmux.js
15772
17921
  init_dist();
15773
- import { execFile as execFileCb, execFileSync as execFileSync7 } from "child_process";
15774
- import { promisify as promisify2 } from "util";
15775
- var execFile3 = promisify2(execFileCb);
17922
+ import { execFile as execFileCb, execFileSync as execFileSync9 } from "child_process";
17923
+ import { promisify as promisify3 } from "util";
17924
+ var execFile4 = promisify3(execFileCb);
15776
17925
  function createCmuxNotifier(_scope) {
15777
17926
  return {
15778
17927
  name: "cmux",
15779
17928
  async probe() {
15780
17929
  try {
15781
- execFileSync7(resolveCmuxBin(), ["capabilities"], { encoding: "utf-8", stdio: "pipe", timeout: CMUX_TIMEOUT });
17930
+ execFileSync9(resolveCmuxBin(), ["capabilities"], { encoding: "utf-8", stdio: "pipe", timeout: CMUX_TIMEOUT });
15782
17931
  return { installed: true, reachable: true };
15783
17932
  } catch (err) {
15784
17933
  const code = err.code;
@@ -15789,7 +17938,7 @@ function createCmuxNotifier(_scope) {
15789
17938
  }
15790
17939
  },
15791
17940
  async notify(message) {
15792
- await execFile3("squadrant", ["runtime", "send", "--command", message], { encoding: "utf-8", timeout: CMUX_TIMEOUT });
17941
+ await execFile4("squadrant", ["runtime", "send", "--command", message], { encoding: "utf-8", timeout: CMUX_TIMEOUT });
15793
17942
  }
15794
17943
  };
15795
17944
  }
@@ -15826,9 +17975,9 @@ var NotifierRegistry = class {
15826
17975
  };
15827
17976
 
15828
17977
  // packages/workspaces/dist/workspaces/obsidian.js
15829
- import fs24 from "fs/promises";
17978
+ import fs35 from "fs/promises";
15830
17979
  import { existsSync as existsSync11 } from "fs";
15831
- import path27 from "path";
17980
+ import path40 from "path";
15832
17981
 
15833
17982
  // packages/workspaces/dist/workspaces/registry.js
15834
17983
  init_dist();
@@ -15988,9 +18137,9 @@ import { homedir as homedir11 } from "os";
15988
18137
  // packages/workspaces/dist/cmux-daemon/store-fingerprint.js
15989
18138
  init_dist();
15990
18139
  init_dist2();
15991
- import { execFileSync as execFileSync8 } from "child_process";
18140
+ import { execFileSync as execFileSync10 } from "child_process";
15992
18141
  function defaultExecPs(pid) {
15993
- return execFileSync8("ps", ["-o", "command=", "-p", String(pid)], {
18142
+ return execFileSync10("ps", ["-o", "command=", "-p", String(pid)], {
15994
18143
  encoding: "utf-8",
15995
18144
  stdio: ["ignore", "pipe", "ignore"]
15996
18145
  });
@@ -16378,9 +18527,9 @@ function defaultListFiles(dir) {
16378
18527
  return [];
16379
18528
  }
16380
18529
  }
16381
- function defaultReadFile(path29) {
18530
+ function defaultReadFile(path42) {
16382
18531
  try {
16383
- return readFileSync17(path29, "utf-8");
18532
+ return readFileSync17(path42, "utf-8");
16384
18533
  } catch {
16385
18534
  return void 0;
16386
18535
  }
@@ -16412,7 +18561,9 @@ var CLAUDE_HOOK_EVENTS = [
16412
18561
  ["PermissionRequest", "permission-request"],
16413
18562
  // #763: the turn died on an API error — today squadrant has no source for
16414
18563
  // this at all; without it the watchdog reports a plain stall (wrong story).
16415
- ["StopFailure", "stop-failure"]
18564
+ ["StopFailure", "stop-failure"],
18565
+ // #899: rules whose globs/anchors match the file just read are injected as context.
18566
+ ["PostToolUse", "post-tool-use", "Read"]
16416
18567
  ];
16417
18568
  var DEFAULT_CLI_CMD = "squadrant";
16418
18569
  function hookCommandFor(eventName, sub, hookCmd, gateCmd) {
@@ -16758,16 +18909,16 @@ function extractDetail(sub, payload) {
16758
18909
  }
16759
18910
  return void 0;
16760
18911
  }
16761
- function defaultReadFile2(path29) {
18912
+ function defaultReadFile2(path42) {
16762
18913
  try {
16763
- return readFileSync18(path29, "utf-8");
18914
+ return readFileSync18(path42, "utf-8");
16764
18915
  } catch {
16765
18916
  return void 0;
16766
18917
  }
16767
18918
  }
16768
- function defaultWriteFile(path29, content) {
16769
- mkdirSync8(path29.replace(/\/[^/]+$/, ""), { recursive: true });
16770
- writeFileSync11(path29, content, "utf-8");
18919
+ function defaultWriteFile(path42, content) {
18920
+ mkdirSync8(path42.replace(/\/[^/]+$/, ""), { recursive: true });
18921
+ writeFileSync11(path42, content, "utf-8");
16771
18922
  }
16772
18923
 
16773
18924
  // packages/workspaces/dist/crew-pane.js
@@ -16852,9 +19003,9 @@ init_dist();
16852
19003
 
16853
19004
  // packages/cli/src/lib/daemon-restart-broadcast.ts
16854
19005
  init_dist();
16855
- import path28 from "path";
19006
+ import path41 from "path";
16856
19007
  function statePath2(stateRoot) {
16857
- return path28.join(stateRoot, "daemon-restart-state.json");
19008
+ return path41.join(stateRoot, "daemon-restart-state.json");
16858
19009
  }
16859
19010
  function computeRestartSignature(version, buildMtimeMs) {
16860
19011
  return `${version}::${buildMtimeMs}`;
@@ -16903,7 +19054,7 @@ async function maybeBroadcastDaemonRestart(opts) {
16903
19054
 
16904
19055
  // packages/cli/src/lib/captain-channel-factory.ts
16905
19056
  import { createServer as createServer4, connect as netConnect } from "net";
16906
- import fs25 from "fs";
19057
+ import fs36 from "fs";
16907
19058
  import { join as join26 } from "path";
16908
19059
  import { randomUUID as randomUUID5 } from "crypto";
16909
19060
  import chalk2 from "chalk";
@@ -16912,15 +19063,15 @@ var shared;
16912
19063
  var registryEntryPath = () => join26(CLAUDE_SESSIONS_DIR, `${process.pid}.json`);
16913
19064
  function unregisterSenderIdentity() {
16914
19065
  try {
16915
- fs25.unlinkSync(registryEntryPath());
19066
+ fs36.unlinkSync(registryEntryPath());
16916
19067
  } catch {
16917
19068
  }
16918
19069
  }
16919
19070
  function registerSenderIdentity(socketPath) {
16920
19071
  try {
16921
19072
  unregisterSenderIdentity();
16922
- fs25.mkdirSync(CLAUDE_SESSIONS_DIR, { recursive: true });
16923
- fs25.writeFileSync(
19073
+ fs36.mkdirSync(CLAUDE_SESSIONS_DIR, { recursive: true });
19074
+ fs36.writeFileSync(
16924
19075
  registryEntryPath(),
16925
19076
  JSON.stringify({
16926
19077
  pid: process.pid,
@@ -16946,7 +19097,7 @@ async function sharedReceiptListener() {
16946
19097
  // must never be the reason we refuse to start.
16947
19098
  unlinkStale: (p) => {
16948
19099
  try {
16949
- fs25.unlinkSync(p);
19100
+ fs36.unlinkSync(p);
16950
19101
  } catch {
16951
19102
  }
16952
19103
  },
@@ -16971,7 +19122,7 @@ async function buildCaptainChannel() {
16971
19122
  // in /tmp/x registers cwd "/private/tmp/x" on macOS, so cwd matching silently
16972
19123
  // missed and every captain ping reported accepted-unconfirmed.
16973
19124
  statusFor: (taskId) => readClaudeStatusBySocketPath(captainSocketPath(taskId)),
16974
- wire: (p, e) => writeLine(p, e, { connect: (path29) => netConnect(path29) }),
19125
+ wire: (p, e) => writeLine(p, e, { connect: (path42) => netConnect(path42) }),
16975
19126
  receipts,
16976
19127
  newMsgId: () => randomUUID5(),
16977
19128
  sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
@@ -17137,25 +19288,25 @@ function startSquadrantd(opts = {}) {
17137
19288
  ctx.cancelPromotionsFor = cancelPromotionsFor;
17138
19289
  const codexAppServerSource = new CodexAppServerSource();
17139
19290
  const codexDriver = opts.codexDriver ?? new CodexInteractiveDriver({
17140
- emit: (ev) => {
17141
- const found = ctx.store.listAll().find((r) => r.id === ev.id);
19291
+ emit: (ev2) => {
19292
+ const found = ctx.store.listAll().find((r) => r.id === ev2.id);
17142
19293
  if (!found) return;
17143
- void ctx.d.handle({ kind: "event", project: found.project, event: ev });
17144
- if (ev.type === "task.delta")
17145
- ctx.broadcast(ev.id, { type: "delta", taskId: ev.id, text: ev.chunk });
17146
- else if (ev.type === "task.turn.started")
17147
- ctx.broadcast(ev.id, { type: "turn-started", taskId: ev.id });
17148
- else if (ev.type === "task.turn.completed")
17149
- ctx.broadcast(ev.id, { type: "turn-completed", taskId: ev.id });
17150
- else if (ev.type === "task.input.requested") {
17151
- ctx.broadcast(ev.id, { type: "input-requested", taskId: ev.id, requestId: ev.requestId, question: ev.question });
17152
- ctx.schedulePromotion(ev.id, ev.requestId, "input", ev.question);
17153
- } else if (ev.type === "task.approval.requested") {
17154
- ctx.broadcast(ev.id, { type: "approval-requested", taskId: ev.id, requestId: ev.requestId, question: ev.question, kind: ev.kind });
17155
- ctx.schedulePromotion(ev.id, ev.requestId, "approval", ev.question);
17156
- } else if (ev.type === "task.reattached")
17157
- ctx.broadcast(ev.id, { type: "reattached", taskId: ev.id });
17158
- codexAppServerSource.observe(ev);
19294
+ void ctx.d.handle({ kind: "event", project: found.project, event: ev2 });
19295
+ if (ev2.type === "task.delta")
19296
+ ctx.broadcast(ev2.id, { type: "delta", taskId: ev2.id, text: ev2.chunk });
19297
+ else if (ev2.type === "task.turn.started")
19298
+ ctx.broadcast(ev2.id, { type: "turn-started", taskId: ev2.id });
19299
+ else if (ev2.type === "task.turn.completed")
19300
+ ctx.broadcast(ev2.id, { type: "turn-completed", taskId: ev2.id });
19301
+ else if (ev2.type === "task.input.requested") {
19302
+ ctx.broadcast(ev2.id, { type: "input-requested", taskId: ev2.id, requestId: ev2.requestId, question: ev2.question });
19303
+ ctx.schedulePromotion(ev2.id, ev2.requestId, "input", ev2.question);
19304
+ } else if (ev2.type === "task.approval.requested") {
19305
+ ctx.broadcast(ev2.id, { type: "approval-requested", taskId: ev2.id, requestId: ev2.requestId, question: ev2.question, kind: ev2.kind });
19306
+ ctx.schedulePromotion(ev2.id, ev2.requestId, "approval", ev2.question);
19307
+ } else if (ev2.type === "task.reattached")
19308
+ ctx.broadcast(ev2.id, { type: "reattached", taskId: ev2.id });
19309
+ codexAppServerSource.observe(ev2);
17159
19310
  }
17160
19311
  });
17161
19312
  const violationLogState = /* @__PURE__ */ new Map();
@@ -17179,23 +19330,23 @@ function startSquadrantd(opts = {}) {
17179
19330
  const opencodeRequestIds = { n: 1 };
17180
19331
  const eventsSource = createEventsSource({
17181
19332
  adapters: [createOpencodeFactAdapter({ nextRequestId: () => opencodeRequestIds.n++ })],
17182
- emit: (ev) => {
17183
- const found = store.listAll().find((r) => r.id === ev.id);
19333
+ emit: (ev2) => {
19334
+ const found = store.listAll().find((r) => r.id === ev2.id);
17184
19335
  if (!found) return;
17185
- void ctx.d.handle({ kind: "event", project: found.project, event: ev });
17186
- if (ev.type === "task.approval.requested")
17187
- ctx.schedulePromotion(ev.id, ev.requestId, "approval", ev.question);
19336
+ void ctx.d.handle({ kind: "event", project: found.project, event: ev2 });
19337
+ if (ev2.type === "task.approval.requested")
19338
+ ctx.schedulePromotion(ev2.id, ev2.requestId, "approval", ev2.question);
17188
19339
  },
17189
19340
  onViolation: onEventsViolation,
17190
19341
  check: { stallBudgetMs: 6e4, disagreeWindowMs: 5e3 }
17191
19342
  });
17192
19343
  const opencodeBridge = opts.opencodeBridge ?? new OpencodeSseBridge({
17193
- emit: (ev) => {
17194
- const found = store.listAll().find((r) => r.id === ev.id);
19344
+ emit: (ev2) => {
19345
+ const found = store.listAll().find((r) => r.id === ev2.id);
17195
19346
  if (!found) return;
17196
- void ctx.d.handle({ kind: "event", project: found.project, event: ev });
17197
- if (ev.type === "task.approval.requested")
17198
- ctx.schedulePromotion(ev.id, ev.requestId, "approval", ev.question);
19347
+ void ctx.d.handle({ kind: "event", project: found.project, event: ev2 });
19348
+ if (ev2.type === "task.approval.requested")
19349
+ ctx.schedulePromotion(ev2.id, ev2.requestId, "approval", ev2.question);
17199
19350
  },
17200
19351
  ingest: (raw, taskId) => eventsSource.ingest("opencode-sse", raw, { taskId }),
17201
19352
  // #828: a crew wrapped by auto-gate has BOTH bash and edit at "ask" in its
@@ -17215,10 +19366,10 @@ function startSquadrantd(opts = {}) {
17215
19366
  log
17216
19367
  });
17217
19368
  const cmuxEventsBridge = opts.cmuxEventsBridge ?? new CmuxEventsBridge({
17218
- emit: (ev) => {
17219
- const found = store.listAll().find((r) => r.id === ev.id);
19369
+ emit: (ev2) => {
19370
+ const found = store.listAll().find((r) => r.id === ev2.id);
17220
19371
  if (!found) return;
17221
- void ctx.d.handle({ kind: "event", project: found.project, event: ev });
19372
+ void ctx.d.handle({ kind: "event", project: found.project, event: ev2 });
17222
19373
  },
17223
19374
  resolve: (hook) => {
17224
19375
  if (!hook.cwd) return void 0;
@@ -17574,7 +19725,20 @@ if (process.argv[1] && process.argv[1].endsWith("squadrantd.js")) {
17574
19725
  `);
17575
19726
  process.exit(0);
17576
19727
  }
17577
- const h = startSquadrantd({ sweepMs: 3e4 });
19728
+ const h = startSquadrantd({
19729
+ sweepMs: 3e4,
19730
+ // #898: KB reconcile scheduler on the 60s tick. Enqueues to the home captain's mailbox; never spawns.
19731
+ knowledgeTick: async () => {
19732
+ const stateRoot = join27(homedir14(), ".config", "squadrant", "state");
19733
+ await tickKnowledgeSchedule({
19734
+ cfg: loadConfig(),
19735
+ stateRoot,
19736
+ log: (m) => process.stderr.write(`[squadrantd] ${m}
19737
+ `),
19738
+ enqueue: (project, text) => appendCaptainMessage({ stateRoot, project, text, source: "daemon" })
19739
+ });
19740
+ }
19741
+ });
17578
19742
  const shutdown = (signal) => {
17579
19743
  void h.stop(signal).finally(() => process.exit(0));
17580
19744
  };