@a-company/paradigm 3.44.0 → 5.3.3

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.
Files changed (168) hide show
  1. package/dist/{accept-orchestration-ZUWQUHSK.js → accept-orchestration-GX2YRWM4.js} +5 -5
  2. package/dist/{add-VSPZ6FM4.js → add-FZRKEGH4.js} +1 -1
  3. package/dist/agent-HYKC2LAK.js +387 -0
  4. package/dist/agent-loader-SJPJJS33.js +36 -0
  5. package/dist/{agents-suggest-65SER5IS.js → agents-suggest-DNSYJ6IA.js} +1 -1
  6. package/dist/{aggregate-SV3VGEIL.js → aggregate-H57K7PNV.js} +1 -1
  7. package/dist/{assess-UHBDYIK7.js → assess-4WVXZLZQ.js} +2 -2
  8. package/dist/{auto-24ICVUH4.js → auto-QFS5NHQU.js} +1 -1
  9. package/dist/{beacon-3SJV4DAP.js → beacon-KXZXYQHX.js} +1 -1
  10. package/dist/{calibration-WWHK73WU.js → calibration-V46G7JTY.js} +2 -2
  11. package/dist/{check-OLI6AUS6.js → check-OWAIWV23.js} +1 -1
  12. package/dist/{chunk-RP6TZYGE.js → chunk-2IO7JAG2.js} +1 -1
  13. package/dist/chunk-2T6BTYBN.js +712 -0
  14. package/dist/{chunk-CDMAMDSG.js → chunk-5VKJBNJL.js} +13 -5
  15. package/dist/{chunk-KB4XJWE3.js → chunk-6N3JTACN.js} +98 -437
  16. package/dist/chunk-7N7GSU6K.js +34 -0
  17. package/dist/chunk-A2L4TSLZ.js +526 -0
  18. package/dist/{chunk-P7XSBJE3.js → chunk-ABVQGRF7.js} +1 -1
  19. package/dist/{chunk-HIKKOCXY.js → chunk-EI32ZBE6.js} +1 -1
  20. package/dist/{chunk-QIOCFXDQ.js → chunk-EKGMAM62.js} +1 -1
  21. package/dist/chunk-EZ3GOCYC.js +132 -0
  22. package/dist/chunk-GGMI6C2L.js +1075 -0
  23. package/dist/{chunk-DS5QY37M.js → chunk-GTR2TBIJ.js} +247 -15
  24. package/dist/{chunk-QDXI2DHR.js → chunk-J2JEQRT3.js} +1 -1
  25. package/dist/{chunk-AKIMFN6I.js → chunk-JASGXLK3.js} +2 -2
  26. package/dist/chunk-KVDYJLTC.js +121 -0
  27. package/dist/{chunk-J4E6K5MG.js → chunk-LSRABQIY.js} +25 -1
  28. package/dist/chunk-MCMOGQMU.js +145 -0
  29. package/dist/{chunk-ZXMDA7VB.js → chunk-PDX44BCA.js} +1 -6
  30. package/dist/{chunk-SOBTKFSP.js → chunk-S2HO5MLR.js} +5 -0
  31. package/dist/{chunk-2SKXFXIT.js → chunk-S3ORKP3V.js} +10 -15
  32. package/dist/{chunk-ZMQA6SCO.js → chunk-S6MZ2IEX.js} +628 -228
  33. package/dist/chunk-TAIJOFOE.js +124 -0
  34. package/dist/{chunk-FS3WTUHY.js → chunk-TXESEO7Y.js} +6 -6
  35. package/dist/{chunk-7COU5S2Z.js → chunk-VL67H5IC.js} +1 -1
  36. package/dist/{chunk-QWA26UNO.js → chunk-WQITYKHM.js} +7 -7
  37. package/dist/{chunk-MW5DMGBB.js → chunk-YMDLDELF.js} +114 -55
  38. package/dist/{claude-63ISJAZK.js → claude-FRRWJSTJ.js} +1 -1
  39. package/dist/{claude-cli-ABML5RHX.js → claude-cli-XJLK2X4L.js} +1 -1
  40. package/dist/{claude-code-JRLMRPTO.js → claude-code-HTBA4XRB.js} +1 -1
  41. package/dist/{claude-code-teams-CAJBEFIZ.js → claude-code-teams-T4SP24MD.js} +1 -1
  42. package/dist/{conductor-HLWYWUVH.js → conductor-PGPDVIVE.js} +1 -1
  43. package/dist/{config-schema-3YNIFJCJ.js → config-schema-EA4XALGG.js} +4 -2
  44. package/dist/{constellation-FAGT45TU.js → constellation-A26CCGQS.js} +1 -1
  45. package/dist/{context-audit-557EO6PK.js → context-audit-RLO3ETRP.js} +8 -5
  46. package/dist/{cost-XEBADYFT.js → cost-BGM32XJU.js} +1 -1
  47. package/dist/{cost-UD3WPEKZ.js → cost-VI46A4XL.js} +1 -1
  48. package/dist/{cursor-cli-QUOOF2N4.js → cursor-cli-JVEZGHWQ.js} +1 -1
  49. package/dist/{cursorrules-3TKZ4E4R.js → cursorrules-HLIKJJZT.js} +1 -1
  50. package/dist/decision-loader-WWCLIQPJ.js +20 -0
  51. package/dist/{delete-RRK4RL6Y.js → delete-KBRPQLPC.js} +2 -2
  52. package/dist/{diff-IP5CIARP.js → diff-RQLLNAFI.js} +5 -5
  53. package/dist/{discipline-5F5OVTXB.js → discipline-FA4OZXIS.js} +1 -1
  54. package/dist/{dist-UXWV4OKX.js → dist-34NA5RS5.js} +1 -1
  55. package/dist/{dist-5QE2BB2B-X6DYVSUL.js → dist-5QE2BB2B-5S3T6Y3T.js} +1 -1
  56. package/dist/{dist-CM3MVWWW.js → dist-77JDTVAY.js} +1 -0
  57. package/dist/{dist-POMVY6WP.js → dist-QK4SQAK7.js} +1 -1
  58. package/dist/{dist-3RVKEJRT.js → dist-TA6LSC2Q.js} +1 -1
  59. package/dist/docs-LVLRPBAW.js +155 -0
  60. package/dist/docs-PBZB7LYP.js +89 -0
  61. package/dist/{doctor-GKZJU7QG.js → doctor-ULBOHEIC.js} +3 -3
  62. package/dist/{drift-YGT4LJ7Q.js → drift-R5NRKFHI.js} +1 -1
  63. package/dist/{echo-A6HD5UP7.js → echo-O2LY7CC2.js} +1 -1
  64. package/dist/{edit-4CLNN5JG.js → edit-R2HNLMOG.js} +2 -2
  65. package/dist/event-25OJKDCE.js +31 -0
  66. package/dist/{export-T7CMMJIB.js → export-IWVL7XLF.js} +1 -1
  67. package/dist/{flow-UFMPVOEM.js → flow-CRRVV3O3.js} +2 -2
  68. package/dist/{global-HHUJSBG5.js → global-3NG5JXUB.js} +1 -1
  69. package/dist/graduate-USAWGBJM.js +160 -0
  70. package/dist/{graph-YYUXI3F7.js → graph-VHUMAAS6.js} +2 -2
  71. package/dist/{graph-server-ZPXRSGCW.js → graph-server-YL22VBBN.js} +1 -1
  72. package/dist/{habits-RG5SVKXP.js → habits-OL5NGPXO.js} +3 -3
  73. package/dist/{history-CETCSUCP.js → history-WOWC573W.js} +1 -1
  74. package/dist/{hooks-TCUHQMPF.js → hooks-HFWSCGPV.js} +2 -2
  75. package/dist/index.js +307 -184
  76. package/dist/{integrity-MK2OP5TA.js → integrity-IHO4FZTS.js} +1 -1
  77. package/dist/{integrity-checker-J7YXRTBT.js → integrity-checker-PSKJA5SB.js} +1 -0
  78. package/dist/journal-loader-5EYSBFFY.js +18 -0
  79. package/dist/{lint-HYWGS3JJ.js → lint-K6CJGGPH.js} +1 -1
  80. package/dist/{list-IUCYPGMK.js → list-4YK7QKFF.js} +1 -1
  81. package/dist/{list-BTLFHSRC.js → list-ENR7Q4CR.js} +2 -2
  82. package/dist/{lore-loader-VTEEZDX3.js → lore-loader-7NO6N6FT.js} +4 -1
  83. package/dist/{lore-server-NOOAHKJX.js → lore-server-UNJY5KC3.js} +1 -1
  84. package/dist/{manual-AFJ2J2V3.js → manual-G6FISID5.js} +1 -1
  85. package/dist/mcp.js +3954 -479
  86. package/dist/{migrate-FQVGQNXZ.js → migrate-LS45DNEV.js} +2 -2
  87. package/dist/{migrate-assessments-JP6Q5KME.js → migrate-assessments-RGH4O6IX.js} +2 -2
  88. package/dist/nomination-engine-Q4XSXFKT.js +40 -0
  89. package/dist/notebook-YWIYGEHV.js +155 -0
  90. package/dist/{orchestrate-A226N6FC.js → orchestrate-XZA33TJC.js} +5 -5
  91. package/dist/peers-DEOUIZM6.js +82 -0
  92. package/dist/persona-UHAHIVST.js +390 -0
  93. package/dist/{pipeline-3G2FRAKM.js → pipeline-L4HCSBGN.js} +1 -1
  94. package/dist/{platform-server-KHL6ZPPN.js → platform-server-PMD57BEG.js} +264 -18
  95. package/dist/{plugin-update-checker-HMRPGY5Z.js → plugin-update-checker-ELOEEQYS.js} +1 -0
  96. package/dist/{portal-check-FF5EKZE5.js → portal-check-NPYGII2D.js} +2 -2
  97. package/dist/{portal-compliance-VU4NIFEN.js → portal-compliance-J7DGAPFX.js} +2 -2
  98. package/dist/{probe-7JK7IDNI.js → probe-MHL5HQZ2.js} +3 -3
  99. package/dist/{promote-XO63XMAN.js → promote-F6ZYZZAL.js} +2 -2
  100. package/dist/{providers-YNFSL6HK.js → providers-GK7PB2OL.js} +2 -2
  101. package/dist/{quiz-I75NU2QQ.js → quiz-M66SC7F7.js} +1 -1
  102. package/dist/{record-46CLR4OG.js → record-RA4WR2BO.js} +2 -2
  103. package/dist/{reindex-WIJMCJ4A.js → reindex-HRA2AUS6.js} +3 -2
  104. package/dist/{remember-4EUZKIIB.js → remember-HBWJ655S.js} +1 -1
  105. package/dist/{retag-KC4JVRLE.js → retag-3OLCVDEQ.js} +2 -2
  106. package/dist/{review-Q7M4CRB5.js → review-27ATYTD2.js} +2 -2
  107. package/dist/review-57QMURZV.js +334 -0
  108. package/dist/{ripple-RI3LOT6R.js → ripple-JPBXP5I3.js} +1 -1
  109. package/dist/{sentinel-UOIGJWHH.js → sentinel-4XIG4STA.js} +2 -2
  110. package/dist/{sentinel-bridge-APDXYAZS.js → sentinel-bridge-MDUXTQRL.js} +2 -2
  111. package/dist/{serve-JVXSRSUB.js → serve-FLTFTM3P.js} +2 -2
  112. package/dist/{serve-22A4XOIG.js → serve-INL7SNBK.js} +2 -2
  113. package/dist/{serve-2YJ6D2Y6.js → serve-KBSE36PL.js} +4 -4
  114. package/dist/{server-JV6UFGWZ.js → server-54SKYFFY.js} +2 -2
  115. package/dist/{server-RDLQ3DK7.js → server-XUOIO7E6.js} +1 -1
  116. package/dist/{setup-YNZJQLW7.js → setup-EDS27WUR.js} +1 -1
  117. package/dist/{setup-M2ZKLKNN.js → setup-KO5AFC4K.js} +2 -2
  118. package/dist/{shift-LNMKFYLR.js → shift-VFG23DLA.js} +16 -16
  119. package/dist/{show-P7GYO43X.js → show-5PV5KFJE.js} +2 -2
  120. package/dist/{show-PKZMYKRN.js → show-NQKYX6WQ.js} +1 -1
  121. package/dist/{snapshot-Y3COXK4T.js → snapshot-BK4RBPCG.js} +1 -1
  122. package/dist/{spawn-SSXZX45U.js → spawn-AW6GDECS.js} +3 -3
  123. package/dist/{status-KLHALGW4.js → status-WGIAQODY.js} +1 -1
  124. package/dist/{summary-5NQNOD3F.js → summary-NIRABMF5.js} +2 -2
  125. package/dist/{sweep-EZU3GU6S.js → sweep-QMHNSIY5.js} +2 -2
  126. package/dist/{switch-WYUMVNA5.js → switch-6EJPZDIA.js} +1 -1
  127. package/dist/{symphony-EYRGGVNE.js → symphony-4OCY36AI.js} +350 -29
  128. package/dist/{symphony-QWOEKZMC.js → symphony-B75X2MME.js} +20 -2
  129. package/dist/symphony-peers-2ZQYLRNI.js +34 -0
  130. package/dist/symphony-peers-OL7F6M5S.js +121 -0
  131. package/dist/symphony-relay-UJYUXN65.js +710 -0
  132. package/dist/{sync-ZM4Q3R4U.js → sync-VEHUH4OA.js} +3 -3
  133. package/dist/{sync-llms-JIPP3XX4.js → sync-llms-YHCFIE6X.js} +2 -2
  134. package/dist/{task-loader-7M2FCBX6.js → task-loader-LDYWQSLM.js} +1 -0
  135. package/dist/{team-HGLJXWQG.js → team-7HG7XK5C.js} +6 -6
  136. package/dist/{test-WTR5Q33E.js → test-566CP5KC.js} +1 -1
  137. package/dist/{thread-3WM7KKID.js → thread-N754I4D5.js} +1 -1
  138. package/dist/{timeline-ANC7LVDL.js → timeline-M3CICQFE.js} +2 -2
  139. package/dist/{triage-IZ4MDYNB.js → triage-HHYGT3HY.js} +1 -1
  140. package/dist/{tutorial-GC6QL4US.js → tutorial-KD22SUNO.js} +1 -1
  141. package/dist/university-content/courses/.purpose +66 -0
  142. package/dist/university-content/courses/para-401.json +146 -0
  143. package/dist/university-content/courses/para-501.json +151 -0
  144. package/dist/university-content/courses/para-601.json +608 -0
  145. package/dist/university-content/plsat/.purpose +6 -0
  146. package/dist/university-content/plsat/v2.0.json +2 -2
  147. package/dist/university-content/plsat/v3.0.json +563 -3
  148. package/dist/university-content/reference.json +91 -0
  149. package/dist/{upgrade-ANX3LVSA.js → upgrade-H5PF32BW.js} +2 -2
  150. package/dist/{validate-GD5XWILV.js → validate-CNKEKO6A.js} +1 -1
  151. package/dist/{validate-ITC5D6QG.js → validate-MB5ULIHS.js} +1 -1
  152. package/dist/{validate-ZVPNN4FL.js → validate-QH3LADM6.js} +1 -1
  153. package/dist/{watch-X64UK7K4.js → watch-2TKP5PVL.js} +3 -3
  154. package/dist/{watch-ERBEJUJW.js → watch-ZF4ML6CD.js} +2 -2
  155. package/dist/{wisdom-L2WC7J62.js → wisdom-AATMGNFA.js} +1 -1
  156. package/dist/work-log-loader-5L45XNYZ.js +14 -0
  157. package/dist/{workspace-UIUTHZTD.js → workspace-6E6OSRNU.js} +4 -4
  158. package/package.json +1 -1
  159. package/platform-ui/dist/assets/DocsSection-ByAgPzWV.js +1 -0
  160. package/platform-ui/dist/assets/DocsSection-CjdO6R-u.css +1 -0
  161. package/platform-ui/dist/assets/{GitSection-BD3Ze06e.js → GitSection-BLovj9yT.js} +1 -1
  162. package/platform-ui/dist/assets/{GraphSection-SglITfSs.js → GraphSection-C5PCPUFl.js} +1 -1
  163. package/platform-ui/dist/assets/{LoreSection-bR5Km4Fd.js → LoreSection-BftejTla.js} +1 -1
  164. package/platform-ui/dist/assets/{SentinelSection-QSpAZArG.js → SentinelSection-CnYcasN7.js} +1 -1
  165. package/platform-ui/dist/assets/{SymphonySection-CobYJgvg.js → SymphonySection-BpmqCHeK.js} +1 -1
  166. package/platform-ui/dist/assets/{index-DbxeSMkV.js → index-G9JnWEs_.js} +10 -10
  167. package/platform-ui/dist/index.html +1 -1
  168. package/dist/dist-PSF5CP4I.js +0 -7294
