@launchsecure/launch-kit 0.0.26 → 0.0.27

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 (86) hide show
  1. package/dist/chart-client/assets/index-CJ4mgRRF.css +1 -0
  2. package/dist/chart-client/assets/{index-Bk1hawjD.js → index-Ccy-DpI-.js} +46 -42
  3. package/dist/chart-client/index.html +2 -2
  4. package/dist/client/assets/index-DI5qSR_w.css +32 -0
  5. package/dist/client/assets/index-Dp0_okva.js +294 -0
  6. package/dist/client/index.html +2 -2
  7. package/dist/council-client/assets/index-C_-vAM9L.css +1 -0
  8. package/dist/council-client/index.html +2 -2
  9. package/dist/deck-client/assets/{_baseUniq-C2xT_eYu.js → _baseUniq-W2JQDmje.js} +1 -1
  10. package/dist/deck-client/assets/{arc-CmVL9pGd.js → arc-DIBWAId9.js} +1 -1
  11. package/dist/deck-client/assets/{architectureDiagram-Q4EWVU46-BSFgdjve.js → architectureDiagram-Q4EWVU46-CAIRMvJK.js} +1 -1
  12. package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-DuLzscvP.js → blockDiagram-DXYQGD6D-BeNaNiOi.js} +1 -1
  13. package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-CfCJB8eY.js → c4Diagram-AHTNJAMY-B9Ozi62h.js} +1 -1
  14. package/dist/deck-client/assets/channel-CRdozqbp.js +1 -0
  15. package/dist/deck-client/assets/{chunk-4BX2VUAB-DxmLYTWZ.js → chunk-4BX2VUAB-D7AZ47dt.js} +1 -1
  16. package/dist/deck-client/assets/{chunk-4TB4RGXK-CCnf7GFE.js → chunk-4TB4RGXK-DnVnNPcI.js} +1 -1
  17. package/dist/deck-client/assets/{chunk-55IACEB6-Db9DApcj.js → chunk-55IACEB6-UKYs-YNd.js} +1 -1
  18. package/dist/deck-client/assets/{chunk-EDXVE4YY-DmYDq8ZI.js → chunk-EDXVE4YY-D43b-SKn.js} +1 -1
  19. package/dist/deck-client/assets/{chunk-FMBD7UC4-BGhUlF20.js → chunk-FMBD7UC4-QzBAoyyW.js} +1 -1
  20. package/dist/deck-client/assets/{chunk-OYMX7WX6-CpEnicQZ.js → chunk-OYMX7WX6-Cjif4r6W.js} +1 -1
  21. package/dist/deck-client/assets/{chunk-QZHKN3VN-Doa7LKwf.js → chunk-QZHKN3VN-CqLDirEI.js} +1 -1
  22. package/dist/deck-client/assets/{chunk-YZCP3GAM-CpkIlH6V.js → chunk-YZCP3GAM-_FQvmMs4.js} +1 -1
  23. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-lIZMp57W.js +1 -0
  24. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-lIZMp57W.js +1 -0
  25. package/dist/deck-client/assets/clone-BtWeSTyJ.js +1 -0
  26. package/dist/deck-client/assets/{cose-bilkent-S5V4N54A-Bkh8Bfcb.js → cose-bilkent-S5V4N54A-rfrocesE.js} +1 -1
  27. package/dist/deck-client/assets/{dagre-KV5264BT-Bp0XpTgH.js → dagre-KV5264BT-Bv_7DJat.js} +1 -1
  28. package/dist/deck-client/assets/{diagram-5BDNPKRD-ZHiyGYPQ.js → diagram-5BDNPKRD-4F1414G5.js} +1 -1
  29. package/dist/deck-client/assets/{diagram-G4DWMVQ6-BW-Q8_H5.js → diagram-G4DWMVQ6-C4-Pszqm.js} +1 -1
  30. package/dist/deck-client/assets/{diagram-MMDJMWI5-6I3LTafu.js → diagram-MMDJMWI5-B647TIx9.js} +1 -1
  31. package/dist/deck-client/assets/{diagram-TYMM5635-CyM5YK28.js → diagram-TYMM5635-BFAqpezd.js} +1 -1
  32. package/dist/deck-client/assets/{erDiagram-SMLLAGMA-CjNxVJHk.js → erDiagram-SMLLAGMA-BfBfrJOC.js} +1 -1
  33. package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-BDQHuAJR.js → flowDiagram-DWJPFMVM-DX9YAYes.js} +1 -1
  34. package/dist/deck-client/assets/{ganttDiagram-T4ZO3ILL-B7MnkpbP.js → ganttDiagram-T4ZO3ILL-DCuiy7wF.js} +1 -1
  35. package/dist/deck-client/assets/{gitGraphDiagram-UUTBAWPF-C9dZAcYD.js → gitGraphDiagram-UUTBAWPF-CGp1IXUh.js} +1 -1
  36. package/dist/deck-client/assets/{graph-CjdBnzUy.js → graph-B7g8aoxv.js} +1 -1
  37. package/dist/deck-client/assets/{index-DeIVPW63.js → index-Dg1r-WSN.js} +3 -3
  38. package/dist/deck-client/assets/index-DsIZ3LqL.css +1 -0
  39. package/dist/deck-client/assets/{infoDiagram-42DDH7IO-C7d3iRC3.js → infoDiagram-42DDH7IO-L3fahMkF.js} +1 -1
  40. package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-BcYGKj09.js → ishikawaDiagram-UXIWVN3A-aS_EjWBZ.js} +1 -1
  41. package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-DqFlRrOL.js → journeyDiagram-VCZTEJTY-djTSQZF9.js} +1 -1
  42. package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-BJhPp1NR.js → kanban-definition-6JOO6SKY-CcTHo4CM.js} +1 -1
  43. package/dist/deck-client/assets/{layout-DIeS6GvK.js → layout-mEJiadb7.js} +1 -1
  44. package/dist/deck-client/assets/{linear-He_yJy5H.js → linear-XgTKqyRu.js} +1 -1
  45. package/dist/deck-client/assets/{min-DQ6Kx06t.js → min-Ct9jZdpd.js} +1 -1
  46. package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-sQ62L8T2.js → mindmap-definition-QFDTVHPH-BaFxCGNU.js} +1 -1
  47. package/dist/deck-client/assets/{pieDiagram-DEJITSTG-BqCWmU2K.js → pieDiagram-DEJITSTG-CIbYYjtw.js} +1 -1
  48. package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-rQ1TJOoe.js → quadrantDiagram-34T5L4WZ-D9EtCOvh.js} +1 -1
  49. package/dist/deck-client/assets/{requirementDiagram-MS252O5E-BO2MPBOM.js → requirementDiagram-MS252O5E-xeni9eVG.js} +1 -1
  50. package/dist/deck-client/assets/{sankeyDiagram-XADWPNL6-BgsHEVex.js → sankeyDiagram-XADWPNL6-LYeknz9h.js} +1 -1
  51. package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-B3j1yMLU.js → sequenceDiagram-FGHM5R23-RDbsKFZf.js} +1 -1
  52. package/dist/deck-client/assets/{stateDiagram-FHFEXIEX-C8jFlZou.js → stateDiagram-FHFEXIEX-BH1Zjglk.js} +1 -1
  53. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-BrV78NDR.js +1 -0
  54. package/dist/deck-client/assets/{timeline-definition-GMOUNBTQ-tM-qo4Zk.js → timeline-definition-GMOUNBTQ-IFXxKptt.js} +1 -1
  55. package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-B0-6kOEu.js → vennDiagram-DHZGUBPP-D-sLkQs9.js} +1 -1
  56. package/dist/deck-client/assets/{wardley-RL74JXVD-HpBk07P-.js → wardley-RL74JXVD-C010F8l4.js} +1 -1
  57. package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-BkA1NLDE.js → wardleyDiagram-NUSXRM2D-BTjjuDU3.js} +1 -1
  58. package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-CEKGSuI-.js → xychartDiagram-5P7HB3ND-AYbv92n-.js} +1 -1
  59. package/dist/deck-client/index.html +2 -2
  60. package/dist/server/chart-serve.js +3836 -3750
  61. package/dist/server/cli.js +8746 -8224
  62. package/dist/server/council-entry.js +17 -5
  63. package/dist/server/council-serve.js +8 -3
  64. package/dist/server/deck-mcp-entry.js +24 -12
  65. package/dist/server/deck-serve.js +11 -8
  66. package/dist/server/fb-wizard.js +0 -0
  67. package/dist/server/graph-mcp-entry.js +5005 -4865
  68. package/dist/server/init-entry.js +609 -0
  69. package/dist/server/orbit-entry.js +2272 -0
  70. package/dist/server/parse-worker-entry.js +4721 -0
  71. package/dist/server/recall-entry.js +356 -18
  72. package/package.json +29 -23
  73. package/scaffolds/migrate-safety/.github/workflows/backup-on-migration.yml +72 -0
  74. package/scaffolds/migrate-safety/docs/migrations-runbook.md +172 -0
  75. package/scaffolds/migrate-safety/scripts/migrate-with-backup.sh +294 -0
  76. package/dist/chart-client/assets/index-DpaGa3bY.css +0 -1
  77. package/dist/client/assets/index-Bfel4OQ5.css +0 -32
  78. package/dist/client/assets/index-eC-WuUWB.js +0 -291
  79. package/dist/council-client/assets/index-P5kMsT5a.css +0 -1
  80. package/dist/deck-client/assets/channel-B4aNO8ZB.js +0 -1
  81. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-BHTI0yWz.js +0 -1
  82. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-BHTI0yWz.js +0 -1
  83. package/dist/deck-client/assets/clone-HduFm7qU.js +0 -1
  84. package/dist/deck-client/assets/index-LKZDAS9S.css +0 -1
  85. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-BoqepHW0.js +0 -1
  86. /package/dist/council-client/assets/{index-Cs_MVXHf.js → index-Dt4zWKSj.js} +0 -0
@@ -0,0 +1,4721 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // src/server/launch-kit-paths.ts
34
+ var LAUNCHSECURE_DIR, LAUNCHCHART_CONFIG_FILE;
35
+ var init_launch_kit_paths = __esm({
36
+ "src/server/launch-kit-paths.ts"() {
37
+ "use strict";
38
+ LAUNCHSECURE_DIR = ".launchsecure";
39
+ LAUNCHCHART_CONFIG_FILE = ".launchchart.json";
40
+ }
41
+ });
42
+
43
+ // src/server/graph/core/parse-failure-cache.ts
44
+ function hashContent(content) {
45
+ return "sha256:" + (0, import_node_crypto.createHash)("sha256").update(content).digest("hex");
46
+ }
47
+ function setActiveFailureCache(cache) {
48
+ activeCache = cache;
49
+ }
50
+ function getActiveFailureCache() {
51
+ return activeCache;
52
+ }
53
+ var import_node_crypto, import_node_fs4, import_node_path4, SCHEMA_VERSION, ParseFailureCache, activeCache;
54
+ var init_parse_failure_cache = __esm({
55
+ "src/server/graph/core/parse-failure-cache.ts"() {
56
+ "use strict";
57
+ import_node_crypto = require("node:crypto");
58
+ import_node_fs4 = require("node:fs");
59
+ import_node_path4 = require("node:path");
60
+ init_launch_kit_paths();
61
+ SCHEMA_VERSION = 1;
62
+ ParseFailureCache = class {
63
+ constructor(rootDir, treeSitterFingerprint) {
64
+ this.failures = /* @__PURE__ */ new Map();
65
+ this.dirty = false;
66
+ this.cacheFilePath = (0, import_node_path4.join)(rootDir, LAUNCHSECURE_DIR, "graphs", ".failures.json");
67
+ this.fingerprint = treeSitterFingerprint;
68
+ this.load();
69
+ }
70
+ /** Path the cache persists to. Exposed so tier 3 can audit it. */
71
+ get filePath() {
72
+ return this.cacheFilePath;
73
+ }
74
+ /**
75
+ * Return the entry for absPath IF the cached entry's contentHash matches the
76
+ * current bytes. Otherwise null (caller should attempt a real parse).
77
+ */
78
+ shouldSkip(absPath, content) {
79
+ const cached = this.failures.get(absPath);
80
+ if (!cached) return null;
81
+ if (cached.contentHash !== hashContent(content)) return null;
82
+ return cached;
83
+ }
84
+ /** Record a parse failure. Increments consecutiveAttempts when hash matches. */
85
+ recordFailure(absPath, content, error) {
86
+ const now = (/* @__PURE__ */ new Date()).toISOString();
87
+ const hash = hashContent(content);
88
+ const prior = this.failures.get(absPath);
89
+ const entry = {
90
+ contentHash: hash,
91
+ failedAt: prior && prior.contentHash === hash ? prior.failedAt : now,
92
+ lastAttemptAt: now,
93
+ lastError: error,
94
+ consecutiveAttempts: prior && prior.contentHash === hash ? prior.consecutiveAttempts + 1 : 1
95
+ };
96
+ this.failures.set(absPath, entry);
97
+ this.dirty = true;
98
+ }
99
+ /** Bump the lastAttemptAt timestamp on a skip without changing the failure record. */
100
+ bumpAttempt(absPath) {
101
+ const cached = this.failures.get(absPath);
102
+ if (!cached) return;
103
+ cached.lastAttemptAt = (/* @__PURE__ */ new Date()).toISOString();
104
+ this.dirty = true;
105
+ }
106
+ /** Clear a single entry (call on successful parse). */
107
+ clear(absPath) {
108
+ if (!this.failures.has(absPath)) return false;
109
+ this.failures.delete(absPath);
110
+ this.dirty = true;
111
+ return true;
112
+ }
113
+ /** Wipe the entire cache (call after a successful tier-3 full regen). */
114
+ clearAll() {
115
+ if (this.failures.size === 0) return;
116
+ this.failures.clear();
117
+ this.dirty = true;
118
+ }
119
+ /** Snapshot for meta/status surfacing. */
120
+ list() {
121
+ const out = [];
122
+ for (const [path, entry] of this.failures) out.push({ path, ...entry });
123
+ return out;
124
+ }
125
+ size() {
126
+ return this.failures.size;
127
+ }
128
+ /**
129
+ * Atomically persist to disk. No-op if nothing has changed since last flush.
130
+ * Uses temp + rename so a torn write never corrupts the cache.
131
+ */
132
+ flush() {
133
+ if (!this.dirty) return;
134
+ const payload = {
135
+ schemaVersion: SCHEMA_VERSION,
136
+ treeSitterFingerprint: this.fingerprint,
137
+ failures: Object.fromEntries(this.failures)
138
+ };
139
+ const dir = (0, import_node_path4.dirname)(this.cacheFilePath);
140
+ const tmp = this.cacheFilePath + ".tmp";
141
+ try {
142
+ require("node:fs").mkdirSync(dir, { recursive: true });
143
+ (0, import_node_fs4.writeFileSync)(tmp, JSON.stringify(payload, null, 2) + "\n", "utf-8");
144
+ (0, import_node_fs4.renameSync)(tmp, this.cacheFilePath);
145
+ this.dirty = false;
146
+ } catch (e) {
147
+ process.stderr.write(`[lc-failure-cache] flush failed for ${this.cacheFilePath}: ${e.message}
148
+ `);
149
+ }
150
+ }
151
+ load() {
152
+ if (!(0, import_node_fs4.existsSync)(this.cacheFilePath)) return;
153
+ let parsed;
154
+ try {
155
+ parsed = JSON.parse((0, import_node_fs4.readFileSync)(this.cacheFilePath, "utf-8"));
156
+ } catch (e) {
157
+ process.stderr.write(`[lc-failure-cache] cache file unreadable, starting fresh: ${e.message}
158
+ `);
159
+ return;
160
+ }
161
+ if (!parsed || parsed.schemaVersion !== SCHEMA_VERSION) {
162
+ process.stderr.write(`[lc-failure-cache] schema version mismatch, discarding cache
163
+ `);
164
+ return;
165
+ }
166
+ if (parsed.treeSitterFingerprint !== this.fingerprint) {
167
+ process.stderr.write(`[lc-failure-cache] tree-sitter fingerprint changed, retrying all cached failures
168
+ `);
169
+ return;
170
+ }
171
+ for (const [path, entry] of Object.entries(parsed.failures)) {
172
+ this.failures.set(path, entry);
173
+ }
174
+ }
175
+ };
176
+ activeCache = null;
177
+ }
178
+ });
179
+
180
+ // src/server/graph/core/ts-extractor.ts
181
+ var ts_extractor_exports = {};
182
+ __export(ts_extractor_exports, {
183
+ ParseCascadeError: () => ParseCascadeError,
184
+ classifyFile: () => classifyFile,
185
+ classifyRouteAgainstMiddleware: () => classifyRouteAgainstMiddleware,
186
+ createQuery: () => createQuery,
187
+ extractAuthWrappersTS: () => extractAuthWrappersTS,
188
+ extractDbCallsTS: () => extractDbCallsTS,
189
+ extractDeep: () => extractDeep,
190
+ extractEffects: () => extractEffects,
191
+ extractMiddlewareAuthTS: () => extractMiddlewareAuthTS,
192
+ getTreeSitterFingerprint: () => getTreeSitterFingerprint,
193
+ initTreeSitter: () => initTreeSitter,
194
+ middlewarePatternToRegex: () => middlewarePatternToRegex,
195
+ parseCodeTS: () => parseCodeTS,
196
+ parseFileTS: () => parseFileTS,
197
+ parseSource: () => parseSource,
198
+ setExtractorConfig: () => setExtractorConfig
199
+ });
200
+ async function initTreeSitter() {
201
+ if (initialized) return;
202
+ if (initPromise) return initPromise;
203
+ initPromise = (async () => {
204
+ const TreeSitter = require("web-tree-sitter");
205
+ await TreeSitter.init();
206
+ const wasmPath = require.resolve("tree-sitter-typescript/tree-sitter-tsx.wasm");
207
+ tsxLanguage = await TreeSitter.Language.load(wasmPath);
208
+ TreeSitterCtor = TreeSitter;
209
+ parserInstance = new TreeSitterCtor();
210
+ parserInstance.setLanguage(tsxLanguage);
211
+ initialized = true;
212
+ })();
213
+ return initPromise;
214
+ }
215
+ function ensureInit() {
216
+ if (!initialized || !tsxLanguage || !parserInstance) {
217
+ throw new Error("Tree-sitter not initialized. Call initTreeSitter() first.");
218
+ }
219
+ }
220
+ function getTreeSitterFingerprint() {
221
+ const read = (pkg) => {
222
+ try {
223
+ return String(require(`${pkg}/package.json`).version ?? "unknown");
224
+ } catch {
225
+ return "unknown";
226
+ }
227
+ };
228
+ return `web-tree-sitter@${read("web-tree-sitter")}+tree-sitter-typescript@${read("tree-sitter-typescript")}`;
229
+ }
230
+ function getQuery(name) {
231
+ ensureInit();
232
+ const cached = queryCache.get(name);
233
+ if (cached) return cached;
234
+ const scmPath = (0, import_node_path5.join)(queriesDir, `${name}.scm`);
235
+ const scm = (0, import_node_fs5.readFileSync)(scmPath, "utf-8");
236
+ const query = tsxLanguage.query(scm);
237
+ queryCache.set(name, query);
238
+ return query;
239
+ }
240
+ function recreateParser() {
241
+ if (!TreeSitterCtor || !tsxLanguage) {
242
+ throw new Error("recreateParser() called before initTreeSitter()");
243
+ }
244
+ try {
245
+ parserInstance?.delete?.();
246
+ } catch {
247
+ }
248
+ parserInstance = new TreeSitterCtor();
249
+ parserInstance.setLanguage(tsxLanguage);
250
+ }
251
+ function parseSource(absPath) {
252
+ ensureInit();
253
+ let content;
254
+ try {
255
+ const stat = (0, import_node_fs5.statSync)(absPath);
256
+ if (stat.size > MAX_PARSEABLE_BYTES) {
257
+ process.stderr.write(`[lc-extractor] skipping ${absPath}: ${stat.size} bytes exceeds max ${MAX_PARSEABLE_BYTES}
258
+ `);
259
+ return null;
260
+ }
261
+ content = (0, import_node_fs5.readFileSync)(absPath, "utf-8");
262
+ } catch (e) {
263
+ process.stderr.write(`[lc-extractor] read failed for ${absPath}: ${e instanceof Error ? e.message : String(e)}
264
+ `);
265
+ return null;
266
+ }
267
+ const cache = getActiveFailureCache();
268
+ if (cache) {
269
+ const skipEntry = cache.shouldSkip(absPath, content);
270
+ if (skipEntry) {
271
+ cache.bumpAttempt(absPath);
272
+ process.stderr.write(`[lc-extractor] skipping ${absPath}: known-bad (${skipEntry.consecutiveAttempts} attempts, last error: ${skipEntry.lastError})
273
+ `);
274
+ return null;
275
+ }
276
+ }
277
+ try {
278
+ const tree = parserInstance.parse(content);
279
+ consecutiveParseFailures = 0;
280
+ cache?.clear(absPath);
281
+ return tree;
282
+ } catch (e) {
283
+ consecutiveParseFailures++;
284
+ const msg = e instanceof Error ? e.message : String(e);
285
+ process.stderr.write(
286
+ `[lc-extractor] parse failed for ${absPath}: ${msg} (consecutive failures: ${consecutiveParseFailures}/${MAX_CONSECUTIVE_PARSE_FAILURES})
287
+ `
288
+ );
289
+ cache?.recordFailure(absPath, content, msg);
290
+ try {
291
+ recreateParser();
292
+ } catch (recreateErr) {
293
+ const rmsg = recreateErr instanceof Error ? recreateErr.message : String(recreateErr);
294
+ process.stderr.write(`[lc-extractor] FATAL: recreateParser failed after ${absPath}: ${rmsg}
295
+ `);
296
+ consecutiveParseFailures = 0;
297
+ throw recreateErr;
298
+ }
299
+ if (consecutiveParseFailures >= MAX_CONSECUTIVE_PARSE_FAILURES) {
300
+ const tripCount = consecutiveParseFailures;
301
+ consecutiveParseFailures = 0;
302
+ throw new ParseCascadeError(absPath, tripCount);
303
+ }
304
+ return null;
305
+ }
306
+ }
307
+ function parseCodeTS(code) {
308
+ ensureInit();
309
+ return parserInstance.parse(code);
310
+ }
311
+ function createQuery(pattern) {
312
+ ensureInit();
313
+ return tsxLanguage.query(pattern);
314
+ }
315
+ function setExtractorConfig(config) {
316
+ extraDbIdentifiers = config.dbIdentifiers ?? [];
317
+ extraMutationMethods = config.mutationMethods ?? [];
318
+ }
319
+ function getMutationMethods() {
320
+ return /* @__PURE__ */ new Set([...PRISMA_MUTATION_METHODS_BUILTIN, ...extraMutationMethods]);
321
+ }
322
+ function getFallbackDbIdentifiers() {
323
+ return /* @__PURE__ */ new Set([...DB_IDENTIFIERS_FALLBACK, ...extraDbIdentifiers]);
324
+ }
325
+ function looksLikeUrl(s) {
326
+ return s.startsWith("/") || /^(https?:)?\/\//i.test(s);
327
+ }
328
+ function templateStartsWithSlash(text) {
329
+ const content = text.slice(1);
330
+ return content.startsWith("/");
331
+ }
332
+ function captureMap(match) {
333
+ const map = {};
334
+ for (const c of match.captures) {
335
+ map[c.name] = c.node.text;
336
+ }
337
+ return map;
338
+ }
339
+ function childrenOfType(node, type) {
340
+ return node.children.filter((n) => n.type === type);
341
+ }
342
+ function childOfType(node, type) {
343
+ return node.children.find((n) => n.type === type);
344
+ }
345
+ function emptyParsedFile(absPath) {
346
+ return {
347
+ name: absPath.split("/").pop() ?? absPath,
348
+ exports: [],
349
+ imports: [],
350
+ reExports: [],
351
+ jsxElements: /* @__PURE__ */ new Set(),
352
+ navigations: [],
353
+ fetchCalls: []
354
+ };
355
+ }
356
+ function parseFileTS(absPath) {
357
+ const tree = parseSource(absPath);
358
+ if (!tree) return emptyParsedFile(absPath);
359
+ const root = tree.rootNode;
360
+ const imports = [];
361
+ const importStatements = childrenOfType(root, "import_statement");
362
+ for (const stmt of importStatements) {
363
+ const sourceNode = childOfType(stmt, "string");
364
+ const frag = sourceNode ? childOfType(sourceNode, "string_fragment") : void 0;
365
+ if (!frag) continue;
366
+ const specifier = frag.text;
367
+ const hasTypeKeyword = stmt.children.some(
368
+ (n) => n.type === "type" && n.text === "type"
369
+ );
370
+ const clause = childOfType(stmt, "import_clause");
371
+ if (!clause) {
372
+ imports.push({ names: [], specifier, isTypeOnly: false, typeNames: /* @__PURE__ */ new Set() });
373
+ continue;
374
+ }
375
+ const names = [];
376
+ const typeNames = /* @__PURE__ */ new Set();
377
+ const defaultId = childOfType(clause, "identifier");
378
+ if (defaultId) names.push(defaultId.text);
379
+ const nsImport = childOfType(clause, "namespace_import");
380
+ if (nsImport) {
381
+ const nsId = childOfType(nsImport, "identifier");
382
+ if (nsId) names.push(nsId.text);
383
+ }
384
+ const namedImports = childOfType(clause, "named_imports");
385
+ if (namedImports) {
386
+ for (const spec of childrenOfType(namedImports, "import_specifier")) {
387
+ const identifiers = childrenOfType(spec, "identifier");
388
+ const importedName = identifiers.length > 1 ? identifiers[identifiers.length - 1].text : identifiers[0]?.text;
389
+ if (importedName) {
390
+ names.push(importedName);
391
+ const specIsType = spec.children.some(
392
+ (n) => n.type === "type" && n.text === "type"
393
+ );
394
+ if (specIsType) typeNames.add(importedName);
395
+ }
396
+ }
397
+ }
398
+ if (names.length > 0 || hasTypeKeyword) {
399
+ imports.push({ names, specifier, isTypeOnly: hasTypeKeyword, typeNames });
400
+ }
401
+ }
402
+ const importQuery = getQuery("imports");
403
+ const importMatches = importQuery.matches(root);
404
+ for (const m of importMatches) {
405
+ const caps = captureMap(m);
406
+ if (caps["import.dynamic"]) {
407
+ imports.push({
408
+ names: [],
409
+ specifier: caps["import.dynamic"],
410
+ isTypeOnly: false,
411
+ typeNames: /* @__PURE__ */ new Set()
412
+ });
413
+ }
414
+ }
415
+ const exportsSet = /* @__PURE__ */ new Set();
416
+ const exportsOrdered = [];
417
+ let defaultName = null;
418
+ let firstValueExport = null;
419
+ let firstTypeExport = null;
420
+ function addExport(name2, kind) {
421
+ if (!exportsSet.has(name2)) {
422
+ exportsSet.add(name2);
423
+ exportsOrdered.push(name2);
424
+ }
425
+ if (kind === "default") defaultName = name2;
426
+ else if (kind === "value" && !firstValueExport) firstValueExport = name2;
427
+ else if (kind === "type" && !firstTypeExport) firstTypeExport = name2;
428
+ }
429
+ const exportQuery = getQuery("exports");
430
+ const exportMatches = exportQuery.matches(root);
431
+ for (const m of exportMatches) {
432
+ const caps = captureMap(m);
433
+ if (caps["export.default.func"]) addExport(caps["export.default.func"], "default");
434
+ else if (caps["export.default.class"]) addExport(caps["export.default.class"], "default");
435
+ else if (caps["export.default.value"]) addExport(caps["export.default.value"], "default");
436
+ else if (caps["export.named.func"]) addExport(caps["export.named.func"], "value");
437
+ else if (caps["export.named.class"]) addExport(caps["export.named.class"], "value");
438
+ else if (caps["export.named.const"]) addExport(caps["export.named.const"], "value");
439
+ else if (caps["export.named.enum"]) addExport(caps["export.named.enum"], "value");
440
+ else if (caps["export.named.type"]) addExport(caps["export.named.type"], "type");
441
+ else if (caps["export.named.interface"]) addExport(caps["export.named.interface"], "type");
442
+ else if (caps["export.bare.name"]) addExport(caps["export.bare.name"], "value");
443
+ if (caps["reexport.name"]) addExport(caps["reexport.name"], "value");
444
+ }
445
+ for (const stmt of childrenOfType(root, "export_statement")) {
446
+ const hasDefault = stmt.children.some((n) => n.text === "default");
447
+ if (hasDefault && !defaultName) {
448
+ defaultName = "default";
449
+ }
450
+ }
451
+ const reExports = [];
452
+ for (const m of exportMatches) {
453
+ const caps = captureMap(m);
454
+ if (caps["reexport.name"] && caps["reexport.source"]) {
455
+ reExports.push({ name: caps["reexport.name"], from: caps["reexport.source"] });
456
+ }
457
+ if (caps["reexport.wildcard.source"]) {
458
+ reExports.push({ name: "*", from: caps["reexport.wildcard.source"], isWildcard: true });
459
+ }
460
+ }
461
+ const jsxElements = /* @__PURE__ */ new Set();
462
+ const jsxQuery = getQuery("jsx-elements");
463
+ const jsxCaptures = jsxQuery.captures(root);
464
+ for (const c of jsxCaptures) {
465
+ if (c.name === "jsx.tag") {
466
+ if (/^[A-Z]/.test(c.node.text)) {
467
+ jsxElements.add(c.node.text);
468
+ }
469
+ } else if (c.name === "jsx.member_tag") {
470
+ const rootName = c.node.text.split(".")[0];
471
+ if (rootName && /^[A-Z]/.test(rootName)) {
472
+ jsxElements.add(rootName);
473
+ }
474
+ }
475
+ }
476
+ const navigations = [];
477
+ const navQuery = getQuery("navigations");
478
+ const navMatches = navQuery.matches(root);
479
+ for (const m of navMatches) {
480
+ const caps = captureMap(m);
481
+ if (caps["nav.target.literal"] && caps["nav.method"]) {
482
+ navigations.push({
483
+ kind: caps["nav.method"] === "push" ? "router-push" : "router-replace",
484
+ target: caps["nav.target.literal"],
485
+ isTemplate: false
486
+ });
487
+ }
488
+ if (caps["nav.target.template"] && caps["nav.method.template"]) {
489
+ navigations.push({
490
+ kind: caps["nav.method.template"] === "push" ? "router-push" : "router-replace",
491
+ target: caps["nav.target.template"],
492
+ isTemplate: true
493
+ });
494
+ }
495
+ const linkLiteral = caps["nav.link.literal"] || caps["nav.link.literal.self"];
496
+ if (linkLiteral) {
497
+ navigations.push({ kind: "link-href", target: linkLiteral, isTemplate: false });
498
+ }
499
+ const linkTemplate = caps["nav.link.template"] || caps["nav.link.template.self"];
500
+ if (linkTemplate) {
501
+ navigations.push({ kind: "link-href", target: linkTemplate, isTemplate: true });
502
+ }
503
+ if (caps["nav.redirect.literal"]) {
504
+ navigations.push({ kind: "router-replace", target: caps["nav.redirect.literal"], isTemplate: false });
505
+ }
506
+ if (caps["nav.redirect.template"]) {
507
+ navigations.push({ kind: "router-replace", target: caps["nav.redirect.template"], isTemplate: true });
508
+ }
509
+ if (caps["nav.window.literal"]) {
510
+ navigations.push({ kind: "window-location", target: caps["nav.window.literal"], isTemplate: false });
511
+ }
512
+ if (caps["nav.window.assign.target"]) {
513
+ navigations.push({ kind: "window-location", target: caps["nav.window.assign.target"], isTemplate: false });
514
+ }
515
+ }
516
+ const fetchCalls = [];
517
+ const fetchQuery = getQuery("fetch-calls");
518
+ const fetchMatches = fetchQuery.matches(root);
519
+ for (const m of fetchMatches) {
520
+ const caps = captureMap(m);
521
+ if (caps["fetch.url.literal"] && looksLikeUrl(caps["fetch.url.literal"])) {
522
+ fetchCalls.push({ url: caps["fetch.url.literal"], isTemplate: false, kind: "fetch" });
523
+ }
524
+ if (caps["fetch.url.template"] && templateStartsWithSlash(caps["fetch.url.template"])) {
525
+ fetchCalls.push({ url: caps["fetch.url.template"], isTemplate: true, kind: "fetch" });
526
+ }
527
+ const clientUrl = caps["fetch.client.url.literal"] || caps["fetch.await.url.literal"];
528
+ const clientMethod = caps["fetch.method"] || caps["fetch.await.method"];
529
+ if (clientUrl && clientMethod && looksLikeUrl(clientUrl)) {
530
+ fetchCalls.push({
531
+ method: clientMethod.toUpperCase(),
532
+ url: clientUrl,
533
+ isTemplate: false,
534
+ kind: "client-method"
535
+ });
536
+ }
537
+ const clientUrlTpl = caps["fetch.client.url.template"] || caps["fetch.await.url.template"];
538
+ const clientMethodTpl = caps["fetch.method.template"] || caps["fetch.await.method.template"];
539
+ if (clientUrlTpl && clientMethodTpl && templateStartsWithSlash(clientUrlTpl)) {
540
+ fetchCalls.push({
541
+ method: clientMethodTpl.toUpperCase(),
542
+ url: clientUrlTpl,
543
+ isTemplate: true,
544
+ kind: "client-method"
545
+ });
546
+ }
547
+ }
548
+ const name = defaultName ?? firstValueExport ?? firstTypeExport ?? "";
549
+ return { name, exports: exportsOrdered, imports, reExports, jsxElements, navigations, fetchCalls };
550
+ }
551
+ function extractDbCallsTS(absPath) {
552
+ const tree = parseSource(absPath);
553
+ if (!tree) return [];
554
+ const root = tree.rootNode;
555
+ const dbQuery = getQuery("db-calls");
556
+ const matches = dbQuery.matches(root);
557
+ const dbIdentifiers = /* @__PURE__ */ new Set();
558
+ for (const stmt of childrenOfType(root, "import_statement")) {
559
+ const sourceNode = childOfType(stmt, "string");
560
+ const frag = sourceNode ? childOfType(sourceNode, "string_fragment") : void 0;
561
+ if (!frag) continue;
562
+ const spec = frag.text;
563
+ if (spec.includes("prisma") || spec.includes("/db") || spec === "@prisma/client") {
564
+ const clause = childOfType(stmt, "import_clause");
565
+ if (clause) {
566
+ const defaultId = childOfType(clause, "identifier");
567
+ if (defaultId) dbIdentifiers.add(defaultId.text);
568
+ const named = childOfType(clause, "named_imports");
569
+ if (named) {
570
+ for (const specNode of childrenOfType(named, "import_specifier")) {
571
+ const ids = childrenOfType(specNode, "identifier");
572
+ const importedName = ids[ids.length - 1];
573
+ if (importedName) dbIdentifiers.add(importedName.text);
574
+ }
575
+ }
576
+ }
577
+ }
578
+ }
579
+ if (dbIdentifiers.size === 0) {
580
+ for (const id of getFallbackDbIdentifiers()) dbIdentifiers.add(id);
581
+ } else {
582
+ for (const id of extraDbIdentifiers) dbIdentifiers.add(id);
583
+ }
584
+ const calls = [];
585
+ const seen = /* @__PURE__ */ new Set();
586
+ for (const m of matches) {
587
+ const caps = captureMap(m);
588
+ const sbTable = caps["sb.table"];
589
+ const sbMethod = caps["sb.method"];
590
+ if (sbTable && sbMethod) {
591
+ const key2 = `sql:${sbTable}.${sbMethod}`;
592
+ if (seen.has(key2)) continue;
593
+ seen.add(key2);
594
+ const isMutation = SUPABASE_MUTATION_METHODS_BUILTIN.has(sbMethod) || extraMutationMethods.includes(sbMethod);
595
+ calls.push({ model: sbTable, method: sbMethod, isMutation, kind: "sql" });
596
+ continue;
597
+ }
598
+ const identifier = caps["db.identifier"];
599
+ const model = caps["db.model"];
600
+ const method = caps["db.method"];
601
+ if (!identifier || !model || !method) continue;
602
+ if (!dbIdentifiers.has(identifier)) continue;
603
+ const key = `orm:${model}.${method}`;
604
+ if (seen.has(key)) continue;
605
+ seen.add(key);
606
+ calls.push({ model, method, isMutation: getMutationMethods().has(method), kind: "orm" });
607
+ }
608
+ return calls;
609
+ }
610
+ function classifyFile(absPath) {
611
+ const fileName = require("path").basename(absPath);
612
+ if (fileName.includes(".test.") || fileName.includes(".spec.") || fileName.includes("__test")) return "test";
613
+ if (fileName.includes(".stories.")) return "story";
614
+ if (fileName === "middleware.ts" || fileName === "middleware.tsx") return "middleware";
615
+ const tree = parseSource(absPath);
616
+ if (!tree) return "util";
617
+ const root = tree.rootNode;
618
+ const classifyQuery = getQuery("classify");
619
+ const captures = classifyQuery.captures(root);
620
+ const capNames = new Set(captures.map((c) => c.name));
621
+ if (capNames.has("http_export") || capNames.has("http_export_fn")) return "endpoint";
622
+ if (capNames.has("use_server_directive") && fileName !== "page.tsx" && fileName !== "layout.tsx") {
623
+ return "server-action";
624
+ }
625
+ if (fileName === "page.tsx" && capNames.has("has_jsx")) return "page";
626
+ if (fileName === "layout.tsx" && capNames.has("has_jsx")) return "layout";
627
+ if (capNames.has("has_create_context") || capNames.has("has_create_context_bare")) return "context";
628
+ if (capNames.has("has_jsx")) return "component";
629
+ if (capNames.has("hook_decl") || capNames.has("hook_const")) return "hook";
630
+ if (fileName.includes("config") || fileName.includes(".config.")) return "config";
631
+ return "lib";
632
+ }
633
+ function extractAuthWrappersTS(absPath) {
634
+ const tree = parseSource(absPath);
635
+ if (!tree) return /* @__PURE__ */ new Set();
636
+ const root = tree.rootNode;
637
+ const wrapperQuery = getQuery("wrappers");
638
+ const matches = wrapperQuery.matches(root);
639
+ const wrappers = /* @__PURE__ */ new Set();
640
+ for (const m of matches) {
641
+ const caps = captureMap(m);
642
+ if (caps["wrapper.fn_name"]) {
643
+ wrappers.add(caps["wrapper.fn_name"]);
644
+ }
645
+ }
646
+ const inlineHelpers = /* @__PURE__ */ new Set();
647
+ for (const stmt of childrenOfType(root, "import_statement")) {
648
+ const sourceNode = childOfType(stmt, "string");
649
+ const frag = sourceNode ? childOfType(sourceNode, "string_fragment") : void 0;
650
+ if (!frag) continue;
651
+ const provider = INLINE_AUTH_IMPORTS.find((p) => p.module.test(frag.text));
652
+ if (!provider) continue;
653
+ const clause = childOfType(stmt, "import_clause");
654
+ if (!clause) continue;
655
+ const named = childOfType(clause, "named_imports");
656
+ if (!named) continue;
657
+ for (const specNode of childrenOfType(named, "import_specifier")) {
658
+ const ids = childrenOfType(specNode, "identifier");
659
+ const importedName = ids[0]?.text;
660
+ const localName = ids[ids.length - 1]?.text;
661
+ if (importedName && provider.helpers.includes(importedName) && localName) {
662
+ inlineHelpers.add(localName);
663
+ }
664
+ }
665
+ }
666
+ if (inlineHelpers.size > 0) {
667
+ const text = root.text;
668
+ for (const name of inlineHelpers) {
669
+ const re = new RegExp(`\\b${name}\\s*\\(`);
670
+ if (re.test(text)) {
671
+ wrappers.add("inline");
672
+ break;
673
+ }
674
+ }
675
+ }
676
+ return wrappers;
677
+ }
678
+ function inferIntentFromName(name) {
679
+ if (TRUST_AS_PROTECT_KEYS.has(name)) {
680
+ return { intent: "protect", hint: `Next.js config.${name} \u2014 paths matched run through middleware` };
681
+ }
682
+ for (const re of EXEMPT_NAME_PATTERNS) {
683
+ if (re.test(name)) return { intent: "exempt", hint: `name "${name}" matches /${re.source}/` };
684
+ }
685
+ for (const re of PROTECT_NAME_PATTERNS) {
686
+ if (re.test(name)) return { intent: "protect", hint: `name "${name}" matches /${re.source}/` };
687
+ }
688
+ return { intent: "ambiguous", hint: `name "${name}" has no exempt/protect signal` };
689
+ }
690
+ function looksLikeRoutePattern(s) {
691
+ return s.startsWith("/") && !s.startsWith("//");
692
+ }
693
+ function collectStringsFromArray(arrNode) {
694
+ const out = [];
695
+ for (const child of arrNode.children) {
696
+ if (child.type !== "string") continue;
697
+ const frag = childOfType(child, "string_fragment");
698
+ if (frag) out.push(frag.text);
699
+ }
700
+ return out;
701
+ }
702
+ function findArrayInValue(valueNode) {
703
+ if (!valueNode) return null;
704
+ if (valueNode.type === "array") return valueNode;
705
+ if (valueNode.type === "call_expression") {
706
+ const args = childOfType(valueNode, "arguments");
707
+ if (!args) return null;
708
+ for (const c of args.children) {
709
+ if (c.type === "array") return c;
710
+ }
711
+ }
712
+ return null;
713
+ }
714
+ function extractMatcherFromDeclarator(decl) {
715
+ const nameNode = childOfType(decl, "identifier");
716
+ if (!nameNode) return null;
717
+ let valueNode;
718
+ for (const c of decl.children) {
719
+ if (c.type === "identifier" || c.type === "=" || c.type === "type_annotation") continue;
720
+ valueNode = c;
721
+ }
722
+ const arr = findArrayInValue(valueNode);
723
+ if (!arr) return null;
724
+ const strings = collectStringsFromArray(arr);
725
+ const routes = strings.filter(looksLikeRoutePattern);
726
+ if (routes.length === 0) return null;
727
+ const { intent, hint } = inferIntentFromName(nameNode.text);
728
+ return { name: nameNode.text, patterns: routes, intent, hint };
729
+ }
730
+ function extractMatchersFromObject(objNode) {
731
+ const out = [];
732
+ for (const pair of childrenOfType(objNode, "pair")) {
733
+ const key = childOfType(pair, "property_identifier");
734
+ if (!key) continue;
735
+ const arr = pair.children.find((c) => c.type === "array");
736
+ if (!arr) continue;
737
+ const routes = collectStringsFromArray(arr).filter(looksLikeRoutePattern);
738
+ if (routes.length === 0) continue;
739
+ const { intent, hint } = inferIntentFromName(key.text);
740
+ out.push({ name: key.text, patterns: routes, intent, hint });
741
+ }
742
+ return out;
743
+ }
744
+ function detectFallthroughProtect(root) {
745
+ const text = root.text;
746
+ const signals = [
747
+ /\bauth\.protect\s*\(/,
748
+ /\bauth\(\)\.protect\s*\(/,
749
+ /\bredirect\s*\(\s*['"`]\/(sign-?in|log-?in|auth)/i,
750
+ /\bNextResponse\.redirect\s*\(/,
751
+ /\bthrow\s+new\s+\w*Unauthorized/i
752
+ ];
753
+ return signals.some((re) => re.test(text));
754
+ }
755
+ function extractMiddlewareAuthTS(absPath) {
756
+ if (!require("node:fs").existsSync(absPath)) return null;
757
+ const tree = parseSource(absPath);
758
+ if (!tree) return null;
759
+ const root = tree.rootNode;
760
+ const matchers = [];
761
+ for (const stmt of root.children) {
762
+ if (stmt.type !== "lexical_declaration" && stmt.type !== "variable_declaration") continue;
763
+ for (const decl of childrenOfType(stmt, "variable_declarator")) {
764
+ const m = extractMatcherFromDeclarator(decl);
765
+ if (m) matchers.push(m);
766
+ let valueNode;
767
+ for (const c of decl.children) {
768
+ if (c.type === "identifier" || c.type === "=" || c.type === "type_annotation") continue;
769
+ valueNode = c;
770
+ }
771
+ if (valueNode?.type === "object") {
772
+ for (const inner of extractMatchersFromObject(valueNode)) matchers.push(inner);
773
+ }
774
+ }
775
+ }
776
+ for (const stmt of root.children) {
777
+ if (stmt.type !== "export_statement") continue;
778
+ const decl = childOfType(stmt, "lexical_declaration") ?? childOfType(stmt, "variable_declaration");
779
+ if (!decl) continue;
780
+ for (const d of childrenOfType(decl, "variable_declarator")) {
781
+ let valueNode;
782
+ for (const c of d.children) {
783
+ if (c.type === "identifier" || c.type === "=" || c.type === "type_annotation") continue;
784
+ valueNode = c;
785
+ }
786
+ if (valueNode?.type === "object") {
787
+ for (const inner of extractMatchersFromObject(valueNode)) matchers.push(inner);
788
+ }
789
+ const m = extractMatcherFromDeclarator(d);
790
+ if (m && !matchers.some((x) => x.name === m.name && x.patterns.join() === m.patterns.join())) {
791
+ matchers.push(m);
792
+ }
793
+ }
794
+ }
795
+ return {
796
+ file: absPath,
797
+ matchers,
798
+ hasFallthroughProtect: detectFallthroughProtect(root)
799
+ };
800
+ }
801
+ function middlewarePatternToRegex(pattern) {
802
+ const negLookaheadMatch = /^(\/?)\(\(\?\!([^)]+)\)(\.\*|\.\+)\)$/.exec(pattern);
803
+ if (negLookaheadMatch) {
804
+ const [, lead, altsRaw, body] = negLookaheadMatch;
805
+ const escapedAlts = altsRaw.split("|").map((alt) => alt.trim().replace(/[.+*?^${}()|[\]\\]/g, "\\$&")).join("|");
806
+ try {
807
+ return new RegExp(`^${lead}(?!${escapedAlts})${body}$`);
808
+ } catch {
809
+ return null;
810
+ }
811
+ }
812
+ if (/\(\?\!/.test(pattern)) return null;
813
+ if (pattern.startsWith("(")) return null;
814
+ let src = "^";
815
+ let i = 0;
816
+ while (i < pattern.length) {
817
+ const ch = pattern[i];
818
+ if (ch === "(" && pattern.slice(i, i + 4) === "(.*)") {
819
+ src += ".*";
820
+ i += 4;
821
+ continue;
822
+ }
823
+ if (ch === ":") {
824
+ i++;
825
+ while (i < pattern.length && /[a-zA-Z0-9_]/.test(pattern[i])) i++;
826
+ src += "[^/]+";
827
+ continue;
828
+ }
829
+ if (ch === "*") {
830
+ i++;
831
+ while (i < pattern.length && /[a-zA-Z0-9_]/.test(pattern[i])) i++;
832
+ if (pattern[i] === "?") {
833
+ i++;
834
+ src += ".*";
835
+ continue;
836
+ }
837
+ src += ".+";
838
+ continue;
839
+ }
840
+ if (/[.\\+?^${}()|[\]]/.test(ch)) {
841
+ src += "\\" + ch;
842
+ i++;
843
+ continue;
844
+ }
845
+ src += ch;
846
+ i++;
847
+ }
848
+ src += "$";
849
+ try {
850
+ return new RegExp(src);
851
+ } catch {
852
+ return null;
853
+ }
854
+ }
855
+ function classifyRouteAgainstMiddleware(routePath, info) {
856
+ for (const m of info.matchers) {
857
+ if (m.intent === "ambiguous") continue;
858
+ for (const pat of m.patterns) {
859
+ const re = middlewarePatternToRegex(pat);
860
+ if (!re) continue;
861
+ if (re.test(routePath)) return { intent: m.intent, matcher: m.name, pattern: pat };
862
+ }
863
+ }
864
+ return null;
865
+ }
866
+ function trunc(s, max = 120) {
867
+ return s.length > max ? s.slice(0, max) + "..." : s;
868
+ }
869
+ function dedup(arr) {
870
+ return Array.from(new Set(arr));
871
+ }
872
+ function firstStringArg(callExpr) {
873
+ const args = callExpr.childForFieldName("arguments");
874
+ if (!args) return null;
875
+ for (const c of args.namedChildren) {
876
+ if (c.type === "string") {
877
+ const frag = c.namedChildren.find((cc) => cc.type === "string_fragment");
878
+ return frag ? frag.text : c.text.replace(/^['"`]|['"`]$/g, "");
879
+ }
880
+ if (c.type === "template_string") {
881
+ return c.text.replace(/^`|`$/g, "");
882
+ }
883
+ }
884
+ return null;
885
+ }
886
+ function isFunctionLike(node) {
887
+ return node.type === "arrow_function" || node.type === "function_expression" || node.type === "function_declaration" || node.type === "method_definition" || node.type === "generator_function" || node.type === "generator_function_declaration";
888
+ }
889
+ function stringLiteralValue(n) {
890
+ if (n.type !== "string") return null;
891
+ const frag = n.namedChildren.find((c) => c.type === "string_fragment");
892
+ if (frag) return frag.text;
893
+ return n.text.replace(/^['"`]|['"`]$/g, "");
894
+ }
895
+ function nthStringArg(callExpr, idx) {
896
+ const args = callExpr.childForFieldName("arguments");
897
+ if (!args) return null;
898
+ const child = args.namedChildren[idx];
899
+ return child ? stringLiteralValue(child) : null;
900
+ }
901
+ function objectArgIdValues(callExpr) {
902
+ const args = callExpr.childForFieldName("arguments");
903
+ if (!args) return [];
904
+ const out = [];
905
+ for (const arg of args.namedChildren) {
906
+ if (arg.type !== "object") continue;
907
+ for (const pair of arg.namedChildren) {
908
+ if (pair.type !== "pair") continue;
909
+ const key = pair.childForFieldName("key");
910
+ const val = pair.childForFieldName("value");
911
+ if (!key || !val) continue;
912
+ const keyText = key.type === "property_identifier" ? key.text : stringLiteralValue(key) ?? key.text;
913
+ if (keyText !== "id") continue;
914
+ const strVal = stringLiteralValue(val);
915
+ if (strVal) out.push(strVal);
916
+ }
917
+ }
918
+ return out;
919
+ }
920
+ function extractEffects(node, moduleOnly = false) {
921
+ const calls = [];
922
+ const dom_writes = [];
923
+ const subscribes = [];
924
+ const timers = [];
925
+ const persists = [];
926
+ const fetches = [];
927
+ const globals = [];
928
+ const dom_ids = [];
929
+ const window_events = [];
930
+ const storage_keys = [];
931
+ const fetch_urls = [];
932
+ function visit(n, depth) {
933
+ if (moduleOnly && depth > 0 && isFunctionLike(n)) return;
934
+ if (n.type === "call_expression") {
935
+ const fn = n.childForFieldName("function");
936
+ if (fn) {
937
+ const fnText = fn.text;
938
+ calls.push(fnText);
939
+ for (const id of objectArgIdValues(n)) dom_ids.push(id);
940
+ if (fn.type === "identifier") {
941
+ if (TIMER_FNS.has(fnText)) timers.push(fnText);
942
+ if (fnText === "fetch") {
943
+ const url = firstStringArg(n);
944
+ fetches.push(url ? `fetch("${url}")` : "fetch(...)");
945
+ if (url) fetch_urls.push(url);
946
+ }
947
+ } else if (fn.type === "member_expression") {
948
+ const obj = fn.childForFieldName("object");
949
+ const prop = fn.childForFieldName("property");
950
+ const objText = obj?.text ?? "";
951
+ const propText = prop?.text ?? "";
952
+ if (STORAGE_OBJECTS.has(objText)) {
953
+ const key = firstStringArg(n);
954
+ persists.push(key ? `${objText}.${propText}("${key}")` : `${objText}.${propText}(\u2026)`);
955
+ if (key) storage_keys.push(key);
956
+ } else if (propText === "addEventListener") {
957
+ const event = firstStringArg(n);
958
+ subscribes.push(event ? `${objText}.addEventListener("${event}")` : `${objText}.addEventListener(\u2026)`);
959
+ if (event && (objText === "window" || objText === "document")) {
960
+ window_events.push(`${objText}:${event}`);
961
+ }
962
+ } else if (propText === "removeEventListener") {
963
+ const event = firstStringArg(n);
964
+ subscribes.push(event ? `${objText}.removeEventListener("${event}")` : `${objText}.removeEventListener(\u2026)`);
965
+ } else if (propText === "subscribe" || propText === "on") {
966
+ subscribes.push(`${objText}.${propText}(\u2026)`);
967
+ } else if (DOM_METHOD_NAMES.has(propText)) {
968
+ dom_writes.push(`${objText}.${propText}(\u2026)`);
969
+ if (propText === "setAttribute" && nthStringArg(n, 0) === "id") {
970
+ const idVal = nthStringArg(n, 1);
971
+ if (idVal) dom_ids.push(idVal);
972
+ }
973
+ } else if (objText.endsWith(".classList") && CLASSLIST_METHODS.has(propText)) {
974
+ dom_writes.push(`${objText}.${propText}(\u2026)`);
975
+ } else if (objText === "history" && HISTORY_METHODS.has(propText)) {
976
+ globals.push(`history.${propText}(\u2026)`);
977
+ } else if (objText === "location" && LOCATION_METHODS.has(propText)) {
978
+ globals.push(`location.${propText}(\u2026)`);
979
+ } else if (objText === "window" && propText === "matchMedia") {
980
+ globals.push("window.matchMedia(\u2026)");
981
+ }
982
+ }
983
+ }
984
+ } else if (n.type === "assignment_expression") {
985
+ const lhs = n.children.find((c) => c.type !== "=");
986
+ const rhs = n.childForFieldName("right");
987
+ if (lhs?.type === "member_expression") {
988
+ const prop = lhs.childForFieldName("property");
989
+ const propText = prop?.text ?? "";
990
+ const lhsText = lhs.text;
991
+ if (lhsText.startsWith("document.cookie")) {
992
+ persists.push("document.cookie =");
993
+ } else if (lhsText.startsWith("document.title") || lhsText.startsWith("location.")) {
994
+ globals.push(`${lhsText} =`);
995
+ } else if (ASSIGN_DOM_PROPS.has(propText) || lhsText.includes(".style.") || lhsText.includes(".dataset.")) {
996
+ dom_writes.push(`${lhsText} =`);
997
+ if (propText === "id" && rhs) {
998
+ const idVal = stringLiteralValue(rhs);
999
+ if (idVal) dom_ids.push(idVal);
1000
+ }
1001
+ }
1002
+ }
1003
+ }
1004
+ for (const child of n.children) visit(child, depth + 1);
1005
+ }
1006
+ visit(node, 0);
1007
+ const out = {};
1008
+ if (calls.length) out.calls = dedup(calls);
1009
+ if (dom_writes.length) out.dom_writes = dedup(dom_writes);
1010
+ if (subscribes.length) out.subscribes = dedup(subscribes);
1011
+ if (timers.length) out.timers = dedup(timers);
1012
+ if (persists.length) out.persists = dedup(persists);
1013
+ if (fetches.length) out.fetches = dedup(fetches);
1014
+ if (globals.length) out.globals = dedup(globals);
1015
+ if (dom_ids.length) out.dom_ids = dedup(dom_ids);
1016
+ if (window_events.length) out.window_events = dedup(window_events);
1017
+ if (storage_keys.length) out.storage_keys = dedup(storage_keys);
1018
+ if (fetch_urls.length) out.fetch_urls = dedup(fetch_urls);
1019
+ return out;
1020
+ }
1021
+ function extractDeep(absPath) {
1022
+ const tree = parseSource(absPath);
1023
+ if (!tree) {
1024
+ return { elements: [], stateVars: [], conditions: [], variables: [], responses: [], params: [] };
1025
+ }
1026
+ const root = tree.rootNode;
1027
+ const elements = [];
1028
+ const elQuery = getQuery("deep/jsx-semantic");
1029
+ const elMatches = elQuery.matches(root);
1030
+ const elementMap = /* @__PURE__ */ new Map();
1031
+ for (const m of elMatches) {
1032
+ const tag = m.captures.find((c) => c.name === "el.tag")?.node;
1033
+ if (!tag || !/^[A-Z]/.test(tag.text)) continue;
1034
+ const elNode = m.captures.find((c) => c.name === "el.self" || c.name === "el.open")?.node;
1035
+ const key = elNode ? `${elNode.startPosition.row}:${elNode.startPosition.column}` : `${tag.startPosition.row}:${tag.startPosition.column}`;
1036
+ if (!elementMap.has(key)) {
1037
+ elementMap.set(key, { tag: tag.text, props: {}, nodeKey: key });
1038
+ }
1039
+ const entry = elementMap.get(key);
1040
+ const propName = m.captures.find((c) => c.name === "el.prop.name")?.node.text;
1041
+ const propVal = m.captures.find((c) => c.name === "el.prop.value.str")?.node.text;
1042
+ const propExpr = m.captures.find((c) => c.name === "el.prop.value.expr")?.node.text;
1043
+ if (propName) {
1044
+ entry.props[propName] = propVal ?? (propExpr ? trunc(propExpr, 60) : "true");
1045
+ }
1046
+ }
1047
+ for (const m of elMatches) {
1048
+ const textTag = m.captures.find((c) => c.name === "el.text.tag")?.node;
1049
+ const textContent = m.captures.find((c) => c.name === "el.text.content")?.node;
1050
+ if (textTag && textContent && /^[A-Z]/.test(textTag.text)) {
1051
+ const key = `${textTag.startPosition.row}:${textTag.startPosition.column}`;
1052
+ if (!elementMap.has(key)) {
1053
+ elementMap.set(key, { tag: textTag.text, props: {}, nodeKey: key });
1054
+ }
1055
+ const entry = elementMap.get(key);
1056
+ const text = textContent.text.trim();
1057
+ if (text) entry.props["_text"] = text;
1058
+ }
1059
+ }
1060
+ for (const entry of elementMap.values()) {
1061
+ const el = { tag: entry.tag, props: entry.props };
1062
+ const hasExpr = Object.values(entry.props).some((v) => v.includes("{") || v.includes("("));
1063
+ if (hasExpr) el.dynamic = true;
1064
+ if (entry.props["_text"]) {
1065
+ el.text = entry.props["_text"];
1066
+ delete el.props["_text"];
1067
+ }
1068
+ elements.push(el);
1069
+ }
1070
+ const stateVars = [];
1071
+ const hookQuery = getQuery("deep/state-hooks");
1072
+ const hookMatches = hookQuery.matches(root);
1073
+ for (const m of hookMatches) {
1074
+ const caps = captureMap(m);
1075
+ if (caps["hook.var"] && caps["hook.setter"] && caps["hook.fn"]) {
1076
+ stateVars.push({
1077
+ name: caps["hook.var"],
1078
+ setter: caps["hook.setter"],
1079
+ hook: caps["hook.fn"],
1080
+ init: trunc(caps["hook.init"] ?? "", 80)
1081
+ });
1082
+ }
1083
+ if (caps["reducer.var"] && caps["reducer.dispatch"]) {
1084
+ stateVars.push({
1085
+ name: caps["reducer.var"],
1086
+ setter: caps["reducer.dispatch"],
1087
+ hook: "useReducer",
1088
+ init: trunc(caps["reducer.init"] ?? "", 80)
1089
+ });
1090
+ }
1091
+ }
1092
+ const conditions = [];
1093
+ const condQuery = getQuery("deep/conditions");
1094
+ const condMatches = condQuery.matches(root);
1095
+ for (const m of condMatches) {
1096
+ const caps = captureMap(m);
1097
+ if (caps["cond.test"]) {
1098
+ conditions.push({
1099
+ kind: "if",
1100
+ test: trunc(caps["cond.test"], 100),
1101
+ consequence: caps["cond.consequence"] ? trunc(caps["cond.consequence"], 80) : void 0
1102
+ });
1103
+ }
1104
+ if (caps["ternary.test"]) {
1105
+ conditions.push({
1106
+ kind: "ternary",
1107
+ test: trunc(caps["ternary.test"], 100)
1108
+ });
1109
+ }
1110
+ }
1111
+ const variables = [];
1112
+ const varQuery = getQuery("deep/variables");
1113
+ const varMatches = varQuery.matches(root);
1114
+ for (const m of varMatches) {
1115
+ const caps = captureMap(m);
1116
+ const declNode = m.captures.find((c) => c.name === "var.decl" || c.name === "var.destructured" || c.name === "var.array")?.node;
1117
+ const kind = declNode?.children.find((n) => n.type === "const" || n.type === "let" || n.type === "var")?.type ?? "const";
1118
+ const initNode = m.captures.find((c) => c.name === "var.init")?.node;
1119
+ if (caps["var.name"] && caps["var.init"]) {
1120
+ const variable = {
1121
+ name: caps["var.name"],
1122
+ kind,
1123
+ init: trunc(caps["var.init"], 100)
1124
+ };
1125
+ if (initNode && isFunctionLike(initNode)) {
1126
+ const eff = extractEffects(initNode);
1127
+ if (Object.keys(eff).length > 0) variable.effects = eff;
1128
+ }
1129
+ variables.push(variable);
1130
+ }
1131
+ if (caps["var.destructured.obj"]) {
1132
+ variables.push({
1133
+ name: trunc(caps["var.destructured.obj"], 60),
1134
+ kind,
1135
+ init: trunc(caps["var.destructured.init"], 100)
1136
+ });
1137
+ }
1138
+ if (caps["var.array.pattern"]) {
1139
+ variables.push({
1140
+ name: trunc(caps["var.array.pattern"], 60),
1141
+ kind,
1142
+ init: trunc(caps["var.array.init"], 100)
1143
+ });
1144
+ }
1145
+ }
1146
+ const responses = [];
1147
+ const respQuery = getQuery("deep/responses");
1148
+ const respMatches = respQuery.matches(root);
1149
+ const explicitBodies = /* @__PURE__ */ new Set();
1150
+ for (const m of respMatches) {
1151
+ const caps = captureMap(m);
1152
+ if (caps["resp.status"] && caps["resp.body"]) {
1153
+ explicitBodies.add(trunc(caps["resp.body"], 80));
1154
+ responses.push({
1155
+ status: caps["resp.status"],
1156
+ body: trunc(caps["resp.body"], 80)
1157
+ });
1158
+ }
1159
+ }
1160
+ for (const m of respMatches) {
1161
+ const caps = captureMap(m);
1162
+ if (caps["resp.body.default"] && !caps["resp.status"]) {
1163
+ const bodyText = trunc(caps["resp.body.default"], 80);
1164
+ if (!explicitBodies.has(bodyText)) {
1165
+ responses.push({ status: "200", body: bodyText });
1166
+ }
1167
+ }
1168
+ }
1169
+ const params = [];
1170
+ const paramQuery = getQuery("deep/request-params");
1171
+ const paramMatches = paramQuery.matches(root);
1172
+ for (const m of paramMatches) {
1173
+ const caps = captureMap(m);
1174
+ if (caps["param.name"]) {
1175
+ params.push({ name: caps["param.name"], source: "body-field" });
1176
+ }
1177
+ if (caps["param.body"]) {
1178
+ params.push({ name: caps["param.body"], source: "body" });
1179
+ }
1180
+ }
1181
+ const fileEffects = extractEffects(
1182
+ root,
1183
+ /* moduleOnly */
1184
+ false
1185
+ );
1186
+ const hasEffects = Object.keys(fileEffects).length > 0;
1187
+ return {
1188
+ elements,
1189
+ stateVars,
1190
+ conditions,
1191
+ variables,
1192
+ responses,
1193
+ params,
1194
+ ...hasEffects ? { effects: fileEffects } : {}
1195
+ };
1196
+ }
1197
+ var import_node_fs5, import_node_path5, tsxLanguage, parserInstance, TreeSitterCtor, initPromise, initialized, queriesDir, queryCache, MAX_PARSEABLE_BYTES, MAX_CONSECUTIVE_PARSE_FAILURES, consecutiveParseFailures, ParseCascadeError, PRISMA_MUTATION_METHODS_BUILTIN, SUPABASE_MUTATION_METHODS_BUILTIN, DB_IDENTIFIERS_FALLBACK, extraDbIdentifiers, extraMutationMethods, INLINE_AUTH_IMPORTS, EXEMPT_NAME_PATTERNS, PROTECT_NAME_PATTERNS, TRUST_AS_PROTECT_KEYS, TIMER_FNS, DOM_METHOD_NAMES, CLASSLIST_METHODS, STORAGE_OBJECTS, HISTORY_METHODS, LOCATION_METHODS, ASSIGN_DOM_PROPS;
1198
+ var init_ts_extractor = __esm({
1199
+ "src/server/graph/core/ts-extractor.ts"() {
1200
+ "use strict";
1201
+ import_node_fs5 = require("node:fs");
1202
+ import_node_path5 = require("node:path");
1203
+ init_parse_failure_cache();
1204
+ initialized = false;
1205
+ queriesDir = (() => {
1206
+ const srcPath = (0, import_node_path5.join)((0, import_node_path5.dirname)(__filename), "..", "queries");
1207
+ if (require("fs").existsSync(srcPath)) return srcPath;
1208
+ return (0, import_node_path5.join)((0, import_node_path5.dirname)(__filename), "graph", "queries");
1209
+ })();
1210
+ queryCache = /* @__PURE__ */ new Map();
1211
+ MAX_PARSEABLE_BYTES = 2 * 1024 * 1024;
1212
+ MAX_CONSECUTIVE_PARSE_FAILURES = 10;
1213
+ consecutiveParseFailures = 0;
1214
+ ParseCascadeError = class extends Error {
1215
+ constructor(lastPath, failureCount) {
1216
+ super(
1217
+ `tree-sitter parse cascade: ${failureCount} consecutive WASM failures (last file: ${lastPath}). The shared Parser's WASM heap is likely corrupted; aborting regen so the graph isn't silently degraded. Restart the chart server to reinitialize, then re-run generate_graph.`
1218
+ );
1219
+ this.lastPath = lastPath;
1220
+ this.failureCount = failureCount;
1221
+ this.name = "ParseCascadeError";
1222
+ }
1223
+ };
1224
+ PRISMA_MUTATION_METHODS_BUILTIN = [
1225
+ "create",
1226
+ "createMany",
1227
+ "createManyAndReturn",
1228
+ "update",
1229
+ "updateMany",
1230
+ "updateManyAndReturn",
1231
+ "upsert",
1232
+ "delete",
1233
+ "deleteMany"
1234
+ ];
1235
+ SUPABASE_MUTATION_METHODS_BUILTIN = /* @__PURE__ */ new Set([
1236
+ "insert",
1237
+ "update",
1238
+ "delete",
1239
+ "upsert"
1240
+ ]);
1241
+ DB_IDENTIFIERS_FALLBACK = ["db", "prisma"];
1242
+ extraDbIdentifiers = [];
1243
+ extraMutationMethods = [];
1244
+ INLINE_AUTH_IMPORTS = [
1245
+ { module: /^@clerk\/nextjs(\/server)?$/, helpers: ["auth", "currentUser"] },
1246
+ { module: /^next-auth(\/.+)?$/, helpers: ["auth", "getServerSession"] },
1247
+ { module: /^@auth\//, helpers: ["auth"] },
1248
+ { module: /^@supabase\/auth-helpers/, helpers: ["createServerClient"] }
1249
+ ];
1250
+ EXEMPT_NAME_PATTERNS = [
1251
+ /^is_?public/i,
1252
+ /^public_?routes?/i,
1253
+ /^public_?paths?/i,
1254
+ /^whitelist/i,
1255
+ /^allowlist/i,
1256
+ /^unauthenticated/i,
1257
+ /^anonymous/i,
1258
+ /^guest/i,
1259
+ /^skip_?auth/i,
1260
+ /^bypass/i
1261
+ ];
1262
+ PROTECT_NAME_PATTERNS = [
1263
+ /^is_?protected/i,
1264
+ /^protected_?routes?/i,
1265
+ /^protected_?paths?/i,
1266
+ /^require_?auth/i,
1267
+ /^auth_?required/i,
1268
+ /^private_?routes?/i,
1269
+ /^is_?admin/i,
1270
+ /^admin_?routes?/i,
1271
+ /^secured/i
1272
+ ];
1273
+ TRUST_AS_PROTECT_KEYS = /* @__PURE__ */ new Set(["matcher", "matchers"]);
1274
+ TIMER_FNS = /* @__PURE__ */ new Set([
1275
+ "setInterval",
1276
+ "setTimeout",
1277
+ "clearInterval",
1278
+ "clearTimeout",
1279
+ "requestAnimationFrame",
1280
+ "cancelAnimationFrame",
1281
+ "queueMicrotask"
1282
+ ]);
1283
+ DOM_METHOD_NAMES = /* @__PURE__ */ new Set([
1284
+ "setAttribute",
1285
+ "removeAttribute",
1286
+ "appendChild",
1287
+ "removeChild",
1288
+ "replaceChildren",
1289
+ "replaceWith",
1290
+ "insertBefore",
1291
+ "append",
1292
+ "prepend",
1293
+ "remove",
1294
+ "before",
1295
+ "after",
1296
+ "insertAdjacentHTML",
1297
+ "insertAdjacentElement"
1298
+ ]);
1299
+ CLASSLIST_METHODS = /* @__PURE__ */ new Set(["add", "remove", "toggle", "replace"]);
1300
+ STORAGE_OBJECTS = /* @__PURE__ */ new Set(["localStorage", "sessionStorage"]);
1301
+ HISTORY_METHODS = /* @__PURE__ */ new Set(["pushState", "replaceState", "back", "forward", "go"]);
1302
+ LOCATION_METHODS = /* @__PURE__ */ new Set(["assign", "replace", "reload"]);
1303
+ ASSIGN_DOM_PROPS = /* @__PURE__ */ new Set([
1304
+ "textContent",
1305
+ "innerHTML",
1306
+ "innerText",
1307
+ "value",
1308
+ "src",
1309
+ "href",
1310
+ "className",
1311
+ "id",
1312
+ "checked",
1313
+ "selected",
1314
+ "disabled"
1315
+ ]);
1316
+ }
1317
+ });
1318
+
1319
+ // src/server/parse-worker-entry.ts
1320
+ var parse_worker_entry_exports = {};
1321
+ module.exports = __toCommonJS(parse_worker_entry_exports);
1322
+ var import_node_worker_threads = require("node:worker_threads");
1323
+
1324
+ // src/server/graph/core/config.ts
1325
+ var import_node_fs = require("node:fs");
1326
+ var import_node_path = require("node:path");
1327
+ init_launch_kit_paths();
1328
+ function loadConfig(rootDir) {
1329
+ const configPath = (0, import_node_path.join)(rootDir, LAUNCHCHART_CONFIG_FILE);
1330
+ if (!(0, import_node_fs.existsSync)(configPath)) return {};
1331
+ try {
1332
+ return JSON.parse((0, import_node_fs.readFileSync)(configPath, "utf-8"));
1333
+ } catch {
1334
+ return {};
1335
+ }
1336
+ }
1337
+
1338
+ // src/server/graph/core/graph-builder.ts
1339
+ var import_node_fs13 = require("node:fs");
1340
+ var import_node_path14 = require("node:path");
1341
+ init_launch_kit_paths();
1342
+
1343
+ // src/server/graph/core/parser-registry.ts
1344
+ var import_node_path13 = require("node:path");
1345
+
1346
+ // src/server/graph/parsers/ts/typescript-project.ts
1347
+ var import_node_fs6 = require("node:fs");
1348
+ var import_node_path6 = require("node:path");
1349
+
1350
+ // src/server/graph/core/resolve-paths.ts
1351
+ var import_node_fs3 = require("node:fs");
1352
+ var import_node_path3 = require("node:path");
1353
+
1354
+ // src/server/graph/core/walk.ts
1355
+ var import_node_fs2 = require("node:fs");
1356
+ var import_node_path2 = require("node:path");
1357
+ var DEFAULT_IGNORE_DIRS = /* @__PURE__ */ new Set([
1358
+ "node_modules",
1359
+ "dist",
1360
+ "build",
1361
+ "out",
1362
+ "coverage"
1363
+ ]);
1364
+ function walk(dir, exts) {
1365
+ const results = [];
1366
+ if (!(0, import_node_fs2.existsSync)(dir)) return results;
1367
+ for (const entry of (0, import_node_fs2.readdirSync)(dir, { withFileTypes: true })) {
1368
+ const full = (0, import_node_path2.join)(dir, entry.name);
1369
+ if (entry.isDirectory()) {
1370
+ results.push(...walk(full, exts));
1371
+ } else if (exts.includes((0, import_node_path2.extname)(entry.name))) {
1372
+ results.push(full);
1373
+ }
1374
+ }
1375
+ return results;
1376
+ }
1377
+ function walkWithIgnore(dir, exts, opts = {}) {
1378
+ const results = [];
1379
+ if (!(0, import_node_fs2.existsSync)(dir)) return results;
1380
+ const skip = opts.extraIgnore ? /* @__PURE__ */ new Set([...DEFAULT_IGNORE_DIRS, ...opts.extraIgnore]) : DEFAULT_IGNORE_DIRS;
1381
+ for (const entry of (0, import_node_fs2.readdirSync)(dir, { withFileTypes: true })) {
1382
+ if (entry.isDirectory()) {
1383
+ if (entry.name.startsWith(".")) continue;
1384
+ if (skip.has(entry.name)) continue;
1385
+ results.push(...walkWithIgnore((0, import_node_path2.join)(dir, entry.name), exts, opts));
1386
+ } else if (exts.includes((0, import_node_path2.extname)(entry.name))) {
1387
+ results.push((0, import_node_path2.join)(dir, entry.name));
1388
+ }
1389
+ }
1390
+ return results;
1391
+ }
1392
+
1393
+ // src/server/graph/core/resolve-paths.ts
1394
+ function hasSqlFiles(dir) {
1395
+ if (!(0, import_node_fs3.existsSync)(dir)) return false;
1396
+ try {
1397
+ return (0, import_node_fs3.readdirSync)(dir, { withFileTypes: true }).some(
1398
+ (e) => e.isFile() && e.name.endsWith(".sql")
1399
+ );
1400
+ } catch {
1401
+ return false;
1402
+ }
1403
+ }
1404
+ function hasNestedMigrationSql(dir) {
1405
+ if (!(0, import_node_fs3.existsSync)(dir)) return false;
1406
+ try {
1407
+ return (0, import_node_fs3.readdirSync)(dir, { withFileTypes: true }).some(
1408
+ (e) => e.isDirectory() && (0, import_node_fs3.existsSync)((0, import_node_path3.join)(dir, e.name, "migration.sql"))
1409
+ );
1410
+ } catch {
1411
+ return false;
1412
+ }
1413
+ }
1414
+ function resolveDbFromDir(dir) {
1415
+ if (!(0, import_node_fs3.existsSync)(dir)) return { kind: "none", schemaPath: null, migrationsDir: null };
1416
+ const schemaPath = (0, import_node_path3.join)(dir, "schema.prisma");
1417
+ if ((0, import_node_fs3.existsSync)(schemaPath)) {
1418
+ const migrationsDir2 = (0, import_node_path3.join)(dir, "migrations");
1419
+ return {
1420
+ kind: "prisma",
1421
+ schemaPath,
1422
+ migrationsDir: (0, import_node_fs3.existsSync)(migrationsDir2) ? migrationsDir2 : null
1423
+ };
1424
+ }
1425
+ const migrationsDir = (0, import_node_path3.join)(dir, "migrations");
1426
+ if (hasSqlFiles(migrationsDir) || hasNestedMigrationSql(migrationsDir)) {
1427
+ return { kind: "sql-migrations", migrationsDir, schemaPath: null };
1428
+ }
1429
+ if (hasSqlFiles(dir) || hasNestedMigrationSql(dir)) {
1430
+ return { kind: "sql-migrations", migrationsDir: dir, schemaPath: null };
1431
+ }
1432
+ return { kind: "none", schemaPath: null, migrationsDir: null };
1433
+ }
1434
+ function detectDbConfig(rootDir, config) {
1435
+ if (config.paths?.dbDir) {
1436
+ return resolveDbFromDir((0, import_node_path3.join)(rootDir, config.paths.dbDir));
1437
+ }
1438
+ const candidates = ["prisma", "supabase", "drizzle", (0, import_node_path3.join)("db", "migrations"), "migrations"];
1439
+ for (const c of candidates) {
1440
+ const dir = (0, import_node_path3.join)(rootDir, c);
1441
+ const resolved = resolveDbFromDir(dir);
1442
+ if (resolved.kind !== "none") return resolved;
1443
+ }
1444
+ return { kind: "none", schemaPath: null, migrationsDir: null };
1445
+ }
1446
+ function detectDbDir(rootDir, config, dbConfig) {
1447
+ if (config.paths?.dbDir) return (0, import_node_path3.join)(rootDir, config.paths.dbDir);
1448
+ if (dbConfig.kind === "prisma") return (0, import_node_path3.dirname)(dbConfig.schemaPath);
1449
+ if (dbConfig.kind === "sql-migrations") return dbConfig.migrationsDir;
1450
+ return null;
1451
+ }
1452
+ var NON_SOURCE_DIRS = /* @__PURE__ */ new Set([
1453
+ ...DEFAULT_IGNORE_DIRS,
1454
+ // DB conventions (handled by db parsers)
1455
+ "prisma",
1456
+ "supabase",
1457
+ "drizzle",
1458
+ "migrations",
1459
+ // Web assets
1460
+ "public",
1461
+ "static",
1462
+ "assets",
1463
+ // Docs
1464
+ "docs",
1465
+ "documentation",
1466
+ // Test dirs (project tests aren't part of the structural graph)
1467
+ "tests",
1468
+ "__tests__",
1469
+ "e2e",
1470
+ "playwright",
1471
+ "cypress",
1472
+ // Monorepo workspace roots — separate graph projects per .launchchart.json
1473
+ "packages",
1474
+ "apps",
1475
+ "services",
1476
+ "libs"
1477
+ ]);
1478
+ function dirHasTSFiles(dir) {
1479
+ if (!(0, import_node_fs3.existsSync)(dir)) return false;
1480
+ try {
1481
+ const stack = [dir];
1482
+ while (stack.length > 0) {
1483
+ const cur = stack.pop();
1484
+ const entries = (0, import_node_fs3.readdirSync)(cur, { withFileTypes: true });
1485
+ for (const e of entries) {
1486
+ if (e.isFile() && (e.name.endsWith(".ts") || e.name.endsWith(".tsx"))) return true;
1487
+ if (e.isDirectory() && !e.name.startsWith(".") && !DEFAULT_IGNORE_DIRS.has(e.name)) {
1488
+ stack.push((0, import_node_path3.join)(cur, e.name));
1489
+ }
1490
+ }
1491
+ }
1492
+ } catch {
1493
+ }
1494
+ return false;
1495
+ }
1496
+ function collectCodeBearingChildren(dir, extraSkip) {
1497
+ if (!(0, import_node_fs3.existsSync)(dir)) return [];
1498
+ const out = [];
1499
+ try {
1500
+ for (const entry of (0, import_node_fs3.readdirSync)(dir, { withFileTypes: true })) {
1501
+ if (!entry.isDirectory()) continue;
1502
+ if (entry.name.startsWith(".")) continue;
1503
+ if (NON_SOURCE_DIRS.has(entry.name)) continue;
1504
+ if (extraSkip?.has(entry.name)) continue;
1505
+ const full = (0, import_node_path3.join)(dir, entry.name);
1506
+ if (dirHasTSFiles(full)) out.push(full);
1507
+ }
1508
+ } catch {
1509
+ }
1510
+ return out;
1511
+ }
1512
+ function detectSrcRoots(rootDir, srcDir, appDir, config) {
1513
+ if (config.paths?.srcRoots && config.paths.srcRoots.length > 0) {
1514
+ const roots2 = /* @__PURE__ */ new Set();
1515
+ roots2.add(appDir);
1516
+ for (const r of config.paths.srcRoots) {
1517
+ const abs = (0, import_node_path3.isAbsolute)(r) ? r : (0, import_node_path3.resolve)(rootDir, r);
1518
+ roots2.add(abs);
1519
+ }
1520
+ return [...roots2];
1521
+ }
1522
+ const roots = /* @__PURE__ */ new Set();
1523
+ roots.add(appDir);
1524
+ for (const c of collectCodeBearingChildren(srcDir)) roots.add(c);
1525
+ if (srcDir !== rootDir) {
1526
+ const skipSrcWrapper = /* @__PURE__ */ new Set([(0, import_node_path3.basename)(srcDir)]);
1527
+ for (const c of collectCodeBearingChildren(rootDir, skipSrcWrapper)) roots.add(c);
1528
+ }
1529
+ return [...roots];
1530
+ }
1531
+ var CONVENTION_NAMES = ["middleware.ts", "middleware.tsx", "instrumentation.ts", "instrumentation.tsx"];
1532
+ function detectConventionFiles(rootDir, srcDir) {
1533
+ const out = [];
1534
+ const seen = /* @__PURE__ */ new Set();
1535
+ const dirs = srcDir === rootDir ? [rootDir] : [srcDir, rootDir];
1536
+ for (const dir of dirs) {
1537
+ for (const name of CONVENTION_NAMES) {
1538
+ const full = (0, import_node_path3.join)(dir, name);
1539
+ if (!seen.has(full) && (0, import_node_fs3.existsSync)(full)) {
1540
+ try {
1541
+ if ((0, import_node_fs3.statSync)(full).isFile()) {
1542
+ seen.add(full);
1543
+ out.push(full);
1544
+ }
1545
+ } catch {
1546
+ }
1547
+ }
1548
+ }
1549
+ }
1550
+ return out;
1551
+ }
1552
+ function resolveProjectPaths(rootDir, config) {
1553
+ let srcDir;
1554
+ let appDir;
1555
+ if (config.paths?.appDir) {
1556
+ appDir = (0, import_node_path3.join)(rootDir, config.paths.appDir);
1557
+ srcDir = config.paths.srcDir ? (0, import_node_path3.join)(rootDir, config.paths.srcDir) : (0, import_node_path3.dirname)(appDir);
1558
+ } else {
1559
+ const srcApp = (0, import_node_path3.join)(rootDir, "src", "app");
1560
+ const rootApp = (0, import_node_path3.join)(rootDir, "app");
1561
+ if ((0, import_node_fs3.existsSync)(srcApp)) {
1562
+ srcDir = (0, import_node_path3.join)(rootDir, "src");
1563
+ appDir = srcApp;
1564
+ } else if ((0, import_node_fs3.existsSync)(rootApp)) {
1565
+ srcDir = rootDir;
1566
+ appDir = rootApp;
1567
+ } else {
1568
+ return null;
1569
+ }
1570
+ }
1571
+ const apiDir = (0, import_node_path3.join)(appDir, "api");
1572
+ const dbConfig = detectDbConfig(rootDir, config);
1573
+ const dbDir = detectDbDir(rootDir, config, dbConfig);
1574
+ const srcRoots = detectSrcRoots(rootDir, srcDir, appDir, config);
1575
+ const conventionFiles = detectConventionFiles(rootDir, srcDir);
1576
+ return { srcDir, appDir, apiDir, dbDir, srcRoots, conventionFiles, dbConfig };
1577
+ }
1578
+
1579
+ // src/server/graph/parsers/ts/typescript-project.ts
1580
+ init_ts_extractor();
1581
+ var HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]);
1582
+ var CLASSIFICATION_TO_LAYER = {
1583
+ endpoint: "api",
1584
+ "server-action": "api",
1585
+ page: "ui",
1586
+ layout: "ui",
1587
+ component: "ui",
1588
+ ui: "ui",
1589
+ hook: "ui",
1590
+ context: "ui",
1591
+ config: "ui",
1592
+ lib: "ui",
1593
+ "mcp-tool": "ui",
1594
+ middleware: "ui",
1595
+ external: "ui"
1596
+ };
1597
+ function toNodeId(srcDir, rootDir, absPath) {
1598
+ const relFromSrc = (0, import_node_path6.relative)(srcDir, absPath).replace(/\\/g, "/");
1599
+ if (relFromSrc.startsWith("..")) {
1600
+ return (0, import_node_path6.relative)(rootDir, absPath).replace(/\\/g, "/");
1601
+ }
1602
+ return relFromSrc;
1603
+ }
1604
+ function resolveImport(srcDir, specifier) {
1605
+ if (!specifier.startsWith("@/")) return null;
1606
+ const rel = specifier.slice(2);
1607
+ const base = (0, import_node_path6.join)(srcDir, rel);
1608
+ for (const c of [base, base + ".ts", base + ".tsx", (0, import_node_path6.join)(base, "index.ts"), (0, import_node_path6.join)(base, "index.tsx")]) {
1609
+ if ((0, import_node_fs6.existsSync)(c) && (0, import_node_fs6.statSync)(c).isFile()) return c;
1610
+ }
1611
+ return null;
1612
+ }
1613
+ function resolveRelativeImport(fromFile, specifier) {
1614
+ const base = (0, import_node_path6.join)((0, import_node_path6.dirname)(fromFile), specifier);
1615
+ for (const c of [base, base + ".ts", base + ".tsx", (0, import_node_path6.join)(base, "index.ts"), (0, import_node_path6.join)(base, "index.tsx")]) {
1616
+ if ((0, import_node_fs6.existsSync)(c) && (0, import_node_fs6.statSync)(c).isFile()) return c;
1617
+ }
1618
+ return null;
1619
+ }
1620
+ function resolveBarrelMap(barrelAbsPath, parsedByPath, memo, visiting) {
1621
+ const cached = memo.get(barrelAbsPath);
1622
+ if (cached) return cached;
1623
+ if (visiting.has(barrelAbsPath)) return /* @__PURE__ */ new Map();
1624
+ visiting.add(barrelAbsPath);
1625
+ const parsed = parsedByPath.get(barrelAbsPath);
1626
+ const map = /* @__PURE__ */ new Map();
1627
+ if (!parsed) {
1628
+ visiting.delete(barrelAbsPath);
1629
+ memo.set(barrelAbsPath, map);
1630
+ return map;
1631
+ }
1632
+ for (const re of parsed.reExports) {
1633
+ if (!re.from.startsWith(".")) continue;
1634
+ const resolved = resolveRelativeImport(barrelAbsPath, re.from);
1635
+ if (!resolved) continue;
1636
+ if (re.isWildcard) {
1637
+ const targetBn = (0, import_node_path6.basename)(resolved);
1638
+ const targetIsBarrel = targetBn === "index.ts" || targetBn === "index.tsx";
1639
+ if (targetIsBarrel) {
1640
+ const nested = resolveBarrelMap(resolved, parsedByPath, memo, visiting);
1641
+ for (const [name, target] of nested) {
1642
+ if (!map.has(name)) map.set(name, target);
1643
+ }
1644
+ } else {
1645
+ const targetParsed = parsedByPath.get(resolved);
1646
+ if (targetParsed) {
1647
+ for (const exp of targetParsed.exports) {
1648
+ if (!map.has(exp)) map.set(exp, resolved);
1649
+ }
1650
+ }
1651
+ }
1652
+ } else {
1653
+ if (!map.has(re.name)) map.set(re.name, resolved);
1654
+ }
1655
+ }
1656
+ visiting.delete(barrelAbsPath);
1657
+ memo.set(barrelAbsPath, map);
1658
+ return map;
1659
+ }
1660
+ function buildAllBarrelMaps(srcDir, parsedByPath) {
1661
+ const barrels = /* @__PURE__ */ new Map();
1662
+ const memo = /* @__PURE__ */ new Map();
1663
+ for (const [absPath, parsed] of parsedByPath) {
1664
+ const bn = (0, import_node_path6.basename)(absPath);
1665
+ if (bn !== "index.ts" && bn !== "index.tsx") continue;
1666
+ if (parsed.reExports.length === 0) continue;
1667
+ const map = resolveBarrelMap(absPath, parsedByPath, memo, /* @__PURE__ */ new Set());
1668
+ if (map.size > 0) {
1669
+ const barrelId = (0, import_node_path6.relative)(srcDir, (0, import_node_path6.dirname)(absPath)).replace(/\\/g, "/");
1670
+ barrels.set(barrelId, map);
1671
+ }
1672
+ }
1673
+ return barrels;
1674
+ }
1675
+ function classifyType(absPath, id) {
1676
+ const contentType = classifyFile(absPath);
1677
+ if (contentType === "lib" && id.startsWith("server/mcp/")) return "mcp-tool";
1678
+ if (/^app\/(.+\/)?page\.tsx$/.test(id)) return "page";
1679
+ if (/^app\/(.+\/)?layout\.tsx$/.test(id)) return "layout";
1680
+ return contentType;
1681
+ }
1682
+ function extractRoute(id) {
1683
+ if (!id.endsWith("/page.tsx")) return null;
1684
+ let route = id.replace(/^app\//, "/").replace(/\/page\.tsx$/, "");
1685
+ route = route.replace(/\/\([^)]+\)/g, "");
1686
+ route = route.replace(/\[\[\.\.\.([^\]]+)\]\]/g, "*$1?");
1687
+ route = route.replace(/\[\.\.\.([^\]]+)\]/g, "*$1");
1688
+ route = route.replace(/\[([^\]]+)\]/g, ":$1");
1689
+ route = route.replace(/\/+/g, "/");
1690
+ if (!route.startsWith("/")) route = "/" + route;
1691
+ return route || "/";
1692
+ }
1693
+ function nameFromFilename(absPath) {
1694
+ return (0, import_node_path6.basename)(absPath, (0, import_node_path6.extname)(absPath)).replace(/[-_](\w)/g, (_, c) => c.toUpperCase()).replace(/^(\w)/, (_, c) => c.toUpperCase());
1695
+ }
1696
+ function filePathToAppRoute(appDir, absPath) {
1697
+ let route = ("/" + (0, import_node_path6.relative)(appDir, absPath).replace(/\\/g, "/")).replace(/\/route\.tsx?$/, "");
1698
+ route = route.replace(/\/\([^)]+\)/g, "");
1699
+ route = route.replace(/\[\[\.\.\.([^\]]+)\]\]/g, "*$1?");
1700
+ route = route.replace(/\[\.\.\.([^\]]+)\]/g, "*$1");
1701
+ route = route.replace(/\[([^\]]+)\]/g, ":$1");
1702
+ route = route.replace(/\/+/g, "/");
1703
+ return route === "" ? "/" : route;
1704
+ }
1705
+ function camelToPascal(s) {
1706
+ if (!s) return s;
1707
+ return s.charAt(0).toUpperCase() + s.slice(1);
1708
+ }
1709
+ function resolveTemplateLiteralRoute(template, routeToNodeId) {
1710
+ const parameterized = template.replace(/\$\{([^}]+)\}/g, (_, expr) => {
1711
+ const cleaned = expr.trim();
1712
+ if (cleaned.includes(".")) {
1713
+ const parts = cleaned.split(".");
1714
+ const last = parts[parts.length - 1];
1715
+ const secondLast = parts.length > 1 ? parts[parts.length - 2] : "";
1716
+ if (last === "slug" && secondLast === "project") return ":projectSlug";
1717
+ if (last === "slug") return ":projectSlug";
1718
+ if (last === "id" && /cred/i.test(secondLast)) return ":credentialId";
1719
+ if (last === "id" && /run/i.test(secondLast)) return ":runId";
1720
+ if (last === "sha") return ":commitSha";
1721
+ if (last === "id") return ":id";
1722
+ return `:${last}`;
1723
+ }
1724
+ if (/orgSlug/i.test(cleaned)) return ":orgSlug";
1725
+ if (/projectSlug/i.test(cleaned)) return ":projectSlug";
1726
+ if (/runId/i.test(cleaned)) return ":runId";
1727
+ if (/credentialId/i.test(cleaned)) return ":credentialId";
1728
+ if (/commitSha/i.test(cleaned)) return ":commitSha";
1729
+ if (/token/i.test(cleaned)) return ":token";
1730
+ return `:${cleaned}`;
1731
+ });
1732
+ const normalized = parameterized.replace(/\/+/g, "/").replace(/\/$/, "") || "/";
1733
+ if (routeToNodeId.has(normalized)) return routeToNodeId.get(normalized);
1734
+ let bestScore = -1;
1735
+ let bestId = null;
1736
+ for (const [route, nodeId] of routeToNodeId) {
1737
+ const score = routeMatchScore(normalized, route);
1738
+ if (score > bestScore) {
1739
+ bestScore = score;
1740
+ bestId = nodeId;
1741
+ }
1742
+ }
1743
+ return bestScore > 0 ? bestId : null;
1744
+ }
1745
+ function routeMatchScore(candidate, known) {
1746
+ const segsA = candidate.split("/");
1747
+ const segsB = known.split("/");
1748
+ let score = 0;
1749
+ let i = 0, j = 0;
1750
+ while (i < segsA.length && j < segsB.length) {
1751
+ const a = segsA[i], b = segsB[j];
1752
+ if (b.startsWith("*") && b.endsWith("?")) {
1753
+ score += 1;
1754
+ return score;
1755
+ }
1756
+ if (b.startsWith("*")) {
1757
+ const remaining = segsA.length - i;
1758
+ if (remaining < 1) return -1;
1759
+ score += 1 + remaining;
1760
+ return score;
1761
+ }
1762
+ if (a === b) {
1763
+ score += 3;
1764
+ i++;
1765
+ j++;
1766
+ continue;
1767
+ }
1768
+ if (a.startsWith(":") && b.startsWith(":")) {
1769
+ score += 2;
1770
+ i++;
1771
+ j++;
1772
+ continue;
1773
+ }
1774
+ if (a.startsWith(":") || b.startsWith(":")) {
1775
+ i++;
1776
+ j++;
1777
+ continue;
1778
+ }
1779
+ return -1;
1780
+ }
1781
+ if (i === segsA.length) {
1782
+ while (j < segsB.length) {
1783
+ const b = segsB[j];
1784
+ if (b.startsWith("*") && b.endsWith("?")) {
1785
+ score += 1;
1786
+ j++;
1787
+ continue;
1788
+ }
1789
+ return -1;
1790
+ }
1791
+ return score;
1792
+ }
1793
+ return -1;
1794
+ }
1795
+ function templateToRoute(template) {
1796
+ return template.replace(/\$\{([^}]+)\}/g, (_, expr) => {
1797
+ const cleaned = expr.trim();
1798
+ if (cleaned.includes(".")) {
1799
+ const parts = cleaned.split(".");
1800
+ const last = parts[parts.length - 1];
1801
+ const secondLast = parts.length > 1 ? parts[parts.length - 2] : "";
1802
+ if (last === "slug" && /project/i.test(secondLast)) return ":projectSlug";
1803
+ if (last === "slug") return ":slug";
1804
+ if (last === "sha") return ":commitSha";
1805
+ return `:${last}`;
1806
+ }
1807
+ return `:${cleaned}`;
1808
+ });
1809
+ }
1810
+ function matchRouteToPage(route, routeToNodeId) {
1811
+ const normalized = route.replace(/\/$/, "") || "/";
1812
+ if (routeToNodeId.has(normalized)) return routeToNodeId.get(normalized);
1813
+ return null;
1814
+ }
1815
+ function extractEdges(srcDir, rootDir, absPath, sourceId, parsed, nodeIdSet, barrelMaps, routeToNodeId) {
1816
+ const edges = [];
1817
+ const flagged = [];
1818
+ const seen = /* @__PURE__ */ new Set();
1819
+ function addEdge(target, type, label) {
1820
+ const key = `${sourceId}\u2192${target}\u2192${type}`;
1821
+ if (seen.has(key)) return;
1822
+ seen.add(key);
1823
+ const edge = { source: sourceId, target, type };
1824
+ if (label) edge.label = label;
1825
+ edges.push(edge);
1826
+ }
1827
+ function edgeTypeFor(isTypeOnlyImport, importedNames) {
1828
+ if (isTypeOnlyImport) return "imports_type";
1829
+ const anyRendered = importedNames.some((n) => parsed.jsxElements.has(n));
1830
+ if (anyRendered) return "renders";
1831
+ return "imports";
1832
+ }
1833
+ for (const imp of parsed.imports) {
1834
+ const { names, specifier, isTypeOnly, typeNames } = imp;
1835
+ if (specifier.startsWith("@/")) {
1836
+ const relToSrc = specifier.slice(2);
1837
+ const barrelMap = barrelMaps.get(relToSrc);
1838
+ if (barrelMap && names.length > 0) {
1839
+ const byTarget = /* @__PURE__ */ new Map();
1840
+ for (const name of names) {
1841
+ const targetAbs = barrelMap.get(name);
1842
+ if (targetAbs) {
1843
+ const targetId = toNodeId(srcDir, rootDir, targetAbs);
1844
+ if (nodeIdSet.has(targetId)) {
1845
+ if (!byTarget.has(targetId)) byTarget.set(targetId, []);
1846
+ byTarget.get(targetId).push(name);
1847
+ }
1848
+ }
1849
+ }
1850
+ for (const [targetId, targetNames] of byTarget) {
1851
+ const allType = isTypeOnly || targetNames.every((n) => typeNames.has(n));
1852
+ addEdge(targetId, edgeTypeFor(allType, targetNames));
1853
+ }
1854
+ } else {
1855
+ const resolved = resolveImport(srcDir, specifier);
1856
+ if (resolved) {
1857
+ const targetId = toNodeId(srcDir, rootDir, resolved);
1858
+ if (nodeIdSet.has(targetId) && !targetId.endsWith("/index.ts") && !targetId.endsWith("/index.tsx")) {
1859
+ const allType = isTypeOnly || names.length > 0 && names.every((n) => typeNames.has(n));
1860
+ addEdge(targetId, edgeTypeFor(allType, names));
1861
+ }
1862
+ }
1863
+ }
1864
+ } else if (specifier.startsWith(".")) {
1865
+ const resolved = resolveRelativeImport(absPath, specifier);
1866
+ if (resolved) {
1867
+ const targetId = toNodeId(srcDir, rootDir, resolved);
1868
+ if (nodeIdSet.has(targetId) && !targetId.endsWith("/index.ts") && !targetId.endsWith("/index.tsx")) {
1869
+ const allType = isTypeOnly || names.length > 0 && names.every((n) => typeNames.has(n));
1870
+ addEdge(targetId, edgeTypeFor(allType, names));
1871
+ }
1872
+ }
1873
+ }
1874
+ }
1875
+ for (const nav of parsed.navigations) {
1876
+ if (nav.kind === "window-location") {
1877
+ flagged.push({
1878
+ source: sourceId,
1879
+ target: "EXTERNAL",
1880
+ type: "navigates",
1881
+ label: `window.location to ${nav.target}`,
1882
+ confidence: "high"
1883
+ });
1884
+ continue;
1885
+ }
1886
+ if (!nav.isTemplate) {
1887
+ const targetId = matchRouteToPage(nav.target, routeToNodeId);
1888
+ if (targetId && targetId !== sourceId) {
1889
+ const label = nav.kind === "link-href" ? `Link to ${nav.target}` : `router.${nav.kind === "router-push" ? "push" : "replace"}('${nav.target}')`;
1890
+ addEdge(targetId, "navigates", label);
1891
+ }
1892
+ } else {
1893
+ const template = nav.target.replace(/^`|`$/g, "");
1894
+ if (!template.includes("${")) continue;
1895
+ const targetId = resolveTemplateLiteralRoute(template, routeToNodeId);
1896
+ if (targetId && targetId !== sourceId) {
1897
+ const label = nav.kind === "link-href" ? `Link to ${templateToRoute(template)}` : `router.${nav.kind === "router-push" ? "push" : "replace"}('${templateToRoute(template)}')`;
1898
+ addEdge(targetId, "navigates", label);
1899
+ } else {
1900
+ flagged.push({
1901
+ source: sourceId,
1902
+ target: "DYNAMIC",
1903
+ type: "navigates",
1904
+ label: nav.kind === "link-href" ? `Link with template: \`${template}\`` : `router.${nav.kind === "router-push" ? "push" : "replace"} with template: \`${template}\``,
1905
+ confidence: "medium"
1906
+ });
1907
+ }
1908
+ }
1909
+ }
1910
+ return { edges, flagged };
1911
+ }
1912
+ function detect(rootDir) {
1913
+ const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
1914
+ return paths !== null;
1915
+ }
1916
+ function generate(rootDir) {
1917
+ const config = loadConfig(rootDir);
1918
+ const paths = resolveProjectPaths(rootDir, config);
1919
+ const srcDir = paths.srcDir;
1920
+ const allDiscovered = [];
1921
+ const discoveredSet = /* @__PURE__ */ new Set();
1922
+ for (const root of paths.srcRoots) {
1923
+ for (const f of walk(root, [".tsx", ".ts"])) {
1924
+ if (!discoveredSet.has(f)) {
1925
+ discoveredSet.add(f);
1926
+ allDiscovered.push(f);
1927
+ }
1928
+ }
1929
+ }
1930
+ for (const conv of paths.conventionFiles) {
1931
+ if (!discoveredSet.has(conv)) {
1932
+ discoveredSet.add(conv);
1933
+ allDiscovered.push(conv);
1934
+ }
1935
+ }
1936
+ const parsedByPath = /* @__PURE__ */ new Map();
1937
+ for (const absPath of allDiscovered) {
1938
+ parsedByPath.set(absPath, parseFileTS(absPath));
1939
+ }
1940
+ const barrelMaps = buildAllBarrelMaps(srcDir, parsedByPath);
1941
+ const uiNodes = [];
1942
+ const apiNodes = [];
1943
+ const nodeIdSet = /* @__PURE__ */ new Set();
1944
+ const routeToNodeId = /* @__PURE__ */ new Map();
1945
+ const fileSet = allDiscovered.filter((f) => !(0, import_node_path6.basename)(f).startsWith("index."));
1946
+ for (const absPath of fileSet) {
1947
+ const id = toNodeId(srcDir, rootDir, absPath);
1948
+ const type = classifyType(absPath, id);
1949
+ if (type === "test" || type === "story") continue;
1950
+ const parsed = parsedByPath.get(absPath);
1951
+ const name = parsed.name || nameFromFilename(absPath);
1952
+ const layer = CLASSIFICATION_TO_LAYER[type] ?? "ui";
1953
+ nodeIdSet.add(id);
1954
+ if (layer === "api") {
1955
+ const dbCalls = extractDbCallsTS(absPath);
1956
+ const authWrappers = extractAuthWrappersTS(absPath);
1957
+ const deep = extractDeep(absPath);
1958
+ const mutations = dbCalls.filter((c) => c.isMutation);
1959
+ const mutates = mutations.length > 0;
1960
+ const authStrategy = [...authWrappers];
1961
+ const isServerAction = type === "server-action";
1962
+ const methods = [];
1963
+ if (!isServerAction) {
1964
+ for (const exp of parsed.exports) {
1965
+ if (HTTP_METHODS.has(exp)) methods.push(exp);
1966
+ }
1967
+ }
1968
+ const routePath = isServerAction ? null : filePathToAppRoute(paths.appDir, absPath);
1969
+ const actions = isServerAction ? parsed.exports.filter((e) => !HTTP_METHODS.has(e)) : [];
1970
+ apiNodes.push({
1971
+ id,
1972
+ type: isServerAction ? "server-action" : "endpoint",
1973
+ // For HTTP routes: name = URL path. For Server Actions: name = file id +
1974
+ // exported action names — the callable surface, not a URL.
1975
+ name: isServerAction ? actions.length > 0 ? `${id} (${actions.join(", ")})` : id : routePath,
1976
+ layer: "api",
1977
+ path: routePath,
1978
+ // null for Server Actions
1979
+ methods,
1980
+ handler: id,
1981
+ mutates,
1982
+ ...isServerAction ? { actions } : {},
1983
+ auth: authStrategy.length > 0 ? authStrategy : ["public"],
1984
+ db_models: [...new Set(dbCalls.map((c) => c.model))],
1985
+ db_operations: [...new Set(dbCalls.map((c) => `${c.model}.${c.method}`))],
1986
+ conditions: deep.conditions,
1987
+ variables: deep.variables,
1988
+ responses: deep.responses,
1989
+ params: deep.params,
1990
+ ...deep.effects ? { effects: deep.effects } : {},
1991
+ _dbCalls: dbCalls
1992
+ // temp: used for cross-ref building below
1993
+ });
1994
+ } else {
1995
+ const route = extractRoute(id);
1996
+ const deep = extractDeep(absPath);
1997
+ const dbCalls = extractDbCallsTS(absPath);
1998
+ const authWrappers = type === "page" || type === "layout" ? [...extractAuthWrappersTS(absPath)] : [];
1999
+ uiNodes.push({
2000
+ id,
2001
+ type,
2002
+ name,
2003
+ layer: "ui",
2004
+ route,
2005
+ exports: parsed.exports,
2006
+ elements: deep.elements,
2007
+ stateVars: deep.stateVars,
2008
+ conditions: deep.conditions,
2009
+ variables: deep.variables,
2010
+ ...deep.effects ? { effects: deep.effects } : {},
2011
+ ...authWrappers.length > 0 ? { auth: authWrappers } : {},
2012
+ ...dbCalls.length > 0 ? { _dbCalls: dbCalls } : {}
2013
+ });
2014
+ if (route) {
2015
+ routeToNodeId.set(route, id);
2016
+ const trimmed = route.replace(/\/\*[^/]+\?$/, "");
2017
+ if (trimmed && trimmed !== route && !routeToNodeId.has(trimmed)) {
2018
+ routeToNodeId.set(trimmed, id);
2019
+ }
2020
+ }
2021
+ }
2022
+ }
2023
+ const uiEdges = [];
2024
+ const uiFlagged = [];
2025
+ for (const absPath of fileSet) {
2026
+ const id = toNodeId(srcDir, rootDir, absPath);
2027
+ if (!nodeIdSet.has(id)) continue;
2028
+ const parsed = parsedByPath.get(absPath);
2029
+ const { edges, flagged } = extractEdges(
2030
+ srcDir,
2031
+ rootDir,
2032
+ absPath,
2033
+ id,
2034
+ parsed,
2035
+ nodeIdSet,
2036
+ barrelMaps,
2037
+ routeToNodeId
2038
+ );
2039
+ uiEdges.push(...edges);
2040
+ uiFlagged.push(...flagged);
2041
+ }
2042
+ const layoutsById = /* @__PURE__ */ new Set();
2043
+ for (const n of uiNodes) {
2044
+ if (n.type === "layout") layoutsById.add(n.id);
2045
+ }
2046
+ function findClosestLayout(pageId) {
2047
+ let dir = pageId.replace(/\/page\.tsx$/, "");
2048
+ while (dir.length > 0) {
2049
+ const candidate = `${dir}/layout.tsx`;
2050
+ if (layoutsById.has(candidate)) return candidate;
2051
+ const slash = dir.lastIndexOf("/");
2052
+ if (slash < 0) break;
2053
+ dir = dir.slice(0, slash);
2054
+ }
2055
+ if (layoutsById.has("app/layout.tsx")) return "app/layout.tsx";
2056
+ return null;
2057
+ }
2058
+ for (const n of uiNodes) {
2059
+ if (n.type !== "page") continue;
2060
+ const layoutId = findClosestLayout(n.id);
2061
+ if (layoutId && layoutId !== n.id) {
2062
+ uiEdges.push({ source: layoutId, target: n.id, type: "wraps" });
2063
+ }
2064
+ }
2065
+ const fetchCallEntries = [];
2066
+ for (const absPath of fileSet) {
2067
+ const sourceId = toNodeId(srcDir, rootDir, absPath);
2068
+ if (!nodeIdSet.has(sourceId)) continue;
2069
+ const parsed = parsedByPath.get(absPath);
2070
+ if (parsed.fetchCalls.length === 0) continue;
2071
+ fetchCallEntries.push({
2072
+ nodeId: sourceId,
2073
+ calls: parsed.fetchCalls.map((c) => ({
2074
+ url: c.url,
2075
+ method: c.method,
2076
+ isTemplate: c.isTemplate,
2077
+ isConcat: c.isConcat,
2078
+ kind: c.kind
2079
+ }))
2080
+ });
2081
+ }
2082
+ const externalScanned = new Set(allDiscovered.map((f) => f.replace(/\\/g, "/")));
2083
+ const externalCandidates = walkWithIgnore(rootDir, [".ts", ".tsx"], { extraIgnore: /* @__PURE__ */ new Set(["src"]) });
2084
+ for (const absPath of externalCandidates) {
2085
+ const normalized = absPath.replace(/\\/g, "/");
2086
+ if (externalScanned.has(normalized)) continue;
2087
+ let parsed;
2088
+ try {
2089
+ parsed = parseFileTS(absPath);
2090
+ } catch {
2091
+ continue;
2092
+ }
2093
+ const externalId = (0, import_node_path6.relative)(rootDir, absPath).replace(/\\/g, "/");
2094
+ const edgesFromThis = [];
2095
+ const seen = /* @__PURE__ */ new Set();
2096
+ for (const imp of parsed.imports) {
2097
+ const { specifier, names } = imp;
2098
+ let resolved = null;
2099
+ if (specifier.startsWith("@/")) {
2100
+ const relToSrc = specifier.slice(2);
2101
+ const barrelMap = barrelMaps.get(relToSrc);
2102
+ if (barrelMap && names.length > 0) {
2103
+ for (const name of names) {
2104
+ const targetAbs = barrelMap.get(name);
2105
+ if (!targetAbs) continue;
2106
+ const targetId2 = toNodeId(srcDir, rootDir, targetAbs);
2107
+ if (!nodeIdSet.has(targetId2)) continue;
2108
+ const key2 = `${externalId}\u2192${targetId2}`;
2109
+ if (seen.has(key2)) continue;
2110
+ seen.add(key2);
2111
+ edgesFromThis.push({ source: externalId, target: targetId2, type: "imports" });
2112
+ }
2113
+ continue;
2114
+ }
2115
+ resolved = resolveImport(srcDir, specifier);
2116
+ } else if (specifier.startsWith(".")) {
2117
+ resolved = resolveRelativeImport(absPath, specifier);
2118
+ }
2119
+ if (!resolved) continue;
2120
+ const targetId = toNodeId(srcDir, rootDir, resolved);
2121
+ if (!nodeIdSet.has(targetId)) continue;
2122
+ if (targetId.endsWith("/index.ts") || targetId.endsWith("/index.tsx")) continue;
2123
+ const key = `${externalId}\u2192${targetId}`;
2124
+ if (seen.has(key)) continue;
2125
+ seen.add(key);
2126
+ edgesFromThis.push({ source: externalId, target: targetId, type: "imports" });
2127
+ }
2128
+ if (edgesFromThis.length === 0) continue;
2129
+ uiNodes.push({
2130
+ id: externalId,
2131
+ type: "external",
2132
+ name: parsed.name || nameFromFilename(absPath),
2133
+ layer: "ui",
2134
+ route: null,
2135
+ exports: parsed.exports
2136
+ });
2137
+ nodeIdSet.add(externalId);
2138
+ uiEdges.push(...edgesFromThis);
2139
+ }
2140
+ const tablesByFile = /* @__PURE__ */ new Map();
2141
+ const allDbNodes = [...apiNodes, ...uiNodes];
2142
+ for (const node of allDbNodes) {
2143
+ const calls = node._dbCalls;
2144
+ if (!calls || calls.length === 0) continue;
2145
+ const map = /* @__PURE__ */ new Map();
2146
+ for (const c of calls) {
2147
+ const key = `${c.kind}:${c.model}:${c.isMutation ? "m" : "r"}`;
2148
+ if (!map.has(key)) {
2149
+ map.set(key, { model: c.model, method: c.method, isMutation: c.isMutation, kind: c.kind, via: [] });
2150
+ }
2151
+ }
2152
+ tablesByFile.set(node.id, map);
2153
+ }
2154
+ const reverseRuntimeImports = /* @__PURE__ */ new Map();
2155
+ for (const edge of uiEdges) {
2156
+ if (edge.type !== "imports" && edge.type !== "renders") continue;
2157
+ if (!reverseRuntimeImports.has(edge.target)) {
2158
+ reverseRuntimeImports.set(edge.target, /* @__PURE__ */ new Set());
2159
+ }
2160
+ reverseRuntimeImports.get(edge.target).add(edge.source);
2161
+ }
2162
+ let changed = true;
2163
+ let iterations = 0;
2164
+ while (changed && iterations < 50) {
2165
+ changed = false;
2166
+ iterations++;
2167
+ for (const [target, tableMap] of [...tablesByFile]) {
2168
+ const importers = reverseRuntimeImports.get(target);
2169
+ if (!importers) continue;
2170
+ for (const importer of importers) {
2171
+ if (importer === target) continue;
2172
+ let importerMap = tablesByFile.get(importer);
2173
+ if (!importerMap) {
2174
+ importerMap = /* @__PURE__ */ new Map();
2175
+ tablesByFile.set(importer, importerMap);
2176
+ }
2177
+ for (const [key, call] of tableMap) {
2178
+ if (importerMap.has(key)) continue;
2179
+ importerMap.set(key, {
2180
+ model: call.model,
2181
+ method: null,
2182
+ isMutation: call.isMutation,
2183
+ kind: call.kind,
2184
+ via: [...call.via, target]
2185
+ });
2186
+ changed = true;
2187
+ }
2188
+ }
2189
+ }
2190
+ }
2191
+ for (const node of apiNodes) {
2192
+ const map = tablesByFile.get(node.id);
2193
+ if (!map) continue;
2194
+ node.db_models = [...new Set([...map.values()].map((c) => c.model))];
2195
+ node.db_operations = [...new Set(
2196
+ [...map.values()].filter((c) => c.via.length === 0 && c.method).map((c) => `${c.model}.${c.method}`)
2197
+ )];
2198
+ node.mutates = [...map.values()].some((c) => c.isMutation);
2199
+ }
2200
+ const apiCrossRefs = [];
2201
+ for (const node of apiNodes) {
2202
+ const map = tablesByFile.get(node.id);
2203
+ if (!map) {
2204
+ delete node._dbCalls;
2205
+ continue;
2206
+ }
2207
+ const seenTargets = /* @__PURE__ */ new Set();
2208
+ for (const call of map.values()) {
2209
+ const target = call.kind === "sql" ? call.model : camelToPascal(call.model);
2210
+ if (seenTargets.has(target)) continue;
2211
+ seenTargets.add(target);
2212
+ const isTransitive = call.via.length > 0;
2213
+ const ref = {
2214
+ source: node.id,
2215
+ target,
2216
+ type: call.isMutation ? isTransitive ? "mutates_via" : "mutates" : isTransitive ? "reads_via" : "reads",
2217
+ layer: "db"
2218
+ };
2219
+ if (isTransitive) ref.via = call.via;
2220
+ apiCrossRefs.push(ref);
2221
+ }
2222
+ delete node._dbCalls;
2223
+ }
2224
+ const uiCrossRefs = [];
2225
+ for (const node of uiNodes) {
2226
+ const map = tablesByFile.get(node.id);
2227
+ if (!map) {
2228
+ if (node._dbCalls) delete node._dbCalls;
2229
+ continue;
2230
+ }
2231
+ const seenTargets = /* @__PURE__ */ new Set();
2232
+ for (const call of map.values()) {
2233
+ const target = call.kind === "sql" ? call.model : camelToPascal(call.model);
2234
+ if (seenTargets.has(target)) continue;
2235
+ seenTargets.add(target);
2236
+ const isTransitive = call.via.length > 0;
2237
+ const ref = {
2238
+ source: node.id,
2239
+ target,
2240
+ type: call.isMutation ? isTransitive ? "mutates_via" : "mutates" : isTransitive ? "reads_via" : "reads",
2241
+ layer: "db"
2242
+ };
2243
+ if (isTransitive) ref.via = call.via;
2244
+ uiCrossRefs.push(ref);
2245
+ }
2246
+ if (node._dbCalls) delete node._dbCalls;
2247
+ }
2248
+ uiCrossRefs.sort((a, b) => a.source.localeCompare(b.source) || a.target.localeCompare(b.target));
2249
+ const middlewareInfos = [];
2250
+ for (const conv of paths.conventionFiles) {
2251
+ if (!/middleware\.tsx?$/.test(conv)) continue;
2252
+ try {
2253
+ const info = extractMiddlewareAuthTS(conv);
2254
+ if (info && info.matchers.length > 0) middlewareInfos.push(info);
2255
+ } catch {
2256
+ }
2257
+ }
2258
+ if (middlewareInfos.length > 0) {
2259
+ let setAuth2 = function(node, newTags, replaceAll) {
2260
+ const existing = node.auth ?? [];
2261
+ const meaningful = existing.filter((a) => a !== "public");
2262
+ const merged = replaceAll ? newTags : [.../* @__PURE__ */ new Set([...newTags, ...meaningful])];
2263
+ node.auth = merged.length > 0 ? merged : ["public"];
2264
+ }, applyMiddleware2 = function(node, routePath) {
2265
+ let resolved = null;
2266
+ let label = "";
2267
+ let hasAnyExemptMatcher = false;
2268
+ let hasAnyFallthrough = false;
2269
+ for (const info of middlewareInfos) {
2270
+ if (info.hasFallthroughProtect) hasAnyFallthrough = true;
2271
+ if (info.matchers.some((m) => m.intent === "exempt")) hasAnyExemptMatcher = true;
2272
+ const c = classifyRouteAgainstMiddleware(routePath, info);
2273
+ if (!c) continue;
2274
+ if (!resolved) {
2275
+ resolved = c.intent;
2276
+ label = c.matcher;
2277
+ }
2278
+ }
2279
+ if (resolved === "exempt") {
2280
+ setAuth2(node, ["public"], true);
2281
+ return;
2282
+ }
2283
+ if (resolved === "protect") {
2284
+ setAuth2(node, [`middleware:${label}`], false);
2285
+ return;
2286
+ }
2287
+ if (hasAnyExemptMatcher && hasAnyFallthrough) {
2288
+ setAuth2(node, ["middleware-protected"], false);
2289
+ }
2290
+ };
2291
+ var setAuth = setAuth2, applyMiddleware = applyMiddleware2;
2292
+ for (const node of apiNodes) {
2293
+ const routePath = node.path;
2294
+ if (!routePath) continue;
2295
+ applyMiddleware2(node, routePath);
2296
+ }
2297
+ for (const node of uiNodes) {
2298
+ const route = node.route;
2299
+ if (!route) continue;
2300
+ applyMiddleware2(node, route);
2301
+ }
2302
+ }
2303
+ const apiNodeIds = new Set(apiNodes.map((n) => n.id));
2304
+ const apiEdges = [];
2305
+ const uiOnlyEdges = [];
2306
+ for (const edge of uiEdges) {
2307
+ if (apiNodeIds.has(edge.source)) {
2308
+ apiEdges.push(edge);
2309
+ } else {
2310
+ uiOnlyEdges.push(edge);
2311
+ }
2312
+ }
2313
+ const flaggedSet = /* @__PURE__ */ new Set();
2314
+ const dedupedFlagged = uiFlagged.filter((f) => {
2315
+ const key = `${f.source}\u2192${f.target}\u2192${f.label}`;
2316
+ if (flaggedSet.has(key)) return false;
2317
+ flaggedSet.add(key);
2318
+ return true;
2319
+ });
2320
+ const typePriority = {
2321
+ layout: 0,
2322
+ page: 1,
2323
+ component: 2,
2324
+ ui: 3,
2325
+ context: 4,
2326
+ config: 5,
2327
+ util: 6,
2328
+ hook: 7,
2329
+ lib: 8
2330
+ };
2331
+ uiNodes.sort((a, b) => (typePriority[a.type] ?? 99) - (typePriority[b.type] ?? 99) || a.id.localeCompare(b.id));
2332
+ uiOnlyEdges.sort((a, b) => a.source.localeCompare(b.source) || a.target.localeCompare(b.target));
2333
+ apiNodes.sort((a, b) => (a.path ?? "").localeCompare(b.path ?? ""));
2334
+ apiCrossRefs.sort((a, b) => a.source.localeCompare(b.source) || a.target.localeCompare(b.target));
2335
+ const byType = (t) => uiNodes.filter((n) => n.type === t).length;
2336
+ const uiStats = {
2337
+ total_pages: byType("page"),
2338
+ total_layouts: byType("layout"),
2339
+ total_components: byType("component"),
2340
+ total_ui: byType("ui"),
2341
+ total_hooks: byType("hook"),
2342
+ total_contexts: byType("context"),
2343
+ total_configs: byType("config"),
2344
+ total_utils: byType("util"),
2345
+ total_libs: byType("lib"),
2346
+ total_external: byType("external"),
2347
+ total_edges: uiOnlyEdges.length,
2348
+ total_flagged: dedupedFlagged.length
2349
+ };
2350
+ const stripLayer = (nodes) => nodes.map(({ layer: _, ...rest }) => rest);
2351
+ const result = /* @__PURE__ */ new Map();
2352
+ result.set("ui", {
2353
+ metadata: {
2354
+ generated: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
2355
+ scope: "main-app-only",
2356
+ app_root: "src/",
2357
+ layer: "ui",
2358
+ parser: "typescript-project",
2359
+ ...uiStats,
2360
+ notes: "Auto-generated via TypeScript AST \u2014 edges derived from actual imports, renders from JSX usage, navigations from router/Link calls."
2361
+ },
2362
+ nodes: stripLayer(uiNodes),
2363
+ edges: uiOnlyEdges,
2364
+ cross_refs: uiCrossRefs,
2365
+ contradictions: [],
2366
+ warnings: [],
2367
+ flagged_edges: dedupedFlagged,
2368
+ patterns: {
2369
+ total_nodes: uiNodes.length,
2370
+ by_type: uiStats,
2371
+ by_edge_type: {
2372
+ renders: uiOnlyEdges.filter((e) => e.type === "renders").length,
2373
+ imports: uiOnlyEdges.filter((e) => e.type === "imports").length,
2374
+ navigates: uiOnlyEdges.filter((e) => e.type === "navigates").length
2375
+ },
2376
+ fetch_calls: fetchCallEntries
2377
+ }
2378
+ });
2379
+ if (apiNodes.length > 0) {
2380
+ const mutatorNodes = apiNodes.filter((n) => n.mutates).length;
2381
+ const readOnlyNodes = apiNodes.filter((n) => !n.mutates).length;
2382
+ const authUsage = {};
2383
+ let endpointsWithAuth = 0;
2384
+ for (const n of apiNodes) {
2385
+ const auth = n.auth;
2386
+ if (auth.length > 0 && auth[0] !== "public") {
2387
+ endpointsWithAuth++;
2388
+ for (const w of auth) {
2389
+ authUsage[w] = (authUsage[w] ?? 0) + 1;
2390
+ }
2391
+ }
2392
+ }
2393
+ const mutatorCount = {};
2394
+ for (const n of apiNodes) {
2395
+ const ops = n.db_operations;
2396
+ for (const op of ops) {
2397
+ const method = op.split(".")[1];
2398
+ if (method) mutatorCount[method] = (mutatorCount[method] ?? 0) + 1;
2399
+ }
2400
+ }
2401
+ result.set("api", {
2402
+ metadata: {
2403
+ generated: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
2404
+ scope: "main-app-only",
2405
+ stack: "nextjs-app-router",
2406
+ layer: "api",
2407
+ parser: "typescript-project",
2408
+ total_endpoints: apiNodes.length,
2409
+ total_methods: apiNodes.reduce((sum, n) => sum + n.methods.length, 0),
2410
+ endpoints_with_auth: endpointsWithAuth,
2411
+ endpoints_with_db_access: apiNodes.filter((n) => n.db_models.length > 0).length,
2412
+ mutator_endpoints: mutatorNodes,
2413
+ read_only_endpoints: readOnlyNodes
2414
+ },
2415
+ nodes: stripLayer(apiNodes),
2416
+ edges: apiEdges,
2417
+ cross_refs: apiCrossRefs,
2418
+ contradictions: [],
2419
+ warnings: [],
2420
+ flagged_edges: [],
2421
+ patterns: {
2422
+ total_endpoints: apiNodes.length,
2423
+ methods_breakdown: [...HTTP_METHODS].reduce((acc, m) => {
2424
+ acc[m] = apiNodes.filter((n) => n.methods.includes(m)).length;
2425
+ return acc;
2426
+ }, {}),
2427
+ auth_strategies: authUsage,
2428
+ mutation_operations: mutatorCount,
2429
+ mutator_vs_reader: { mutators: mutatorNodes, readers: readOnlyNodes }
2430
+ }
2431
+ });
2432
+ }
2433
+ return result;
2434
+ }
2435
+ var typescriptProjectParser = {
2436
+ id: "typescript-project",
2437
+ layers: ["ui", "api"],
2438
+ detect,
2439
+ generate
2440
+ };
2441
+
2442
+ // src/server/graph/parsers/db/prisma-schema.ts
2443
+ var import_node_fs7 = require("node:fs");
2444
+ function parseModels(content) {
2445
+ const nodes = [];
2446
+ const relations = [];
2447
+ const modelRe = /model\s+(\w+)\s*\{([^}]+)\}/g;
2448
+ let m;
2449
+ while ((m = modelRe.exec(content)) !== null) {
2450
+ const modelName = m[1];
2451
+ const body = m[2];
2452
+ const columns = [];
2453
+ const lines = body.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("//") && !l.startsWith("@@"));
2454
+ for (const line of lines) {
2455
+ const fieldMatch = line.match(/^(\w+)\s+(\S+)(.*)/);
2456
+ if (!fieldMatch) continue;
2457
+ const fieldName = fieldMatch[1];
2458
+ const fieldType = fieldMatch[2];
2459
+ const rest = fieldMatch[3] ?? "";
2460
+ const commentMatch = rest.match(/\/\/\s*(.+)$/);
2461
+ const comment = commentMatch ? commentMatch[1].trim() : null;
2462
+ const restNoComment = commentMatch ? rest.slice(0, commentMatch.index).trim() : rest;
2463
+ const isPrimary = restNoComment.includes("@id");
2464
+ const isUnique = restNoComment.includes("@unique");
2465
+ const isNullable = fieldType.endsWith("?");
2466
+ const baseType = fieldType.replace(/[?\[\]]/g, "");
2467
+ const defaultMatch = restNoComment.match(/@default\(([^)]+)\)/);
2468
+ const defaultVal = defaultMatch ? defaultMatch[1] : null;
2469
+ const relationMatch = restNoComment.match(/@relation\(([^)]*)\)/);
2470
+ const isRelationField = !!relationMatch;
2471
+ columns.push({
2472
+ name: fieldName,
2473
+ type: fieldType,
2474
+ primary: isPrimary,
2475
+ unique: isUnique,
2476
+ nullable: isNullable,
2477
+ default: defaultVal,
2478
+ isRelation: isRelationField,
2479
+ comment
2480
+ });
2481
+ if (relationMatch) {
2482
+ const relArgs = relationMatch[1];
2483
+ const fieldsMatch = relArgs.match(/fields:\s*\[([^\]]+)\]/);
2484
+ const refsMatch = relArgs.match(/references:\s*\[([^\]]+)\]/);
2485
+ const onDeleteMatch = relArgs.match(/onDelete:\s*(\w+)/);
2486
+ if (fieldsMatch && refsMatch) {
2487
+ const fk = fieldsMatch[1].trim();
2488
+ relations.push({
2489
+ source: modelName,
2490
+ target: baseType,
2491
+ type: "belongs_to",
2492
+ fk,
2493
+ onDelete: onDeleteMatch ? onDeleteMatch[1] : null
2494
+ });
2495
+ }
2496
+ }
2497
+ if (fieldType.endsWith("[]") && !relationMatch) {
2498
+ relations.push({
2499
+ source: modelName,
2500
+ target: baseType,
2501
+ type: "has_many",
2502
+ fk: null,
2503
+ onDelete: null
2504
+ });
2505
+ }
2506
+ }
2507
+ nodes.push({
2508
+ id: modelName,
2509
+ type: "table",
2510
+ name: modelName,
2511
+ columns: columns.filter((c) => !c.isRelation || c.primary)
2512
+ });
2513
+ }
2514
+ return { nodes, relations };
2515
+ }
2516
+ function parseEnums(content) {
2517
+ const nodes = [];
2518
+ const enumRe = /enum\s+(\w+)\s*\{([^}]+)\}/g;
2519
+ let m;
2520
+ while ((m = enumRe.exec(content)) !== null) {
2521
+ const enumName = m[1];
2522
+ const body = m[2];
2523
+ const values = body.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("//"));
2524
+ nodes.push({
2525
+ id: enumName,
2526
+ type: "enum",
2527
+ name: enumName,
2528
+ values
2529
+ });
2530
+ }
2531
+ return nodes;
2532
+ }
2533
+ function detect2(rootDir) {
2534
+ const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
2535
+ return paths?.dbConfig.kind === "prisma" && (0, import_node_fs7.existsSync)(paths.dbConfig.schemaPath);
2536
+ }
2537
+ function generate2(rootDir) {
2538
+ const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
2539
+ if (paths.dbConfig.kind !== "prisma") {
2540
+ return {
2541
+ metadata: { generated: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), layer: "db", source: "none" },
2542
+ nodes: [],
2543
+ edges: [],
2544
+ cross_refs: [],
2545
+ contradictions: [],
2546
+ warnings: [],
2547
+ flagged_edges: [],
2548
+ patterns: { total_tables: 0, total_enums: 0, total_relations: 0 }
2549
+ };
2550
+ }
2551
+ const schemaPath = paths.dbConfig.schemaPath;
2552
+ const content = (0, import_node_fs7.readFileSync)(schemaPath, "utf-8");
2553
+ const { nodes: modelNodes, relations } = parseModels(content);
2554
+ const enumNodes = parseEnums(content);
2555
+ const allNodes = [...modelNodes, ...enumNodes];
2556
+ const edges = relations.map((r) => ({
2557
+ source: r.source,
2558
+ target: r.target,
2559
+ type: r.type,
2560
+ fk: r.fk,
2561
+ onDelete: r.onDelete
2562
+ }));
2563
+ allNodes.sort((a, b) => {
2564
+ if (a.type !== b.type) return a.type === "table" ? -1 : 1;
2565
+ return a.name.localeCompare(b.name);
2566
+ });
2567
+ edges.sort((a, b) => a.source.localeCompare(b.source) || a.target.localeCompare(b.target));
2568
+ return {
2569
+ metadata: {
2570
+ generated: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
2571
+ scope: "prisma-schema",
2572
+ source: schemaPath,
2573
+ provider: "postgresql",
2574
+ layer: "db",
2575
+ total_models: modelNodes.length,
2576
+ total_enums: enumNodes.length,
2577
+ total_relations: edges.length
2578
+ },
2579
+ nodes: allNodes,
2580
+ edges,
2581
+ cross_refs: [],
2582
+ contradictions: [],
2583
+ warnings: [
2584
+ {
2585
+ type: "schema_file_only",
2586
+ detail: "Live DB introspection not yet implemented. Graph derived from prisma/schema.prisma."
2587
+ }
2588
+ ],
2589
+ flagged_edges: [],
2590
+ patterns: {
2591
+ total_tables: modelNodes.length,
2592
+ total_enums: enumNodes.length,
2593
+ total_relations: edges.length,
2594
+ relation_types: {
2595
+ belongs_to: edges.filter((e) => e.type === "belongs_to").length,
2596
+ has_many: edges.filter((e) => e.type === "has_many").length
2597
+ }
2598
+ }
2599
+ };
2600
+ }
2601
+ var prismaSchemaParser = {
2602
+ id: "prisma-schema",
2603
+ layer: "db",
2604
+ detect: detect2,
2605
+ generate: generate2
2606
+ };
2607
+
2608
+ // src/server/graph/parsers/db/sql-migrations.ts
2609
+ var import_node_fs8 = require("node:fs");
2610
+ var import_node_path7 = require("node:path");
2611
+ var PG_TO_PRISMA = {
2612
+ "TEXT": "String",
2613
+ "VARCHAR": "String",
2614
+ "CHAR": "String",
2615
+ "INTEGER": "Int",
2616
+ "INT": "Int",
2617
+ "SMALLINT": "Int",
2618
+ "BIGINT": "BigInt",
2619
+ "SERIAL": "Int",
2620
+ "BOOLEAN": "Boolean",
2621
+ "BOOL": "Boolean",
2622
+ "TIMESTAMP(3)": "DateTime",
2623
+ "TIMESTAMP": "DateTime",
2624
+ "TIMESTAMPTZ": "DateTime",
2625
+ "DATE": "DateTime",
2626
+ "DOUBLE PRECISION": "Float",
2627
+ "FLOAT": "Float",
2628
+ "REAL": "Float",
2629
+ "DECIMAL": "Decimal",
2630
+ "NUMERIC": "Decimal",
2631
+ "JSONB": "Json",
2632
+ "JSON": "Json",
2633
+ "BYTEA": "Bytes",
2634
+ "UUID": "String",
2635
+ "TEXT[]": "String[]"
2636
+ };
2637
+ function pgTypeToPrisma(pgType) {
2638
+ const upper = pgType.toUpperCase().trim();
2639
+ return PG_TO_PRISMA[upper] ?? upper;
2640
+ }
2641
+ var ID = `(?:"[\\w$]+"|[\\w$]+)`;
2642
+ var QID = `(?:${ID}\\.)?${ID}`;
2643
+ function bareName(captured) {
2644
+ const parts = captured.split(".");
2645
+ const last = parts[parts.length - 1];
2646
+ return last.replace(/^"(.*)"$/, "$1").trim();
2647
+ }
2648
+ function parseCreateTable(sql, state) {
2649
+ const re = new RegExp(
2650
+ `CREATE\\s+TABLE\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?(${QID})\\s*\\(([\\s\\S]*?)\\);`,
2651
+ "gi"
2652
+ );
2653
+ let m;
2654
+ while ((m = re.exec(sql)) !== null) {
2655
+ const tableName = bareName(m[1]);
2656
+ const body = m[2];
2657
+ const columns = /* @__PURE__ */ new Map();
2658
+ let primaryCol = null;
2659
+ const inlineFks = [];
2660
+ const lines = splitTopLevelCommas(body);
2661
+ for (const raw of lines) {
2662
+ const trimmed = raw.trim().replace(/,\s*$/, "");
2663
+ if (!trimmed || trimmed.startsWith("--")) continue;
2664
+ const namedPk = trimmed.match(new RegExp(`^CONSTRAINT\\s+${ID}\\s+PRIMARY\\s+KEY\\s*\\(\\s*(${QID})`, "i"));
2665
+ if (namedPk) {
2666
+ primaryCol = bareName(namedPk[1]);
2667
+ continue;
2668
+ }
2669
+ const tablePk = trimmed.match(new RegExp(`^PRIMARY\\s+KEY\\s*\\(\\s*(${QID})`, "i"));
2670
+ if (tablePk) {
2671
+ primaryCol = bareName(tablePk[1]);
2672
+ continue;
2673
+ }
2674
+ if (/^UNIQUE\s*\(/i.test(trimmed)) continue;
2675
+ const namedFk = trimmed.match(new RegExp(
2676
+ `^CONSTRAINT\\s+(${ID})\\s+FOREIGN\\s+KEY\\s*\\(\\s*(${QID})\\s*\\)\\s+REFERENCES\\s+(${QID})\\s*\\(\\s*(${QID})\\s*\\)(?:\\s+ON\\s+DELETE\\s+(\\w+(?:\\s+\\w+)?))?`,
2677
+ "i"
2678
+ ));
2679
+ if (namedFk) {
2680
+ inlineFks.push({
2681
+ constraintName: bareName(namedFk[1]),
2682
+ sourceTable: tableName,
2683
+ sourceColumn: bareName(namedFk[2]),
2684
+ targetTable: bareName(namedFk[3]),
2685
+ targetColumn: bareName(namedFk[4]),
2686
+ onDelete: namedFk[5] ?? null
2687
+ });
2688
+ continue;
2689
+ }
2690
+ const bareFk = trimmed.match(new RegExp(
2691
+ `^FOREIGN\\s+KEY\\s*\\(\\s*(${QID})\\s*\\)\\s+REFERENCES\\s+(${QID})\\s*\\(\\s*(${QID})\\s*\\)(?:\\s+ON\\s+DELETE\\s+(\\w+(?:\\s+\\w+)?))?`,
2692
+ "i"
2693
+ ));
2694
+ if (bareFk) {
2695
+ inlineFks.push({
2696
+ constraintName: `${tableName}_${bareName(bareFk[1])}_fkey`,
2697
+ sourceTable: tableName,
2698
+ sourceColumn: bareName(bareFk[1]),
2699
+ targetTable: bareName(bareFk[2]),
2700
+ targetColumn: bareName(bareFk[3]),
2701
+ onDelete: bareFk[4] ?? null
2702
+ });
2703
+ continue;
2704
+ }
2705
+ if (/^CONSTRAINT\s/i.test(trimmed)) continue;
2706
+ const colMatch = trimmed.match(new RegExp(`^(${ID})\\s+(.+)`, "i"));
2707
+ if (!colMatch) continue;
2708
+ const colName = bareName(colMatch[1]);
2709
+ let rest = colMatch[2];
2710
+ const inlineRefMatch = rest.match(new RegExp(
2711
+ `\\bREFERENCES\\s+(${QID})\\s*\\(\\s*(${QID})\\s*\\)(?:\\s+ON\\s+DELETE\\s+(\\w+(?:\\s+\\w+)?))?`,
2712
+ "i"
2713
+ ));
2714
+ if (inlineRefMatch) {
2715
+ inlineFks.push({
2716
+ constraintName: `${tableName}_${colName}_fkey`,
2717
+ sourceTable: tableName,
2718
+ sourceColumn: colName,
2719
+ targetTable: bareName(inlineRefMatch[1]),
2720
+ targetColumn: bareName(inlineRefMatch[2]),
2721
+ onDelete: inlineRefMatch[3] ?? null
2722
+ });
2723
+ rest = rest.replace(inlineRefMatch[0], "").trim();
2724
+ }
2725
+ const isNotNull = /\bNOT\s+NULL\b/i.test(rest);
2726
+ const isPrimaryKey = /\bPRIMARY\s+KEY\b/i.test(rest);
2727
+ const isUnique = /\bUNIQUE\b/i.test(rest);
2728
+ const defaultMatch = rest.match(/\bDEFAULT\s+(.+?)(?:\s*,?\s*$)/i);
2729
+ const defaultVal = defaultMatch ? defaultMatch[1].trim() : null;
2730
+ let colType = rest.replace(/\bNOT\s+NULL\b/gi, "").replace(/\bPRIMARY\s+KEY\b/gi, "").replace(/\bUNIQUE\b/gi, "").replace(/\bDEFAULT\s+.*/gi, "").trim().replace(/,\s*$/, "").trim();
2731
+ columns.set(colName, {
2732
+ name: colName,
2733
+ type: colType,
2734
+ nullable: !isNotNull && !isPrimaryKey,
2735
+ primary: isPrimaryKey,
2736
+ unique: isUnique,
2737
+ default: defaultVal
2738
+ });
2739
+ if (isPrimaryKey) primaryCol = colName;
2740
+ }
2741
+ if (primaryCol && columns.has(primaryCol)) {
2742
+ columns.get(primaryCol).primary = true;
2743
+ }
2744
+ state.tables.set(tableName, { name: tableName, columns });
2745
+ state.fks.push(...inlineFks);
2746
+ }
2747
+ }
2748
+ function splitTopLevelCommas(body) {
2749
+ const out = [];
2750
+ let depth = 0;
2751
+ let buf = "";
2752
+ let inString = null;
2753
+ for (const ch of body) {
2754
+ if (inString) {
2755
+ buf += ch;
2756
+ if (ch === inString) inString = null;
2757
+ continue;
2758
+ }
2759
+ if (ch === "'" || ch === '"') {
2760
+ inString = ch;
2761
+ buf += ch;
2762
+ continue;
2763
+ }
2764
+ if (ch === "(") depth++;
2765
+ else if (ch === ")") depth--;
2766
+ if (ch === "," && depth === 0) {
2767
+ out.push(buf);
2768
+ buf = "";
2769
+ continue;
2770
+ }
2771
+ buf += ch;
2772
+ }
2773
+ if (buf.trim()) out.push(buf);
2774
+ return out;
2775
+ }
2776
+ function parseCreateEnum(sql, state) {
2777
+ const re = new RegExp(
2778
+ `CREATE\\s+TYPE\\s+(${QID})\\s+AS\\s+ENUM\\s*\\(([^)]+)\\)`,
2779
+ "gi"
2780
+ );
2781
+ let m;
2782
+ while ((m = re.exec(sql)) !== null) {
2783
+ const enumName = bareName(m[1]);
2784
+ const valuesStr = m[2];
2785
+ const values = new Set(
2786
+ valuesStr.split(",").map((v) => v.trim().replace(/^'(.*)'$/, "$1")).filter(Boolean)
2787
+ );
2788
+ state.enums.set(enumName, { name: enumName, values });
2789
+ }
2790
+ }
2791
+ function parseAlterTable(sql, state) {
2792
+ const addColRe = new RegExp(
2793
+ `ALTER\\s+TABLE\\s+(${QID})\\s+ADD\\s+COLUMN\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?(${QID})\\s+(.+?);`,
2794
+ "gi"
2795
+ );
2796
+ let m;
2797
+ while ((m = addColRe.exec(sql)) !== null) {
2798
+ const tableName = bareName(m[1]);
2799
+ const colName = bareName(m[2]);
2800
+ let rest = m[3];
2801
+ const table = state.tables.get(tableName);
2802
+ if (!table) continue;
2803
+ const isNotNull = /\bNOT\s+NULL\b/i.test(rest);
2804
+ const defaultMatch = rest.match(/\bDEFAULT\s+(.+?)$/i);
2805
+ const defaultVal = defaultMatch ? defaultMatch[1].trim() : null;
2806
+ let colType = rest.replace(/\bNOT\s+NULL\b/gi, "").replace(/\bDEFAULT\s+.*/gi, "").trim();
2807
+ table.columns.set(colName, {
2808
+ name: colName,
2809
+ type: colType,
2810
+ nullable: !isNotNull,
2811
+ primary: false,
2812
+ unique: false,
2813
+ default: defaultVal
2814
+ });
2815
+ }
2816
+ const dropColRe = new RegExp(
2817
+ `ALTER\\s+TABLE\\s+(${QID})\\s+DROP\\s+COLUMN\\s+(?:IF\\s+EXISTS\\s+)?(${QID})`,
2818
+ "gi"
2819
+ );
2820
+ while ((m = dropColRe.exec(sql)) !== null) {
2821
+ const table = state.tables.get(bareName(m[1]));
2822
+ if (table) table.columns.delete(bareName(m[2]));
2823
+ }
2824
+ const fkRe = new RegExp(
2825
+ `ALTER\\s+TABLE\\s+(${QID})\\s+ADD\\s+CONSTRAINT\\s+(${ID})\\s+FOREIGN\\s+KEY\\s*\\(\\s*(${QID})\\s*\\)\\s+REFERENCES\\s+(${QID})\\s*\\(\\s*(${QID})\\s*\\)(?:\\s+ON\\s+DELETE\\s+(\\w+(?:\\s+\\w+)?))?`,
2826
+ "gi"
2827
+ );
2828
+ while ((m = fkRe.exec(sql)) !== null) {
2829
+ state.fks.push({
2830
+ constraintName: bareName(m[2]),
2831
+ sourceTable: bareName(m[1]),
2832
+ sourceColumn: bareName(m[3]),
2833
+ targetTable: bareName(m[4]),
2834
+ targetColumn: bareName(m[5]),
2835
+ onDelete: m[6] ?? null
2836
+ });
2837
+ }
2838
+ }
2839
+ function parseAlterEnum(sql, state) {
2840
+ const re = new RegExp(
2841
+ `ALTER\\s+TYPE\\s+(${QID})\\s+ADD\\s+VALUE\\s+'([^']+)'`,
2842
+ "gi"
2843
+ );
2844
+ let m;
2845
+ while ((m = re.exec(sql)) !== null) {
2846
+ const en = state.enums.get(bareName(m[1]));
2847
+ if (en) en.values.add(m[2]);
2848
+ }
2849
+ }
2850
+ function parseDropTable(sql, state) {
2851
+ const re = new RegExp(
2852
+ `DROP\\s+TABLE\\s+(?:IF\\s+EXISTS\\s+)?(${QID})`,
2853
+ "gi"
2854
+ );
2855
+ let m;
2856
+ while ((m = re.exec(sql)) !== null) {
2857
+ const dropped = bareName(m[1]);
2858
+ state.tables.delete(dropped);
2859
+ state.fks = state.fks.filter((fk) => fk.sourceTable !== dropped && fk.targetTable !== dropped);
2860
+ }
2861
+ }
2862
+ function parseUniqueIndex(sql, state) {
2863
+ const re = new RegExp(
2864
+ `CREATE\\s+UNIQUE\\s+INDEX\\s+(?:(?:IF\\s+NOT\\s+EXISTS\\s+)?(?:${ID}\\s+)?)?ON\\s+(${QID})\\s*\\(\\s*(${QID})\\s*\\)`,
2865
+ "gi"
2866
+ );
2867
+ let m;
2868
+ while ((m = re.exec(sql)) !== null) {
2869
+ const tableName = bareName(m[1]);
2870
+ const colName = bareName(m[2]);
2871
+ const table = state.tables.get(tableName);
2872
+ const col = table?.columns.get(colName);
2873
+ if (col) col.unique = true;
2874
+ if (!state.uniqueIndexes.has(tableName)) state.uniqueIndexes.set(tableName, /* @__PURE__ */ new Set());
2875
+ state.uniqueIndexes.get(tableName).add(colName);
2876
+ }
2877
+ }
2878
+ function discoverMigrationFiles(migrationsDir) {
2879
+ if (!(0, import_node_fs8.existsSync)(migrationsDir)) return [];
2880
+ const out = [];
2881
+ const entries = (0, import_node_fs8.readdirSync)(migrationsDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
2882
+ for (const entry of entries) {
2883
+ if (entry.isDirectory()) {
2884
+ const sqlPath = (0, import_node_path7.join)(migrationsDir, entry.name, "migration.sql");
2885
+ if ((0, import_node_fs8.existsSync)(sqlPath)) out.push(sqlPath);
2886
+ } else if (entry.isFile() && entry.name.endsWith(".sql")) {
2887
+ out.push((0, import_node_path7.join)(migrationsDir, entry.name));
2888
+ }
2889
+ }
2890
+ return out;
2891
+ }
2892
+ function parseMigrations(migrationsDir) {
2893
+ const state = {
2894
+ tables: /* @__PURE__ */ new Map(),
2895
+ enums: /* @__PURE__ */ new Map(),
2896
+ fks: [],
2897
+ uniqueIndexes: /* @__PURE__ */ new Map()
2898
+ };
2899
+ if (!migrationsDir) return state;
2900
+ for (const sqlPath of discoverMigrationFiles(migrationsDir)) {
2901
+ const sql = (0, import_node_fs8.readFileSync)(sqlPath, "utf-8");
2902
+ parseCreateEnum(sql, state);
2903
+ parseCreateTable(sql, state);
2904
+ parseAlterTable(sql, state);
2905
+ parseAlterEnum(sql, state);
2906
+ parseDropTable(sql, state);
2907
+ parseUniqueIndex(sql, state);
2908
+ }
2909
+ return state;
2910
+ }
2911
+ function loadPrismaState(schemaPath) {
2912
+ if (!schemaPath || !(0, import_node_fs8.existsSync)(schemaPath)) return null;
2913
+ const content = (0, import_node_fs8.readFileSync)(schemaPath, "utf-8");
2914
+ const tables = /* @__PURE__ */ new Map();
2915
+ const enums = /* @__PURE__ */ new Map();
2916
+ const relations = [];
2917
+ const modelRe = /model\s+(\w+)\s*\{([^}]+)\}/g;
2918
+ let m;
2919
+ while ((m = modelRe.exec(content)) !== null) {
2920
+ const modelName = m[1];
2921
+ const body = m[2];
2922
+ const cols = [];
2923
+ for (const line of body.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("//") && !l.startsWith("@@"))) {
2924
+ const fm = line.match(/^(\w+)\s+(\S+)(.*)/);
2925
+ if (!fm) continue;
2926
+ const fieldName = fm[1];
2927
+ const fieldType = fm[2];
2928
+ const rest = fm[3] ?? "";
2929
+ const baseType = fieldType.replace(/[?\[\]]/g, "");
2930
+ const isRelation = rest.includes("@relation");
2931
+ if (isRelation) {
2932
+ const relArgs = rest.match(/@relation\(([^)]*)\)/)?.[1] ?? "";
2933
+ const fieldsMatch = relArgs.match(/fields:\s*\[([^\]]+)\]/);
2934
+ if (fieldsMatch) {
2935
+ relations.push({ source: modelName, target: baseType, fk: fieldsMatch[1].trim() });
2936
+ }
2937
+ continue;
2938
+ }
2939
+ if (fieldType.endsWith("[]") || fieldType.endsWith("?") && content.includes(`model ${baseType}`)) {
2940
+ if (new RegExp(`model\\s+${baseType}\\s*\\{`).test(content)) continue;
2941
+ }
2942
+ cols.push({
2943
+ name: fieldName,
2944
+ type: fieldType.replace("?", ""),
2945
+ nullable: fieldType.endsWith("?") || fieldType.includes("?"),
2946
+ primary: rest.includes("@id"),
2947
+ unique: rest.includes("@unique")
2948
+ });
2949
+ }
2950
+ tables.set(modelName, { columns: cols });
2951
+ }
2952
+ const enumRe = /enum\s+(\w+)\s*\{([^}]+)\}/g;
2953
+ while ((m = enumRe.exec(content)) !== null) {
2954
+ const values = m[2].split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("//"));
2955
+ enums.set(m[1], values);
2956
+ }
2957
+ return { tables, enums, relations };
2958
+ }
2959
+ function verify(sqlState, prisma) {
2960
+ const contradictions = [];
2961
+ const flaggedEdges = [];
2962
+ for (const [name] of sqlState.tables) {
2963
+ if (!prisma.tables.has(name)) {
2964
+ contradictions.push({
2965
+ entity: name,
2966
+ source_a: "sql-migrations",
2967
+ source_b: "prisma-schema",
2968
+ detail: `Table "${name}" exists in migrations but not in schema.prisma`
2969
+ });
2970
+ }
2971
+ }
2972
+ for (const [name] of prisma.tables) {
2973
+ if (!sqlState.tables.has(name)) {
2974
+ contradictions.push({
2975
+ entity: name,
2976
+ source_a: "prisma-schema",
2977
+ source_b: "sql-migrations",
2978
+ detail: `Table "${name}" in schema.prisma has no CREATE TABLE in migrations`
2979
+ });
2980
+ }
2981
+ }
2982
+ for (const [tableName, prismaTable] of prisma.tables) {
2983
+ const sqlTable = sqlState.tables.get(tableName);
2984
+ if (!sqlTable) continue;
2985
+ for (const prismaCol of prismaTable.columns) {
2986
+ const sqlCol = sqlTable.columns.get(prismaCol.name);
2987
+ if (!sqlCol) {
2988
+ contradictions.push({
2989
+ entity: `${tableName}.${prismaCol.name}`,
2990
+ source_a: "prisma-schema",
2991
+ source_b: "sql-migrations",
2992
+ detail: `Column "${tableName}.${prismaCol.name}" in schema.prisma but not in migrations`
2993
+ });
2994
+ continue;
2995
+ }
2996
+ const mappedSqlType = pgTypeToPrisma(sqlCol.type);
2997
+ const prismaBaseType = prismaCol.type.replace(/[?\[\]]/g, "");
2998
+ if (mappedSqlType !== prismaBaseType && mappedSqlType !== prismaCol.type) {
2999
+ if (!sqlState.enums.has(prismaBaseType)) {
3000
+ contradictions.push({
3001
+ entity: `${tableName}.${prismaCol.name}`,
3002
+ source_a: "sql-migrations",
3003
+ source_b: "prisma-schema",
3004
+ detail: `Column "${tableName}.${prismaCol.name}": SQL type "${sqlCol.type}" (\u2192${mappedSqlType}) vs Prisma type "${prismaCol.type}"`
3005
+ });
3006
+ }
3007
+ }
3008
+ if (sqlCol.nullable !== prismaCol.nullable) {
3009
+ contradictions.push({
3010
+ entity: `${tableName}.${prismaCol.name}`,
3011
+ source_a: "sql-migrations",
3012
+ source_b: "prisma-schema",
3013
+ detail: `Column "${tableName}.${prismaCol.name}": ${sqlCol.nullable ? "nullable" : "NOT NULL"} in SQL vs ${prismaCol.nullable ? "optional" : "required"} in Prisma`
3014
+ });
3015
+ }
3016
+ }
3017
+ for (const [colName] of sqlTable.columns) {
3018
+ const inPrisma = prismaTable.columns.some((c) => c.name === colName);
3019
+ if (!inPrisma) {
3020
+ contradictions.push({
3021
+ entity: `${tableName}.${colName}`,
3022
+ source_a: "sql-migrations",
3023
+ source_b: "prisma-schema",
3024
+ detail: `Column "${tableName}.${colName}" in migrations but not in schema.prisma`
3025
+ });
3026
+ }
3027
+ }
3028
+ }
3029
+ for (const [name, sqlEnum] of sqlState.enums) {
3030
+ const prismaValues = prisma.enums.get(name);
3031
+ if (!prismaValues) {
3032
+ contradictions.push({
3033
+ entity: name,
3034
+ source_a: "sql-migrations",
3035
+ source_b: "prisma-schema",
3036
+ detail: `Enum "${name}" exists in migrations but not in schema.prisma`
3037
+ });
3038
+ continue;
3039
+ }
3040
+ const prismaSet = new Set(prismaValues);
3041
+ for (const val of sqlEnum.values) {
3042
+ if (!prismaSet.has(val)) {
3043
+ contradictions.push({
3044
+ entity: `${name}.${val}`,
3045
+ source_a: "sql-migrations",
3046
+ source_b: "prisma-schema",
3047
+ detail: `Enum "${name}": value "${val}" in migrations but not in schema.prisma`
3048
+ });
3049
+ }
3050
+ }
3051
+ for (const val of prismaValues) {
3052
+ if (!sqlEnum.values.has(val)) {
3053
+ contradictions.push({
3054
+ entity: `${name}.${val}`,
3055
+ source_a: "prisma-schema",
3056
+ source_b: "sql-migrations",
3057
+ detail: `Enum "${name}": value "${val}" in schema.prisma but not in migrations`
3058
+ });
3059
+ }
3060
+ }
3061
+ }
3062
+ const prismaFkSet = new Set(prisma.relations.map((r) => `${r.source}|${r.target}|${r.fk}`));
3063
+ for (const fk of sqlState.fks) {
3064
+ const key = `${fk.sourceTable}|${fk.targetTable}|${fk.sourceColumn}`;
3065
+ if (!prismaFkSet.has(key)) {
3066
+ flaggedEdges.push({
3067
+ source: fk.sourceTable,
3068
+ target: fk.targetTable,
3069
+ type: "belongs_to",
3070
+ label: `FK "${fk.constraintName}" (${fk.sourceColumn}\u2192${fk.targetTable}) in migrations but no @relation in schema.prisma`,
3071
+ confidence: "high"
3072
+ });
3073
+ }
3074
+ }
3075
+ return { contradictions, flaggedEdges };
3076
+ }
3077
+ function migrationsDirFor(rootDir) {
3078
+ const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
3079
+ if (!paths) return null;
3080
+ if (paths.dbConfig.kind === "prisma" || paths.dbConfig.kind === "sql-migrations") {
3081
+ return paths.dbConfig.migrationsDir;
3082
+ }
3083
+ return null;
3084
+ }
3085
+ function schemaPathFor(rootDir) {
3086
+ const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
3087
+ if (!paths) return null;
3088
+ return paths.dbConfig.kind === "prisma" ? paths.dbConfig.schemaPath : null;
3089
+ }
3090
+ function detect3(rootDir) {
3091
+ const dir = migrationsDirFor(rootDir);
3092
+ if (!dir) return false;
3093
+ return discoverMigrationFiles(dir).length > 0;
3094
+ }
3095
+ function generate3(rootDir) {
3096
+ const migrationsDir = migrationsDirFor(rootDir);
3097
+ const sqlState = parseMigrations(migrationsDir);
3098
+ const prisma = loadPrismaState(schemaPathFor(rootDir));
3099
+ const prismaTableIds = prisma ? new Set(prisma.tables.keys()) : /* @__PURE__ */ new Set();
3100
+ const prismaEnumIds = prisma ? new Set(prisma.enums.keys()) : /* @__PURE__ */ new Set();
3101
+ const nodes = [];
3102
+ for (const [name, table] of sqlState.tables) {
3103
+ if (prismaTableIds.has(name)) continue;
3104
+ nodes.push({
3105
+ id: name,
3106
+ type: "table",
3107
+ name,
3108
+ source: "sql",
3109
+ columns: [...table.columns.values()].map((c) => ({
3110
+ name: c.name,
3111
+ type: c.type,
3112
+ primary: c.primary,
3113
+ unique: c.unique,
3114
+ nullable: c.nullable,
3115
+ default: c.default,
3116
+ isRelation: false,
3117
+ comment: null
3118
+ }))
3119
+ });
3120
+ }
3121
+ for (const [name, sqlEnum] of sqlState.enums) {
3122
+ if (prismaEnumIds.has(name)) continue;
3123
+ nodes.push({
3124
+ id: name,
3125
+ type: "enum",
3126
+ name,
3127
+ source: "sql",
3128
+ values: [...sqlEnum.values]
3129
+ });
3130
+ }
3131
+ const sqlOnlyTables = new Set(nodes.filter((n) => n.type === "table").map((n) => n.id));
3132
+ const edges = sqlState.fks.filter((fk) => sqlOnlyTables.has(fk.sourceTable)).map((fk) => ({
3133
+ source: fk.sourceTable,
3134
+ target: fk.targetTable,
3135
+ type: "belongs_to",
3136
+ fk: fk.sourceColumn,
3137
+ onDelete: fk.onDelete
3138
+ }));
3139
+ const { contradictions, flaggedEdges } = prisma ? verify(sqlState, prisma) : { contradictions: [], flaggedEdges: [] };
3140
+ return {
3141
+ metadata: {
3142
+ generated: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
3143
+ scope: "sql-migrations",
3144
+ source: migrationsDir ?? "none",
3145
+ layer: "db",
3146
+ sql_tables: sqlState.tables.size,
3147
+ sql_enums: sqlState.enums.size,
3148
+ sql_fks: sqlState.fks.length,
3149
+ additive_nodes: nodes.length,
3150
+ contradictions_found: contradictions.length,
3151
+ flagged_edges_found: flaggedEdges.length
3152
+ },
3153
+ nodes,
3154
+ edges,
3155
+ cross_refs: [],
3156
+ contradictions,
3157
+ warnings: [],
3158
+ flagged_edges: flaggedEdges
3159
+ };
3160
+ }
3161
+ var sqlMigrationsParser = {
3162
+ id: "sql-migrations",
3163
+ layer: "db",
3164
+ detect: detect3,
3165
+ generate: generate3
3166
+ };
3167
+
3168
+ // src/server/graph/core/api-route-matching.ts
3169
+ init_launch_kit_paths();
3170
+ function loadApiRoutesFromOutput(apiOutput) {
3171
+ const routes = [];
3172
+ for (const n of apiOutput.nodes) {
3173
+ const path = n.path;
3174
+ if (!path || typeof path !== "string") continue;
3175
+ routes.push({
3176
+ path,
3177
+ nodeId: n.id,
3178
+ segments: path.split("/").filter(Boolean)
3179
+ });
3180
+ }
3181
+ return routes;
3182
+ }
3183
+ function buildApiPathMap(routes) {
3184
+ const map = /* @__PURE__ */ new Map();
3185
+ for (const r of routes) {
3186
+ if (!map.has(r.path)) map.set(r.path, r.nodeId);
3187
+ const trimmed = r.path.replace(/\/\*[^/]+\?$/, "");
3188
+ if (trimmed && trimmed !== r.path && !map.has(trimmed)) {
3189
+ map.set(trimmed, r.nodeId);
3190
+ }
3191
+ }
3192
+ return map;
3193
+ }
3194
+ function normalizeFetchUrl(raw) {
3195
+ let s = raw.replace(/^`|`$/g, "");
3196
+ const qIdx = s.indexOf("?");
3197
+ if (qIdx >= 0) s = s.slice(0, qIdx);
3198
+ const hIdx = s.indexOf("#");
3199
+ if (hIdx >= 0) s = s.slice(0, hIdx);
3200
+ let hadInterpolation = false;
3201
+ s = s.replace(/\$\{([^}]+)\}/g, (_, expr) => {
3202
+ hadInterpolation = true;
3203
+ const cleaned = expr.trim();
3204
+ const last = cleaned.split(".").pop() ?? cleaned;
3205
+ const name = last.replace(/[^\w]/g, "") || "param";
3206
+ return ":" + name;
3207
+ });
3208
+ s = s.replace(/\/+/g, "/");
3209
+ if (s.length > 1 && s.endsWith("/")) s = s.slice(0, -1);
3210
+ return { path: s || "/", hadInterpolation };
3211
+ }
3212
+ function scoreApiRouteMatch(candidate, known) {
3213
+ let score = 0;
3214
+ let i = 0, j = 0;
3215
+ while (i < candidate.length && j < known.length) {
3216
+ const a = candidate[i];
3217
+ const b = known[j];
3218
+ if (b.startsWith("*") && b.endsWith("?")) {
3219
+ score += 1;
3220
+ return score;
3221
+ }
3222
+ if (b.startsWith("*")) {
3223
+ const remaining = candidate.length - i;
3224
+ if (remaining < 1) return -1;
3225
+ score += 1 + remaining;
3226
+ return score;
3227
+ }
3228
+ if (a === b) {
3229
+ score += 3;
3230
+ i++;
3231
+ j++;
3232
+ continue;
3233
+ }
3234
+ if (a.startsWith(":") && b.startsWith(":")) {
3235
+ score += 2;
3236
+ i++;
3237
+ j++;
3238
+ continue;
3239
+ }
3240
+ if (a.startsWith(":") || b.startsWith(":")) {
3241
+ score += 1;
3242
+ i++;
3243
+ j++;
3244
+ continue;
3245
+ }
3246
+ return -1;
3247
+ }
3248
+ if (i === candidate.length) {
3249
+ while (j < known.length) {
3250
+ const b = known[j];
3251
+ if (b.startsWith("*") && b.endsWith("?")) {
3252
+ score += 1;
3253
+ j++;
3254
+ continue;
3255
+ }
3256
+ return -1;
3257
+ }
3258
+ return score;
3259
+ }
3260
+ return -1;
3261
+ }
3262
+ function resolveFetchCall(call, apiPathMap, apiRoutes) {
3263
+ const raw = call.url;
3264
+ if (/^(https?:)?\/\//i.test(raw)) {
3265
+ return { kind: "external", normalizedUrl: raw };
3266
+ }
3267
+ if (call.isConcat) {
3268
+ return { kind: "dynamic", normalizedUrl: raw };
3269
+ }
3270
+ const { path, hadInterpolation } = normalizeFetchUrl(raw);
3271
+ if (!path.startsWith("/")) {
3272
+ return { kind: "unresolved", normalizedUrl: path };
3273
+ }
3274
+ const segs = path.split("/").filter(Boolean);
3275
+ if (hadInterpolation && segs.length > 0 && segs[0].startsWith(":")) {
3276
+ return { kind: "dynamic", normalizedUrl: path };
3277
+ }
3278
+ const exact = apiPathMap.get(path);
3279
+ if (exact) return { kind: "resolved", nodeId: exact, normalizedUrl: path };
3280
+ let bestScore = -1;
3281
+ let bestId = null;
3282
+ for (const r of apiRoutes) {
3283
+ const score = scoreApiRouteMatch(segs, r.segments);
3284
+ if (score > bestScore) {
3285
+ bestScore = score;
3286
+ bestId = r.nodeId;
3287
+ }
3288
+ }
3289
+ if (bestId && bestScore > 0) {
3290
+ return { kind: "resolved", nodeId: bestId, normalizedUrl: path };
3291
+ }
3292
+ return { kind: "unresolved", normalizedUrl: path };
3293
+ }
3294
+ function resolveUrlPath(urlPath, apiPathMap, apiRoutes) {
3295
+ const { path, hadInterpolation } = normalizeFetchUrl(urlPath);
3296
+ if (!path.startsWith("/")) {
3297
+ return { kind: "unresolved", normalizedUrl: path };
3298
+ }
3299
+ const segs = path.split("/").filter(Boolean);
3300
+ if (hadInterpolation && segs.length > 0 && segs[0].startsWith(":")) {
3301
+ return { kind: "dynamic", normalizedUrl: path };
3302
+ }
3303
+ const exact = apiPathMap.get(path);
3304
+ if (exact) return { kind: "resolved", nodeId: exact, normalizedUrl: path };
3305
+ let bestScore = -1;
3306
+ let bestId = null;
3307
+ for (const r of apiRoutes) {
3308
+ const score = scoreApiRouteMatch(segs, r.segments);
3309
+ if (score > bestScore) {
3310
+ bestScore = score;
3311
+ bestId = r.nodeId;
3312
+ }
3313
+ }
3314
+ if (bestId && bestScore > 0) {
3315
+ return { kind: "resolved", nodeId: bestId, normalizedUrl: path };
3316
+ }
3317
+ return { kind: "unresolved", normalizedUrl: path };
3318
+ }
3319
+
3320
+ // src/server/graph/parsers/crosslayer/fetch-resolver.ts
3321
+ var fetchResolverParser = {
3322
+ id: "fetch-resolver",
3323
+ layer: "crosslayer",
3324
+ concern: "api-binding",
3325
+ detect(_rootDir) {
3326
+ return true;
3327
+ },
3328
+ generate(_rootDir, layerOutputs) {
3329
+ const uiOutput = layerOutputs.get("ui");
3330
+ const apiOutput = layerOutputs.get("api");
3331
+ if (!uiOutput || !apiOutput) {
3332
+ return { cross_refs: [], flagged_edges: [], warnings: [] };
3333
+ }
3334
+ const apiRoutes = loadApiRoutesFromOutput(apiOutput);
3335
+ const apiPathMap = buildApiPathMap(apiRoutes);
3336
+ const fetchCallEntries = uiOutput.patterns?.fetch_calls ?? [];
3337
+ if (fetchCallEntries.length === 0) {
3338
+ return { cross_refs: [], flagged_edges: [], warnings: [] };
3339
+ }
3340
+ const includeExternal = process.env.LAUNCH_CHART_INCLUDE_EXTERNAL_FETCHES === "1";
3341
+ const crossRefs = [];
3342
+ const flaggedEdges = [];
3343
+ const seen = /* @__PURE__ */ new Set();
3344
+ let resolvedCount = 0;
3345
+ let dynamicCount = 0;
3346
+ let unresolvedCount = 0;
3347
+ let externalCount = 0;
3348
+ for (const entry of fetchCallEntries) {
3349
+ for (const call of entry.calls) {
3350
+ const result = resolveFetchCall(call, apiPathMap, apiRoutes);
3351
+ const methodTag = call.method ?? (call.kind === "fetch" ? "GET?" : "?");
3352
+ if (result.kind === "resolved" && result.nodeId) {
3353
+ const key = `${entry.nodeId}\u2192${result.nodeId}\u2192calls_api`;
3354
+ if (seen.has(key)) continue;
3355
+ seen.add(key);
3356
+ crossRefs.push({
3357
+ source: entry.nodeId,
3358
+ target: result.nodeId,
3359
+ type: "calls_api",
3360
+ layer: "api"
3361
+ });
3362
+ resolvedCount++;
3363
+ continue;
3364
+ }
3365
+ if (result.kind === "dynamic") {
3366
+ dynamicCount++;
3367
+ flaggedEdges.push({
3368
+ source: entry.nodeId,
3369
+ target: "DYNAMIC",
3370
+ type: "calls_api",
3371
+ label: call.isConcat ? `${methodTag} fetch with concat: ${call.url}` : `${methodTag} fetch with template: ${call.url}`,
3372
+ confidence: call.isConcat ? "low" : "medium"
3373
+ });
3374
+ continue;
3375
+ }
3376
+ if (result.kind === "external") {
3377
+ externalCount++;
3378
+ if (!includeExternal) continue;
3379
+ flaggedEdges.push({
3380
+ source: entry.nodeId,
3381
+ target: "EXTERNAL",
3382
+ type: "calls_external",
3383
+ label: `${methodTag} external fetch: ${call.url}`,
3384
+ confidence: "high"
3385
+ });
3386
+ continue;
3387
+ }
3388
+ unresolvedCount++;
3389
+ flaggedEdges.push({
3390
+ source: entry.nodeId,
3391
+ target: "UNRESOLVED",
3392
+ type: "calls_api",
3393
+ label: `${methodTag} fetch to unknown path: ${result.normalizedUrl}`,
3394
+ confidence: "medium"
3395
+ });
3396
+ }
3397
+ }
3398
+ return {
3399
+ cross_refs: crossRefs,
3400
+ flagged_edges: flaggedEdges,
3401
+ warnings: [],
3402
+ patterns: {
3403
+ api_call_detection: {
3404
+ resolved: resolvedCount,
3405
+ dynamic: dynamicCount,
3406
+ unresolved: unresolvedCount,
3407
+ external: externalCount
3408
+ }
3409
+ }
3410
+ };
3411
+ }
3412
+ };
3413
+
3414
+ // src/server/graph/parsers/crosslayer/api-annotations.ts
3415
+ var import_node_fs9 = require("node:fs");
3416
+ var import_node_path8 = require("node:path");
3417
+ var API_ANNOTATION_RE = /@api\s+(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+(\/\S+)/g;
3418
+ function toNodeId2(srcDir, rootDir, absPath) {
3419
+ const relFromSrc = (0, import_node_path8.relative)(srcDir, absPath).replace(/\\/g, "/");
3420
+ if (relFromSrc.startsWith("..")) {
3421
+ return (0, import_node_path8.relative)(rootDir, absPath).replace(/\\/g, "/");
3422
+ }
3423
+ return relFromSrc;
3424
+ }
3425
+ var apiAnnotationsParser = {
3426
+ id: "api-annotations",
3427
+ layer: "crosslayer",
3428
+ concern: "api-binding",
3429
+ detect(rootDir) {
3430
+ return resolveProjectPaths(rootDir, loadConfig(rootDir)) !== null;
3431
+ },
3432
+ generate(rootDir, layerOutputs) {
3433
+ const apiOutput = layerOutputs.get("api");
3434
+ if (!apiOutput) {
3435
+ return { cross_refs: [], flagged_edges: [], warnings: [] };
3436
+ }
3437
+ const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
3438
+ if (!paths) {
3439
+ return { cross_refs: [], flagged_edges: [], warnings: [] };
3440
+ }
3441
+ const uiOutput = layerOutputs.get("ui");
3442
+ const uiNodeIds = new Set(uiOutput?.nodes.map((n) => n.id) ?? []);
3443
+ const apiRoutes = loadApiRoutesFromOutput(apiOutput);
3444
+ const apiPathMap = buildApiPathMap(apiRoutes);
3445
+ const srcDir = paths.srcDir;
3446
+ const seen = /* @__PURE__ */ new Set();
3447
+ const files = [];
3448
+ for (const root of paths.srcRoots) {
3449
+ for (const f of walk(root, [".ts", ".tsx"])) {
3450
+ if (!seen.has(f)) {
3451
+ seen.add(f);
3452
+ files.push(f);
3453
+ }
3454
+ }
3455
+ }
3456
+ for (const conv of paths.conventionFiles) {
3457
+ if (!seen.has(conv)) {
3458
+ seen.add(conv);
3459
+ files.push(conv);
3460
+ }
3461
+ }
3462
+ const crossRefs = [];
3463
+ const flaggedEdges = [];
3464
+ const seenEdge = /* @__PURE__ */ new Set();
3465
+ for (const absPath of files) {
3466
+ const content = (0, import_node_fs9.readFileSync)(absPath, "utf-8");
3467
+ const sourceId = toNodeId2(srcDir, rootDir, absPath);
3468
+ if (!uiNodeIds.has(sourceId)) continue;
3469
+ let match;
3470
+ API_ANNOTATION_RE.lastIndex = 0;
3471
+ while ((match = API_ANNOTATION_RE.exec(content)) !== null) {
3472
+ const method = match[1];
3473
+ const urlPath = match[2];
3474
+ const result = resolveUrlPath(urlPath, apiPathMap, apiRoutes);
3475
+ if (result.kind === "resolved" && result.nodeId) {
3476
+ const key = `${sourceId}|${result.nodeId}|calls_api`;
3477
+ if (seenEdge.has(key)) continue;
3478
+ seenEdge.add(key);
3479
+ crossRefs.push({
3480
+ source: sourceId,
3481
+ target: result.nodeId,
3482
+ type: "calls_api",
3483
+ layer: "api"
3484
+ });
3485
+ } else {
3486
+ flaggedEdges.push({
3487
+ source: sourceId,
3488
+ target: "UNRESOLVED",
3489
+ type: "annotation_unresolved",
3490
+ label: `@api ${method} ${urlPath} \u2014 no matching API route found`,
3491
+ confidence: "high"
3492
+ });
3493
+ }
3494
+ }
3495
+ }
3496
+ return {
3497
+ cross_refs: crossRefs,
3498
+ flagged_edges: flaggedEdges,
3499
+ warnings: [],
3500
+ patterns: {
3501
+ annotations_found: crossRefs.length + flaggedEdges.length,
3502
+ annotations_resolved: crossRefs.length,
3503
+ annotations_unresolved: flaggedEdges.length
3504
+ }
3505
+ };
3506
+ }
3507
+ };
3508
+
3509
+ // src/server/graph/parsers/crosslayer/url-literal-scanner.ts
3510
+ var import_node_fs10 = require("node:fs");
3511
+ var import_node_path9 = require("node:path");
3512
+ var URL_LITERAL_RE = /['"`](\/[a-zA-Z][^'"`\s]*?)['"`]/g;
3513
+ function toNodeId3(srcDir, rootDir, absPath) {
3514
+ const relFromSrc = (0, import_node_path9.relative)(srcDir, absPath).replace(/\\/g, "/");
3515
+ if (relFromSrc.startsWith("..")) {
3516
+ return (0, import_node_path9.relative)(rootDir, absPath).replace(/\\/g, "/");
3517
+ }
3518
+ return relFromSrc;
3519
+ }
3520
+ var urlLiteralScannerParser = {
3521
+ id: "url-literal-scanner",
3522
+ layer: "crosslayer",
3523
+ concern: "api-binding",
3524
+ detect(rootDir) {
3525
+ const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
3526
+ return paths !== null;
3527
+ },
3528
+ generate(rootDir, layerOutputs) {
3529
+ const apiOutput = layerOutputs.get("api");
3530
+ if (!apiOutput) {
3531
+ return { cross_refs: [], flagged_edges: [], warnings: [] };
3532
+ }
3533
+ const uiOutput = layerOutputs.get("ui");
3534
+ const uiNodeIds = new Set(uiOutput?.nodes.map((n) => n.id) ?? []);
3535
+ const apiRoutes = loadApiRoutesFromOutput(apiOutput);
3536
+ const apiPathMap = buildApiPathMap(apiRoutes);
3537
+ const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
3538
+ const srcDir = paths.srcDir;
3539
+ const seenFile = /* @__PURE__ */ new Set();
3540
+ const files = [];
3541
+ for (const root of paths.srcRoots) {
3542
+ for (const f of walk(root, [".ts", ".tsx"])) {
3543
+ if (!seenFile.has(f)) {
3544
+ seenFile.add(f);
3545
+ files.push(f);
3546
+ }
3547
+ }
3548
+ }
3549
+ for (const conv of paths.conventionFiles) {
3550
+ if (!seenFile.has(conv)) {
3551
+ seenFile.add(conv);
3552
+ files.push(conv);
3553
+ }
3554
+ }
3555
+ const crossRefs = [];
3556
+ const seen = /* @__PURE__ */ new Set();
3557
+ for (const absPath of files) {
3558
+ const sourceId = toNodeId3(srcDir, rootDir, absPath);
3559
+ if (!uiNodeIds.has(sourceId)) continue;
3560
+ const content = (0, import_node_fs10.readFileSync)(absPath, "utf-8");
3561
+ let match;
3562
+ URL_LITERAL_RE.lastIndex = 0;
3563
+ while ((match = URL_LITERAL_RE.exec(content)) !== null) {
3564
+ const urlPath = match[1];
3565
+ const result = resolveUrlPath(urlPath, apiPathMap, apiRoutes);
3566
+ if (result.kind === "resolved" && result.nodeId) {
3567
+ const key = `${sourceId}|${result.nodeId}|references_api`;
3568
+ if (seen.has(key)) continue;
3569
+ seen.add(key);
3570
+ crossRefs.push({
3571
+ source: sourceId,
3572
+ target: result.nodeId,
3573
+ type: "references_api",
3574
+ layer: "api"
3575
+ });
3576
+ }
3577
+ }
3578
+ }
3579
+ return {
3580
+ cross_refs: crossRefs,
3581
+ flagged_edges: [],
3582
+ warnings: [],
3583
+ patterns: {
3584
+ url_literals_resolved: crossRefs.length
3585
+ }
3586
+ };
3587
+ }
3588
+ };
3589
+
3590
+ // src/server/graph/parsers/static/static-values.ts
3591
+ var import_node_fs11 = require("node:fs");
3592
+ var import_node_path10 = require("node:path");
3593
+ var parseCode = null;
3594
+ function tryLoadTreeSitter() {
3595
+ if (parseCode) return true;
3596
+ try {
3597
+ const extractor = (init_ts_extractor(), __toCommonJS(ts_extractor_exports));
3598
+ if (typeof extractor.parseCodeTS === "function") {
3599
+ parseCode = extractor.parseCodeTS;
3600
+ return true;
3601
+ }
3602
+ } catch {
3603
+ }
3604
+ return false;
3605
+ }
3606
+ var SHARED_MODELS = /* @__PURE__ */ new Set(["permission", "role", "tag"]);
3607
+ var DB_MODELS = /* @__PURE__ */ new Set(["subscriptionPlan", "providerDefinition", "pipelineMasterTemplate"]);
3608
+ function classifyScope(source, model) {
3609
+ if (source.includes("prisma/schema.prisma")) return "shared";
3610
+ if (source.includes("prisma/seed") && model) {
3611
+ if (SHARED_MODELS.has(model)) return "shared";
3612
+ if (DB_MODELS.has(model)) return "db";
3613
+ return "shared";
3614
+ }
3615
+ if (source.startsWith("src/client/") || source.startsWith("src/app/")) return "fe";
3616
+ if (source.startsWith("src/server/")) return "be";
3617
+ if (source.startsWith("src/config/")) return "be";
3618
+ if (source.startsWith("src/lib/")) return "shared";
3619
+ return "shared";
3620
+ }
3621
+ function extractEnumValues(rootDir) {
3622
+ const nodes = [];
3623
+ const edges = [];
3624
+ const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
3625
+ const schemaPaths = [];
3626
+ if (paths?.dbConfig.kind === "prisma" && paths.dbConfig.schemaPath) {
3627
+ schemaPaths.push(paths.dbConfig.schemaPath);
3628
+ schemaPaths.push((0, import_node_path10.join)((0, import_node_path10.dirname)(paths.dbConfig.schemaPath), "schema"));
3629
+ } else {
3630
+ schemaPaths.push((0, import_node_path10.join)(rootDir, "prisma", "schema.prisma"));
3631
+ schemaPaths.push((0, import_node_path10.join)(rootDir, "prisma", "schema"));
3632
+ }
3633
+ let content = "";
3634
+ for (const p of schemaPaths) {
3635
+ if ((0, import_node_fs11.existsSync)(p)) {
3636
+ try {
3637
+ const stat = (0, import_node_fs11.statSync)(p);
3638
+ if (stat.isFile()) {
3639
+ content = (0, import_node_fs11.readFileSync)(p, "utf-8");
3640
+ } else if (stat.isDirectory()) {
3641
+ const files = (0, import_node_fs11.readdirSync)(p).filter((f) => f.endsWith(".prisma"));
3642
+ content = files.map((f) => (0, import_node_fs11.readFileSync)((0, import_node_path10.join)(p, f), "utf-8")).join("\n");
3643
+ }
3644
+ } catch {
3645
+ continue;
3646
+ }
3647
+ break;
3648
+ }
3649
+ }
3650
+ if (!content) return { nodes, edges };
3651
+ const enumRe = /enum\s+(\w+)\s*\{([^}]+)\}/g;
3652
+ let m;
3653
+ while ((m = enumRe.exec(content)) !== null) {
3654
+ const enumName = m[1];
3655
+ const body = m[2];
3656
+ const values = body.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("//"));
3657
+ const scope = classifyScope("prisma/schema.prisma");
3658
+ for (const val of values) {
3659
+ const nodeId = `${enumName}.${val}`;
3660
+ nodes.push({
3661
+ id: nodeId,
3662
+ type: "enum_value",
3663
+ name: val,
3664
+ parentEnum: enumName,
3665
+ value: val,
3666
+ scope,
3667
+ source: "prisma/schema.prisma"
3668
+ });
3669
+ edges.push({
3670
+ source: nodeId,
3671
+ target: `enum:${enumName}`,
3672
+ type: "member_of"
3673
+ });
3674
+ }
3675
+ nodes.push({
3676
+ id: `enum:${enumName}`,
3677
+ type: "enum_group",
3678
+ name: enumName,
3679
+ valueCount: values.length,
3680
+ scope,
3681
+ source: "prisma/schema.prisma"
3682
+ });
3683
+ }
3684
+ return { nodes, edges };
3685
+ }
3686
+ function extractPropsFromObjectNode(node) {
3687
+ const props = {};
3688
+ for (const child of node.namedChildren) {
3689
+ if (child.type !== "pair") continue;
3690
+ const keyNode = child.childForFieldName("key");
3691
+ const valNode = child.childForFieldName("value");
3692
+ if (!keyNode || !valNode) continue;
3693
+ const key = keyNode.type === "property_identifier" ? keyNode.text : keyNode.text.replace(/['"]/g, "");
3694
+ if (valNode.type === "string" || valNode.type === "template_string") {
3695
+ const fragment = valNode.namedChildren.find((c) => c.type === "string_fragment" || c.type === "template_substitution");
3696
+ props[key] = fragment ? fragment.text : valNode.text.replace(/^['"`]|['"`]$/g, "");
3697
+ } else if (valNode.type === "number") {
3698
+ props[key] = valNode.text;
3699
+ } else if (valNode.type === "true" || valNode.type === "false") {
3700
+ props[key] = valNode.text;
3701
+ }
3702
+ }
3703
+ return props;
3704
+ }
3705
+ function extractStringArrayFromNode(node) {
3706
+ const values = [];
3707
+ for (const child of node.namedChildren) {
3708
+ if (child.type === "string") {
3709
+ const frag = child.namedChildren.find((c) => c.type === "string_fragment");
3710
+ if (frag) values.push(frag.text);
3711
+ }
3712
+ }
3713
+ return values;
3714
+ }
3715
+ function findArrayDecl(root, varName) {
3716
+ function walk2(node) {
3717
+ if (node.type === "variable_declarator") {
3718
+ const nameNode = node.childForFieldName("name");
3719
+ const valueNode = node.childForFieldName("value");
3720
+ if (nameNode?.text === varName && valueNode?.type === "array") {
3721
+ return valueNode;
3722
+ }
3723
+ if (nameNode?.text === varName && valueNode?.type === "as_expression") {
3724
+ const inner = valueNode.namedChildren.find((c) => c.type === "array");
3725
+ if (inner) return inner;
3726
+ }
3727
+ }
3728
+ for (const child of node.namedChildren) {
3729
+ const found = walk2(child);
3730
+ if (found) return found;
3731
+ }
3732
+ return null;
3733
+ }
3734
+ return walk2(root);
3735
+ }
3736
+ function extractObjectPropsRegex(objStr) {
3737
+ const props = {};
3738
+ const propRe = /(\w+):\s*['"]([^'"]*)['"]/g;
3739
+ let m;
3740
+ while ((m = propRe.exec(objStr)) !== null) props[m[1]] = m[2];
3741
+ const numRe = /(\w+):\s*(-?\d+(?:\.\d+)?)\s*[,\n}]/g;
3742
+ while ((m = numRe.exec(objStr)) !== null) if (!props[m[1]]) props[m[1]] = m[2];
3743
+ return props;
3744
+ }
3745
+ function extractStringArrayRegex(arrStr) {
3746
+ return (arrStr.match(/'([^']+)'/g) ?? []).map((s) => s.replace(/'/g, ""));
3747
+ }
3748
+ function splitArrayObjectsRegex(arrayBody) {
3749
+ const objects = [];
3750
+ let depth = 0;
3751
+ let start = -1;
3752
+ for (let i = 0; i < arrayBody.length; i++) {
3753
+ if (arrayBody[i] === "{") {
3754
+ if (depth === 0) start = i;
3755
+ depth++;
3756
+ } else if (arrayBody[i] === "}") {
3757
+ depth--;
3758
+ if (depth === 0 && start >= 0) {
3759
+ objects.push(arrayBody.slice(start, i + 1));
3760
+ start = -1;
3761
+ }
3762
+ }
3763
+ }
3764
+ return objects;
3765
+ }
3766
+ function detectSeededArrays(content, sourceFile) {
3767
+ const results = [];
3768
+ const forOfRe = /for\s*\(\s*const\s+\w+\s+of\s+(\w+)\s*\)\s*\{/g;
3769
+ let fm;
3770
+ while ((fm = forOfRe.exec(content)) !== null) {
3771
+ const arrayName = fm[1];
3772
+ const lookahead = content.slice(fm.index + fm[0].length, fm.index + fm[0].length + 500);
3773
+ const prismaMatch = lookahead.match(/prisma\.(\w+)\.(create|upsert|update|createMany|findFirst)/);
3774
+ if (!prismaMatch) continue;
3775
+ results.push({ arrayName, prismaModel: prismaMatch[1], sourceFile });
3776
+ }
3777
+ return results;
3778
+ }
3779
+ function pickIdField(props) {
3780
+ for (const key of ["key", "slug", "id", "name", "templateId"]) {
3781
+ if (props[key]) return props[key];
3782
+ }
3783
+ return null;
3784
+ }
3785
+ function pickNameField(props) {
3786
+ for (const key of ["name", "slug", "key", "id"]) {
3787
+ if (props[key]) return props[key];
3788
+ }
3789
+ return null;
3790
+ }
3791
+ function modelToNodeType(model) {
3792
+ return `seed_${model.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "")}`;
3793
+ }
3794
+ function extractSeedData(rootDir) {
3795
+ const nodes = [];
3796
+ const edges = [];
3797
+ const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
3798
+ const candidates = [];
3799
+ if (paths?.dbDir) {
3800
+ candidates.push((0, import_node_path10.join)(paths.dbDir, "seed.ts"));
3801
+ candidates.push((0, import_node_path10.join)(paths.dbDir, "seed.js"));
3802
+ } else {
3803
+ candidates.push((0, import_node_path10.join)(rootDir, "prisma", "seed.ts"));
3804
+ candidates.push((0, import_node_path10.join)(rootDir, "prisma", "seed.js"));
3805
+ }
3806
+ const baseRoots = paths?.srcRoots ?? [(0, import_node_path10.join)(rootDir, "src")];
3807
+ for (const root of baseRoots) {
3808
+ candidates.push((0, import_node_path10.join)(root, "server", "lib", "system-tags.ts"));
3809
+ }
3810
+ const seedFiles = candidates.filter((p) => {
3811
+ try {
3812
+ return (0, import_node_fs11.existsSync)(p) && (0, import_node_fs11.statSync)(p).isFile();
3813
+ } catch {
3814
+ return false;
3815
+ }
3816
+ });
3817
+ const useTreeSitter = tryLoadTreeSitter();
3818
+ for (const filePath of seedFiles) {
3819
+ const content = (0, import_node_fs11.readFileSync)(filePath, "utf-8");
3820
+ const relPath = (0, import_node_path10.relative)(rootDir, filePath);
3821
+ const seeded = detectSeededArrays(content, relPath);
3822
+ let astRoot = null;
3823
+ if (useTreeSitter && parseCode) {
3824
+ try {
3825
+ const tree = parseCode(content);
3826
+ astRoot = tree.rootNode;
3827
+ } catch {
3828
+ }
3829
+ }
3830
+ for (const { arrayName, prismaModel, sourceFile } of seeded) {
3831
+ const nodeType = modelToNodeType(prismaModel);
3832
+ const scope = classifyScope(sourceFile, prismaModel);
3833
+ if (astRoot) {
3834
+ const arrayNode = findArrayDecl(astRoot, arrayName);
3835
+ if (!arrayNode) continue;
3836
+ for (const child of arrayNode.namedChildren) {
3837
+ if (child.type !== "object") continue;
3838
+ const props = extractPropsFromObjectNode(child);
3839
+ const idVal = pickIdField(props);
3840
+ if (!idVal) continue;
3841
+ const nameVal = pickNameField(props) ?? idVal;
3842
+ const nodeId = `seed:${prismaModel}:${idVal}`;
3843
+ const { scope: _seedScope, ...safeProps } = props;
3844
+ nodes.push({
3845
+ id: nodeId,
3846
+ type: nodeType,
3847
+ name: nameVal,
3848
+ value: idVal,
3849
+ model: prismaModel,
3850
+ ...safeProps,
3851
+ seedScope: _seedScope ?? void 0,
3852
+ // preserve as seedScope
3853
+ scope,
3854
+ source: sourceFile
3855
+ });
3856
+ const permsArrayNode = child.namedChildren.filter((c) => c.type === "pair").find((c) => c.childForFieldName("key")?.text === "permissions");
3857
+ if (permsArrayNode) {
3858
+ const valNode = permsArrayNode.childForFieldName("value");
3859
+ if (valNode?.type === "array") {
3860
+ const permKeys = extractStringArrayFromNode(valNode);
3861
+ for (const pk of permKeys) {
3862
+ if (pk === "*") continue;
3863
+ edges.push({ source: nodeId, target: `seed:permission:${pk}`, type: "grants" });
3864
+ }
3865
+ }
3866
+ }
3867
+ }
3868
+ } else {
3869
+ const constRe = new RegExp(`const\\s+${arrayName}\\s*(?::[^=]+)?=\\s*\\[`, "g");
3870
+ const cm = constRe.exec(content);
3871
+ if (!cm) continue;
3872
+ const bracketStart = cm.index + cm[0].length - 1;
3873
+ let depth = 1, i = bracketStart + 1;
3874
+ while (i < content.length && depth > 0) {
3875
+ if (content[i] === "[") depth++;
3876
+ else if (content[i] === "]") depth--;
3877
+ i++;
3878
+ }
3879
+ if (depth !== 0) continue;
3880
+ const body = content.slice(bracketStart + 1, i - 1);
3881
+ const objects = splitArrayObjectsRegex(body);
3882
+ for (const objStr of objects) {
3883
+ const props = extractObjectPropsRegex(objStr);
3884
+ const idVal = pickIdField(props);
3885
+ if (!idVal) continue;
3886
+ const nameVal = pickNameField(props) ?? idVal;
3887
+ const nodeId = `seed:${prismaModel}:${idVal}`;
3888
+ const { scope: _rScope, ...rSafeProps } = props;
3889
+ nodes.push({
3890
+ id: nodeId,
3891
+ type: nodeType,
3892
+ name: nameVal,
3893
+ value: idVal,
3894
+ model: prismaModel,
3895
+ ...rSafeProps,
3896
+ seedScope: _rScope ?? void 0,
3897
+ scope,
3898
+ source: sourceFile
3899
+ });
3900
+ const permArrayMatch = objStr.match(/permissions:\s*\[([^\]]*)\]/);
3901
+ if (permArrayMatch) {
3902
+ for (const pk of extractStringArrayRegex(permArrayMatch[1])) {
3903
+ if (pk === "*") continue;
3904
+ edges.push({ source: nodeId, target: `seed:permission:${pk}`, type: "grants" });
3905
+ }
3906
+ }
3907
+ }
3908
+ }
3909
+ }
3910
+ }
3911
+ return { nodes, edges };
3912
+ }
3913
+ function walkDir(dir, exts) {
3914
+ if (!(0, import_node_fs11.existsSync)(dir)) return [];
3915
+ const results = [];
3916
+ for (const entry of (0, import_node_fs11.readdirSync)(dir, { withFileTypes: true })) {
3917
+ if (entry.name === "node_modules" || entry.name === ".next" || entry.name === "dist") continue;
3918
+ const full = (0, import_node_path10.join)(dir, entry.name);
3919
+ if (entry.isDirectory()) results.push(...walkDir(full, exts));
3920
+ else if (exts.some((ext) => entry.name.endsWith(ext))) results.push(full);
3921
+ }
3922
+ return results;
3923
+ }
3924
+ function extractConstants(rootDir) {
3925
+ const nodes = [];
3926
+ const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
3927
+ const roots = paths?.srcRoots ?? [(0, import_node_path10.join)(rootDir, "src")];
3928
+ const seenFile = /* @__PURE__ */ new Set();
3929
+ const allFiles = [];
3930
+ for (const root of roots) {
3931
+ for (const f of walkDir(root, [".ts", ".tsx"])) {
3932
+ if (!seenFile.has(f)) {
3933
+ seenFile.add(f);
3934
+ allFiles.push(f);
3935
+ }
3936
+ }
3937
+ }
3938
+ if (allFiles.length === 0) return { nodes };
3939
+ for (const filePath of allFiles) {
3940
+ const content = (0, import_node_fs11.readFileSync)(filePath, "utf-8");
3941
+ const relPath = (0, import_node_path10.relative)(rootDir, filePath);
3942
+ const constArrayRe = /export\s+const\s+([A-Z][A-Z_0-9]+)\s*(?::[^=]+)?\s*=\s*\[/g;
3943
+ let cm;
3944
+ while ((cm = constArrayRe.exec(content)) !== null) {
3945
+ const constName = cm[1];
3946
+ const bracketStart = cm.index + cm[0].length - 1;
3947
+ let depth = 1, i = bracketStart + 1;
3948
+ while (i < content.length && depth > 0) {
3949
+ if (content[i] === "[") depth++;
3950
+ else if (content[i] === "]") depth--;
3951
+ i++;
3952
+ }
3953
+ if (depth !== 0) continue;
3954
+ const body = content.slice(bracketStart + 1, i - 1);
3955
+ const stringValues = extractStringArrayRegex(body);
3956
+ const objectCount = splitArrayObjectsRegex(body).length;
3957
+ const valueCount = Math.max(stringValues.length, objectCount);
3958
+ if (valueCount < 2) continue;
3959
+ const scope = classifyScope(relPath);
3960
+ nodes.push({
3961
+ id: `const:${constName}`,
3962
+ type: "constant",
3963
+ name: constName,
3964
+ valueCount,
3965
+ values: stringValues.length > 0 && stringValues.length <= 30 ? stringValues : void 0,
3966
+ scope,
3967
+ source: relPath
3968
+ });
3969
+ }
3970
+ }
3971
+ return { nodes };
3972
+ }
3973
+ function detect4(rootDir) {
3974
+ const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
3975
+ if (paths?.dbConfig.kind === "prisma" && paths.dbConfig.schemaPath && (0, import_node_fs11.existsSync)(paths.dbConfig.schemaPath)) {
3976
+ return true;
3977
+ }
3978
+ if (paths?.dbDir) {
3979
+ if ((0, import_node_fs11.existsSync)((0, import_node_path10.join)(paths.dbDir, "seed.ts")) || (0, import_node_fs11.existsSync)((0, import_node_path10.join)(paths.dbDir, "seed.js"))) return true;
3980
+ }
3981
+ return (0, import_node_fs11.existsSync)((0, import_node_path10.join)(rootDir, "prisma", "schema.prisma")) || (0, import_node_fs11.existsSync)((0, import_node_path10.join)(rootDir, "prisma", "seed.ts"));
3982
+ }
3983
+ function generate4(rootDir) {
3984
+ const enumResult = extractEnumValues(rootDir);
3985
+ const seedResult = extractSeedData(rootDir);
3986
+ const constResult = extractConstants(rootDir);
3987
+ const allNodes = [...enumResult.nodes, ...seedResult.nodes, ...constResult.nodes];
3988
+ const allEdges = [...enumResult.edges, ...seedResult.edges];
3989
+ const typeOrder = {
3990
+ enum_group: 0,
3991
+ enum_value: 1,
3992
+ seed_permission: 2,
3993
+ seed_role: 3,
3994
+ seed_tag: 4,
3995
+ seed_plan: 5,
3996
+ seed_provider: 6,
3997
+ constant: 7
3998
+ };
3999
+ allNodes.sort((a, b) => {
4000
+ const ta = typeOrder[a.type] ?? 8;
4001
+ const tb = typeOrder[b.type] ?? 8;
4002
+ if (ta !== tb) return ta - tb;
4003
+ return a.name.localeCompare(b.name);
4004
+ });
4005
+ const enumGroups = allNodes.filter((n) => n.type === "enum_group").length;
4006
+ const enumValues = allNodes.filter((n) => n.type === "enum_value").length;
4007
+ const seedNodes = allNodes.filter((n) => n.type.startsWith("seed_")).length;
4008
+ const constNodes = allNodes.filter((n) => n.type === "constant").length;
4009
+ const byScope = {};
4010
+ for (const n of allNodes) {
4011
+ const s = n.scope ?? "unknown";
4012
+ byScope[s] = (byScope[s] ?? 0) + 1;
4013
+ }
4014
+ return {
4015
+ metadata: {
4016
+ generated: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
4017
+ scope: "static-values",
4018
+ layer: "static",
4019
+ enum_groups: enumGroups,
4020
+ enum_values: enumValues,
4021
+ seed_records: seedNodes,
4022
+ constants: constNodes,
4023
+ total_nodes: allNodes.length,
4024
+ total_edges: allEdges.length,
4025
+ parser: tryLoadTreeSitter() ? "tree-sitter" : "regex-fallback"
4026
+ },
4027
+ nodes: allNodes,
4028
+ edges: allEdges,
4029
+ cross_refs: [],
4030
+ contradictions: [],
4031
+ warnings: [],
4032
+ flagged_edges: [],
4033
+ patterns: {
4034
+ by_type: {
4035
+ enum_group: enumGroups,
4036
+ enum_value: enumValues,
4037
+ seed_permission: allNodes.filter((n) => n.type === "seed_permission").length,
4038
+ seed_role: allNodes.filter((n) => n.type === "seed_role").length,
4039
+ seed_tag: allNodes.filter((n) => n.type === "seed_tag").length,
4040
+ seed_plan: allNodes.filter((n) => n.type === "seed_plan").length,
4041
+ seed_provider: allNodes.filter((n) => n.type === "seed_provider").length,
4042
+ constant: constNodes
4043
+ },
4044
+ by_scope: byScope
4045
+ }
4046
+ };
4047
+ }
4048
+ var staticValuesParser = {
4049
+ id: "static-values",
4050
+ layer: "static",
4051
+ detect: detect4,
4052
+ generate: generate4
4053
+ };
4054
+
4055
+ // src/server/graph/parsers/crosslayer/static-ref-scanner.ts
4056
+ var import_node_fs12 = require("node:fs");
4057
+ var import_node_path11 = require("node:path");
4058
+ var MIN_VALUE_LENGTH = 4;
4059
+ var SKIP_VALUES = /* @__PURE__ */ new Set([
4060
+ "true",
4061
+ "false",
4062
+ "null",
4063
+ "undefined",
4064
+ "none",
4065
+ "default",
4066
+ "name",
4067
+ "type",
4068
+ "data",
4069
+ "text",
4070
+ "info",
4071
+ "error",
4072
+ "open",
4073
+ "read",
4074
+ "user",
4075
+ "test",
4076
+ "json",
4077
+ "form"
4078
+ ]);
4079
+ function isInCommentOrType(node) {
4080
+ let current = node.parent;
4081
+ while (current) {
4082
+ if (current.type === "comment" || current.type === "type_annotation" || current.type === "type_alias_declaration" || current.type === "interface_declaration" || current.type === "jsdoc") {
4083
+ return true;
4084
+ }
4085
+ current = current.parent;
4086
+ }
4087
+ return false;
4088
+ }
4089
+ function collectStaticRefs(root, valueLookup) {
4090
+ const refs = [];
4091
+ const seen = /* @__PURE__ */ new Set();
4092
+ function visit(node) {
4093
+ if (node.type === "string_fragment") {
4094
+ const val = node.text;
4095
+ if (val.length >= MIN_VALUE_LENGTH && !SKIP_VALUES.has(val.toLowerCase())) {
4096
+ const targets = valueLookup.get(val);
4097
+ if (targets && !isInCommentOrType(node)) {
4098
+ for (const t of targets) {
4099
+ const key = t;
4100
+ if (!seen.has(key)) {
4101
+ seen.add(key);
4102
+ refs.push({ value: val, targetIds: [t] });
4103
+ }
4104
+ }
4105
+ }
4106
+ }
4107
+ }
4108
+ if (node.type === "member_expression") {
4109
+ const objNode = node.namedChildren.find((c) => c.type === "identifier");
4110
+ const propNode = node.namedChildren.find((c) => c.type === "property_identifier");
4111
+ if (objNode && propNode) {
4112
+ const combined = `${objNode.text}.${propNode.text}`;
4113
+ const targets = valueLookup.get(propNode.text);
4114
+ const directTarget = valueLookup.get(combined);
4115
+ if (directTarget && !isInCommentOrType(node)) {
4116
+ for (const t of directTarget) {
4117
+ if (!seen.has(t)) {
4118
+ seen.add(t);
4119
+ refs.push({ value: combined, targetIds: [t] });
4120
+ }
4121
+ }
4122
+ } else if (targets && !isInCommentOrType(node)) {
4123
+ for (const t of targets) {
4124
+ if (!seen.has(t)) {
4125
+ seen.add(t);
4126
+ refs.push({ value: propNode.text, targetIds: [t] });
4127
+ }
4128
+ }
4129
+ }
4130
+ }
4131
+ }
4132
+ for (const child of node.namedChildren) {
4133
+ visit(child);
4134
+ }
4135
+ }
4136
+ visit(root);
4137
+ return refs;
4138
+ }
4139
+ function collectStaticRefsRegex(content, valueLookup, allValues) {
4140
+ const refs = [];
4141
+ const seen = /* @__PURE__ */ new Set();
4142
+ const escaped = allValues.map((v) => v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
4143
+ const pattern = new RegExp(
4144
+ `(?:['"\`])(${escaped.join("|")})(?:['"\`])|\\b(\\w+)\\.(${escaped.join("|")})\\b`,
4145
+ "g"
4146
+ );
4147
+ let match;
4148
+ pattern.lastIndex = 0;
4149
+ while ((match = pattern.exec(content)) !== null) {
4150
+ const val = match[1] ?? match[3];
4151
+ if (!val) continue;
4152
+ const targets = valueLookup.get(val);
4153
+ if (!targets) continue;
4154
+ for (const t of targets) {
4155
+ if (!seen.has(t)) {
4156
+ seen.add(t);
4157
+ refs.push({ value: val, targetIds: [t] });
4158
+ }
4159
+ }
4160
+ }
4161
+ return refs;
4162
+ }
4163
+ var staticRefScannerParser = {
4164
+ id: "static-ref-scanner",
4165
+ layer: "crosslayer",
4166
+ concern: "static-ref",
4167
+ detect(rootDir) {
4168
+ const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
4169
+ return paths !== null;
4170
+ },
4171
+ generate(rootDir, layerOutputs) {
4172
+ const staticOutput = layerOutputs.get("static");
4173
+ if (!staticOutput || !Array.isArray(staticOutput.nodes) || staticOutput.nodes.length === 0) {
4174
+ return { cross_refs: [], flagged_edges: [], warnings: [] };
4175
+ }
4176
+ const valueLookup = /* @__PURE__ */ new Map();
4177
+ for (const node of staticOutput.nodes) {
4178
+ const type = node.type;
4179
+ let valueStr = null;
4180
+ if (type === "enum_value") {
4181
+ valueStr = node.value;
4182
+ const fullId = node.id;
4183
+ if (!valueLookup.has(fullId)) valueLookup.set(fullId, []);
4184
+ valueLookup.get(fullId).push(node.id);
4185
+ } else if (type.startsWith("seed_")) {
4186
+ valueStr = node.value;
4187
+ }
4188
+ if (!valueStr || valueStr.length < MIN_VALUE_LENGTH || SKIP_VALUES.has(valueStr.toLowerCase())) continue;
4189
+ if (!valueLookup.has(valueStr)) valueLookup.set(valueStr, []);
4190
+ valueLookup.get(valueStr).push(node.id);
4191
+ }
4192
+ const allValues = [...valueLookup.keys()].sort((a, b) => b.length - a.length);
4193
+ if (allValues.length === 0) {
4194
+ return { cross_refs: [], flagged_edges: [], warnings: [] };
4195
+ }
4196
+ const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
4197
+ if (!paths) return { cross_refs: [], flagged_edges: [], warnings: [] };
4198
+ const srcDir = paths.srcDir;
4199
+ const seenFile = /* @__PURE__ */ new Set();
4200
+ const files = [];
4201
+ for (const root of paths.srcRoots) {
4202
+ for (const f of walkWithIgnore(root, [".ts", ".tsx"])) {
4203
+ if (!seenFile.has(f)) {
4204
+ seenFile.add(f);
4205
+ files.push(f);
4206
+ }
4207
+ }
4208
+ }
4209
+ for (const conv of paths.conventionFiles) {
4210
+ if (!seenFile.has(conv)) {
4211
+ seenFile.add(conv);
4212
+ files.push(conv);
4213
+ }
4214
+ }
4215
+ const uiOutput = layerOutputs.get("ui");
4216
+ const apiOutput = layerOutputs.get("api");
4217
+ const uiNodeIds = new Set(uiOutput?.nodes.map((n) => n.id) ?? []);
4218
+ const apiNodeIds = new Set(apiOutput?.nodes.map((n) => n.id) ?? []);
4219
+ let parseCode2 = null;
4220
+ try {
4221
+ const extractor = (init_ts_extractor(), __toCommonJS(ts_extractor_exports));
4222
+ if (typeof extractor.parseCodeTS === "function") {
4223
+ parseCode2 = extractor.parseCodeTS;
4224
+ }
4225
+ } catch {
4226
+ }
4227
+ const crossRefs = [];
4228
+ const seen = /* @__PURE__ */ new Set();
4229
+ let filesScanned = 0;
4230
+ for (const absPath of files) {
4231
+ const relFromSrc = (0, import_node_path11.relative)(srcDir, absPath).replace(/\\/g, "/");
4232
+ const sourceId = relFromSrc.startsWith("..") ? (0, import_node_path11.relative)(rootDir, absPath).replace(/\\/g, "/") : relFromSrc;
4233
+ const sourceLayer = uiNodeIds.has(sourceId) ? "ui" : apiNodeIds.has(sourceId) ? "api" : null;
4234
+ if (!sourceLayer) continue;
4235
+ const content = (0, import_node_fs12.readFileSync)(absPath, "utf-8");
4236
+ filesScanned++;
4237
+ let fileRefs;
4238
+ if (parseCode2) {
4239
+ try {
4240
+ const tree = parseCode2(content);
4241
+ fileRefs = collectStaticRefs(tree.rootNode, valueLookup);
4242
+ } catch {
4243
+ fileRefs = collectStaticRefsRegex(content, valueLookup, allValues);
4244
+ }
4245
+ } else {
4246
+ fileRefs = collectStaticRefsRegex(content, valueLookup, allValues);
4247
+ }
4248
+ for (const ref of fileRefs) {
4249
+ for (const targetId of ref.targetIds) {
4250
+ const key = `${sourceId}|${targetId}`;
4251
+ if (seen.has(key)) continue;
4252
+ seen.add(key);
4253
+ crossRefs.push({
4254
+ source: sourceId,
4255
+ target: targetId,
4256
+ type: "references_static",
4257
+ layer: "static"
4258
+ });
4259
+ }
4260
+ }
4261
+ }
4262
+ return {
4263
+ cross_refs: crossRefs,
4264
+ flagged_edges: [],
4265
+ warnings: [],
4266
+ patterns: {
4267
+ files_scanned: filesScanned,
4268
+ static_values_tracked: valueLookup.size,
4269
+ references_found: crossRefs.length,
4270
+ parser: parseCode2 ? "tree-sitter" : "regex-fallback"
4271
+ }
4272
+ };
4273
+ }
4274
+ };
4275
+
4276
+ // src/server/graph/parsers/crosslayer/middleware-gates.ts
4277
+ var import_node_path12 = require("node:path");
4278
+ init_ts_extractor();
4279
+ function toNodeId4(srcDir, rootDir, absPath) {
4280
+ const relFromSrc = (0, import_node_path12.relative)(srcDir, absPath).replace(/\\/g, "/");
4281
+ if (relFromSrc.startsWith("..")) {
4282
+ return (0, import_node_path12.relative)(rootDir, absPath).replace(/\\/g, "/");
4283
+ }
4284
+ return relFromSrc;
4285
+ }
4286
+ function collectTargets(apiOutput, uiOutput) {
4287
+ const out = [];
4288
+ for (const n of apiOutput?.nodes ?? []) {
4289
+ if (n.type !== "endpoint") continue;
4290
+ const path = n.path;
4291
+ if (typeof path !== "string" || !path) continue;
4292
+ out.push({ id: n.id, route: path, layer: "api" });
4293
+ }
4294
+ for (const n of uiOutput?.nodes ?? []) {
4295
+ if (n.type !== "page") continue;
4296
+ const route = n.route;
4297
+ if (typeof route !== "string" || !route) continue;
4298
+ out.push({ id: n.id, route, layer: "ui" });
4299
+ }
4300
+ return out;
4301
+ }
4302
+ var middlewareGatesParser = {
4303
+ id: "middleware-gates",
4304
+ layer: "crosslayer",
4305
+ concern: "auth-gate",
4306
+ detect(rootDir) {
4307
+ const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
4308
+ if (!paths) return false;
4309
+ return paths.conventionFiles.some((f) => /middleware\.tsx?$/.test(f));
4310
+ },
4311
+ generate(rootDir, layerOutputs) {
4312
+ const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
4313
+ if (!paths) return { cross_refs: [], flagged_edges: [], warnings: [] };
4314
+ const middlewareFiles = paths.conventionFiles.filter((f) => /middleware\.tsx?$/.test(f));
4315
+ if (middlewareFiles.length === 0) {
4316
+ return { cross_refs: [], flagged_edges: [], warnings: [] };
4317
+ }
4318
+ const apiOutput = layerOutputs.get("api");
4319
+ const uiOutput = layerOutputs.get("ui");
4320
+ const targets = collectTargets(apiOutput, uiOutput);
4321
+ const crossRefs = [];
4322
+ const flagged = [];
4323
+ const seenEdge = /* @__PURE__ */ new Set();
4324
+ const seenUnparseable = /* @__PURE__ */ new Set();
4325
+ let protectMatcherCount = 0;
4326
+ for (const file of middlewareFiles) {
4327
+ let info = null;
4328
+ try {
4329
+ info = extractMiddlewareAuthTS(file);
4330
+ } catch {
4331
+ }
4332
+ if (!info || info.matchers.length === 0) continue;
4333
+ const middlewareId = toNodeId4(paths.srcDir, rootDir, file);
4334
+ for (const matcher of info.matchers) {
4335
+ if (matcher.intent !== "protect") continue;
4336
+ protectMatcherCount += 1;
4337
+ for (const pattern of matcher.patterns) {
4338
+ const re = middlewarePatternToRegex(pattern);
4339
+ if (!re) {
4340
+ const key = `${middlewareId}|${pattern}`;
4341
+ if (seenUnparseable.has(key)) continue;
4342
+ seenUnparseable.add(key);
4343
+ flagged.push({
4344
+ source: middlewareId,
4345
+ target: "UNRESOLVED",
4346
+ type: "middleware_pattern_unparseable",
4347
+ label: `pattern "${pattern}" in matcher "${matcher.name}" \u2014 coverage unknown`,
4348
+ confidence: "high"
4349
+ });
4350
+ continue;
4351
+ }
4352
+ for (const target of targets) {
4353
+ if (!re.test(target.route)) continue;
4354
+ const key = `${middlewareId}|${target.id}|protects`;
4355
+ if (seenEdge.has(key)) continue;
4356
+ seenEdge.add(key);
4357
+ crossRefs.push({
4358
+ source: middlewareId,
4359
+ target: target.id,
4360
+ type: "protects",
4361
+ layer: target.layer
4362
+ });
4363
+ }
4364
+ }
4365
+ }
4366
+ }
4367
+ return {
4368
+ cross_refs: crossRefs,
4369
+ flagged_edges: flagged,
4370
+ warnings: [],
4371
+ patterns: {
4372
+ middleware_files: middlewareFiles.length,
4373
+ protect_matchers: protectMatcherCount,
4374
+ protects_edges: crossRefs.length,
4375
+ unparseable_patterns: flagged.length
4376
+ }
4377
+ };
4378
+ }
4379
+ };
4380
+
4381
+ // src/server/graph/core/parser-registry.ts
4382
+ function isMultiLayerParser(p) {
4383
+ return "layers" in p && Array.isArray(p.layers);
4384
+ }
4385
+ var ParserRegistry = class {
4386
+ constructor() {
4387
+ this.singleLayerParsers = /* @__PURE__ */ new Map();
4388
+ this.multiLayerParsers = [];
4389
+ this.ids = /* @__PURE__ */ new Set();
4390
+ }
4391
+ register(parser) {
4392
+ if (this.ids.has(parser.id)) {
4393
+ throw new Error(`Duplicate parser id: ${parser.id}`);
4394
+ }
4395
+ this.ids.add(parser.id);
4396
+ if (isMultiLayerParser(parser)) {
4397
+ this.multiLayerParsers.push(parser);
4398
+ } else {
4399
+ const list = this.singleLayerParsers.get(parser.layer) ?? [];
4400
+ list.push(parser);
4401
+ this.singleLayerParsers.set(parser.layer, list);
4402
+ }
4403
+ }
4404
+ /** Get single-layer parsers for a specific layer. */
4405
+ getParsers(layer) {
4406
+ return this.singleLayerParsers.get(layer) ?? [];
4407
+ }
4408
+ /** Get multi-layer parsers that can produce output for the given layer. */
4409
+ getMultiLayerParsersFor(layer) {
4410
+ return this.multiLayerParsers.filter((p) => p.layers.includes(layer));
4411
+ }
4412
+ /** Get all multi-layer parsers. */
4413
+ getMultiLayerParsers() {
4414
+ return [...this.multiLayerParsers];
4415
+ }
4416
+ getCrossLayerParsers() {
4417
+ return this.singleLayerParsers.get("crosslayer") ?? [];
4418
+ }
4419
+ /** All layers that registered parsers can produce (single + multi). */
4420
+ getAvailableLayers() {
4421
+ const layers = /* @__PURE__ */ new Set();
4422
+ for (const key of this.singleLayerParsers.keys()) {
4423
+ if (key !== "crosslayer") layers.add(key);
4424
+ }
4425
+ for (const mp of this.multiLayerParsers) {
4426
+ for (const l of mp.layers) layers.add(l);
4427
+ }
4428
+ return [...layers];
4429
+ }
4430
+ getAll() {
4431
+ const all = [];
4432
+ for (const list of this.singleLayerParsers.values()) all.push(...list);
4433
+ all.push(...this.multiLayerParsers);
4434
+ return all;
4435
+ }
4436
+ };
4437
+ function registerBuiltins(registry, disabled) {
4438
+ const builtins = [
4439
+ typescriptProjectParser,
4440
+ prismaSchemaParser,
4441
+ sqlMigrationsParser,
4442
+ staticValuesParser,
4443
+ fetchResolverParser,
4444
+ apiAnnotationsParser,
4445
+ urlLiteralScannerParser,
4446
+ staticRefScannerParser,
4447
+ middlewareGatesParser
4448
+ ];
4449
+ for (const parser of builtins) {
4450
+ if (disabled.has(parser.id)) continue;
4451
+ registry.register(parser);
4452
+ }
4453
+ }
4454
+ function loadCustomParsers(registry, config, rootDir, disabled) {
4455
+ for (const entry of config.parsers?.custom ?? []) {
4456
+ try {
4457
+ const absPath = (0, import_node_path13.resolve)(rootDir, entry.path);
4458
+ const mod = require(absPath);
4459
+ const parser = "default" in mod ? mod.default : mod;
4460
+ if (disabled.has(parser.id)) continue;
4461
+ if (!isMultiLayerParser(parser) && parser.layer !== entry.layer) {
4462
+ process.stderr.write(
4463
+ `[launch-chart] custom parser "${parser.id}" declares layer "${parser.layer}" but config says "${entry.layer}" \u2014 using parser's layer
4464
+ `
4465
+ );
4466
+ }
4467
+ if (parser.layer === "crosslayer" && entry.concern && !("concern" in parser && parser.concern)) {
4468
+ parser.concern = entry.concern;
4469
+ }
4470
+ registry.register(parser);
4471
+ } catch (err) {
4472
+ process.stderr.write(`[launch-chart] failed to load custom parser from ${entry.path}: ${err}
4473
+ `);
4474
+ }
4475
+ }
4476
+ }
4477
+ function createRegistry(config, rootDir) {
4478
+ const registry = new ParserRegistry();
4479
+ const disabled = new Set(config.parsers?.disabled ?? []);
4480
+ registerBuiltins(registry, disabled);
4481
+ loadCustomParsers(registry, config, rootDir, disabled);
4482
+ return registry;
4483
+ }
4484
+
4485
+ // src/server/graph/core/merge.ts
4486
+ function mergeGraphOutputs(outputs, layer) {
4487
+ if (outputs.length === 0) {
4488
+ return {
4489
+ metadata: { generated: (/* @__PURE__ */ new Date()).toISOString(), scope: "", layer },
4490
+ nodes: [],
4491
+ edges: [],
4492
+ cross_refs: [],
4493
+ contradictions: [],
4494
+ warnings: [],
4495
+ flagged_edges: []
4496
+ };
4497
+ }
4498
+ if (outputs.length === 1) return outputs[0];
4499
+ const seenNodes = /* @__PURE__ */ new Set();
4500
+ const seenEdges = /* @__PURE__ */ new Set();
4501
+ const seenCrossRefs = /* @__PURE__ */ new Set();
4502
+ const mergedNodes = [];
4503
+ const mergedEdges = [];
4504
+ const mergedCrossRefs = [];
4505
+ const mergedContradictions = [];
4506
+ const mergedWarnings = [];
4507
+ const mergedFlagged = [];
4508
+ const parserIds = [];
4509
+ for (const output of outputs) {
4510
+ if (output.metadata.parser) {
4511
+ parserIds.push(String(output.metadata.parser));
4512
+ }
4513
+ for (const node of output.nodes) {
4514
+ if (seenNodes.has(node.id)) {
4515
+ mergedWarnings.push({
4516
+ type: "merge_conflict",
4517
+ detail: `Node "${node.id}" produced by multiple parsers; keeping first`
4518
+ });
4519
+ continue;
4520
+ }
4521
+ seenNodes.add(node.id);
4522
+ mergedNodes.push(node);
4523
+ }
4524
+ for (const edge of output.edges) {
4525
+ const key = `${edge.source}|${edge.target}|${edge.type}`;
4526
+ if (seenEdges.has(key)) continue;
4527
+ seenEdges.add(key);
4528
+ mergedEdges.push(edge);
4529
+ }
4530
+ for (const ref of output.cross_refs) {
4531
+ const key = `${ref.source}|${ref.target}|${ref.type}`;
4532
+ if (seenCrossRefs.has(key)) continue;
4533
+ seenCrossRefs.add(key);
4534
+ mergedCrossRefs.push(ref);
4535
+ }
4536
+ mergedContradictions.push(...output.contradictions);
4537
+ mergedWarnings.push(...output.warnings);
4538
+ mergedFlagged.push(...output.flagged_edges);
4539
+ }
4540
+ const metadata = {
4541
+ ...outputs[0].metadata,
4542
+ generated: (/* @__PURE__ */ new Date()).toISOString(),
4543
+ parsers: parserIds
4544
+ };
4545
+ return {
4546
+ metadata,
4547
+ nodes: mergedNodes,
4548
+ edges: mergedEdges,
4549
+ cross_refs: mergedCrossRefs,
4550
+ contradictions: mergedContradictions,
4551
+ warnings: mergedWarnings,
4552
+ flagged_edges: mergedFlagged,
4553
+ patterns: outputs[0].patterns
4554
+ };
4555
+ }
4556
+ function dedupCrossRefs(refs) {
4557
+ const seen = /* @__PURE__ */ new Set();
4558
+ const result = [];
4559
+ for (const ref of refs) {
4560
+ const key = `${ref.source}|${ref.target}|${ref.type}`;
4561
+ if (seen.has(key)) continue;
4562
+ seen.add(key);
4563
+ result.push(ref);
4564
+ }
4565
+ return result;
4566
+ }
4567
+ function applyCrossLayerResults(uiOutput, results) {
4568
+ return {
4569
+ ...uiOutput,
4570
+ cross_refs: dedupCrossRefs([
4571
+ ...uiOutput.cross_refs,
4572
+ ...results.flatMap((r) => r.output.cross_refs)
4573
+ ]),
4574
+ flagged_edges: [
4575
+ ...uiOutput.flagged_edges,
4576
+ ...results.flatMap((r) => r.output.flagged_edges)
4577
+ ],
4578
+ warnings: [
4579
+ ...uiOutput.warnings,
4580
+ ...results.flatMap((r) => r.output.warnings)
4581
+ ]
4582
+ };
4583
+ }
4584
+
4585
+ // src/server/graph/core/graph-builder.ts
4586
+ function readGraphFromDisk(rootDir, layer) {
4587
+ const filePath = (0, import_node_path14.join)(rootDir, LAUNCHSECURE_DIR, "graphs", `${layer}.json`);
4588
+ if (!(0, import_node_fs13.existsSync)(filePath)) return null;
4589
+ try {
4590
+ return JSON.parse((0, import_node_fs13.readFileSync)(filePath, "utf-8"));
4591
+ } catch {
4592
+ return null;
4593
+ }
4594
+ }
4595
+ function generateLayer(rootDir, layer) {
4596
+ const config = loadConfig(rootDir);
4597
+ const registry = createRegistry(config, rootDir);
4598
+ const outputs = [];
4599
+ for (const parser of registry.getParsers(layer)) {
4600
+ if (!parser.detect(rootDir)) continue;
4601
+ outputs.push(parser.generate(rootDir));
4602
+ }
4603
+ for (const mp of registry.getMultiLayerParsersFor(layer)) {
4604
+ if (!mp.detect(rootDir)) continue;
4605
+ const multiOutput = mp.generate(rootDir);
4606
+ const layerOutput = multiOutput.get(layer);
4607
+ if (layerOutput) outputs.push(layerOutput);
4608
+ }
4609
+ if (outputs.length === 0) return null;
4610
+ let merged = outputs.length === 1 ? outputs[0] : mergeGraphOutputs(outputs, layer);
4611
+ if (layer === "ui") {
4612
+ const layerOutputs = /* @__PURE__ */ new Map();
4613
+ layerOutputs.set("ui", merged);
4614
+ for (const otherLayer of registry.getAvailableLayers()) {
4615
+ if (otherLayer === "ui") continue;
4616
+ const existing = readGraphFromDisk(rootDir, otherLayer);
4617
+ if (existing) layerOutputs.set(otherLayer, existing);
4618
+ }
4619
+ const crossParsers = registry.getCrossLayerParsers();
4620
+ const crossResults = crossParsers.filter((p) => p.detect(rootDir)).map((p) => ({ parserId: p.id, output: p.generate(rootDir, layerOutputs) }));
4621
+ if (crossResults.length > 0) {
4622
+ merged = applyCrossLayerResults(merged, crossResults);
4623
+ }
4624
+ }
4625
+ return {
4626
+ layer,
4627
+ output: merged,
4628
+ nodeCount: merged.nodes.length,
4629
+ edgeCount: merged.edges.length
4630
+ };
4631
+ }
4632
+ function generateAll(rootDir) {
4633
+ const config = loadConfig(rootDir);
4634
+ const registry = createRegistry(config, rootDir);
4635
+ const allLayers = registry.getAvailableLayers();
4636
+ const layerOrder = [
4637
+ ...allLayers.filter((l) => l !== "ui"),
4638
+ ...allLayers.filter((l) => l === "ui")
4639
+ ];
4640
+ const layerOutputs = /* @__PURE__ */ new Map();
4641
+ const results = [];
4642
+ const multiLayerResults = /* @__PURE__ */ new Map();
4643
+ for (const layer of layerOrder) {
4644
+ const outputs = [];
4645
+ for (const parser of registry.getParsers(layer)) {
4646
+ if (!parser.detect(rootDir)) continue;
4647
+ outputs.push(parser.generate(rootDir));
4648
+ }
4649
+ for (const mp of registry.getMultiLayerParsersFor(layer)) {
4650
+ if (!mp.detect(rootDir)) continue;
4651
+ if (!multiLayerResults.has(mp.id)) {
4652
+ multiLayerResults.set(mp.id, mp.generate(rootDir));
4653
+ }
4654
+ const cached = multiLayerResults.get(mp.id);
4655
+ const layerOutput = cached.get(layer);
4656
+ if (layerOutput) outputs.push(layerOutput);
4657
+ }
4658
+ if (outputs.length === 0) continue;
4659
+ const merged = outputs.length === 1 ? outputs[0] : mergeGraphOutputs(outputs, layer);
4660
+ layerOutputs.set(layer, merged);
4661
+ results.push({
4662
+ layer,
4663
+ output: merged,
4664
+ nodeCount: merged.nodes.length,
4665
+ edgeCount: merged.edges.length
4666
+ });
4667
+ }
4668
+ const crossParsers = registry.getCrossLayerParsers();
4669
+ const crossResults = crossParsers.filter((p) => p.detect(rootDir)).map((p) => ({ parserId: p.id, output: p.generate(rootDir, layerOutputs) }));
4670
+ if (crossResults.length > 0 && layerOutputs.has("ui")) {
4671
+ const uiOutput = layerOutputs.get("ui");
4672
+ const merged = applyCrossLayerResults(uiOutput, crossResults);
4673
+ layerOutputs.set("ui", merged);
4674
+ const uiResult = results.find((r) => r.layer === "ui");
4675
+ if (uiResult) {
4676
+ uiResult.output = merged;
4677
+ uiResult.nodeCount = merged.nodes.length;
4678
+ uiResult.edgeCount = merged.edges.length;
4679
+ }
4680
+ }
4681
+ const byLayer = new Map(results.map((r) => [r.layer, r]));
4682
+ const wellKnownOrder = ["ui", "api", "db"];
4683
+ const extras = [...byLayer.keys()].filter((l) => !wellKnownOrder.includes(l)).sort();
4684
+ return [...wellKnownOrder, ...extras].map((l) => byLayer.get(l)).filter((r) => !!r);
4685
+ }
4686
+
4687
+ // src/server/parse-worker-entry.ts
4688
+ init_parse_failure_cache();
4689
+ init_ts_extractor();
4690
+ if (!import_node_worker_threads.parentPort) {
4691
+ throw new Error("parse-worker-entry must be spawned as a worker thread");
4692
+ }
4693
+ async function run(req) {
4694
+ try {
4695
+ await initTreeSitter();
4696
+ const config = loadConfig(req.rootDir);
4697
+ setExtractorConfig({
4698
+ dbIdentifiers: config.parsers?.patterns?.dbIdentifiers,
4699
+ mutationMethods: config.parsers?.patterns?.mutationMethods
4700
+ });
4701
+ const failureCache = new ParseFailureCache(req.rootDir, getTreeSitterFingerprint());
4702
+ if (!req.layer) failureCache.clearAll();
4703
+ setActiveFailureCache(failureCache);
4704
+ let results;
4705
+ try {
4706
+ results = req.layer ? [generateLayer(req.rootDir, req.layer)].filter((r) => r !== null) : generateAll(req.rootDir);
4707
+ } finally {
4708
+ failureCache.flush();
4709
+ setActiveFailureCache(null);
4710
+ }
4711
+ const failedFiles = failureCache.list().map((f) => f.path);
4712
+ return { ok: true, results, failedFiles };
4713
+ } catch (e) {
4714
+ const err = e instanceof Error ? e : new Error(String(e));
4715
+ return { ok: false, error: { name: err.name, message: err.message, stack: err.stack } };
4716
+ }
4717
+ }
4718
+ import_node_worker_threads.parentPort.once("message", async (msg) => {
4719
+ const reply = await run(msg);
4720
+ import_node_worker_threads.parentPort.postMessage(reply);
4721
+ });