@@ -1,17 +1,266 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ init_lore_loader,
3
4
  loadLoreEntries,
4
5
  loadLoreEntry
5
- } from "./chunk-CDMAMDSG.js";
6
+ } from "./chunk-5VKJBNJL.js";
6
7
  import {
7
8
  checkComponentAnchors,
8
9
  checkIntegrity,
9
10
  checkPurposeHealth
10
11
  } from "./chunk-L27I3CPZ.js";
12
+ import {
13
+ __esm,
14
+ __export,
15
+ __toCommonJS
16
+ } from "./chunk-7N7GSU6K.js";
17
+
18
+ // ../paradigm-mcp/src/utils/aspect-fingerprint.ts
19
+ var aspect_fingerprint_exports = {};
20
+ __export(aspect_fingerprint_exports, {
21
+ contentSearch: () => contentSearch,
22
+ detectFileRename: () => detectFileRename,
23
+ generateFingerprint: () => generateFingerprint,
24
+ levenshteinDistance: () => levenshteinDistance,
25
+ levenshteinSimilarity: () => levenshteinSimilarity,
26
+ searchSiblingFiles: () => searchSiblingFiles,
27
+ slidingWindowSearch: () => slidingWindowSearch
28
+ });
29
+ import * as fs5 from "fs";
30
+ import * as path6 from "path";
31
+ import * as crypto2 from "crypto";
32
+ import { execSync } from "child_process";
33
+ function generateFingerprint(content) {
34
+ const lines = content.split("\n").filter((l) => l.trim() !== "");
35
+ return {
36
+ firstLine: normalizeLine(lines[0] || ""),
37
+ lastLine: normalizeLine(lines[lines.length - 1] || ""),
38
+ lineCount: lines.length,
39
+ structuralHash: extractStructuralHash(lines)
40
+ };
41
+ }
42
+ function extractStructuralHash(lines) {
43
+ const structural = lines.map((l) => l.trim()).filter((l) => STRUCTURAL_TOKENS.test(l)).map((l) => {
44
+ const match = l.match(STRUCTURAL_TOKENS);
45
+ return match ? match[1].trim() : "";
46
+ }).join("|");
47
+ return crypto2.createHash("sha256").update(structural).digest("hex").slice(0, 16);
48
+ }
49
+ function normalizeLine(line) {
50
+ return line.trim().replace(/\s+/g, " ").toLowerCase();
51
+ }
52
+ function levenshteinDistance(a, b) {
53
+ if (a === b) return 0;
54
+ if (a.length === 0) return b.length;
55
+ if (b.length === 0) return a.length;
56
+ if (a.length > b.length) [a, b] = [b, a];
57
+ const aLen = a.length;
58
+ const bLen = b.length;
59
+ if (aLen > 5e3 || bLen > 5e3) {
60
+ return Math.abs(aLen - bLen);
61
+ }
62
+ let prev = new Array(aLen + 1);
63
+ let curr = new Array(aLen + 1);
64
+ for (let i = 0; i <= aLen; i++) prev[i] = i;
65
+ for (let j = 1; j <= bLen; j++) {
66
+ curr[0] = j;
67
+ for (let i = 1; i <= aLen; i++) {
68
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
69
+ curr[i] = Math.min(
70
+ prev[i] + 1,
71
+ // deletion
72
+ curr[i - 1] + 1,
73
+ // insertion
74
+ prev[i - 1] + cost
75
+ // substitution
76
+ );
77
+ }
78
+ [prev, curr] = [curr, prev];
79
+ }
80
+ return prev[aLen];
81
+ }
82
+ function levenshteinSimilarity(a, b) {
83
+ if (a.length === 0 && b.length === 0) return 1;
84
+ const maxLen = Math.max(a.length, b.length);
85
+ const distance = levenshteinDistance(a, b);
86
+ return 1 - distance / maxLen;
87
+ }
88
+ function slidingWindowSearch(fileLines, fingerprint, originalContent, maxResults = 3) {
89
+ const { lineCount } = fingerprint;
90
+ const minWindow = Math.max(1, Math.floor(lineCount * 0.8));
91
+ const maxWindow = Math.ceil(lineCount * 1.2);
92
+ const results = [];
93
+ const normalizedOriginal = normalizeBlock(originalContent);
94
+ for (const windowSize of [lineCount, minWindow, maxWindow]) {
95
+ if (windowSize > fileLines.length) continue;
96
+ for (let start = 0; start <= fileLines.length - windowSize; start++) {
97
+ const windowLines = fileLines.slice(start, start + windowSize);
98
+ const score = scoreWindow(windowLines, fingerprint, normalizedOriginal);
99
+ if (score >= 0.5) {
100
+ const windowContent = windowLines.join("\n");
101
+ results.push({
102
+ windowStart: start + 1,
103
+ // 1-indexed
104
+ windowEnd: start + windowSize,
105
+ similarity: levenshteinSimilarity(normalizeBlock(windowContent), normalizedOriginal),
106
+ score
107
+ });
108
+ }
109
+ }
110
+ }
111
+ const byStart = /* @__PURE__ */ new Map();
112
+ for (const r of results) {
113
+ const existing = byStart.get(r.windowStart);
114
+ if (!existing || r.score > existing.score) {
115
+ byStart.set(r.windowStart, r);
116
+ }
117
+ }
118
+ return Array.from(byStart.values()).sort((a, b) => b.score - a.score).slice(0, maxResults);
119
+ }
120
+ function scoreWindow(windowLines, fingerprint, normalizedOriginal) {
121
+ const nonEmpty = windowLines.filter((l) => l.trim() !== "");
122
+ if (nonEmpty.length === 0) return 0;
123
+ let score = 0;
124
+ const firstLine = normalizeLine(nonEmpty[0]);
125
+ const lastLine = normalizeLine(nonEmpty[nonEmpty.length - 1]);
126
+ let firstLastScore = 0;
127
+ if (firstLine === fingerprint.firstLine) firstLastScore += 0.5;
128
+ if (lastLine === fingerprint.lastLine) firstLastScore += 0.5;
129
+ score += firstLastScore * W_FIRST_LAST;
130
+ const windowStructural = extractStructuralHash(nonEmpty);
131
+ if (windowStructural === fingerprint.structuralHash) {
132
+ score += W_STRUCTURAL;
133
+ }
134
+ const windowContent = nonEmpty.join("\n");
135
+ const similarity = levenshteinSimilarity(normalizeBlock(windowContent), normalizedOriginal);
136
+ if (similarity >= 0.8) {
137
+ score += (similarity - 0.8) / 0.2 * W_LEVENSHTEIN;
138
+ }
139
+ const countRatio = nonEmpty.length / fingerprint.lineCount;
140
+ if (countRatio >= 0.8 && countRatio <= 1.2) {
141
+ const countScore = 1 - Math.abs(1 - countRatio) / 0.2;
142
+ score += countScore * W_LINE_COUNT;
143
+ }
144
+ return score;
145
+ }
146
+ function normalizeBlock(content) {
147
+ return content.split("\n").map((l) => l.trim()).filter((l) => l !== "").join("\n");
148
+ }
149
+ function detectFileRename(rootDir, oldPath) {
150
+ try {
151
+ const result = execSync(
152
+ `git log --follow --diff-filter=R --name-status --format="" -- "${oldPath}"`,
153
+ { cwd: rootDir, encoding: "utf8", timeout: 5e3 }
154
+ ).trim();
155
+ if (!result) return null;
156
+ const lines = result.split("\n");
157
+ for (const line of lines) {
158
+ const parts = line.split(" ");
159
+ if (parts.length >= 3 && parts[0].startsWith("R")) {
160
+ return parts[2];
161
+ }
162
+ }
163
+ return null;
164
+ } catch {
165
+ return null;
166
+ }
167
+ }
168
+ function searchSiblingFiles(rootDir, dirPath, fingerprint, originalContent, maxFiles = 10) {
169
+ const absoluteDir = path6.isAbsolute(dirPath) ? dirPath : path6.join(rootDir, dirPath);
170
+ if (!fs5.existsSync(absoluteDir)) return [];
171
+ const results = [];
172
+ try {
173
+ const files = fs5.readdirSync(absoluteDir).filter((f) => !f.startsWith(".") && fs5.statSync(path6.join(absoluteDir, f)).isFile()).slice(0, maxFiles);
174
+ for (const file of files) {
175
+ try {
176
+ const content = fs5.readFileSync(path6.join(absoluteDir, file), "utf8");
177
+ const lines = content.split("\n");
178
+ const matches = slidingWindowSearch(lines, fingerprint, originalContent, 1);
179
+ if (matches.length > 0 && matches[0].score >= 0.7) {
180
+ const relPath = path6.relative(rootDir, path6.join(absoluteDir, file));
181
+ results.push({
182
+ file: relPath,
183
+ score: matches[0].score,
184
+ start: matches[0].windowStart,
185
+ end: matches[0].windowEnd
186
+ });
187
+ }
188
+ } catch {
189
+ }
190
+ }
191
+ } catch {
192
+ return [];
193
+ }
194
+ return results.sort((a, b) => b.score - a.score);
195
+ }
196
+ function contentSearch(rootDir, filePath, originalContent, autoHeal = true) {
197
+ const fingerprint = generateFingerprint(originalContent);
198
+ const absolutePath = path6.isAbsolute(filePath) ? filePath : path6.join(rootDir, filePath);
199
+ if (fs5.existsSync(absolutePath)) {
200
+ const fileContent = fs5.readFileSync(absolutePath, "utf8");
201
+ const fileLines = fileContent.split("\n");
202
+ const matches = slidingWindowSearch(fileLines, fingerprint, originalContent);
203
+ if (matches.length > 0) {
204
+ const best = matches[0];
205
+ return {
206
+ found: best.score >= 0.7,
207
+ score: best.score,
208
+ suggestedStart: best.windowStart,
209
+ suggestedEnd: best.windowEnd,
210
+ similarity: best.similarity
211
+ };
212
+ }
213
+ }
214
+ const renamedTo = detectFileRename(rootDir, filePath);
215
+ if (renamedTo) {
216
+ const renamedPath = path6.join(rootDir, renamedTo);
217
+ if (fs5.existsSync(renamedPath)) {
218
+ const renamedContent = fs5.readFileSync(renamedPath, "utf8");
219
+ const renamedLines = renamedContent.split("\n");
220
+ const matches = slidingWindowSearch(renamedLines, fingerprint, originalContent);
221
+ if (matches.length > 0 && matches[0].score >= 0.7) {
222
+ return {
223
+ found: true,
224
+ score: matches[0].score,
225
+ suggestedStart: matches[0].windowStart,
226
+ suggestedEnd: matches[0].windowEnd,
227
+ suggestedPath: renamedTo,
228
+ similarity: matches[0].similarity
229
+ };
230
+ }
231
+ }
232
+ }
233
+ const dirPath = path6.dirname(filePath);
234
+ const siblingResults = searchSiblingFiles(rootDir, dirPath, fingerprint, originalContent);
235
+ if (siblingResults.length > 0 && siblingResults[0].score >= 0.7) {
236
+ const best = siblingResults[0];
237
+ return {
238
+ found: true,
239
+ score: best.score,
240
+ suggestedStart: best.start,
241
+ suggestedEnd: best.end,
242
+ suggestedPath: best.file !== filePath ? best.file : void 0,
243
+ similarity: best.score
244
+ // approximate
245
+ };
246
+ }
247
+ return { found: false, score: 0 };
248
+ }
249
+ var STRUCTURAL_TOKENS, W_FIRST_LAST, W_STRUCTURAL, W_LEVENSHTEIN, W_LINE_COUNT;
250
+ var init_aspect_fingerprint = __esm({
251
+ "../paradigm-mcp/src/utils/aspect-fingerprint.ts"() {
252
+ "use strict";
253
+ STRUCTURAL_TOKENS = /^\s*(function |class |if |else |for |while |switch |case |return |export |import |const |let |var |async |await |try |catch |throw |struct |enum |protocol |guard |def |fn )/;
254
+ W_FIRST_LAST = 0.4;
255
+ W_STRUCTURAL = 0.3;
256
+ W_LEVENSHTEIN = 0.2;
257
+ W_LINE_COUNT = 0.1;
258
+ }
259
+ });
11
260
 
12
261
  // ../paradigm-mcp/src/tools/reindex.ts
13
- import * as fs9 from "fs";
14
- import * as path10 from "path";
262
+ import * as fs10 from "fs";
263
+ import * as path11 from "path";
15
264
  import * as yaml8 from "js-yaml";
16
265
 
17
266
  // ../premise/core/dist/index.js
@@ -1979,6 +2228,7 @@ var SessionTracker = class {
1979
2228
  context: data.context,
1980
2229
  timestamp: Date.now(),
1981
2230
  sessionId: this.session.sessionId,
2231
+ externalId: data.externalId,
1982
2232
  plan: data.plan,
1983
2233
  modifiedFiles: data.modifiedFiles,
1984
2234
  symbolsTouched: data.symbolsTouched,
@@ -2185,8 +2435,8 @@ var SessionTracker = class {
2185
2435
  * Extract resource type from URI
2186
2436
  */
2187
2437
  extractResourceType(uri) {
2188
- const path11 = uri.replace("paradigm://", "");
2189
- const firstPart = path11.split("/")[0];
2438
+ const path12 = uri.replace("paradigm://", "");
2439
+ const firstPart = path12.split("/")[0];
2190
2440
  return firstPart || "unknown";
2191
2441
  }
2192
2442
  /**
@@ -2531,6 +2781,10 @@ function getContextToolsList() {
2531
2781
  type: "string",
2532
2782
  description: "What's top-of-mind right now (1-3 sentences)"
2533
2783
  },
2784
+ externalId: {
2785
+ type: "string",
2786
+ description: 'Optional: deterministic ID from external source for automatic session recovery (e.g. "linear:PROJ-123", "github:owner/repo#42")'
2787
+ },
2534
2788
  plan: {
2535
2789
  type: "string",
2536
2790
  description: "Optional: the current plan or approach"
@@ -2573,6 +2827,10 @@ function getContextToolsList() {
2573
2827
  type: "string",
2574
2828
  description: "What's top-of-mind right now (1-3 sentences)"
2575
2829
  },
2830
+ externalId: {
2831
+ type: "string",
2832
+ description: 'Optional: deterministic ID from external source for automatic session recovery (e.g. "linear:PROJ-123", "github:owner/repo#42")'
2833
+ },
2576
2834
  plan: {
2577
2835
  type: "string",
2578
2836
  description: "Optional: the current plan or approach"
@@ -2852,6 +3110,7 @@ ${nextSteps.map((step, i) => `${i + 1}. ${step}`).join("\n") || "(none specified
2852
3110
  tracker2.setRootDir(_ctx.rootDir);
2853
3111
  const phase = args.phase;
2854
3112
  const context = args.context;
3113
+ const externalId = args.externalId;
2855
3114
  const plan = args.plan;
2856
3115
  const modifiedFiles = args.modifiedFiles;
2857
3116
  const symbolsTouched = args.symbolsTouched;
@@ -2859,6 +3118,7 @@ ${nextSteps.map((step, i) => `${i + 1}. ${step}`).join("\n") || "(none specified
2859
3118
  const { checkpoint, persisted } = tracker2.saveCheckpoint({
2860
3119
  phase,
2861
3120
  context,
3121
+ externalId,
2862
3122
  plan,
2863
3123
  modifiedFiles,
2864
3124
  symbolsTouched,
@@ -2874,6 +3134,7 @@ ${nextSteps.map((step, i) => `${i + 1}. ${step}`).join("\n") || "(none specified
2874
3134
  phase: checkpoint.phase,
2875
3135
  context: checkpoint.context,
2876
3136
  sessionId: checkpoint.sessionId,
3137
+ ...checkpoint.externalId ? { externalId: checkpoint.externalId } : {},
2877
3138
  timestamp: new Date(checkpoint.timestamp).toISOString(),
2878
3139
  modifiedFiles: checkpoint.modifiedFiles?.length || 0,
2879
3140
  symbolsTouched: checkpoint.symbolsTouched?.length || 0,
@@ -2927,7 +3188,7 @@ async function buildRecoveryPreamble(rootDir) {
2927
3188
  }
2928
3189
  }
2929
3190
  try {
2930
- const { loadTasks } = await import("./task-loader-7M2FCBX6.js");
3191
+ const { loadTasks } = await import("./task-loader-LDYWQSLM.js");
2931
3192
  const openTasks = await loadTasks(rootDir, { status: "open", limit: 5 });
2932
3193
  if (openTasks.length > 0) {
2933
3194
  lines.push("");
@@ -2940,7 +3201,7 @@ async function buildRecoveryPreamble(rootDir) {
2940
3201
  } catch {
2941
3202
  }
2942
3203
  try {
2943
- const { loadLoreEntries: loadLoreEntries2 } = await import("./lore-loader-VTEEZDX3.js");
3204
+ const { loadLoreEntries: loadLoreEntries2 } = await import("./lore-loader-7NO6N6FT.js");
2944
3205
  const arcEntries = await loadLoreEntries2(rootDir, { limit: 10 });
2945
3206
  const entriesWithArcs = arcEntries.filter((e) => e.tags?.some((t) => t.startsWith("arc:")));
2946
3207
  if (entriesWithArcs.length > 0) {
@@ -2961,6 +3222,21 @@ async function buildRecoveryPreamble(rootDir) {
2961
3222
  }
2962
3223
  } catch {
2963
3224
  }
3225
+ try {
3226
+ const { loadNominations } = await import("./nomination-engine-Q4XSXFKT.js");
3227
+ const urgent = loadNominations(rootDir, { pending_only: true }).filter((n) => n.urgency === "critical" || n.urgency === "high");
3228
+ if (urgent.length > 0) {
3229
+ lines.push("");
3230
+ lines.push("Ambient nominations (urgent):");
3231
+ for (const n of urgent.slice(0, 5)) {
3232
+ lines.push(` [${n.urgency}] ${n.brief}`);
3233
+ }
3234
+ if (urgent.length > 5) {
3235
+ lines.push(` ... and ${urgent.length - 5} more. Use paradigm_ambient_nominations to see all.`);
3236
+ }
3237
+ }
3238
+ } catch {
3239
+ }
2964
3240
  lines.push("");
2965
3241
  lines.push("IMPORTANT: Present a brief summary of this recovery data to the user, then ask what they would like to do: (1) Continue \u2014 pick up where the last session left off, (2) Discard \u2014 ignore the previous session and start fresh, or (3) let them describe what they want to work on instead. Do NOT automatically continue without asking.");
2966
3242
  lines.push("---");
@@ -3018,10 +3294,10 @@ var ToolCache = class {
3018
3294
  var toolCache = new ToolCache(3e4);
3019
3295
 
3020
3296
  // ../paradigm-mcp/src/utils/aspect-graph.ts
3021
- import * as fs5 from "fs";
3022
- import * as path6 from "path";
3023
- import * as crypto2 from "crypto";
3024
- import { execSync } from "child_process";
3297
+ import * as fs6 from "fs";
3298
+ import * as path7 from "path";
3299
+ import * as crypto3 from "crypto";
3300
+ import { execSync as execSync2 } from "child_process";
3025
3301
  import initSqlJs from "sql.js";
3026
3302
  var cachedSQL = null;
3027
3303
  async function getSqlJs() {
@@ -3097,6 +3373,23 @@ var SCHEMA_STATEMENTS = [
3097
3373
  PRIMARY KEY (aspect_id, access_type)
3098
3374
  )`
3099
3375
  ];
3376
+ var MIGRATIONS = [
3377
+ `ALTER TABLE anchors ADD COLUMN original_content TEXT`,
3378
+ `CREATE TABLE IF NOT EXISTS anchor_history (
3379
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
3380
+ anchor_id INTEGER NOT NULL,
3381
+ action TEXT NOT NULL,
3382
+ old_start INTEGER,
3383
+ old_end INTEGER,
3384
+ new_start INTEGER,
3385
+ new_end INTEGER,
3386
+ old_path TEXT,
3387
+ new_path TEXT,
3388
+ confidence REAL,
3389
+ commit_hash TEXT,
3390
+ healed_at TEXT NOT NULL
3391
+ )`
3392
+ ];
3100
3393
  var FTS_SQL = `CREATE VIRTUAL TABLE IF NOT EXISTS aspects_fts USING fts5(id, description, enforcement, tags)`;
3101
3394
  function queryRows(db, sql, params) {
3102
3395
  const stmt = db.prepare(sql);
@@ -3124,15 +3417,15 @@ function queryOne(db, sql, params) {
3124
3417
  }
3125
3418
  async function openAspectGraph(rootDir) {
3126
3419
  const SQL = await getSqlJs();
3127
- const dbDir = path6.join(rootDir, ".paradigm");
3128
- const dbPath = path6.join(dbDir, "aspect-graph.db");
3420
+ const dbDir = path7.join(rootDir, ".paradigm");
3421
+ const dbPath = path7.join(dbDir, "aspect-graph.db");
3129
3422
  let db;
3130
- if (fs5.existsSync(dbPath)) {
3131
- const buffer = fs5.readFileSync(dbPath);
3423
+ if (fs6.existsSync(dbPath)) {
3424
+ const buffer = fs6.readFileSync(dbPath);
3132
3425
  db = new SQL.Database(buffer);
3133
3426
  } else {
3134
- if (!fs5.existsSync(dbDir)) {
3135
- fs5.mkdirSync(dbDir, { recursive: true });
3427
+ if (!fs6.existsSync(dbDir)) {
3428
+ fs6.mkdirSync(dbDir, { recursive: true });
3136
3429
  }
3137
3430
  db = new SQL.Database();
3138
3431
  }
@@ -3143,17 +3436,23 @@ async function openAspectGraph(rootDir) {
3143
3436
  db.run(FTS_SQL);
3144
3437
  } catch {
3145
3438
  }
3439
+ for (const migration of MIGRATIONS) {
3440
+ try {
3441
+ db.run(migration);
3442
+ } catch {
3443
+ }
3444
+ }
3146
3445
  return db;
3147
3446
  }
3148
3447
  function closeAspectGraph(db, rootDir) {
3149
3448
  if (rootDir) {
3150
- const dbDir = path6.join(rootDir, ".paradigm");
3151
- if (!fs5.existsSync(dbDir)) {
3152
- fs5.mkdirSync(dbDir, { recursive: true });
3449
+ const dbDir = path7.join(rootDir, ".paradigm");
3450
+ if (!fs6.existsSync(dbDir)) {
3451
+ fs6.mkdirSync(dbDir, { recursive: true });
3153
3452
  }
3154
- const dbPath = path6.join(dbDir, "aspect-graph.db");
3453
+ const dbPath = path7.join(dbDir, "aspect-graph.db");
3155
3454
  const data = db.export();
3156
- fs5.writeFileSync(dbPath, Buffer.from(data));
3455
+ fs6.writeFileSync(dbPath, Buffer.from(data));
3157
3456
  }
3158
3457
  db.close();
3159
3458
  }
@@ -3162,7 +3461,7 @@ function materializeAspects(db, symbols, rootDir) {
3162
3461
  const now = (/* @__PURE__ */ new Date()).toISOString();
3163
3462
  let headCommit = null;
3164
3463
  try {
3165
- headCommit = execSync("git rev-parse HEAD", { cwd: rootDir, encoding: "utf8" }).trim();
3464
+ headCommit = execSync2("git rev-parse HEAD", { cwd: rootDir, encoding: "utf8" }).trim();
3166
3465
  } catch {
3167
3466
  }
3168
3467
  db.run("DELETE FROM anchors");
@@ -3200,9 +3499,9 @@ function materializeAspects(db, symbols, rootDir) {
3200
3499
  const { startLine, endLine } = resolveAnchorLines(anchor);
3201
3500
  const hashes = computeAnchorHash(anchor, null);
3202
3501
  db.run(
3203
- `INSERT INTO anchors (aspect_id, file_path, start_line, end_line, content_hash, normalized_hash, materialized_at_commit, last_verified)
3204
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
3205
- [entry.symbol, anchor.path, startLine, endLine, hashes.exact, hashes.normalized, headCommit, now]
3502
+ `INSERT INTO anchors (aspect_id, file_path, start_line, end_line, content_hash, normalized_hash, materialized_at_commit, last_verified, original_content)
3503
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3504
+ [entry.symbol, anchor.path, startLine, endLine, hashes.exact, hashes.normalized, headCommit, now, hashes.normalizedContent]
3206
3505
  );
3207
3506
  }
3208
3507
  }
@@ -3289,8 +3588,8 @@ function checkDrift(db, rootDir, aspectId, autoHeal = true) {
3289
3588
  const anchorRows = aspectId ? queryRows(db, "SELECT * FROM anchors WHERE aspect_id = ?", [aspectId]) : queryRows(db, "SELECT * FROM anchors");
3290
3589
  const results = [];
3291
3590
  for (const anchor of anchorRows) {
3292
- const absolutePath = path6.isAbsolute(anchor.file_path) ? anchor.file_path : path6.join(rootDir, anchor.file_path);
3293
- if (!fs5.existsSync(absolutePath)) {
3591
+ const absolutePath = path7.isAbsolute(anchor.file_path) ? anchor.file_path : path7.join(rootDir, anchor.file_path);
3592
+ if (!fs6.existsSync(absolutePath)) {
3294
3593
  results.push({
3295
3594
  aspectId: anchor.aspect_id,
3296
3595
  path: anchor.file_path,
@@ -3304,12 +3603,12 @@ function checkDrift(db, rootDir, aspectId, autoHeal = true) {
3304
3603
  continue;
3305
3604
  }
3306
3605
  try {
3307
- const fileContent = fs5.readFileSync(absolutePath, "utf8");
3606
+ const fileContent = fs6.readFileSync(absolutePath, "utf8");
3308
3607
  const lines = fileContent.split("\n");
3309
3608
  const startIdx = Math.max(0, anchor.start_line - 1);
3310
3609
  const endIdx = Math.min(lines.length, anchor.end_line);
3311
3610
  const sliceContent = lines.slice(startIdx, endIdx).join("\n");
3312
- const currentExactHash = crypto2.createHash("sha256").update(sliceContent).digest("hex");
3611
+ const currentExactHash = crypto3.createHash("sha256").update(sliceContent).digest("hex");
3313
3612
  if (anchor.content_hash != null && currentExactHash === anchor.content_hash) {
3314
3613
  results.push({
3315
3614
  aspectId: anchor.aspect_id,
@@ -3326,7 +3625,7 @@ function checkDrift(db, rootDir, aspectId, autoHeal = true) {
3326
3625
  }
3327
3626
  continue;
3328
3627
  }
3329
- const currentNormalizedHash = crypto2.createHash("sha256").update(normalizeForHash(sliceContent)).digest("hex");
3628
+ const currentNormalizedHash = crypto3.createHash("sha256").update(normalizeForHash(sliceContent)).digest("hex");
3330
3629
  if (anchor.normalized_hash != null && currentNormalizedHash === anchor.normalized_hash) {
3331
3630
  db.run("UPDATE anchors SET content_hash = ?, drifted = 0 WHERE id = ?", [currentExactHash, anchor.id]);
3332
3631
  results.push({
@@ -3371,7 +3670,7 @@ function checkDrift(db, rootDir, aspectId, autoHeal = true) {
3371
3670
  const shiftedStartIdx = Math.max(0, mapping.currentStart - 1);
3372
3671
  const shiftedEndIdx = Math.min(lines.length, mapping.currentEnd);
3373
3672
  const shiftedContent = lines.slice(shiftedStartIdx, shiftedEndIdx).join("\n");
3374
- const shiftedExactHash = crypto2.createHash("sha256").update(shiftedContent).digest("hex");
3673
+ const shiftedExactHash = crypto3.createHash("sha256").update(shiftedContent).digest("hex");
3375
3674
  if (anchor.content_hash != null && shiftedExactHash === anchor.content_hash) {
3376
3675
  const healed = autoHeal;
3377
3676
  if (healed) {
@@ -3411,10 +3710,10 @@ function checkDrift(db, rootDir, aspectId, autoHeal = true) {
3411
3710
  });
3412
3711
  resolvedByGit = true;
3413
3712
  } else {
3414
- const shiftedNormalized = crypto2.createHash("sha256").update(normalizeForHash(shiftedContent)).digest("hex");
3713
+ const shiftedNormalized = crypto3.createHash("sha256").update(normalizeForHash(shiftedContent)).digest("hex");
3415
3714
  if (anchor.normalized_hash != null && shiftedNormalized === anchor.normalized_hash) {
3416
3715
  if (autoHeal) {
3417
- const shiftedNewHash = crypto2.createHash("sha256").update(shiftedContent).digest("hex");
3716
+ const shiftedNewHash = crypto3.createHash("sha256").update(shiftedContent).digest("hex");
3418
3717
  db.run(
3419
3718
  "UPDATE anchors SET start_line = ?, end_line = ?, content_hash = ?, drifted = 0 WHERE id = ?",
3420
3719
  [mapping.currentStart, mapping.currentEnd, shiftedNewHash, anchor.id]
@@ -3455,6 +3754,73 @@ function checkDrift(db, rootDir, aspectId, autoHeal = true) {
3455
3754
  }
3456
3755
  }
3457
3756
  if (resolvedByGit) continue;
3757
+ if (anchor.original_content) {
3758
+ const { contentSearch: contentSearch2 } = (init_aspect_fingerprint(), __toCommonJS(aspect_fingerprint_exports));
3759
+ const searchResult = contentSearch2(rootDir, anchor.file_path, anchor.original_content, autoHeal);
3760
+ if (searchResult.found && searchResult.score >= 0.7) {
3761
+ const isAutoHeal = autoHeal && searchResult.score >= 0.85 && !searchResult.suggestedPath;
3762
+ if (isAutoHeal && searchResult.suggestedStart && searchResult.suggestedEnd) {
3763
+ db.run(
3764
+ "UPDATE anchors SET start_line = ?, end_line = ?, drifted = 0 WHERE id = ?",
3765
+ [searchResult.suggestedStart, searchResult.suggestedEnd, anchor.id]
3766
+ );
3767
+ try {
3768
+ db.run(
3769
+ `INSERT INTO anchor_history (anchor_id, action, old_start, old_end, new_start, new_end, confidence, healed_at)
3770
+ VALUES (?, 'relocated', ?, ?, ?, ?, ?, ?)`,
3771
+ [anchor.id, anchor.start_line, anchor.end_line, searchResult.suggestedStart, searchResult.suggestedEnd, searchResult.score, (/* @__PURE__ */ new Date()).toISOString()]
3772
+ );
3773
+ } catch {
3774
+ }
3775
+ const aspectRow = queryRows(
3776
+ db,
3777
+ "SELECT defined_in FROM aspects WHERE id = ?",
3778
+ [anchor.aspect_id]
3779
+ );
3780
+ if (aspectRow.length > 0) {
3781
+ healAnchorInPurposeFile(
3782
+ rootDir,
3783
+ aspectRow[0].defined_in,
3784
+ anchor.file_path,
3785
+ anchor.start_line,
3786
+ anchor.end_line,
3787
+ searchResult.suggestedStart,
3788
+ searchResult.suggestedEnd
3789
+ );
3790
+ }
3791
+ results.push({
3792
+ aspectId: anchor.aspect_id,
3793
+ path: anchor.file_path,
3794
+ startLine: searchResult.suggestedStart,
3795
+ endLine: searchResult.suggestedEnd,
3796
+ status: "relocated",
3797
+ resolvedBy: "content-search",
3798
+ exists: true,
3799
+ similarity: searchResult.similarity,
3800
+ suggestedStart: searchResult.suggestedStart,
3801
+ suggestedEnd: searchResult.suggestedEnd,
3802
+ autoHealed: true,
3803
+ drifted: false
3804
+ });
3805
+ continue;
3806
+ }
3807
+ results.push({
3808
+ aspectId: anchor.aspect_id,
3809
+ path: searchResult.suggestedPath || anchor.file_path,
3810
+ startLine: anchor.start_line,
3811
+ endLine: anchor.end_line,
3812
+ status: "relocated",
3813
+ resolvedBy: "content-search",
3814
+ exists: true,
3815
+ similarity: searchResult.similarity,
3816
+ suggestedStart: searchResult.suggestedStart,
3817
+ suggestedEnd: searchResult.suggestedEnd,
3818
+ autoHealed: false,
3819
+ drifted: true
3820
+ });
3821
+ continue;
3822
+ }
3823
+ }
3458
3824
  db.run("UPDATE anchors SET drifted = 1 WHERE id = ?", [anchor.id]);
3459
3825
  results.push({
3460
3826
  aspectId: anchor.aspect_id,
@@ -3515,7 +3881,7 @@ function parseUnifiedDiffHunks(diffOutput) {
3515
3881
  function computeLineShift(rootDir, filePath, fromCommit, originalStart, originalEnd) {
3516
3882
  let diff;
3517
3883
  try {
3518
- diff = execSync(
3884
+ diff = execSync2(
3519
3885
  `git diff ${fromCommit}..HEAD --unified=0 -- "${filePath}"`,
3520
3886
  { cwd: rootDir, encoding: "utf8", timeout: 5e3 }
3521
3887
  );
@@ -3547,15 +3913,15 @@ function computeLineShift(rootDir, filePath, fromCommit, originalStart, original
3547
3913
  };
3548
3914
  }
3549
3915
  function healAnchorInPurposeFile(rootDir, purposeFilePath, anchorFilePath, oldStart, oldEnd, newStart, newEnd) {
3550
- const absolutePurpose = path6.isAbsolute(purposeFilePath) ? purposeFilePath : path6.join(rootDir, purposeFilePath);
3551
- if (!fs5.existsSync(absolutePurpose)) return false;
3916
+ const absolutePurpose = path7.isAbsolute(purposeFilePath) ? purposeFilePath : path7.join(rootDir, purposeFilePath);
3917
+ if (!fs6.existsSync(absolutePurpose)) return false;
3552
3918
  try {
3553
- const content = fs5.readFileSync(absolutePurpose, "utf8");
3919
+ const content = fs6.readFileSync(absolutePurpose, "utf8");
3554
3920
  const oldAnchor = oldStart === oldEnd ? `${anchorFilePath}:${oldStart}` : `${anchorFilePath}:${oldStart}-${oldEnd}`;
3555
3921
  const newAnchor = newStart === newEnd ? `${anchorFilePath}:${newStart}` : `${anchorFilePath}:${newStart}-${newEnd}`;
3556
3922
  if (!content.includes(oldAnchor)) return false;
3557
3923
  const updated = content.replace(oldAnchor, newAnchor);
3558
- fs5.writeFileSync(absolutePurpose, updated, "utf8");
3924
+ fs6.writeFileSync(absolutePurpose, updated, "utf8");
3559
3925
  return true;
3560
3926
  } catch {
3561
3927
  return false;
@@ -3565,21 +3931,22 @@ function normalizeForHash(content) {
3565
3931
  return content.split("\n").map((line) => line.trimEnd()).filter((line) => line.trim() !== "").map((line) => line.replace(/\s+/g, " ")).join("\n");
3566
3932
  }
3567
3933
  function computeAnchorHash(anchor, rootDir) {
3568
- if (!rootDir) return { exact: null, normalized: null };
3569
- const absolutePath = path6.isAbsolute(anchor.path) ? anchor.path : path6.join(rootDir, anchor.path);
3570
- if (!fs5.existsSync(absolutePath)) return { exact: null, normalized: null };
3934
+ if (!rootDir) return { exact: null, normalized: null, normalizedContent: null };
3935
+ const absolutePath = path7.isAbsolute(anchor.path) ? anchor.path : path7.join(rootDir, anchor.path);
3936
+ if (!fs6.existsSync(absolutePath)) return { exact: null, normalized: null, normalizedContent: null };
3571
3937
  try {
3572
- const fileContent = fs5.readFileSync(absolutePath, "utf8");
3938
+ const fileContent = fs6.readFileSync(absolutePath, "utf8");
3573
3939
  const lines = fileContent.split("\n");
3574
3940
  const { startLine, endLine } = resolveAnchorLines(anchor);
3575
3941
  const startIdx = Math.max(0, startLine - 1);
3576
3942
  const endIdx = Math.min(lines.length, endLine);
3577
3943
  const sliceContent = lines.slice(startIdx, endIdx).join("\n");
3578
- const exact = crypto2.createHash("sha256").update(sliceContent).digest("hex");
3579
- const normalized = crypto2.createHash("sha256").update(normalizeForHash(sliceContent)).digest("hex");
3580
- return { exact, normalized };
3944
+ const normalizedContent = normalizeForHash(sliceContent);
3945
+ const exact = crypto3.createHash("sha256").update(sliceContent).digest("hex");
3946
+ const normalized = crypto3.createHash("sha256").update(normalizedContent).digest("hex");
3947
+ return { exact, normalized, normalizedContent };
3581
3948
  } catch {
3582
- return { exact: null, normalized: null };
3949
+ return { exact: null, normalized: null, normalizedContent: null };
3583
3950
  }
3584
3951
  }
3585
3952
  function inferCategory(data, entry) {
@@ -3600,6 +3967,7 @@ function inferSeverity(data, entry) {
3600
3967
  }
3601
3968
 
3602
3969
  // ../paradigm-mcp/src/utils/aspect-lore-bridge.ts
3970
+ init_lore_loader();
3603
3971
  var LORE_ID_PATTERN = /L-\d{4}-\d{2}-\d{2}-\d{3}/g;
3604
3972
  async function materializeLoreLinks(db, rootDir) {
3605
3973
  db.run("DELETE FROM lore_links");
@@ -3795,19 +4163,19 @@ function toLoreSummary(entry) {
3795
4163
  }
3796
4164
 
3797
4165
  // ../paradigm-mcp/src/utils/personas-loader.ts
3798
- import * as fs6 from "fs";
3799
- import * as path7 from "path";
4166
+ import * as fs7 from "fs";
4167
+ import * as path8 from "path";
3800
4168
  import * as yaml5 from "js-yaml";
3801
4169
  var PERSONAS_ROOT = ".paradigm/personas";
3802
4170
  var INDEX_FILE = "index.yaml";
3803
4171
  async function loadPersonas(rootDir, filter) {
3804
- const personasDir = path7.join(rootDir, PERSONAS_ROOT);
3805
- if (!fs6.existsSync(personasDir)) return [];
3806
- const files = fs6.readdirSync(personasDir).filter((f) => f.endsWith(".persona"));
4172
+ const personasDir = path8.join(rootDir, PERSONAS_ROOT);
4173
+ if (!fs7.existsSync(personasDir)) return [];
4174
+ const files = fs7.readdirSync(personasDir).filter((f) => f.endsWith(".persona"));
3807
4175
  const personas = [];
3808
4176
  for (const file of files) {
3809
4177
  try {
3810
- const content = fs6.readFileSync(path7.join(personasDir, file), "utf8");
4178
+ const content = fs7.readFileSync(path8.join(personasDir, file), "utf8");
3811
4179
  const persona = yaml5.load(content);
3812
4180
  if (persona && persona.id) {
3813
4181
  personas.push(persona);
@@ -3818,10 +4186,10 @@ async function loadPersonas(rootDir, filter) {
3818
4186
  return applyFilter(personas, filter);
3819
4187
  }
3820
4188
  async function loadPersona(rootDir, id) {
3821
- const filePath = path7.join(rootDir, PERSONAS_ROOT, `${id}.persona`);
3822
- if (!fs6.existsSync(filePath)) return null;
4189
+ const filePath = path8.join(rootDir, PERSONAS_ROOT, `${id}.persona`);
4190
+ if (!fs7.existsSync(filePath)) return null;
3823
4191
  try {
3824
- return yaml5.load(fs6.readFileSync(filePath, "utf8"));
4192
+ return yaml5.load(fs7.readFileSync(filePath, "utf8"));
3825
4193
  } catch {
3826
4194
  return null;
3827
4195
  }
@@ -3851,10 +4219,10 @@ function applyFilter(personas, filter) {
3851
4219
  return result;
3852
4220
  }
3853
4221
  async function createPersona(rootDir, data) {
3854
- const personasDir = path7.join(rootDir, PERSONAS_ROOT);
3855
- fs6.mkdirSync(personasDir, { recursive: true });
3856
- const filePath = path7.join(personasDir, `${data.id}.persona`);
3857
- if (fs6.existsSync(filePath)) {
4222
+ const personasDir = path8.join(rootDir, PERSONAS_ROOT);
4223
+ fs7.mkdirSync(personasDir, { recursive: true });
4224
+ const filePath = path8.join(personasDir, `${data.id}.persona`);
4225
+ if (fs7.existsSync(filePath)) {
3858
4226
  throw new Error(`Persona ${data.id} already exists`);
3859
4227
  }
3860
4228
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -3871,14 +4239,14 @@ async function createPersona(rootDir, data) {
3871
4239
  created: now,
3872
4240
  updated: now
3873
4241
  };
3874
- fs6.writeFileSync(filePath, yaml5.dump(persona, { indent: 2, lineWidth: 120, noRefs: true, sortKeys: false }));
4242
+ fs7.writeFileSync(filePath, yaml5.dump(persona, { indent: 2, lineWidth: 120, noRefs: true, sortKeys: false }));
3875
4243
  await rebuildPersonaIndex(rootDir);
3876
4244
  return data.id;
3877
4245
  }
3878
4246
  async function updatePersona(rootDir, id, partial) {
3879
4247
  const persona = await loadPersona(rootDir, id);
3880
4248
  if (!persona) return false;
3881
- const filePath = path7.join(rootDir, PERSONAS_ROOT, `${id}.persona`);
4249
+ const filePath = path8.join(rootDir, PERSONAS_ROOT, `${id}.persona`);
3882
4250
  const updated = {
3883
4251
  ...persona,
3884
4252
  ...partial,
@@ -3890,13 +4258,13 @@ async function updatePersona(rootDir, id, partial) {
3890
4258
  // immutable
3891
4259
  updated: (/* @__PURE__ */ new Date()).toISOString()
3892
4260
  };
3893
- fs6.writeFileSync(filePath, yaml5.dump(updated, { indent: 2, lineWidth: 120, noRefs: true, sortKeys: false }));
4261
+ fs7.writeFileSync(filePath, yaml5.dump(updated, { indent: 2, lineWidth: 120, noRefs: true, sortKeys: false }));
3894
4262
  await rebuildPersonaIndex(rootDir);
3895
4263
  return true;
3896
4264
  }
3897
4265
  async function deletePersona(rootDir, id) {
3898
- const filePath = path7.join(rootDir, PERSONAS_ROOT, `${id}.persona`);
3899
- if (!fs6.existsSync(filePath)) {
4266
+ const filePath = path8.join(rootDir, PERSONAS_ROOT, `${id}.persona`);
4267
+ if (!fs7.existsSync(filePath)) {
3900
4268
  return { deleted: false, warnings: [] };
3901
4269
  }
3902
4270
  const warnings = [];
@@ -3912,7 +4280,7 @@ async function deletePersona(rootDir, id) {
3912
4280
  }
3913
4281
  }
3914
4282
  }
3915
- fs6.unlinkSync(filePath);
4283
+ fs7.unlinkSync(filePath);
3916
4284
  await rebuildPersonaIndex(rootDir);
3917
4285
  return { deleted: true, warnings };
3918
4286
  }
@@ -4014,12 +4382,12 @@ async function validatePersona(rootDir, persona, deep = false) {
4014
4382
  }
4015
4383
  }
4016
4384
  if (deep) {
4017
- const portalPath = path7.join(rootDir, "portal.yaml");
4385
+ const portalPath = path8.join(rootDir, "portal.yaml");
4018
4386
  let portalGates = [];
4019
4387
  let portalRoutes = [];
4020
- if (fs6.existsSync(portalPath)) {
4388
+ if (fs7.existsSync(portalPath)) {
4021
4389
  try {
4022
- const portal = yaml5.load(fs6.readFileSync(portalPath, "utf8"));
4390
+ const portal = yaml5.load(fs7.readFileSync(portalPath, "utf8"));
4023
4391
  if (portal.gates && typeof portal.gates === "object") {
4024
4392
  portalGates = Object.keys(portal.gates);
4025
4393
  }
@@ -4070,10 +4438,10 @@ async function validatePersona(rootDir, persona, deep = false) {
4070
4438
  }
4071
4439
  }
4072
4440
  let allFlows = [];
4073
- const flowIndexPath = path7.join(rootDir, ".paradigm", "flow-index.json");
4074
- if (fs6.existsSync(flowIndexPath)) {
4441
+ const flowIndexPath = path8.join(rootDir, ".paradigm", "flow-index.json");
4442
+ if (fs7.existsSync(flowIndexPath)) {
4075
4443
  try {
4076
- const flowIndex = JSON.parse(fs6.readFileSync(flowIndexPath, "utf8"));
4444
+ const flowIndex = JSON.parse(fs7.readFileSync(flowIndexPath, "utf8"));
4077
4445
  allFlows = Object.keys(flowIndex.flows || {});
4078
4446
  } catch {
4079
4447
  }
@@ -4150,8 +4518,8 @@ async function detectSpawnCycle(rootDir, startId) {
4150
4518
  return visit(startId);
4151
4519
  }
4152
4520
  async function rebuildPersonaIndex(rootDir) {
4153
- const personasDir = path7.join(rootDir, PERSONAS_ROOT);
4154
- fs6.mkdirSync(personasDir, { recursive: true });
4521
+ const personasDir = path8.join(rootDir, PERSONAS_ROOT);
4522
+ fs7.mkdirSync(personasDir, { recursive: true });
4155
4523
  const personas = await loadPersonas(rootDir);
4156
4524
  const entries = {};
4157
4525
  const gateCoverage = {};
@@ -4190,10 +4558,10 @@ async function rebuildPersonaIndex(rootDir) {
4190
4558
  }
4191
4559
  }
4192
4560
  let uncoveredRoutes = [];
4193
- const portalPath = path7.join(rootDir, "portal.yaml");
4194
- if (fs6.existsSync(portalPath)) {
4561
+ const portalPath = path8.join(rootDir, "portal.yaml");
4562
+ if (fs7.existsSync(portalPath)) {
4195
4563
  try {
4196
- const portal = yaml5.load(fs6.readFileSync(portalPath, "utf8"));
4564
+ const portal = yaml5.load(fs7.readFileSync(portalPath, "utf8"));
4197
4565
  if (portal.routes && typeof portal.routes === "object") {
4198
4566
  const portalRoutes = Object.keys(portal.routes);
4199
4567
  uncoveredRoutes = portalRoutes.filter((pr) => {
@@ -4204,12 +4572,12 @@ async function rebuildPersonaIndex(rootDir) {
4204
4572
  }
4205
4573
  }
4206
4574
  const chains = {};
4207
- const chainsDir = path7.join(personasDir, "chains");
4208
- if (fs6.existsSync(chainsDir)) {
4209
- const chainFiles = fs6.readdirSync(chainsDir).filter((f) => f.endsWith(".yaml"));
4575
+ const chainsDir = path8.join(personasDir, "chains");
4576
+ if (fs7.existsSync(chainsDir)) {
4577
+ const chainFiles = fs7.readdirSync(chainsDir).filter((f) => f.endsWith(".yaml"));
4210
4578
  for (const file of chainFiles) {
4211
4579
  try {
4212
- const content = fs6.readFileSync(path7.join(chainsDir, file), "utf8");
4580
+ const content = fs7.readFileSync(path8.join(chainsDir, file), "utf8");
4213
4581
  const chain = yaml5.load(content);
4214
4582
  if (chain && chain.id) {
4215
4583
  const orderIds = chain.order.map((o) => o.persona);
@@ -4242,8 +4610,8 @@ async function rebuildPersonaIndex(rootDir) {
4242
4610
  route_coverage: routeCoverage,
4243
4611
  uncovered_routes: uncoveredRoutes
4244
4612
  };
4245
- fs6.writeFileSync(
4246
- path7.join(personasDir, INDEX_FILE),
4613
+ fs7.writeFileSync(
4614
+ path8.join(personasDir, INDEX_FILE),
4247
4615
  yaml5.dump(index, { indent: 2, lineWidth: 120, noRefs: true, sortKeys: false })
4248
4616
  );
4249
4617
  return index;
@@ -4262,20 +4630,20 @@ async function getPersonaCoverage(rootDir) {
4262
4630
  }
4263
4631
  let portalGates = [];
4264
4632
  let portalRoutes = [];
4265
- const portalPath = path7.join(rootDir, "portal.yaml");
4266
- if (fs6.existsSync(portalPath)) {
4633
+ const portalPath = path8.join(rootDir, "portal.yaml");
4634
+ if (fs7.existsSync(portalPath)) {
4267
4635
  try {
4268
- const portal = yaml5.load(fs6.readFileSync(portalPath, "utf8"));
4636
+ const portal = yaml5.load(fs7.readFileSync(portalPath, "utf8"));
4269
4637
  if (portal.gates && typeof portal.gates === "object") portalGates = Object.keys(portal.gates);
4270
4638
  if (portal.routes && typeof portal.routes === "object") portalRoutes = Object.keys(portal.routes);
4271
4639
  } catch {
4272
4640
  }
4273
4641
  }
4274
4642
  let allFlows = [];
4275
- const flowIndexPath = path7.join(rootDir, ".paradigm", "flow-index.json");
4276
- if (fs6.existsSync(flowIndexPath)) {
4643
+ const flowIndexPath = path8.join(rootDir, ".paradigm", "flow-index.json");
4644
+ if (fs7.existsSync(flowIndexPath)) {
4277
4645
  try {
4278
- const flowIndex = JSON.parse(fs6.readFileSync(flowIndexPath, "utf8"));
4646
+ const flowIndex = JSON.parse(fs7.readFileSync(flowIndexPath, "utf8"));
4279
4647
  allFlows = Object.keys(flowIndex.flows || {});
4280
4648
  } catch {
4281
4649
  }
@@ -4324,8 +4692,8 @@ async function getAffectedPersonas(rootDir, symbol) {
4324
4692
  }
4325
4693
  return results;
4326
4694
  }
4327
- function deepGet(obj, path11) {
4328
- const parts = path11.split(/[.\[\]]+/).filter(Boolean);
4695
+ function deepGet(obj, path12) {
4696
+ const parts = path12.split(/[.\[\]]+/).filter(Boolean);
4329
4697
  let current = obj;
4330
4698
  for (const part of parts) {
4331
4699
  if (current == null || typeof current !== "object") return void 0;
@@ -4405,7 +4773,7 @@ function assertStep(step, event) {
4405
4773
  async function validateAgainstSentinel(persona, options = {}) {
4406
4774
  const steps = [];
4407
4775
  try {
4408
- const { SentinelStorage } = await import("./dist-CM3MVWWW.js");
4776
+ const { SentinelStorage } = await import("./dist-77JDTVAY.js");
4409
4777
  const storage = new SentinelStorage();
4410
4778
  const events = storage.queryEvents?.({
4411
4779
  schemaId: "paradigm-personas",
@@ -4494,21 +4862,21 @@ async function validateAgainstSentinel(persona, options = {}) {
4494
4862
  }
4495
4863
 
4496
4864
  // ../paradigm-mcp/src/utils/protocol-loader.ts
4497
- import * as fs7 from "fs";
4498
- import * as path8 from "path";
4865
+ import * as fs8 from "fs";
4866
+ import * as path9 from "path";
4499
4867
  import * as yaml6 from "js-yaml";
4500
4868
  var PROTOCOLS_DIR = ".paradigm/protocols";
4501
4869
  var INDEX_FILE2 = "index.yaml";
4502
4870
  async function loadProtocols(rootDir) {
4503
- const protocolsDir = path8.join(rootDir, PROTOCOLS_DIR);
4504
- if (!fs7.existsSync(protocolsDir)) {
4871
+ const protocolsDir = path9.join(rootDir, PROTOCOLS_DIR);
4872
+ if (!fs8.existsSync(protocolsDir)) {
4505
4873
  return [];
4506
4874
  }
4507
- const files = fs7.readdirSync(protocolsDir).filter((f) => f.endsWith(".protocol")).sort();
4875
+ const files = fs8.readdirSync(protocolsDir).filter((f) => f.endsWith(".protocol")).sort();
4508
4876
  const protocols = [];
4509
4877
  for (const file of files) {
4510
4878
  try {
4511
- const content = fs7.readFileSync(path8.join(protocolsDir, file), "utf8");
4879
+ const content = fs8.readFileSync(path9.join(protocolsDir, file), "utf8");
4512
4880
  const protocol = yaml6.load(content);
4513
4881
  if (protocol?.id && protocol?.name) {
4514
4882
  protocols.push(protocol);
@@ -4520,10 +4888,10 @@ async function loadProtocols(rootDir) {
4520
4888
  }
4521
4889
  async function loadProtocol(rootDir, id) {
4522
4890
  const slug = id.replace(/^P-/, "");
4523
- const filePath = path8.join(rootDir, PROTOCOLS_DIR, `${slug}.protocol`);
4524
- if (fs7.existsSync(filePath)) {
4891
+ const filePath = path9.join(rootDir, PROTOCOLS_DIR, `${slug}.protocol`);
4892
+ if (fs8.existsSync(filePath)) {
4525
4893
  try {
4526
- const content = fs7.readFileSync(filePath, "utf8");
4894
+ const content = fs8.readFileSync(filePath, "utf8");
4527
4895
  return yaml6.load(content);
4528
4896
  } catch {
4529
4897
  return null;
@@ -4533,12 +4901,12 @@ async function loadProtocol(rootDir, id) {
4533
4901
  return protocols.find((p) => p.id === id) || null;
4534
4902
  }
4535
4903
  async function loadProtocolIndex(rootDir) {
4536
- const indexPath = path8.join(rootDir, PROTOCOLS_DIR, INDEX_FILE2);
4537
- if (!fs7.existsSync(indexPath)) {
4904
+ const indexPath = path9.join(rootDir, PROTOCOLS_DIR, INDEX_FILE2);
4905
+ if (!fs8.existsSync(indexPath)) {
4538
4906
  return null;
4539
4907
  }
4540
4908
  try {
4541
- const content = fs7.readFileSync(indexPath, "utf8");
4909
+ const content = fs8.readFileSync(indexPath, "utf8");
4542
4910
  return yaml6.load(content);
4543
4911
  } catch {
4544
4912
  return null;
@@ -4590,9 +4958,9 @@ async function searchProtocols(rootDir, task, limit = 3) {
4590
4958
  return scored.slice(0, limit);
4591
4959
  }
4592
4960
  async function recordProtocol(rootDir, protocol) {
4593
- const protocolsDir = path8.join(rootDir, PROTOCOLS_DIR);
4594
- if (!fs7.existsSync(protocolsDir)) {
4595
- fs7.mkdirSync(protocolsDir, { recursive: true });
4961
+ const protocolsDir = path9.join(rootDir, PROTOCOLS_DIR);
4962
+ if (!fs8.existsSync(protocolsDir)) {
4963
+ fs8.mkdirSync(protocolsDir, { recursive: true });
4596
4964
  }
4597
4965
  const slug = slugify(protocol.name);
4598
4966
  const id = `P-${slug}`;
@@ -4612,8 +4980,8 @@ async function recordProtocol(rootDir, protocol) {
4612
4980
  verified_by: protocol.verified_by || "claude-opus-4-6",
4613
4981
  status: "current"
4614
4982
  };
4615
- const filePath = path8.join(protocolsDir, `${slug}.protocol`);
4616
- fs7.writeFileSync(filePath, yaml6.dump(full, { lineWidth: -1, noRefs: true }), "utf8");
4983
+ const filePath = path9.join(protocolsDir, `${slug}.protocol`);
4984
+ fs8.writeFileSync(filePath, yaml6.dump(full, { lineWidth: -1, noRefs: true }), "utf8");
4617
4985
  return id;
4618
4986
  }
4619
4987
  async function updateProtocol(rootDir, id, partial, refresh = false) {
@@ -4633,20 +5001,20 @@ async function updateProtocol(rootDir, id, partial, refresh = false) {
4633
5001
  protocol.verified_by = partial.verified_by || "claude-opus-4-6";
4634
5002
  }
4635
5003
  const slug = id.replace(/^P-/, "");
4636
- const filePath = path8.join(rootDir, PROTOCOLS_DIR, `${slug}.protocol`);
4637
- fs7.writeFileSync(filePath, yaml6.dump(protocol, { lineWidth: -1, noRefs: true }), "utf8");
5004
+ const filePath = path9.join(rootDir, PROTOCOLS_DIR, `${slug}.protocol`);
5005
+ fs8.writeFileSync(filePath, yaml6.dump(protocol, { lineWidth: -1, noRefs: true }), "utf8");
4638
5006
  return true;
4639
5007
  }
4640
5008
  function validateProtocol(rootDir, protocol) {
4641
5009
  const issues = [];
4642
5010
  let status = "current";
4643
5011
  if (protocol.exemplar) {
4644
- const exemplarPath = path8.join(rootDir, protocol.exemplar);
4645
- if (!fs7.existsSync(exemplarPath)) {
5012
+ const exemplarPath = path9.join(rootDir, protocol.exemplar);
5013
+ if (!fs8.existsSync(exemplarPath)) {
4646
5014
  issues.push(`Exemplar missing: ${protocol.exemplar}`);
4647
5015
  status = "broken";
4648
5016
  } else {
4649
- const stat = fs7.statSync(exemplarPath);
5017
+ const stat = fs8.statSync(exemplarPath);
4650
5018
  if (stat.mtime.toISOString() > protocol.last_verified) {
4651
5019
  issues.push(`Exemplar modified since last verified: ${protocol.exemplar}`);
4652
5020
  if (status !== "broken") status = "stale";
@@ -4655,15 +5023,15 @@ function validateProtocol(rootDir, protocol) {
4655
5023
  }
4656
5024
  for (const step of protocol.steps) {
4657
5025
  if (step.template_from) {
4658
- const templatePath = path8.join(rootDir, step.template_from);
4659
- if (!fs7.existsSync(templatePath)) {
5026
+ const templatePath = path9.join(rootDir, step.template_from);
5027
+ if (!fs8.existsSync(templatePath)) {
4660
5028
  issues.push(`Template file missing: ${step.template_from}`);
4661
5029
  status = "broken";
4662
5030
  }
4663
5031
  }
4664
5032
  if (step.action === "modify" && step.target) {
4665
- const targetPath = path8.join(rootDir, step.target);
4666
- if (!step.target.includes("{") && !fs7.existsSync(targetPath)) {
5033
+ const targetPath = path9.join(rootDir, step.target);
5034
+ if (!step.target.includes("{") && !fs8.existsSync(targetPath)) {
4667
5035
  issues.push(`Modify target missing: ${step.target}`);
4668
5036
  status = "broken";
4669
5037
  }
@@ -4682,9 +5050,9 @@ async function rebuildProtocolIndex(rootDir) {
4682
5050
  if (protocol.status !== validation.status) {
4683
5051
  protocol.status = validation.status;
4684
5052
  const slug = protocol.id.replace(/^P-/, "");
4685
- const filePath = path8.join(rootDir, PROTOCOLS_DIR, `${slug}.protocol`);
4686
- if (fs7.existsSync(filePath)) {
4687
- fs7.writeFileSync(filePath, yaml6.dump(protocol, { lineWidth: -1, noRefs: true }), "utf8");
5053
+ const filePath = path9.join(rootDir, PROTOCOLS_DIR, `${slug}.protocol`);
5054
+ if (fs8.existsSync(filePath)) {
5055
+ fs8.writeFileSync(filePath, yaml6.dump(protocol, { lineWidth: -1, noRefs: true }), "utf8");
4688
5056
  }
4689
5057
  }
4690
5058
  switch (validation.status) {
@@ -4718,13 +5086,13 @@ async function rebuildProtocolIndex(rootDir) {
4718
5086
  broken
4719
5087
  }
4720
5088
  };
4721
- const protocolsDir = path8.join(rootDir, PROTOCOLS_DIR);
5089
+ const protocolsDir = path9.join(rootDir, PROTOCOLS_DIR);
4722
5090
  if (protocols.length > 0) {
4723
- if (!fs7.existsSync(protocolsDir)) {
4724
- fs7.mkdirSync(protocolsDir, { recursive: true });
5091
+ if (!fs8.existsSync(protocolsDir)) {
5092
+ fs8.mkdirSync(protocolsDir, { recursive: true });
4725
5093
  }
4726
- const indexPath = path8.join(protocolsDir, INDEX_FILE2);
4727
- fs7.writeFileSync(indexPath, yaml6.dump(index, { lineWidth: -1, noRefs: true }), "utf8");
5094
+ const indexPath = path9.join(protocolsDir, INDEX_FILE2);
5095
+ fs8.writeFileSync(indexPath, yaml6.dump(index, { lineWidth: -1, noRefs: true }), "utf8");
4728
5096
  }
4729
5097
  return index;
4730
5098
  }
@@ -4732,21 +5100,21 @@ function detectProtocolSuggestion(rootDir, filesCreated, filesModified) {
4732
5100
  if (!filesCreated || filesCreated.length < 2) return null;
4733
5101
  const dirGroups = {};
4734
5102
  for (const file of filesCreated) {
4735
- const dir = path8.dirname(file);
5103
+ const dir = path9.dirname(file);
4736
5104
  if (!dirGroups[dir]) dirGroups[dir] = [];
4737
5105
  dirGroups[dir].push(file);
4738
5106
  }
4739
5107
  for (const [dir, created] of Object.entries(dirGroups)) {
4740
5108
  if (created.length < 2) continue;
4741
- const absDir = path8.join(rootDir, dir);
4742
- if (!fs7.existsSync(absDir)) continue;
4743
- const existing = fs7.readdirSync(absDir).filter((f) => {
4744
- const ext = path8.extname(f);
5109
+ const absDir = path9.join(rootDir, dir);
5110
+ if (!fs8.existsSync(absDir)) continue;
5111
+ const existing = fs8.readdirSync(absDir).filter((f) => {
5112
+ const ext = path9.extname(f);
4745
5113
  return [".ts", ".tsx", ".js", ".jsx", ".rs", ".py"].includes(ext);
4746
5114
  });
4747
- const preExisting = existing.filter((f) => !created.some((c) => path8.basename(c) === f));
5115
+ const preExisting = existing.filter((f) => !created.some((c) => path9.basename(c) === f));
4748
5116
  if (preExisting.length > 0) {
4749
- const exemplar = path8.join(dir, preExisting[0]);
5117
+ const exemplar = path9.join(dir, preExisting[0]);
4750
5118
  const steps = [
4751
5119
  ...created.map((f) => ({ action: "create", target: f })),
4752
5120
  ...filesModified.map((f) => ({ action: "modify", target: f }))
@@ -4754,7 +5122,7 @@ function detectProtocolSuggestion(rootDir, filesCreated, filesModified) {
4754
5122
  return {
4755
5123
  hint: `This session created ${created.length} new files in ${dir}/ following existing patterns. Consider recording a protocol.`,
4756
5124
  draft: {
4757
- name: `Add a ${path8.basename(dir).replace(/s$/, "")}`,
5125
+ name: `Add a ${path9.basename(dir).replace(/s$/, "")}`,
4758
5126
  exemplar,
4759
5127
  steps
4760
5128
  }
@@ -4829,8 +5197,8 @@ function slugify(name) {
4829
5197
  }
4830
5198
 
4831
5199
  // ../paradigm-mcp/src/utils/university-loader.ts
4832
- import * as fs8 from "fs";
4833
- import * as path9 from "path";
5200
+ import * as fs9 from "fs";
5201
+ import * as path10 from "path";
4834
5202
  import * as yaml7 from "js-yaml";
4835
5203
  var UNIVERSITY_DIR = ".paradigm/university";
4836
5204
  var CONTENT_DIR = "content";
@@ -4872,12 +5240,12 @@ var DEFAULT_CONFIG = {
4872
5240
  }
4873
5241
  };
4874
5242
  function loadUniversityConfig(rootDir) {
4875
- const configPath = path9.join(rootDir, UNIVERSITY_DIR, CONFIG_FILE);
4876
- if (!fs8.existsSync(configPath)) {
5243
+ const configPath = path10.join(rootDir, UNIVERSITY_DIR, CONFIG_FILE);
5244
+ if (!fs9.existsSync(configPath)) {
4877
5245
  return { ...DEFAULT_CONFIG };
4878
5246
  }
4879
5247
  try {
4880
- const raw = fs8.readFileSync(configPath, "utf8");
5248
+ const raw = fs9.readFileSync(configPath, "utf8");
4881
5249
  const data = yaml7.load(raw);
4882
5250
  if (!data) return { ...DEFAULT_CONFIG };
4883
5251
  return {
@@ -4886,7 +5254,8 @@ function loadUniversityConfig(rootDir) {
4886
5254
  content: {
4887
5255
  categories: data.content?.categories || [],
4888
5256
  defaultDifficulty: data.content?.defaultDifficulty || "beginner",
4889
- requireApproval: data.content?.requireApproval ?? false
5257
+ requireApproval: data.content?.requireApproval ?? false,
5258
+ defaultCategory: data.content?.defaultCategory
4890
5259
  },
4891
5260
  diplomas: {
4892
5261
  includeGlobalPLSAT: data.diplomas?.includeGlobalPLSAT ?? true,
@@ -4898,10 +5267,10 @@ function loadUniversityConfig(rootDir) {
4898
5267
  }
4899
5268
  }
4900
5269
  function loadUniversityIndex(rootDir) {
4901
- const indexPath = path9.join(rootDir, UNIVERSITY_DIR, INDEX_FILE3);
4902
- if (!fs8.existsSync(indexPath)) return null;
5270
+ const indexPath = path10.join(rootDir, UNIVERSITY_DIR, INDEX_FILE3);
5271
+ if (!fs9.existsSync(indexPath)) return null;
4903
5272
  try {
4904
- const raw = fs8.readFileSync(indexPath, "utf8");
5273
+ const raw = fs9.readFileSync(indexPath, "utf8");
4905
5274
  return yaml7.load(raw);
4906
5275
  } catch {
4907
5276
  return null;
@@ -4929,7 +5298,7 @@ function loadNote(rootDir, id) {
4929
5298
  const filePath = resolveContentFile(rootDir, id, ".md");
4930
5299
  if (!filePath) return null;
4931
5300
  try {
4932
- const raw = fs8.readFileSync(filePath, "utf8");
5301
+ const raw = fs9.readFileSync(filePath, "utf8");
4933
5302
  const parsed = parseFrontmatter(raw);
4934
5303
  if (!parsed) return null;
4935
5304
  const fm = parsed.frontmatter;
@@ -4940,18 +5309,18 @@ function loadNote(rootDir, id) {
4940
5309
  }
4941
5310
  function saveNote(rootDir, frontmatter, body) {
4942
5311
  const subdir = frontmatter.type === "policy" ? POLICIES_DIR : NOTES_DIR;
4943
- const dir = path9.join(rootDir, UNIVERSITY_DIR, CONTENT_DIR, subdir);
4944
- fs8.mkdirSync(dir, { recursive: true });
4945
- const filePath = path9.join(dir, `${frontmatter.id}.md`);
5312
+ const dir = path10.join(rootDir, UNIVERSITY_DIR, CONTENT_DIR, subdir);
5313
+ fs9.mkdirSync(dir, { recursive: true });
5314
+ const filePath = path10.join(dir, `${frontmatter.id}.md`);
4946
5315
  const content = serializeFrontmatter(frontmatter, body);
4947
- fs8.writeFileSync(filePath, content, "utf8");
5316
+ fs9.writeFileSync(filePath, content, "utf8");
4948
5317
  return filePath;
4949
5318
  }
4950
5319
  function loadQuiz(rootDir, id) {
4951
5320
  const filePath = resolveContentFile(rootDir, id, ".yaml");
4952
5321
  if (!filePath) return null;
4953
5322
  try {
4954
- const raw = fs8.readFileSync(filePath, "utf8");
5323
+ const raw = fs9.readFileSync(filePath, "utf8");
4955
5324
  const data = yaml7.load(raw);
4956
5325
  if (!data || !data.id) return null;
4957
5326
  return normalizeQuiz(data);
@@ -4960,17 +5329,17 @@ function loadQuiz(rootDir, id) {
4960
5329
  }
4961
5330
  }
4962
5331
  function saveQuiz(rootDir, quiz) {
4963
- const dir = path9.join(rootDir, UNIVERSITY_DIR, CONTENT_DIR, QUIZZES_DIR);
4964
- fs8.mkdirSync(dir, { recursive: true });
4965
- const filePath = path9.join(dir, `${quiz.id}.yaml`);
4966
- fs8.writeFileSync(filePath, yaml7.dump(quiz, { lineWidth: -1, noRefs: true }), "utf8");
5332
+ const dir = path10.join(rootDir, UNIVERSITY_DIR, CONTENT_DIR, QUIZZES_DIR);
5333
+ fs9.mkdirSync(dir, { recursive: true });
5334
+ const filePath = path10.join(dir, `${quiz.id}.yaml`);
5335
+ fs9.writeFileSync(filePath, yaml7.dump(quiz, { lineWidth: -1, noRefs: true }), "utf8");
4967
5336
  return filePath;
4968
5337
  }
4969
5338
  function loadPath(rootDir, id) {
4970
5339
  const filePath = resolveContentFile(rootDir, id, ".yaml");
4971
5340
  if (!filePath) return null;
4972
5341
  try {
4973
- const raw = fs8.readFileSync(filePath, "utf8");
5342
+ const raw = fs9.readFileSync(filePath, "utf8");
4974
5343
  const data = yaml7.load(raw);
4975
5344
  if (!data || !data.id) return null;
4976
5345
  return data;
@@ -4979,21 +5348,21 @@ function loadPath(rootDir, id) {
4979
5348
  }
4980
5349
  }
4981
5350
  function savePath(rootDir, lp) {
4982
- const dir = path9.join(rootDir, UNIVERSITY_DIR, CONTENT_DIR, PATHS_DIR);
4983
- fs8.mkdirSync(dir, { recursive: true });
4984
- const filePath = path9.join(dir, `${lp.id}.yaml`);
4985
- fs8.writeFileSync(filePath, yaml7.dump(lp, { lineWidth: -1, noRefs: true }), "utf8");
5351
+ const dir = path10.join(rootDir, UNIVERSITY_DIR, CONTENT_DIR, PATHS_DIR);
5352
+ fs9.mkdirSync(dir, { recursive: true });
5353
+ const filePath = path10.join(dir, `${lp.id}.yaml`);
5354
+ fs9.writeFileSync(filePath, yaml7.dump(lp, { lineWidth: -1, noRefs: true }), "utf8");
4986
5355
  return filePath;
4987
5356
  }
4988
5357
  function loadDiplomas(rootDir, filter) {
4989
- const dir = path9.join(rootDir, UNIVERSITY_DIR, DIPLOMAS_DIR);
4990
- if (!fs8.existsSync(dir)) return [];
5358
+ const dir = path10.join(rootDir, UNIVERSITY_DIR, DIPLOMAS_DIR);
5359
+ if (!fs9.existsSync(dir)) return [];
4991
5360
  const results = [];
4992
5361
  try {
4993
- const files = fs8.readdirSync(dir).filter((f) => f.endsWith(".yaml"));
5362
+ const files = fs9.readdirSync(dir).filter((f) => f.endsWith(".yaml"));
4994
5363
  for (const file of files) {
4995
5364
  try {
4996
- const raw = fs8.readFileSync(path9.join(dir, file), "utf8");
5365
+ const raw = fs9.readFileSync(path10.join(dir, file), "utf8");
4997
5366
  const diploma = yaml7.load(raw);
4998
5367
  if (!diploma || !diploma.id) continue;
4999
5368
  if (filter?.student && diploma.student !== filter.student) continue;
@@ -5007,10 +5376,10 @@ function loadDiplomas(rootDir, filter) {
5007
5376
  return results.sort((a, b) => b.earnedAt.localeCompare(a.earnedAt));
5008
5377
  }
5009
5378
  function saveDiploma(rootDir, diploma) {
5010
- const dir = path9.join(rootDir, UNIVERSITY_DIR, DIPLOMAS_DIR);
5011
- fs8.mkdirSync(dir, { recursive: true });
5012
- const filePath = path9.join(dir, `${diploma.id}.yaml`);
5013
- fs8.writeFileSync(filePath, yaml7.dump(diploma, { lineWidth: -1, noRefs: true }), "utf8");
5379
+ const dir = path10.join(rootDir, UNIVERSITY_DIR, DIPLOMAS_DIR);
5380
+ fs9.mkdirSync(dir, { recursive: true });
5381
+ const filePath = path10.join(dir, `${diploma.id}.yaml`);
5382
+ fs9.writeFileSync(filePath, yaml7.dump(diploma, { lineWidth: -1, noRefs: true }), "utf8");
5014
5383
  return filePath;
5015
5384
  }
5016
5385
  function searchContent(rootDir, filter) {
@@ -5038,20 +5407,34 @@ function searchContent(rootDir, filter) {
5038
5407
  (e) => e.title.toLowerCase().includes(q) || e.id.toLowerCase().includes(q) || e.tags.some((t) => t.toLowerCase().includes(q))
5039
5408
  );
5040
5409
  }
5410
+ if (filter.category) {
5411
+ results = results.filter((e) => e.category === filter.category);
5412
+ }
5413
+ if (filter.track) {
5414
+ const config = loadUniversityConfig(rootDir);
5415
+ const categoryTrackMap = /* @__PURE__ */ new Map();
5416
+ for (const cat of config.content.categories) {
5417
+ categoryTrackMap.set(cat.id, cat.track || "core");
5418
+ }
5419
+ results = results.filter((e) => {
5420
+ const entryTrack = e.category ? categoryTrackMap.get(e.category) || "core" : "core";
5421
+ return entryTrack === filter.track;
5422
+ });
5423
+ }
5041
5424
  const limit = filter.limit || 20;
5042
5425
  return results.slice(0, limit);
5043
5426
  }
5044
5427
  function rebuildUniversityIndex(rootDir) {
5045
- const uniDir = path9.join(rootDir, UNIVERSITY_DIR);
5046
- const contentDir = path9.join(uniDir, CONTENT_DIR);
5428
+ const uniDir = path10.join(rootDir, UNIVERSITY_DIR);
5429
+ const contentDir = path10.join(uniDir, CONTENT_DIR);
5047
5430
  const entries = [];
5048
5431
  for (const subdir of [NOTES_DIR, POLICIES_DIR]) {
5049
- const dir = path9.join(contentDir, subdir);
5050
- if (!fs8.existsSync(dir)) continue;
5432
+ const dir = path10.join(contentDir, subdir);
5433
+ if (!fs9.existsSync(dir)) continue;
5051
5434
  try {
5052
- for (const file of fs8.readdirSync(dir).filter((f) => f.endsWith(".md"))) {
5435
+ for (const file of fs9.readdirSync(dir).filter((f) => f.endsWith(".md"))) {
5053
5436
  try {
5054
- const raw = fs8.readFileSync(path9.join(dir, file), "utf8");
5437
+ const raw = fs9.readFileSync(path10.join(dir, file), "utf8");
5055
5438
  const parsed = parseFrontmatter(raw);
5056
5439
  if (!parsed) continue;
5057
5440
  const fm = parsed.frontmatter;
@@ -5065,7 +5448,8 @@ function rebuildUniversityIndex(rootDir) {
5065
5448
  tags: Array.isArray(fm.tags) ? fm.tags : [],
5066
5449
  symbols: Array.isArray(fm.symbols) ? fm.symbols : [],
5067
5450
  difficulty: fm.difficulty || "beginner",
5068
- file: `${CONTENT_DIR}/${subdir}/${file}`
5451
+ file: `${CONTENT_DIR}/${subdir}/${file}`,
5452
+ ...fm.category ? { category: fm.category } : {}
5069
5453
  });
5070
5454
  } catch {
5071
5455
  }
@@ -5073,12 +5457,12 @@ function rebuildUniversityIndex(rootDir) {
5073
5457
  } catch {
5074
5458
  }
5075
5459
  }
5076
- const quizDir = path9.join(contentDir, QUIZZES_DIR);
5077
- if (fs8.existsSync(quizDir)) {
5460
+ const quizDir = path10.join(contentDir, QUIZZES_DIR);
5461
+ if (fs9.existsSync(quizDir)) {
5078
5462
  try {
5079
- for (const file of fs8.readdirSync(quizDir).filter((f) => f.endsWith(".yaml"))) {
5463
+ for (const file of fs9.readdirSync(quizDir).filter((f) => f.endsWith(".yaml"))) {
5080
5464
  try {
5081
- const raw = fs8.readFileSync(path9.join(quizDir, file), "utf8");
5465
+ const raw = fs9.readFileSync(path10.join(quizDir, file), "utf8");
5082
5466
  const quiz = yaml7.load(raw);
5083
5467
  if (!quiz || !quiz.id) continue;
5084
5468
  entries.push({
@@ -5091,7 +5475,8 @@ function rebuildUniversityIndex(rootDir) {
5091
5475
  tags: quiz.tags || [],
5092
5476
  symbols: quiz.symbols || [],
5093
5477
  difficulty: quiz.difficulty || "beginner",
5094
- file: `${CONTENT_DIR}/${QUIZZES_DIR}/${file}`
5478
+ file: `${CONTENT_DIR}/${QUIZZES_DIR}/${file}`,
5479
+ ...quiz.category ? { category: quiz.category } : {}
5095
5480
  });
5096
5481
  } catch {
5097
5482
  }
@@ -5099,12 +5484,12 @@ function rebuildUniversityIndex(rootDir) {
5099
5484
  } catch {
5100
5485
  }
5101
5486
  }
5102
- const pathDir = path9.join(contentDir, PATHS_DIR);
5103
- if (fs8.existsSync(pathDir)) {
5487
+ const pathDir = path10.join(contentDir, PATHS_DIR);
5488
+ if (fs9.existsSync(pathDir)) {
5104
5489
  try {
5105
- for (const file of fs8.readdirSync(pathDir).filter((f) => f.endsWith(".yaml"))) {
5490
+ for (const file of fs9.readdirSync(pathDir).filter((f) => f.endsWith(".yaml"))) {
5106
5491
  try {
5107
- const raw = fs8.readFileSync(path9.join(pathDir, file), "utf8");
5492
+ const raw = fs9.readFileSync(path10.join(pathDir, file), "utf8");
5108
5493
  const lp = yaml7.load(raw);
5109
5494
  if (!lp || !lp.id) continue;
5110
5495
  entries.push({
@@ -5116,7 +5501,8 @@ function rebuildUniversityIndex(rootDir) {
5116
5501
  updated: lp.updated || "",
5117
5502
  tags: lp.tags || [],
5118
5503
  symbols: [],
5119
- file: `${CONTENT_DIR}/${PATHS_DIR}/${file}`
5504
+ file: `${CONTENT_DIR}/${PATHS_DIR}/${file}`,
5505
+ ...lp.category ? { category: lp.category } : {}
5120
5506
  });
5121
5507
  } catch {
5122
5508
  }
@@ -5125,10 +5511,10 @@ function rebuildUniversityIndex(rootDir) {
5125
5511
  }
5126
5512
  }
5127
5513
  let diplomaCount = 0;
5128
- const diplomaDir = path9.join(uniDir, DIPLOMAS_DIR);
5129
- if (fs8.existsSync(diplomaDir)) {
5514
+ const diplomaDir = path10.join(uniDir, DIPLOMAS_DIR);
5515
+ if (fs9.existsSync(diplomaDir)) {
5130
5516
  try {
5131
- diplomaCount = fs8.readdirSync(diplomaDir).filter((f) => f.endsWith(".yaml")).length;
5517
+ diplomaCount = fs9.readdirSync(diplomaDir).filter((f) => f.endsWith(".yaml")).length;
5132
5518
  } catch {
5133
5519
  }
5134
5520
  }
@@ -5139,9 +5525,9 @@ function rebuildUniversityIndex(rootDir) {
5139
5525
  entries,
5140
5526
  diplomaCount
5141
5527
  };
5142
- fs8.mkdirSync(uniDir, { recursive: true });
5143
- const indexPath = path9.join(uniDir, INDEX_FILE3);
5144
- fs8.writeFileSync(indexPath, yaml7.dump(index, { lineWidth: -1, noRefs: true }), "utf8");
5528
+ fs9.mkdirSync(uniDir, { recursive: true });
5529
+ const indexPath = path10.join(uniDir, INDEX_FILE3);
5530
+ fs9.writeFileSync(indexPath, yaml7.dump(index, { lineWidth: -1, noRefs: true }), "utf8");
5145
5531
  return index;
5146
5532
  }
5147
5533
  function validateUniversityContent(rootDir, options) {
@@ -5173,11 +5559,14 @@ function validateUniversityContent(rootDir, options) {
5173
5559
  validatePathContent(rootDir, entry.id, allContentIds, issues);
5174
5560
  }
5175
5561
  if (knownSymbols && entry.symbols.length > 0) {
5562
+ const config = loadUniversityConfig(rootDir);
5563
+ const entryCat = config.content.categories.find((c) => c.id === entry.category);
5564
+ const isRelaxed = entryCat?.validationStrictness === "relaxed";
5176
5565
  for (const sym of entry.symbols) {
5177
5566
  if (!knownSymbols.has(sym)) {
5178
5567
  issues.push({
5179
5568
  contentId: entry.id,
5180
- severity: "warning",
5569
+ severity: isRelaxed ? "warning" : "warning",
5181
5570
  check: "broken-symbol-ref",
5182
5571
  message: `Symbol "${sym}" not found in scan-index`,
5183
5572
  fix: `Remove or update the symbol reference in ${entry.id}`
@@ -5297,9 +5686,18 @@ function getAffectedUniversityContent(rootDir, symbol) {
5297
5686
  function getOnboardingSequence(rootDir, student) {
5298
5687
  const index = loadUniversityIndex(rootDir);
5299
5688
  if (!index) {
5300
- return { paths: [], suggestedContent: [], diplomaCount: 0, totalContent: 0 };
5689
+ return { paths: [], suggestedContent: [], extracurricular: [], diplomaCount: 0, totalContent: 0 };
5690
+ }
5691
+ const config = loadUniversityConfig(rootDir);
5692
+ const excludedCategories = /* @__PURE__ */ new Set();
5693
+ for (const cat of config.content.categories) {
5694
+ if (cat.excludeFromOnboarding) {
5695
+ excludedCategories.add(cat.id);
5696
+ }
5301
5697
  }
5302
- const pathEntries = index.entries.filter((e) => e.type === "path");
5698
+ const coreEntries = index.entries.filter((e) => !e.category || !excludedCategories.has(e.category));
5699
+ const extracurricularEntries = index.entries.filter((e) => e.category && excludedCategories.has(e.category));
5700
+ const pathEntries = coreEntries.filter((e) => e.type === "path");
5303
5701
  const diplomas = student ? loadDiplomas(rootDir, { student }) : [];
5304
5702
  const diplomaSourceIds = new Set(diplomas.map((d) => d.source));
5305
5703
  const paths = pathEntries.map((pe) => {
@@ -5311,19 +5709,20 @@ function getOnboardingSequence(rootDir, student) {
5311
5709
  completed: diplomaSourceIds.has(pe.id)
5312
5710
  };
5313
5711
  });
5314
- const suggestedContent = index.entries.filter((e) => e.type !== "path" && (e.difficulty === "beginner" || e.tags.includes("onboarding"))).slice(0, 10);
5712
+ const suggestedContent = coreEntries.filter((e) => e.type !== "path" && (e.difficulty === "beginner" || e.tags.includes("onboarding"))).slice(0, 10);
5315
5713
  return {
5316
5714
  paths,
5317
5715
  suggestedContent,
5716
+ extracurricular: extracurricularEntries,
5318
5717
  diplomaCount: diplomas.length,
5319
5718
  totalContent: index.totalContent
5320
5719
  };
5321
5720
  }
5322
5721
  function resolveContentFile(rootDir, id, ext) {
5323
- const contentDir = path9.join(rootDir, UNIVERSITY_DIR, CONTENT_DIR);
5722
+ const contentDir = path10.join(rootDir, UNIVERSITY_DIR, CONTENT_DIR);
5324
5723
  for (const subdir of [NOTES_DIR, POLICIES_DIR, QUIZZES_DIR, PATHS_DIR]) {
5325
- const filePath = path9.join(contentDir, subdir, `${id}${ext}`);
5326
- if (fs8.existsSync(filePath)) return filePath;
5724
+ const filePath = path10.join(contentDir, subdir, `${id}${ext}`);
5725
+ if (fs9.existsSync(filePath)) return filePath;
5327
5726
  }
5328
5727
  return null;
5329
5728
  }
@@ -5339,7 +5738,8 @@ function normalizeFrontmatter(fm) {
5339
5738
  symbols: Array.isArray(fm.symbols) ? fm.symbols : [],
5340
5739
  difficulty: fm.difficulty || "beginner",
5341
5740
  estimatedMinutes: fm.estimatedMinutes,
5342
- prerequisites: Array.isArray(fm.prerequisites) ? fm.prerequisites : []
5741
+ prerequisites: Array.isArray(fm.prerequisites) ? fm.prerequisites : [],
5742
+ ...fm.category ? { category: fm.category } : {}
5343
5743
  };
5344
5744
  }
5345
5745
  function normalizeQuiz(quiz) {
@@ -5354,10 +5754,10 @@ function normalizeQuiz(quiz) {
5354
5754
  }
5355
5755
  function loadKnownSymbols(rootDir) {
5356
5756
  const symbols = /* @__PURE__ */ new Set();
5357
- const scanIndexPath = path9.join(rootDir, ".paradigm", "scan-index.json");
5358
- if (!fs8.existsSync(scanIndexPath)) return symbols;
5757
+ const scanIndexPath = path10.join(rootDir, ".paradigm", "scan-index.json");
5758
+ if (!fs9.existsSync(scanIndexPath)) return symbols;
5359
5759
  try {
5360
- const raw = fs8.readFileSync(scanIndexPath, "utf8");
5760
+ const raw = fs9.readFileSync(scanIndexPath, "utf8");
5361
5761
  const index = JSON.parse(raw);
5362
5762
  if (index.symbols && Array.isArray(index.symbols)) {
5363
5763
  for (const sym of index.symbols) {
@@ -5389,17 +5789,17 @@ function isContentStale(rootDir, entry, _symbol) {
5389
5789
  if (!entry.updated) return false;
5390
5790
  const contentUpdated = new Date(entry.updated).getTime();
5391
5791
  if (isNaN(contentUpdated)) return false;
5392
- const scanIndexPath = path9.join(rootDir, ".paradigm", "scan-index.json");
5393
- if (!fs8.existsSync(scanIndexPath)) return false;
5792
+ const scanIndexPath = path10.join(rootDir, ".paradigm", "scan-index.json");
5793
+ if (!fs9.existsSync(scanIndexPath)) return false;
5394
5794
  try {
5395
- const raw = fs8.readFileSync(scanIndexPath, "utf8");
5795
+ const raw = fs9.readFileSync(scanIndexPath, "utf8");
5396
5796
  const index = JSON.parse(raw);
5397
5797
  if (index.symbols && Array.isArray(index.symbols)) {
5398
5798
  for (const sym of index.symbols) {
5399
5799
  if (sym.symbol === _symbol && sym.filePath) {
5400
- const purposePath = path9.join(rootDir, sym.filePath);
5401
- if (fs8.existsSync(purposePath)) {
5402
- const stat = fs8.statSync(purposePath);
5800
+ const purposePath = path10.join(rootDir, sym.filePath);
5801
+ if (fs9.existsSync(purposePath)) {
5802
+ const stat = fs9.statSync(purposePath);
5403
5803
  if (stat.mtime.getTime() > contentUpdated) {
5404
5804
  return true;
5405
5805
  }
@@ -5514,10 +5914,10 @@ async function rebuildStaticFiles(rootDir, ctx) {
5514
5914
  } else {
5515
5915
  aggregation = await aggregateFromDirectory(rootDir);
5516
5916
  }
5517
- const projectName = ctx?.projectName || path10.basename(rootDir);
5518
- const paradigmDir = path10.join(rootDir, ".paradigm");
5519
- if (!fs9.existsSync(paradigmDir)) {
5520
- fs9.mkdirSync(paradigmDir, { recursive: true });
5917
+ const projectName = ctx?.projectName || path11.basename(rootDir);
5918
+ const paradigmDir = path11.join(rootDir, ".paradigm");
5919
+ if (!fs10.existsSync(paradigmDir)) {
5920
+ fs10.mkdirSync(paradigmDir, { recursive: true });
5521
5921
  }
5522
5922
  const scanIndex = generateScanIndex(
5523
5923
  {
@@ -5527,12 +5927,12 @@ async function rebuildStaticFiles(rootDir, ctx) {
5527
5927
  },
5528
5928
  { projectName }
5529
5929
  );
5530
- const scanIndexPath = path10.join(paradigmDir, "scan-index.json");
5531
- fs9.writeFileSync(scanIndexPath, serializeScanIndex(scanIndex), "utf8");
5930
+ const scanIndexPath = path11.join(paradigmDir, "scan-index.json");
5931
+ fs10.writeFileSync(scanIndexPath, serializeScanIndex(scanIndex), "utf8");
5532
5932
  filesWritten.push(".paradigm/scan-index.json");
5533
5933
  const navigatorData = buildNavigatorData(rootDir, aggregation);
5534
- const navigatorPath = path10.join(paradigmDir, "navigator.yaml");
5535
- fs9.writeFileSync(
5934
+ const navigatorPath = path11.join(paradigmDir, "navigator.yaml");
5935
+ fs10.writeFileSync(
5536
5936
  navigatorPath,
5537
5937
  yaml8.dump(navigatorData, { indent: 2, lineWidth: 120, noRefs: true, sortKeys: false }),
5538
5938
  "utf8"
@@ -5541,8 +5941,8 @@ async function rebuildStaticFiles(rootDir, ctx) {
5541
5941
  const flowIndex = generateFlowIndex(rootDir, aggregation.purposeFiles);
5542
5942
  let flowCount = 0;
5543
5943
  if (flowIndex && Object.keys(flowIndex.flows).length > 0) {
5544
- const flowIndexPath = path10.join(paradigmDir, "flow-index.json");
5545
- fs9.writeFileSync(flowIndexPath, JSON.stringify(flowIndex, null, 2), "utf8");
5944
+ const flowIndexPath = path11.join(paradigmDir, "flow-index.json");
5945
+ fs10.writeFileSync(flowIndexPath, JSON.stringify(flowIndex, null, 2), "utf8");
5546
5946
  filesWritten.push(".paradigm/flow-index.json");
5547
5947
  flowCount = Object.keys(flowIndex.flows).length;
5548
5948
  }
@@ -5585,8 +5985,8 @@ async function rebuildStaticFiles(rootDir, ctx) {
5585
5985
  }
5586
5986
  let universityStats;
5587
5987
  try {
5588
- const uniDir = path10.join(rootDir, ".paradigm", "university");
5589
- if (fs9.existsSync(uniDir)) {
5988
+ const uniDir = path11.join(rootDir, ".paradigm", "university");
5989
+ if (fs10.existsSync(uniDir)) {
5590
5990
  const uniIndex = rebuildUniversityIndex(rootDir);
5591
5991
  if (uniIndex.totalContent > 0 || uniIndex.diplomaCount > 0) {
5592
5992
  universityStats = {
@@ -5684,7 +6084,7 @@ function buildNavigatorData(rootDir, aggregation) {
5684
6084
  function buildStructure(rootDir) {
5685
6085
  const structure = {};
5686
6086
  for (const [category, patterns] of Object.entries(DIRECTORY_PATTERNS)) {
5687
- const existingPaths = patterns.filter((p) => fs9.existsSync(path10.join(rootDir, p)));
6087
+ const existingPaths = patterns.filter((p) => fs10.existsSync(path11.join(rootDir, p)));
5688
6088
  if (existingPaths.length > 0) {
5689
6089
  const symbolInfo = Object.values(SYMBOL_CATEGORIES).find((s) => s.category === category);
5690
6090
  structure[category] = { paths: existingPaths, symbol: symbolInfo?.prefix || "@" };
@@ -5695,7 +6095,7 @@ function buildStructure(rootDir) {
5695
6095
  function buildKeyFiles(rootDir) {
5696
6096
  const keyFiles = {};
5697
6097
  for (const [category, patterns] of Object.entries(KEY_FILE_PATTERNS)) {
5698
- const existingPaths = patterns.filter((p) => fs9.existsSync(path10.join(rootDir, p)));
6098
+ const existingPaths = patterns.filter((p) => fs10.existsSync(path11.join(rootDir, p)));
5699
6099
  if (existingPaths.length > 0) {
5700
6100
  keyFiles[category] = existingPaths;
5701
6101
  }
@@ -5711,10 +6111,10 @@ function buildSkipPatterns(rootDir) {
5711
6111
  unless_testing: [...DEFAULT_SKIP_PATTERNS.unless_testing],
5712
6112
  unless_docs: [...DEFAULT_SKIP_PATTERNS.unless_docs]
5713
6113
  };
5714
- const gitignorePath = path10.join(rootDir, ".gitignore");
5715
- if (fs9.existsSync(gitignorePath)) {
6114
+ const gitignorePath = path11.join(rootDir, ".gitignore");
6115
+ if (fs10.existsSync(gitignorePath)) {
5716
6116
  try {
5717
- const content = fs9.readFileSync(gitignorePath, "utf8");
6117
+ const content = fs10.readFileSync(gitignorePath, "utf8");
5718
6118
  const gitignorePatterns = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).filter(
5719
6119
  (line) => line.endsWith("/") || line.includes("*") || ["node_modules", "dist", "build", ".cache"].some((p) => line.includes(p))
5720
6120
  ).slice(0, 20);
@@ -5763,11 +6163,11 @@ function buildSymbolMap(symbols, purposeFiles, _rootDir) {
5763
6163
  symbolMap[symbolId] = symbol.filePath;
5764
6164
  } else {
5765
6165
  const matchingPurpose = purposeFiles.find((pf) => {
5766
- const dir = path10.dirname(pf);
6166
+ const dir = path11.dirname(pf);
5767
6167
  return dir.toLowerCase().includes(symbol.id.toLowerCase());
5768
6168
  });
5769
6169
  if (matchingPurpose) {
5770
- symbolMap[symbolId] = path10.dirname(matchingPurpose) + "/";
6170
+ symbolMap[symbolId] = path11.dirname(matchingPurpose) + "/";
5771
6171
  }
5772
6172
  }
5773
6173
  }
@@ -5778,7 +6178,7 @@ function generateFlowIndex(rootDir, purposeFiles) {
5778
6178
  const symbolToFlows = {};
5779
6179
  for (const filePath of purposeFiles) {
5780
6180
  try {
5781
- const content = fs9.readFileSync(filePath, "utf8");
6181
+ const content = fs10.readFileSync(filePath, "utf8");
5782
6182
  const data = yaml8.load(content);
5783
6183
  if (!data?.flows) continue;
5784
6184
  if (Array.isArray(data.flows)) {
@@ -5792,7 +6192,7 @@ function generateFlowIndex(rootDir, purposeFiles) {
5792
6192
  id: flowId,
5793
6193
  description: flow.description || "",
5794
6194
  steps,
5795
- definedIn: path10.relative(rootDir, filePath)
6195
+ definedIn: path11.relative(rootDir, filePath)
5796
6196
  };
5797
6197
  indexFlowSymbols(flowId, steps, symbolToFlows);
5798
6198
  }
@@ -5808,7 +6208,7 @@ function generateFlowIndex(rootDir, purposeFiles) {
5808
6208
  trigger: flowDef.trigger,
5809
6209
  steps,
5810
6210
  validation: flowDef.validation,
5811
- definedIn: path10.relative(rootDir, filePath)
6211
+ definedIn: path11.relative(rootDir, filePath)
5812
6212
  };
5813
6213
  indexFlowSymbols(flowId, steps, symbolToFlows);
5814
6214
  }