@launchsecure/launch-kit 0.0.25 → 0.0.26

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 (126) hide show
  1. package/README.md +50 -0
  2. package/dist/beacon/beacon.mjs +1016 -0
  3. package/dist/beacon/beacon.mjs.map +1 -0
  4. package/dist/beacon/beacon.umd.js +87 -0
  5. package/dist/beacon/beacon.umd.js.map +1 -0
  6. package/dist/beacon/index-DAIDnjfR.mjs +513 -0
  7. package/dist/beacon/index-DAIDnjfR.mjs.map +1 -0
  8. package/dist/beacon/types/capture/element.d.ts +3 -0
  9. package/dist/beacon/types/capture/element.d.ts.map +1 -0
  10. package/dist/beacon/types/capture/framework.d.ts +3 -0
  11. package/dist/beacon/types/capture/framework.d.ts.map +1 -0
  12. package/dist/beacon/types/capture/metadata.d.ts +3 -0
  13. package/dist/beacon/types/capture/metadata.d.ts.map +1 -0
  14. package/dist/beacon/types/capture/overlay.d.ts +7 -0
  15. package/dist/beacon/types/capture/overlay.d.ts.map +1 -0
  16. package/dist/beacon/types/capture/picker.d.ts +12 -0
  17. package/dist/beacon/types/capture/picker.d.ts.map +1 -0
  18. package/dist/beacon/types/capture/screenshot.d.ts +7 -0
  19. package/dist/beacon/types/capture/screenshot.d.ts.map +1 -0
  20. package/dist/beacon/types/capture/selector.d.ts +2 -0
  21. package/dist/beacon/types/capture/selector.d.ts.map +1 -0
  22. package/dist/beacon/types/element.d.ts +50 -0
  23. package/dist/beacon/types/element.d.ts.map +1 -0
  24. package/dist/beacon/types/index.d.ts +4 -0
  25. package/dist/beacon/types/index.d.ts.map +1 -0
  26. package/dist/beacon/types/transport/submit.d.ts +3 -0
  27. package/dist/beacon/types/transport/submit.d.ts.map +1 -0
  28. package/dist/beacon/types/types.d.ts +88 -0
  29. package/dist/beacon/types/types.d.ts.map +1 -0
  30. package/dist/beacon/types/ui/button.d.ts +2 -0
  31. package/dist/beacon/types/ui/button.d.ts.map +1 -0
  32. package/dist/beacon/types/ui/drawer.d.ts +31 -0
  33. package/dist/beacon/types/ui/drawer.d.ts.map +1 -0
  34. package/dist/beacon/types/ui/icons.d.ts +9 -0
  35. package/dist/beacon/types/ui/icons.d.ts.map +1 -0
  36. package/dist/beacon/types/ui/pick-mode-overlay.d.ts +25 -0
  37. package/dist/beacon/types/ui/pick-mode-overlay.d.ts.map +1 -0
  38. package/dist/beacon/types/ui/pin-popover.d.ts +14 -0
  39. package/dist/beacon/types/ui/pin-popover.d.ts.map +1 -0
  40. package/dist/chart-client/assets/{index-C8ANseEa.js → index-Bk1hawjD.js} +63 -58
  41. package/dist/chart-client/assets/index-DpaGa3bY.css +1 -0
  42. package/dist/chart-client/index.html +2 -2
  43. package/dist/client/assets/index-Bfel4OQ5.css +32 -0
  44. package/dist/client/assets/{index-Ds9UP_cj.js → index-eC-WuUWB.js} +58 -58
  45. package/dist/client/index.html +2 -2
  46. package/dist/council-client/assets/{index-Dc41S-R2.js → index-Cs_MVXHf.js} +14 -14
  47. package/dist/council-client/assets/index-P5kMsT5a.css +1 -0
  48. package/dist/council-client/index.html +2 -2
  49. package/dist/deck-client/assets/{_baseUniq-2gclQXo7.js → _baseUniq-C2xT_eYu.js} +1 -1
  50. package/dist/deck-client/assets/{arc-DcMY5Wm0.js → arc-CmVL9pGd.js} +1 -1
  51. package/dist/deck-client/assets/{architectureDiagram-Q4EWVU46-B8iirmmJ.js → architectureDiagram-Q4EWVU46-BSFgdjve.js} +1 -1
  52. package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-B4JBLjmJ.js → blockDiagram-DXYQGD6D-DuLzscvP.js} +1 -1
  53. package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-CojrJAk8.js → c4Diagram-AHTNJAMY-CfCJB8eY.js} +1 -1
  54. package/dist/deck-client/assets/channel-B4aNO8ZB.js +1 -0
  55. package/dist/deck-client/assets/{chunk-4BX2VUAB-Bmb_BMDo.js → chunk-4BX2VUAB-DxmLYTWZ.js} +1 -1
  56. package/dist/deck-client/assets/{chunk-4TB4RGXK-CumBy8qe.js → chunk-4TB4RGXK-CCnf7GFE.js} +1 -1
  57. package/dist/deck-client/assets/{chunk-55IACEB6-Ka8Hb1wD.js → chunk-55IACEB6-Db9DApcj.js} +1 -1
  58. package/dist/deck-client/assets/{chunk-EDXVE4YY-B3sIPiQo.js → chunk-EDXVE4YY-DmYDq8ZI.js} +1 -1
  59. package/dist/deck-client/assets/{chunk-FMBD7UC4-C1tYkaqu.js → chunk-FMBD7UC4-BGhUlF20.js} +1 -1
  60. package/dist/deck-client/assets/{chunk-OYMX7WX6-D7Wacbky.js → chunk-OYMX7WX6-CpEnicQZ.js} +1 -1
  61. package/dist/deck-client/assets/{chunk-QZHKN3VN-ChXI0vO3.js → chunk-QZHKN3VN-Doa7LKwf.js} +1 -1
  62. package/dist/deck-client/assets/{chunk-YZCP3GAM-BXhiqf8u.js → chunk-YZCP3GAM-CpkIlH6V.js} +1 -1
  63. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-BHTI0yWz.js +1 -0
  64. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-BHTI0yWz.js +1 -0
  65. package/dist/deck-client/assets/clone-HduFm7qU.js +1 -0
  66. package/dist/deck-client/assets/{cose-bilkent-S5V4N54A-Bqp3p68D.js → cose-bilkent-S5V4N54A-Bkh8Bfcb.js} +1 -1
  67. package/dist/deck-client/assets/{dagre-KV5264BT-BS-rtyhZ.js → dagre-KV5264BT-Bp0XpTgH.js} +1 -1
  68. package/dist/deck-client/assets/{diagram-5BDNPKRD-BIrj9YGI.js → diagram-5BDNPKRD-ZHiyGYPQ.js} +1 -1
  69. package/dist/deck-client/assets/{diagram-G4DWMVQ6-noHWPIg4.js → diagram-G4DWMVQ6-BW-Q8_H5.js} +1 -1
  70. package/dist/deck-client/assets/{diagram-MMDJMWI5-C2qHxvqV.js → diagram-MMDJMWI5-6I3LTafu.js} +1 -1
  71. package/dist/deck-client/assets/{diagram-TYMM5635-BytnGQr-.js → diagram-TYMM5635-CyM5YK28.js} +1 -1
  72. package/dist/deck-client/assets/{erDiagram-SMLLAGMA-BfK5m2YQ.js → erDiagram-SMLLAGMA-CjNxVJHk.js} +1 -1
  73. package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-Cq925G1Z.js → flowDiagram-DWJPFMVM-BDQHuAJR.js} +1 -1
  74. package/dist/deck-client/assets/{ganttDiagram-T4ZO3ILL-DhhHPAmj.js → ganttDiagram-T4ZO3ILL-B7MnkpbP.js} +1 -1
  75. package/dist/deck-client/assets/{gitGraphDiagram-UUTBAWPF-B3Lc0h9q.js → gitGraphDiagram-UUTBAWPF-C9dZAcYD.js} +1 -1
  76. package/dist/deck-client/assets/{graph-RTawgVWm.js → graph-CjdBnzUy.js} +1 -1
  77. package/dist/deck-client/assets/{index-BfIfJXmS.js → index-DeIVPW63.js} +68 -68
  78. package/dist/deck-client/assets/index-LKZDAS9S.css +1 -0
  79. package/dist/deck-client/assets/{infoDiagram-42DDH7IO-BlR584kX.js → infoDiagram-42DDH7IO-C7d3iRC3.js} +1 -1
  80. package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-DygKoNGY.js → ishikawaDiagram-UXIWVN3A-BcYGKj09.js} +1 -1
  81. package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-BnaiYp9N.js → journeyDiagram-VCZTEJTY-DqFlRrOL.js} +1 -1
  82. package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-BQBUBzJC.js → kanban-definition-6JOO6SKY-BJhPp1NR.js} +1 -1
  83. package/dist/deck-client/assets/{layout-DeZ8HI1T.js → layout-DIeS6GvK.js} +1 -1
  84. package/dist/deck-client/assets/{linear-C6roLi_9.js → linear-He_yJy5H.js} +1 -1
  85. package/dist/deck-client/assets/{min-CbUksbuI.js → min-DQ6Kx06t.js} +1 -1
  86. package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-iNxV62yN.js → mindmap-definition-QFDTVHPH-sQ62L8T2.js} +1 -1
  87. package/dist/deck-client/assets/{pieDiagram-DEJITSTG-DHVA0jaG.js → pieDiagram-DEJITSTG-BqCWmU2K.js} +1 -1
  88. package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-DBeKKLUQ.js → quadrantDiagram-34T5L4WZ-rQ1TJOoe.js} +1 -1
  89. package/dist/deck-client/assets/{requirementDiagram-MS252O5E-CBwITx7p.js → requirementDiagram-MS252O5E-BO2MPBOM.js} +1 -1
  90. package/dist/deck-client/assets/{sankeyDiagram-XADWPNL6-BtE-1YTU.js → sankeyDiagram-XADWPNL6-BgsHEVex.js} +1 -1
  91. package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-DN96yPP2.js → sequenceDiagram-FGHM5R23-B3j1yMLU.js} +1 -1
  92. package/dist/deck-client/assets/{stateDiagram-FHFEXIEX-VUkKC2uJ.js → stateDiagram-FHFEXIEX-C8jFlZou.js} +1 -1
  93. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-BoqepHW0.js +1 -0
  94. package/dist/deck-client/assets/{timeline-definition-GMOUNBTQ-oUeZhRns.js → timeline-definition-GMOUNBTQ-tM-qo4Zk.js} +1 -1
  95. package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-D87fK90n.js → vennDiagram-DHZGUBPP-B0-6kOEu.js} +1 -1
  96. package/dist/deck-client/assets/wardley-RL74JXVD-HpBk07P-.js +162 -0
  97. package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-Ca_i0QRA.js → wardleyDiagram-NUSXRM2D-BkA1NLDE.js} +1 -1
  98. package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-CUOJVIvq.js → xychartDiagram-5P7HB3ND-CEKGSuI-.js} +1 -1
  99. package/dist/deck-client/index.html +2 -2
  100. package/dist/server/chart-serve.js +990 -116
  101. package/dist/server/cli.js +28413 -6982
  102. package/dist/server/council-entry.js +0 -0
  103. package/dist/server/deck-mcp-entry.js +332 -3
  104. package/dist/server/deck-serve.js +288 -0
  105. package/dist/server/fb-wizard.js +0 -0
  106. package/dist/server/graph/queries/classify.scm +8 -0
  107. package/dist/server/graph/queries/exports.scm +7 -0
  108. package/dist/server/graph-mcp-entry.js +1639 -197
  109. package/dist/server/recall-entry.js +1112 -0
  110. package/package.json +47 -21
  111. package/dist/chart-client/assets/index--120d9P9.css +0 -1
  112. package/dist/client/assets/index-Bf8zdL3x.css +0 -32
  113. package/dist/council-client/assets/index-CofZh7pS.css +0 -1
  114. package/dist/deck-client/assets/channel-ERh5jKXV.js +0 -1
  115. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-CMi1Gaev.js +0 -1
  116. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-CMi1Gaev.js +0 -1
  117. package/dist/deck-client/assets/clone-DfWhlD4X.js +0 -1
  118. package/dist/deck-client/assets/index-765AIQ9z.css +0 -1
  119. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-CA0IjulK.js +0 -1
  120. package/dist/deck-client/assets/wardley-RL74JXVD-DYbYcpDp.js +0 -162
  121. package/dist/server/deck-server/deck-mcp-entry.js +0 -1789
  122. package/dist/server/deck-server/deck-serve.js +0 -1275
  123. package/dist/server/server/chart-serve.js +0 -4643
  124. package/dist/server/server/cli.js +0 -13360
  125. package/dist/server/server/fb-wizard.js +0 -136
  126. package/dist/server/server/graph-mcp-entry.js +0 -6776
@@ -57,17 +57,20 @@ var init_config = __esm({
57
57
  // src/server/graph/core/ts-extractor.ts
58
58
  var ts_extractor_exports = {};
59
59
  __export(ts_extractor_exports, {
60
+ ParseCascadeError: () => ParseCascadeError,
60
61
  classifyFile: () => classifyFile,
61
62
  classifyRouteAgainstMiddleware: () => classifyRouteAgainstMiddleware,
62
63
  createQuery: () => createQuery,
63
64
  extractAuthWrappersTS: () => extractAuthWrappersTS,
64
65
  extractDbCallsTS: () => extractDbCallsTS,
65
66
  extractDeep: () => extractDeep,
67
+ extractEffects: () => extractEffects,
66
68
  extractMiddlewareAuthTS: () => extractMiddlewareAuthTS,
67
69
  initTreeSitter: () => initTreeSitter,
68
70
  middlewarePatternToRegex: () => middlewarePatternToRegex,
69
71
  parseCodeTS: () => parseCodeTS,
70
72
  parseFileTS: () => parseFileTS,
73
+ parseSource: () => parseSource,
71
74
  setExtractorConfig: () => setExtractorConfig
72
75
  });
73
76
  async function initTreeSitter() {
@@ -101,8 +104,38 @@ function getQuery(name) {
101
104
  }
102
105
  function parseSource(absPath) {
103
106
  ensureInit();
104
- const content = (0, import_node_fs4.readFileSync)(absPath, "utf-8");
105
- return parserInstance.parse(content);
107
+ let content;
108
+ try {
109
+ const stat = (0, import_node_fs4.statSync)(absPath);
110
+ if (stat.size > MAX_PARSEABLE_BYTES) {
111
+ process.stderr.write(`[lc-extractor] skipping ${absPath}: ${stat.size} bytes exceeds max ${MAX_PARSEABLE_BYTES}
112
+ `);
113
+ return null;
114
+ }
115
+ content = (0, import_node_fs4.readFileSync)(absPath, "utf-8");
116
+ } catch (e) {
117
+ process.stderr.write(`[lc-extractor] read failed for ${absPath}: ${e instanceof Error ? e.message : String(e)}
118
+ `);
119
+ return null;
120
+ }
121
+ try {
122
+ const tree = parserInstance.parse(content);
123
+ consecutiveParseFailures = 0;
124
+ return tree;
125
+ } catch (e) {
126
+ consecutiveParseFailures++;
127
+ const msg = e instanceof Error ? e.message : String(e);
128
+ process.stderr.write(
129
+ `[lc-extractor] parse failed for ${absPath}: ${msg} (consecutive failures: ${consecutiveParseFailures}/${MAX_CONSECUTIVE_PARSE_FAILURES})
130
+ `
131
+ );
132
+ if (consecutiveParseFailures >= MAX_CONSECUTIVE_PARSE_FAILURES) {
133
+ const tripCount = consecutiveParseFailures;
134
+ consecutiveParseFailures = 0;
135
+ throw new ParseCascadeError(absPath, tripCount);
136
+ }
137
+ return null;
138
+ }
106
139
  }
107
140
  function parseCodeTS(code) {
108
141
  ensureInit();
@@ -142,8 +175,20 @@ function childrenOfType(node, type) {
142
175
  function childOfType(node, type) {
143
176
  return node.children.find((n) => n.type === type);
144
177
  }
178
+ function emptyParsedFile(absPath) {
179
+ return {
180
+ name: absPath.split("/").pop() ?? absPath,
181
+ exports: [],
182
+ imports: [],
183
+ reExports: [],
184
+ jsxElements: /* @__PURE__ */ new Set(),
185
+ navigations: [],
186
+ fetchCalls: []
187
+ };
188
+ }
145
189
  function parseFileTS(absPath) {
146
190
  const tree = parseSource(absPath);
191
+ if (!tree) return emptyParsedFile(absPath);
147
192
  const root = tree.rootNode;
148
193
  const imports = [];
149
194
  const importStatements = childrenOfType(root, "import_statement");
@@ -338,6 +383,7 @@ function parseFileTS(absPath) {
338
383
  }
339
384
  function extractDbCallsTS(absPath) {
340
385
  const tree = parseSource(absPath);
386
+ if (!tree) return [];
341
387
  const root = tree.rootNode;
342
388
  const dbQuery = getQuery("db-calls");
343
389
  const matches = dbQuery.matches(root);
@@ -398,7 +444,9 @@ function classifyFile(absPath) {
398
444
  const fileName = require("path").basename(absPath);
399
445
  if (fileName.includes(".test.") || fileName.includes(".spec.") || fileName.includes("__test")) return "test";
400
446
  if (fileName.includes(".stories.")) return "story";
447
+ if (fileName === "middleware.ts" || fileName === "middleware.tsx") return "middleware";
401
448
  const tree = parseSource(absPath);
449
+ if (!tree) return "util";
402
450
  const root = tree.rootNode;
403
451
  const classifyQuery = getQuery("classify");
404
452
  const captures = classifyQuery.captures(root);
@@ -417,6 +465,7 @@ function classifyFile(absPath) {
417
465
  }
418
466
  function extractAuthWrappersTS(absPath) {
419
467
  const tree = parseSource(absPath);
468
+ if (!tree) return /* @__PURE__ */ new Set();
420
469
  const root = tree.rootNode;
421
470
  const wrapperQuery = getQuery("wrappers");
422
471
  const matches = wrapperQuery.matches(root);
@@ -460,6 +509,9 @@ function extractAuthWrappersTS(absPath) {
460
509
  return wrappers;
461
510
  }
462
511
  function inferIntentFromName(name) {
512
+ if (TRUST_AS_PROTECT_KEYS.has(name)) {
513
+ return { intent: "protect", hint: `Next.js config.${name} \u2014 paths matched run through middleware` };
514
+ }
463
515
  for (const re of EXEMPT_NAME_PATTERNS) {
464
516
  if (re.test(name)) return { intent: "exempt", hint: `name "${name}" matches /${re.source}/` };
465
517
  }
@@ -536,6 +588,7 @@ function detectFallthroughProtect(root) {
536
588
  function extractMiddlewareAuthTS(absPath) {
537
589
  if (!require("node:fs").existsSync(absPath)) return null;
538
590
  const tree = parseSource(absPath);
591
+ if (!tree) return null;
539
592
  const root = tree.rootNode;
540
593
  const matchers = [];
541
594
  for (const stmt of root.children) {
@@ -579,6 +632,16 @@ function extractMiddlewareAuthTS(absPath) {
579
632
  };
580
633
  }
581
634
  function middlewarePatternToRegex(pattern) {
635
+ const negLookaheadMatch = /^(\/?)\(\(\?\!([^)]+)\)(\.\*|\.\+)\)$/.exec(pattern);
636
+ if (negLookaheadMatch) {
637
+ const [, lead, altsRaw, body] = negLookaheadMatch;
638
+ const escapedAlts = altsRaw.split("|").map((alt) => alt.trim().replace(/[.+*?^${}()|[\]\\]/g, "\\$&")).join("|");
639
+ try {
640
+ return new RegExp(`^${lead}(?!${escapedAlts})${body}$`);
641
+ } catch {
642
+ return null;
643
+ }
644
+ }
582
645
  if (/\(\?\!/.test(pattern)) return null;
583
646
  if (pattern.startsWith("(")) return null;
584
647
  let src = "^";
@@ -636,8 +699,163 @@ function classifyRouteAgainstMiddleware(routePath, info) {
636
699
  function trunc(s, max = 120) {
637
700
  return s.length > max ? s.slice(0, max) + "..." : s;
638
701
  }
702
+ function dedup(arr) {
703
+ return Array.from(new Set(arr));
704
+ }
705
+ function firstStringArg(callExpr) {
706
+ const args = callExpr.childForFieldName("arguments");
707
+ if (!args) return null;
708
+ for (const c of args.namedChildren) {
709
+ if (c.type === "string") {
710
+ const frag = c.namedChildren.find((cc) => cc.type === "string_fragment");
711
+ return frag ? frag.text : c.text.replace(/^['"`]|['"`]$/g, "");
712
+ }
713
+ if (c.type === "template_string") {
714
+ return c.text.replace(/^`|`$/g, "");
715
+ }
716
+ }
717
+ return null;
718
+ }
719
+ function isFunctionLike(node) {
720
+ 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";
721
+ }
722
+ function stringLiteralValue(n) {
723
+ if (n.type !== "string") return null;
724
+ const frag = n.namedChildren.find((c) => c.type === "string_fragment");
725
+ if (frag) return frag.text;
726
+ return n.text.replace(/^['"`]|['"`]$/g, "");
727
+ }
728
+ function nthStringArg(callExpr, idx) {
729
+ const args = callExpr.childForFieldName("arguments");
730
+ if (!args) return null;
731
+ const child = args.namedChildren[idx];
732
+ return child ? stringLiteralValue(child) : null;
733
+ }
734
+ function objectArgIdValues(callExpr) {
735
+ const args = callExpr.childForFieldName("arguments");
736
+ if (!args) return [];
737
+ const out = [];
738
+ for (const arg of args.namedChildren) {
739
+ if (arg.type !== "object") continue;
740
+ for (const pair of arg.namedChildren) {
741
+ if (pair.type !== "pair") continue;
742
+ const key = pair.childForFieldName("key");
743
+ const val = pair.childForFieldName("value");
744
+ if (!key || !val) continue;
745
+ const keyText = key.type === "property_identifier" ? key.text : stringLiteralValue(key) ?? key.text;
746
+ if (keyText !== "id") continue;
747
+ const strVal = stringLiteralValue(val);
748
+ if (strVal) out.push(strVal);
749
+ }
750
+ }
751
+ return out;
752
+ }
753
+ function extractEffects(node, moduleOnly = false) {
754
+ const calls = [];
755
+ const dom_writes = [];
756
+ const subscribes = [];
757
+ const timers = [];
758
+ const persists = [];
759
+ const fetches = [];
760
+ const globals = [];
761
+ const dom_ids = [];
762
+ const window_events = [];
763
+ const storage_keys = [];
764
+ const fetch_urls = [];
765
+ function visit(n, depth) {
766
+ if (moduleOnly && depth > 0 && isFunctionLike(n)) return;
767
+ if (n.type === "call_expression") {
768
+ const fn = n.childForFieldName("function");
769
+ if (fn) {
770
+ const fnText = fn.text;
771
+ calls.push(fnText);
772
+ for (const id of objectArgIdValues(n)) dom_ids.push(id);
773
+ if (fn.type === "identifier") {
774
+ if (TIMER_FNS.has(fnText)) timers.push(fnText);
775
+ if (fnText === "fetch") {
776
+ const url = firstStringArg(n);
777
+ fetches.push(url ? `fetch("${url}")` : "fetch(...)");
778
+ if (url) fetch_urls.push(url);
779
+ }
780
+ } else if (fn.type === "member_expression") {
781
+ const obj = fn.childForFieldName("object");
782
+ const prop = fn.childForFieldName("property");
783
+ const objText = obj?.text ?? "";
784
+ const propText = prop?.text ?? "";
785
+ if (STORAGE_OBJECTS.has(objText)) {
786
+ const key = firstStringArg(n);
787
+ persists.push(key ? `${objText}.${propText}("${key}")` : `${objText}.${propText}(\u2026)`);
788
+ if (key) storage_keys.push(key);
789
+ } else if (propText === "addEventListener") {
790
+ const event = firstStringArg(n);
791
+ subscribes.push(event ? `${objText}.addEventListener("${event}")` : `${objText}.addEventListener(\u2026)`);
792
+ if (event && (objText === "window" || objText === "document")) {
793
+ window_events.push(`${objText}:${event}`);
794
+ }
795
+ } else if (propText === "removeEventListener") {
796
+ const event = firstStringArg(n);
797
+ subscribes.push(event ? `${objText}.removeEventListener("${event}")` : `${objText}.removeEventListener(\u2026)`);
798
+ } else if (propText === "subscribe" || propText === "on") {
799
+ subscribes.push(`${objText}.${propText}(\u2026)`);
800
+ } else if (DOM_METHOD_NAMES.has(propText)) {
801
+ dom_writes.push(`${objText}.${propText}(\u2026)`);
802
+ if (propText === "setAttribute" && nthStringArg(n, 0) === "id") {
803
+ const idVal = nthStringArg(n, 1);
804
+ if (idVal) dom_ids.push(idVal);
805
+ }
806
+ } else if (objText.endsWith(".classList") && CLASSLIST_METHODS.has(propText)) {
807
+ dom_writes.push(`${objText}.${propText}(\u2026)`);
808
+ } else if (objText === "history" && HISTORY_METHODS.has(propText)) {
809
+ globals.push(`history.${propText}(\u2026)`);
810
+ } else if (objText === "location" && LOCATION_METHODS.has(propText)) {
811
+ globals.push(`location.${propText}(\u2026)`);
812
+ } else if (objText === "window" && propText === "matchMedia") {
813
+ globals.push("window.matchMedia(\u2026)");
814
+ }
815
+ }
816
+ }
817
+ } else if (n.type === "assignment_expression") {
818
+ const lhs = n.children.find((c) => c.type !== "=");
819
+ const rhs = n.childForFieldName("right");
820
+ if (lhs?.type === "member_expression") {
821
+ const prop = lhs.childForFieldName("property");
822
+ const propText = prop?.text ?? "";
823
+ const lhsText = lhs.text;
824
+ if (lhsText.startsWith("document.cookie")) {
825
+ persists.push("document.cookie =");
826
+ } else if (lhsText.startsWith("document.title") || lhsText.startsWith("location.")) {
827
+ globals.push(`${lhsText} =`);
828
+ } else if (ASSIGN_DOM_PROPS.has(propText) || lhsText.includes(".style.") || lhsText.includes(".dataset.")) {
829
+ dom_writes.push(`${lhsText} =`);
830
+ if (propText === "id" && rhs) {
831
+ const idVal = stringLiteralValue(rhs);
832
+ if (idVal) dom_ids.push(idVal);
833
+ }
834
+ }
835
+ }
836
+ }
837
+ for (const child of n.children) visit(child, depth + 1);
838
+ }
839
+ visit(node, 0);
840
+ const out = {};
841
+ if (calls.length) out.calls = dedup(calls);
842
+ if (dom_writes.length) out.dom_writes = dedup(dom_writes);
843
+ if (subscribes.length) out.subscribes = dedup(subscribes);
844
+ if (timers.length) out.timers = dedup(timers);
845
+ if (persists.length) out.persists = dedup(persists);
846
+ if (fetches.length) out.fetches = dedup(fetches);
847
+ if (globals.length) out.globals = dedup(globals);
848
+ if (dom_ids.length) out.dom_ids = dedup(dom_ids);
849
+ if (window_events.length) out.window_events = dedup(window_events);
850
+ if (storage_keys.length) out.storage_keys = dedup(storage_keys);
851
+ if (fetch_urls.length) out.fetch_urls = dedup(fetch_urls);
852
+ return out;
853
+ }
639
854
  function extractDeep(absPath) {
640
855
  const tree = parseSource(absPath);
856
+ if (!tree) {
857
+ return { elements: [], stateVars: [], conditions: [], variables: [], responses: [], params: [] };
858
+ }
641
859
  const root = tree.rootNode;
642
860
  const elements = [];
643
861
  const elQuery = getQuery("deep/jsx-semantic");
@@ -730,12 +948,18 @@ function extractDeep(absPath) {
730
948
  const caps = captureMap(m);
731
949
  const declNode = m.captures.find((c) => c.name === "var.decl" || c.name === "var.destructured" || c.name === "var.array")?.node;
732
950
  const kind = declNode?.children.find((n) => n.type === "const" || n.type === "let" || n.type === "var")?.type ?? "const";
951
+ const initNode = m.captures.find((c) => c.name === "var.init")?.node;
733
952
  if (caps["var.name"] && caps["var.init"]) {
734
- variables.push({
953
+ const variable = {
735
954
  name: caps["var.name"],
736
955
  kind,
737
956
  init: trunc(caps["var.init"], 100)
738
- });
957
+ };
958
+ if (initNode && isFunctionLike(initNode)) {
959
+ const eff = extractEffects(initNode);
960
+ if (Object.keys(eff).length > 0) variable.effects = eff;
961
+ }
962
+ variables.push(variable);
739
963
  }
740
964
  if (caps["var.destructured.obj"]) {
741
965
  variables.push({
@@ -787,9 +1011,23 @@ function extractDeep(absPath) {
787
1011
  params.push({ name: caps["param.body"], source: "body" });
788
1012
  }
789
1013
  }
790
- return { elements, stateVars, conditions, variables, responses, params };
1014
+ const fileEffects = extractEffects(
1015
+ root,
1016
+ /* moduleOnly */
1017
+ false
1018
+ );
1019
+ const hasEffects = Object.keys(fileEffects).length > 0;
1020
+ return {
1021
+ elements,
1022
+ stateVars,
1023
+ conditions,
1024
+ variables,
1025
+ responses,
1026
+ params,
1027
+ ...hasEffects ? { effects: fileEffects } : {}
1028
+ };
791
1029
  }
792
- var import_node_fs4, import_node_path4, tsxLanguage, parserInstance, initPromise, initialized, queriesDir, queryCache, PRISMA_MUTATION_METHODS_BUILTIN, SUPABASE_MUTATION_METHODS_BUILTIN, DB_IDENTIFIERS_FALLBACK, extraDbIdentifiers, extraMutationMethods, INLINE_AUTH_IMPORTS, EXEMPT_NAME_PATTERNS, PROTECT_NAME_PATTERNS;
1030
+ var import_node_fs4, import_node_path4, tsxLanguage, parserInstance, 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;
793
1031
  var init_ts_extractor = __esm({
794
1032
  "src/server/graph/core/ts-extractor.ts"() {
795
1033
  "use strict";
@@ -802,6 +1040,19 @@ var init_ts_extractor = __esm({
802
1040
  return (0, import_node_path4.join)((0, import_node_path4.dirname)(__filename), "graph", "queries");
803
1041
  })();
804
1042
  queryCache = /* @__PURE__ */ new Map();
1043
+ MAX_PARSEABLE_BYTES = 2 * 1024 * 1024;
1044
+ MAX_CONSECUTIVE_PARSE_FAILURES = 10;
1045
+ consecutiveParseFailures = 0;
1046
+ ParseCascadeError = class extends Error {
1047
+ constructor(lastPath, failureCount) {
1048
+ super(
1049
+ `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.`
1050
+ );
1051
+ this.lastPath = lastPath;
1052
+ this.failureCount = failureCount;
1053
+ this.name = "ParseCascadeError";
1054
+ }
1055
+ };
805
1056
  PRISMA_MUTATION_METHODS_BUILTIN = [
806
1057
  "create",
807
1058
  "createMany",
@@ -851,6 +1102,49 @@ var init_ts_extractor = __esm({
851
1102
  /^admin_?routes?/i,
852
1103
  /^secured/i
853
1104
  ];
1105
+ TRUST_AS_PROTECT_KEYS = /* @__PURE__ */ new Set(["matcher", "matchers"]);
1106
+ TIMER_FNS = /* @__PURE__ */ new Set([
1107
+ "setInterval",
1108
+ "setTimeout",
1109
+ "clearInterval",
1110
+ "clearTimeout",
1111
+ "requestAnimationFrame",
1112
+ "cancelAnimationFrame",
1113
+ "queueMicrotask"
1114
+ ]);
1115
+ DOM_METHOD_NAMES = /* @__PURE__ */ new Set([
1116
+ "setAttribute",
1117
+ "removeAttribute",
1118
+ "appendChild",
1119
+ "removeChild",
1120
+ "replaceChildren",
1121
+ "replaceWith",
1122
+ "insertBefore",
1123
+ "append",
1124
+ "prepend",
1125
+ "remove",
1126
+ "before",
1127
+ "after",
1128
+ "insertAdjacentHTML",
1129
+ "insertAdjacentElement"
1130
+ ]);
1131
+ CLASSLIST_METHODS = /* @__PURE__ */ new Set(["add", "remove", "toggle", "replace"]);
1132
+ STORAGE_OBJECTS = /* @__PURE__ */ new Set(["localStorage", "sessionStorage"]);
1133
+ HISTORY_METHODS = /* @__PURE__ */ new Set(["pushState", "replaceState", "back", "forward", "go"]);
1134
+ LOCATION_METHODS = /* @__PURE__ */ new Set(["assign", "replace", "reload"]);
1135
+ ASSIGN_DOM_PROPS = /* @__PURE__ */ new Set([
1136
+ "textContent",
1137
+ "innerHTML",
1138
+ "innerText",
1139
+ "value",
1140
+ "src",
1141
+ "href",
1142
+ "className",
1143
+ "id",
1144
+ "checked",
1145
+ "selected",
1146
+ "disabled"
1147
+ ]);
854
1148
  }
855
1149
  });
856
1150
 
@@ -862,20 +1156,20 @@ __export(chart_serve_exports, {
862
1156
  });
863
1157
  module.exports = __toCommonJS(chart_serve_exports);
864
1158
  var import_node_http = __toESM(require("node:http"));
865
- var import_node_fs18 = __toESM(require("node:fs"));
866
- var import_node_path19 = __toESM(require("node:path"));
1159
+ var import_node_fs20 = __toESM(require("node:fs"));
1160
+ var import_node_path22 = __toESM(require("node:path"));
867
1161
 
868
1162
  // src/server/graph/index.ts
869
- var import_node_fs15 = require("node:fs");
870
- var import_node_path16 = require("node:path");
1163
+ var import_node_fs16 = require("node:fs");
1164
+ var import_node_path18 = require("node:path");
871
1165
 
872
1166
  // src/server/graph/core/graph-builder.ts
873
1167
  var import_node_fs12 = require("node:fs");
874
- var import_node_path12 = require("node:path");
1168
+ var import_node_path13 = require("node:path");
875
1169
  init_config();
876
1170
 
877
1171
  // src/server/graph/core/parser-registry.ts
878
- var import_node_path11 = require("node:path");
1172
+ var import_node_path12 = require("node:path");
879
1173
 
880
1174
  // src/server/graph/parsers/ts/typescript-project.ts
881
1175
  var import_node_fs5 = require("node:fs");
@@ -1126,6 +1420,7 @@ var CLASSIFICATION_TO_LAYER = {
1126
1420
  config: "ui",
1127
1421
  lib: "ui",
1128
1422
  "mcp-tool": "ui",
1423
+ middleware: "ui",
1129
1424
  external: "ui"
1130
1425
  };
1131
1426
  function toNodeId(srcDir, rootDir, absPath) {
@@ -1209,6 +1504,8 @@ function buildAllBarrelMaps(srcDir, parsedByPath) {
1209
1504
  function classifyType(absPath, id) {
1210
1505
  const contentType = classifyFile(absPath);
1211
1506
  if (contentType === "lib" && id.startsWith("server/mcp/")) return "mcp-tool";
1507
+ if (/^app\/(.+\/)?page\.tsx$/.test(id)) return "page";
1508
+ if (/^app\/(.+\/)?layout\.tsx$/.test(id)) return "layout";
1212
1509
  return contentType;
1213
1510
  }
1214
1511
  function extractRoute(id) {
@@ -1519,6 +1816,7 @@ function generate(rootDir) {
1519
1816
  variables: deep.variables,
1520
1817
  responses: deep.responses,
1521
1818
  params: deep.params,
1819
+ ...deep.effects ? { effects: deep.effects } : {},
1522
1820
  _dbCalls: dbCalls
1523
1821
  // temp: used for cross-ref building below
1524
1822
  });
@@ -1538,6 +1836,7 @@ function generate(rootDir) {
1538
1836
  stateVars: deep.stateVars,
1539
1837
  conditions: deep.conditions,
1540
1838
  variables: deep.variables,
1839
+ ...deep.effects ? { effects: deep.effects } : {},
1541
1840
  ...authWrappers.length > 0 ? { auth: authWrappers } : {},
1542
1841
  ...dbCalls.length > 0 ? { _dbCalls: dbCalls } : {}
1543
1842
  });
@@ -3808,6 +4107,112 @@ var staticRefScannerParser = {
3808
4107
  }
3809
4108
  };
3810
4109
 
4110
+ // src/server/graph/parsers/crosslayer/middleware-gates.ts
4111
+ var import_node_path11 = require("node:path");
4112
+ init_ts_extractor();
4113
+ init_config();
4114
+ function toNodeId4(srcDir, rootDir, absPath) {
4115
+ const relFromSrc = (0, import_node_path11.relative)(srcDir, absPath).replace(/\\/g, "/");
4116
+ if (relFromSrc.startsWith("..")) {
4117
+ return (0, import_node_path11.relative)(rootDir, absPath).replace(/\\/g, "/");
4118
+ }
4119
+ return relFromSrc;
4120
+ }
4121
+ function collectTargets(apiOutput, uiOutput) {
4122
+ const out = [];
4123
+ for (const n of apiOutput?.nodes ?? []) {
4124
+ if (n.type !== "endpoint") continue;
4125
+ const path2 = n.path;
4126
+ if (typeof path2 !== "string" || !path2) continue;
4127
+ out.push({ id: n.id, route: path2, layer: "api" });
4128
+ }
4129
+ for (const n of uiOutput?.nodes ?? []) {
4130
+ if (n.type !== "page") continue;
4131
+ const route = n.route;
4132
+ if (typeof route !== "string" || !route) continue;
4133
+ out.push({ id: n.id, route, layer: "ui" });
4134
+ }
4135
+ return out;
4136
+ }
4137
+ var middlewareGatesParser = {
4138
+ id: "middleware-gates",
4139
+ layer: "crosslayer",
4140
+ concern: "auth-gate",
4141
+ detect(rootDir) {
4142
+ const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
4143
+ if (!paths) return false;
4144
+ return paths.conventionFiles.some((f) => /middleware\.tsx?$/.test(f));
4145
+ },
4146
+ generate(rootDir, layerOutputs) {
4147
+ const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
4148
+ if (!paths) return { cross_refs: [], flagged_edges: [], warnings: [] };
4149
+ const middlewareFiles = paths.conventionFiles.filter((f) => /middleware\.tsx?$/.test(f));
4150
+ if (middlewareFiles.length === 0) {
4151
+ return { cross_refs: [], flagged_edges: [], warnings: [] };
4152
+ }
4153
+ const apiOutput = layerOutputs.get("api");
4154
+ const uiOutput = layerOutputs.get("ui");
4155
+ const targets = collectTargets(apiOutput, uiOutput);
4156
+ const crossRefs = [];
4157
+ const flagged = [];
4158
+ const seenEdge = /* @__PURE__ */ new Set();
4159
+ const seenUnparseable = /* @__PURE__ */ new Set();
4160
+ let protectMatcherCount = 0;
4161
+ for (const file of middlewareFiles) {
4162
+ let info = null;
4163
+ try {
4164
+ info = extractMiddlewareAuthTS(file);
4165
+ } catch {
4166
+ }
4167
+ if (!info || info.matchers.length === 0) continue;
4168
+ const middlewareId = toNodeId4(paths.srcDir, rootDir, file);
4169
+ for (const matcher of info.matchers) {
4170
+ if (matcher.intent !== "protect") continue;
4171
+ protectMatcherCount += 1;
4172
+ for (const pattern of matcher.patterns) {
4173
+ const re = middlewarePatternToRegex(pattern);
4174
+ if (!re) {
4175
+ const key = `${middlewareId}|${pattern}`;
4176
+ if (seenUnparseable.has(key)) continue;
4177
+ seenUnparseable.add(key);
4178
+ flagged.push({
4179
+ source: middlewareId,
4180
+ target: "UNRESOLVED",
4181
+ type: "middleware_pattern_unparseable",
4182
+ label: `pattern "${pattern}" in matcher "${matcher.name}" \u2014 coverage unknown`,
4183
+ confidence: "high"
4184
+ });
4185
+ continue;
4186
+ }
4187
+ for (const target of targets) {
4188
+ if (!re.test(target.route)) continue;
4189
+ const key = `${middlewareId}|${target.id}|protects`;
4190
+ if (seenEdge.has(key)) continue;
4191
+ seenEdge.add(key);
4192
+ crossRefs.push({
4193
+ source: middlewareId,
4194
+ target: target.id,
4195
+ type: "protects",
4196
+ layer: target.layer
4197
+ });
4198
+ }
4199
+ }
4200
+ }
4201
+ }
4202
+ return {
4203
+ cross_refs: crossRefs,
4204
+ flagged_edges: flagged,
4205
+ warnings: [],
4206
+ patterns: {
4207
+ middleware_files: middlewareFiles.length,
4208
+ protect_matchers: protectMatcherCount,
4209
+ protects_edges: crossRefs.length,
4210
+ unparseable_patterns: flagged.length
4211
+ }
4212
+ };
4213
+ }
4214
+ };
4215
+
3811
4216
  // src/server/graph/core/parser-registry.ts
3812
4217
  function isMultiLayerParser(p) {
3813
4218
  return "layers" in p && Array.isArray(p.layers);
@@ -3873,7 +4278,8 @@ function registerBuiltins(registry, disabled) {
3873
4278
  fetchResolverParser,
3874
4279
  apiAnnotationsParser,
3875
4280
  urlLiteralScannerParser,
3876
- staticRefScannerParser
4281
+ staticRefScannerParser,
4282
+ middlewareGatesParser
3877
4283
  ];
3878
4284
  for (const parser of builtins) {
3879
4285
  if (disabled.has(parser.id)) continue;
@@ -3883,7 +4289,7 @@ function registerBuiltins(registry, disabled) {
3883
4289
  function loadCustomParsers(registry, config, rootDir, disabled) {
3884
4290
  for (const entry of config.parsers?.custom ?? []) {
3885
4291
  try {
3886
- const absPath = (0, import_node_path11.resolve)(rootDir, entry.path);
4292
+ const absPath = (0, import_node_path12.resolve)(rootDir, entry.path);
3887
4293
  const mod = require(absPath);
3888
4294
  const parser = "default" in mod ? mod.default : mod;
3889
4295
  if (disabled.has(parser.id)) continue;
@@ -4013,7 +4419,7 @@ function applyCrossLayerResults(uiOutput, results) {
4013
4419
 
4014
4420
  // src/server/graph/core/graph-builder.ts
4015
4421
  function readGraphFromDisk(rootDir, layer) {
4016
- const filePath = (0, import_node_path12.join)(rootDir, ".launchsecure", "graphs", `${layer}.json`);
4422
+ const filePath = (0, import_node_path13.join)(rootDir, ".launchsecure", "graphs", `${layer}.json`);
4017
4423
  if (!(0, import_node_fs12.existsSync)(filePath)) return null;
4018
4424
  try {
4019
4425
  return JSON.parse((0, import_node_fs12.readFileSync)(filePath, "utf-8"));
@@ -4117,11 +4523,11 @@ function generateAll(rootDir) {
4117
4523
  init_config();
4118
4524
 
4119
4525
  // src/server/graph/core/tagger-registry.ts
4120
- var import_node_path14 = require("node:path");
4526
+ var import_node_path15 = require("node:path");
4121
4527
 
4122
4528
  // src/server/graph/taggers/module-tagger.ts
4123
4529
  var import_node_fs13 = require("node:fs");
4124
- var import_node_path13 = require("node:path");
4530
+ var import_node_path14 = require("node:path");
4125
4531
  function matchGlob(pattern, id) {
4126
4532
  const patParts = pattern.split("/");
4127
4533
  const idParts = id.split("/");
@@ -4154,13 +4560,13 @@ function detectConventionDirs(rootDir, extraConventionDirs = []) {
4154
4560
  const conventionDirs = [...CONVENTION_DIRS_BUILTIN, ...extraConventionDirs];
4155
4561
  const searchDirs = [
4156
4562
  rootDir,
4157
- (0, import_node_path13.join)(rootDir, "src"),
4158
- (0, import_node_path13.join)(rootDir, "app"),
4159
- (0, import_node_path13.join)(rootDir, "lib")
4563
+ (0, import_node_path14.join)(rootDir, "src"),
4564
+ (0, import_node_path14.join)(rootDir, "app"),
4565
+ (0, import_node_path14.join)(rootDir, "lib")
4160
4566
  ];
4161
4567
  for (const base of searchDirs) {
4162
4568
  for (const convention of conventionDirs) {
4163
- const dir = (0, import_node_path13.join)(base, convention);
4569
+ const dir = (0, import_node_path14.join)(base, convention);
4164
4570
  if (!(0, import_node_fs13.existsSync)(dir)) continue;
4165
4571
  try {
4166
4572
  const stat = (0, import_node_fs13.statSync)(dir);
@@ -4466,7 +4872,7 @@ function loadCustomTaggers(registry, config, rootDir, disabled) {
4466
4872
  for (const entry of config.taggers?.custom ?? []) {
4467
4873
  if (disabled.has(entry.id)) continue;
4468
4874
  try {
4469
- const absPath = (0, import_node_path14.resolve)(rootDir, entry.path);
4875
+ const absPath = (0, import_node_path15.resolve)(rootDir, entry.path);
4470
4876
  const mod = require(absPath);
4471
4877
  const tagger = "default" in mod ? mod.default : mod;
4472
4878
  const override = config.taggers?.trackUntagged?.[tagger.id];
@@ -4490,12 +4896,12 @@ function createTaggerRegistry(config, rootDir) {
4490
4896
 
4491
4897
  // src/server/graph/core/tag-store.ts
4492
4898
  var import_node_fs14 = require("node:fs");
4493
- var import_node_path15 = require("node:path");
4899
+ var import_node_path16 = require("node:path");
4494
4900
  var TAGS_FILENAME = "tags.json";
4495
4901
  var GRAPHS_DIR = ".launchsecure/graphs";
4496
4902
  var tagCache = /* @__PURE__ */ new Map();
4497
4903
  function tagsFilePath(rootDir) {
4498
- return (0, import_node_path15.join)(rootDir, GRAPHS_DIR, TAGS_FILENAME);
4904
+ return (0, import_node_path16.join)(rootDir, GRAPHS_DIR, TAGS_FILENAME);
4499
4905
  }
4500
4906
  function readTagStore(rootDir) {
4501
4907
  const filePath = tagsFilePath(rootDir);
@@ -4516,7 +4922,7 @@ function readTagStore(rootDir) {
4516
4922
  }
4517
4923
  function writeTagStore(rootDir, store) {
4518
4924
  const filePath = tagsFilePath(rootDir);
4519
- const dir = (0, import_node_path15.dirname)(filePath);
4925
+ const dir = (0, import_node_path16.dirname)(filePath);
4520
4926
  (0, import_node_fs14.mkdirSync)(dir, { recursive: true });
4521
4927
  const cleaned = {};
4522
4928
  for (const [nodeId, tags] of Object.entries(store)) {
@@ -4545,26 +4951,89 @@ function removeTag(rootDir, nodeId, key) {
4545
4951
 
4546
4952
  // src/server/graph/index.ts
4547
4953
  init_ts_extractor();
4954
+
4955
+ // src/server/graph/core/effects-index.ts
4956
+ var import_node_fs15 = require("node:fs");
4957
+ var import_node_path17 = require("node:path");
4958
+ function addTo(map, key, nodeId) {
4959
+ const list = map[key];
4960
+ if (!list) {
4961
+ map[key] = [nodeId];
4962
+ } else if (!list.includes(nodeId)) {
4963
+ list.push(nodeId);
4964
+ }
4965
+ }
4966
+ function collisionList(map) {
4967
+ const out = [];
4968
+ for (const [key, nodes] of Object.entries(map)) {
4969
+ if (nodes.length > 1) out.push({ key, nodes: nodes.slice().sort() });
4970
+ }
4971
+ out.sort((a, b) => b.nodes.length - a.nodes.length || a.key.localeCompare(b.key));
4972
+ return out;
4973
+ }
4974
+ function buildEffectsIndex(layerOutputs) {
4975
+ const idx = {
4976
+ generated: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
4977
+ dom_ids: {},
4978
+ window_events: {},
4979
+ storage_keys: {},
4980
+ fetch_urls: {},
4981
+ timers: {},
4982
+ singleton_risks: [],
4983
+ collisions: { dom_ids: [], storage_keys: [], window_events: [] }
4984
+ };
4985
+ const singletonSet = /* @__PURE__ */ new Set();
4986
+ for (const output of Object.values(layerOutputs)) {
4987
+ for (const node of output.nodes) {
4988
+ const eff = node.effects;
4989
+ if (!eff) continue;
4990
+ for (const id of eff.dom_ids ?? []) addTo(idx.dom_ids, id, node.id);
4991
+ for (const ev of eff.window_events ?? []) addTo(idx.window_events, ev, node.id);
4992
+ for (const k of eff.storage_keys ?? []) addTo(idx.storage_keys, k, node.id);
4993
+ for (const u of eff.fetch_urls ?? []) addTo(idx.fetch_urls, u, node.id);
4994
+ for (const t of eff.timers ?? []) addTo(idx.timers, t, node.id);
4995
+ const hasTimer = (eff.timers?.length ?? 0) > 0;
4996
+ const hasWindowListener = (eff.window_events?.length ?? 0) > 0;
4997
+ const hasGlobalMutation = (eff.globals?.length ?? 0) > 0;
4998
+ if (hasTimer || hasWindowListener || hasGlobalMutation) {
4999
+ singletonSet.add(node.id);
5000
+ }
5001
+ }
5002
+ }
5003
+ idx.singleton_risks = Array.from(singletonSet).sort();
5004
+ idx.collisions.dom_ids = collisionList(idx.dom_ids);
5005
+ idx.collisions.storage_keys = collisionList(idx.storage_keys);
5006
+ idx.collisions.window_events = collisionList(idx.window_events);
5007
+ return idx;
5008
+ }
5009
+ function writeEffectsIndex(rootDir, idx) {
5010
+ const path2 = (0, import_node_path17.join)(rootDir, ".launchsecure", "graphs", "effects-index.json");
5011
+ (0, import_node_fs15.writeFileSync)(path2, JSON.stringify(idx, null, 2) + "\n", "utf-8");
5012
+ return path2;
5013
+ }
5014
+
5015
+ // src/server/graph/index.ts
4548
5016
  var GRAPHS_DIR2 = ".launchsecure/graphs";
5017
+ var NON_LAYER_GRAPH_FILES = /* @__PURE__ */ new Set(["tags.json", "effects-index.json"]);
4549
5018
  function getAvailableLayers(rootDir) {
4550
- const dir = (0, import_node_path16.join)(rootDir, GRAPHS_DIR2);
4551
- if (!(0, import_node_fs15.existsSync)(dir)) return [];
4552
- return (0, import_node_fs15.readdirSync)(dir).filter((f) => f.endsWith(".json") && f !== "tags.json").map((f) => f.replace(".json", ""));
5019
+ const dir = (0, import_node_path18.join)(rootDir, GRAPHS_DIR2);
5020
+ if (!(0, import_node_fs16.existsSync)(dir)) return [];
5021
+ return (0, import_node_fs16.readdirSync)(dir).filter((f) => f.endsWith(".json") && !NON_LAYER_GRAPH_FILES.has(f)).map((f) => f.replace(".json", ""));
4553
5022
  }
4554
5023
  var graphCache = /* @__PURE__ */ new Map();
4555
5024
  var taggedCache = /* @__PURE__ */ new Map();
4556
5025
  function graphsDir(rootDir) {
4557
- return (0, import_node_path16.join)(rootDir, GRAPHS_DIR2);
5026
+ return (0, import_node_path18.join)(rootDir, GRAPHS_DIR2);
4558
5027
  }
4559
5028
  function graphFilePath(rootDir, layer) {
4560
- return (0, import_node_path16.join)(graphsDir(rootDir), `${layer}.json`);
5029
+ return (0, import_node_path18.join)(graphsDir(rootDir), `${layer}.json`);
4561
5030
  }
4562
5031
  function tagsFilePath2(rootDir) {
4563
- return (0, import_node_path16.join)(graphsDir(rootDir), "tags.json");
5032
+ return (0, import_node_path18.join)(graphsDir(rootDir), "tags.json");
4564
5033
  }
4565
5034
  function getMtimeMs(filePath) {
4566
- if (!(0, import_node_fs15.existsSync)(filePath)) return 0;
4567
- return (0, import_node_fs15.statSync)(filePath).mtimeMs;
5035
+ if (!(0, import_node_fs16.existsSync)(filePath)) return 0;
5036
+ return (0, import_node_fs16.statSync)(filePath).mtimeMs;
4568
5037
  }
4569
5038
  function invalidateCache(filePath) {
4570
5039
  graphCache.delete(filePath);
@@ -4603,20 +5072,20 @@ function applyTags(graph, layer, rootDir) {
4603
5072
  }
4604
5073
  function readGraphRaw(rootDir, layer) {
4605
5074
  const filePath = graphFilePath(rootDir, layer);
4606
- if (!(0, import_node_fs15.existsSync)(filePath)) return null;
4607
- const stat = (0, import_node_fs15.statSync)(filePath);
5075
+ if (!(0, import_node_fs16.existsSync)(filePath)) return null;
5076
+ const stat = (0, import_node_fs16.statSync)(filePath);
4608
5077
  const cached = graphCache.get(filePath);
4609
5078
  if (cached && cached.mtimeMs === stat.mtimeMs) {
4610
5079
  return cached.graph;
4611
5080
  }
4612
- const content = (0, import_node_fs15.readFileSync)(filePath, "utf-8");
5081
+ const content = (0, import_node_fs16.readFileSync)(filePath, "utf-8");
4613
5082
  const graph = JSON.parse(content);
4614
5083
  graphCache.set(filePath, { mtimeMs: stat.mtimeMs, graph });
4615
5084
  return graph;
4616
5085
  }
4617
5086
  function readGraph(rootDir, layer) {
4618
5087
  const rawFilePath = graphFilePath(rootDir, layer);
4619
- if (!(0, import_node_fs15.existsSync)(rawFilePath)) return null;
5088
+ if (!(0, import_node_fs16.existsSync)(rawFilePath)) return null;
4620
5089
  const rawMtime = getMtimeMs(rawFilePath);
4621
5090
  const tagsMtime = getMtimeMs(tagsFilePath2(rootDir));
4622
5091
  const cacheKey = `${rootDir}:${layer}`;
@@ -4646,24 +5115,24 @@ async function generateGraph(rootDir, layer) {
4646
5115
  mutationMethods: config.parsers?.patterns?.mutationMethods
4647
5116
  });
4648
5117
  const dir = graphsDir(rootDir);
4649
- (0, import_node_fs15.mkdirSync)(dir, { recursive: true });
5118
+ (0, import_node_fs16.mkdirSync)(dir, { recursive: true });
4650
5119
  const results = layer ? [generateLayer(rootDir, layer)].filter((r) => r !== null) : generateAll(rootDir);
4651
5120
  for (const result of results) {
4652
5121
  const filePath = graphFilePath(rootDir, result.layer);
4653
- (0, import_node_fs15.writeFileSync)(filePath, JSON.stringify(result.output, null, 2) + "\n", "utf-8");
5122
+ (0, import_node_fs16.writeFileSync)(filePath, JSON.stringify(result.output, null, 2) + "\n", "utf-8");
4654
5123
  invalidateCache(filePath);
4655
5124
  invalidateTaggedCache(rootDir, result.layer);
4656
5125
  }
4657
5126
  if (!layer) {
4658
5127
  const producedLayers = new Set(results.map((r) => r.layer));
4659
5128
  try {
4660
- for (const f of (0, import_node_fs15.readdirSync)(dir)) {
4661
- if (!f.endsWith(".json") || f === "tags.json") continue;
5129
+ for (const f of (0, import_node_fs16.readdirSync)(dir)) {
5130
+ if (!f.endsWith(".json") || f === "tags.json" || f === "effects-index.json") continue;
4662
5131
  const layerName = f.replace(/\.json$/, "");
4663
5132
  if (producedLayers.has(layerName)) continue;
4664
- const orphan = (0, import_node_path16.join)(dir, f);
5133
+ const orphan = (0, import_node_path18.join)(dir, f);
4665
5134
  try {
4666
- (0, import_node_fs15.unlinkSync)(orphan);
5135
+ (0, import_node_fs16.unlinkSync)(orphan);
4667
5136
  invalidateCache(orphan);
4668
5137
  invalidateTaggedCache(rootDir, layerName);
4669
5138
  process.stderr.write(`[launch-chart] removed orphan layer file: ${f} (no parser produced ${layerName} this run)
@@ -4674,33 +5143,51 @@ async function generateGraph(rootDir, layer) {
4674
5143
  } catch {
4675
5144
  }
4676
5145
  }
5146
+ try {
5147
+ const allLayers = {};
5148
+ for (const r of results) allLayers[r.layer] = r.output;
5149
+ if (layer) {
5150
+ for (const f of (0, import_node_fs16.readdirSync)(dir)) {
5151
+ if (!f.endsWith(".json") || f === "tags.json" || f === "effects-index.json") continue;
5152
+ const layerName = f.replace(/\.json$/, "");
5153
+ if (allLayers[layerName]) continue;
5154
+ const existing = readGraphRaw(rootDir, layerName);
5155
+ if (existing) allLayers[layerName] = existing;
5156
+ }
5157
+ }
5158
+ const idx = buildEffectsIndex(allLayers);
5159
+ writeEffectsIndex(rootDir, idx);
5160
+ } catch (e) {
5161
+ process.stderr.write(`[launch-chart] effects-index build failed: ${e.message}
5162
+ `);
5163
+ }
4677
5164
  return results;
4678
5165
  }
4679
5166
 
4680
5167
  // src/server/lockfile.ts
4681
5168
  var import_node_child_process = require("node:child_process");
4682
- var import_node_fs16 = require("node:fs");
5169
+ var import_node_fs17 = require("node:fs");
4683
5170
  var import_node_os = require("node:os");
4684
- var import_node_path17 = require("node:path");
5171
+ var import_node_path19 = require("node:path");
4685
5172
  function lockDir(projectRoot) {
4686
5173
  if (projectRoot) {
4687
- return (0, import_node_path17.join)(projectRoot, ".launchsecure");
5174
+ return (0, import_node_path19.join)(projectRoot, ".launchsecure");
4688
5175
  }
4689
- return (0, import_node_path17.join)((0, import_node_os.homedir)(), ".launchsecure");
5176
+ return (0, import_node_path19.join)((0, import_node_os.homedir)(), ".launchsecure");
4690
5177
  }
4691
5178
  function lockPath(projectRoot) {
4692
- return (0, import_node_path17.join)(lockDir(projectRoot), "launch-chart.lock");
5179
+ return (0, import_node_path19.join)(lockDir(projectRoot), "launch-chart.lock");
4693
5180
  }
4694
5181
  var _activeProjectRoot;
4695
5182
  function readLock(projectRoot) {
4696
5183
  const root = projectRoot ?? _activeProjectRoot;
4697
5184
  const p = lockPath(root);
4698
- if (!(0, import_node_fs16.existsSync)(p)) {
5185
+ if (!(0, import_node_fs17.existsSync)(p)) {
4699
5186
  if (root) {
4700
5187
  const globalP = lockPath();
4701
- if ((0, import_node_fs16.existsSync)(globalP)) {
5188
+ if ((0, import_node_fs17.existsSync)(globalP)) {
4702
5189
  try {
4703
- const data = JSON.parse((0, import_node_fs16.readFileSync)(globalP, "utf-8"));
5190
+ const data = JSON.parse((0, import_node_fs17.readFileSync)(globalP, "utf-8"));
4704
5191
  if (typeof data.pid === "number" && typeof data.port === "number" && data.cwd === root) {
4705
5192
  return data;
4706
5193
  }
@@ -4711,7 +5198,7 @@ function readLock(projectRoot) {
4711
5198
  return null;
4712
5199
  }
4713
5200
  try {
4714
- const data = JSON.parse((0, import_node_fs16.readFileSync)(p, "utf-8"));
5201
+ const data = JSON.parse((0, import_node_fs17.readFileSync)(p, "utf-8"));
4715
5202
  if (typeof data.pid !== "number" || typeof data.port !== "number") return null;
4716
5203
  return data;
4717
5204
  } catch {
@@ -4748,7 +5235,7 @@ function getLiveLock(projectRoot) {
4748
5235
  const live = listenerPid !== null ? listenerPid === lock.pid : isPidAlive(lock.pid);
4749
5236
  if (!live) {
4750
5237
  try {
4751
- (0, import_node_fs16.unlinkSync)(lockPath(root));
5238
+ (0, import_node_fs17.unlinkSync)(lockPath(root));
4752
5239
  } catch {
4753
5240
  }
4754
5241
  return null;
@@ -4757,14 +5244,14 @@ function getLiveLock(projectRoot) {
4757
5244
  }
4758
5245
  function writeLock(data, projectRoot) {
4759
5246
  const root = projectRoot ?? _activeProjectRoot;
4760
- (0, import_node_fs16.mkdirSync)(lockDir(root), { recursive: true });
4761
- (0, import_node_fs16.writeFileSync)(lockPath(root), JSON.stringify(data, null, 2) + "\n", "utf-8");
5247
+ (0, import_node_fs17.mkdirSync)(lockDir(root), { recursive: true });
5248
+ (0, import_node_fs17.writeFileSync)(lockPath(root), JSON.stringify(data, null, 2) + "\n", "utf-8");
4762
5249
  if (root) _activeProjectRoot = root;
4763
5250
  }
4764
5251
  function clearLock(projectRoot) {
4765
5252
  const root = projectRoot ?? _activeProjectRoot;
4766
5253
  try {
4767
- (0, import_node_fs16.unlinkSync)(lockPath(root));
5254
+ (0, import_node_fs17.unlinkSync)(lockPath(root));
4768
5255
  } catch {
4769
5256
  }
4770
5257
  }
@@ -4773,13 +5260,350 @@ function clearLock(projectRoot) {
4773
5260
  init_config();
4774
5261
 
4775
5262
  // src/server/graph/core/audit-core.ts
4776
- var import_node_fs17 = require("node:fs");
4777
- var import_node_path18 = require("node:path");
5263
+ var import_node_fs19 = require("node:fs");
5264
+ var import_node_path21 = require("node:path");
5265
+
5266
+ // src/server/graph/core/audit-security.ts
5267
+ var import_node_fs18 = require("node:fs");
5268
+ var import_node_path20 = require("node:path");
5269
+ init_ts_extractor();
5270
+ init_config();
5271
+ function collectSourceFiles(rootDir) {
5272
+ const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
5273
+ if (!paths) return null;
5274
+ const seen = /* @__PURE__ */ new Set();
5275
+ const out = [];
5276
+ for (const root of paths.srcRoots) {
5277
+ for (const f of walk(root, [".ts", ".tsx"])) {
5278
+ if (!seen.has(f)) {
5279
+ seen.add(f);
5280
+ out.push(f);
5281
+ }
5282
+ }
5283
+ }
5284
+ for (const conv of paths.conventionFiles) {
5285
+ if (!seen.has(conv)) {
5286
+ seen.add(conv);
5287
+ out.push(conv);
5288
+ }
5289
+ }
5290
+ return out;
5291
+ }
5292
+ function toNodeId5(rootDir, srcDir, absPath) {
5293
+ const relFromSrc = (0, import_node_path20.relative)(srcDir, absPath).replace(/\\/g, "/");
5294
+ if (relFromSrc.startsWith("..")) {
5295
+ return (0, import_node_path20.relative)(rootDir, absPath).replace(/\\/g, "/");
5296
+ }
5297
+ return relFromSrc;
5298
+ }
5299
+ var SECRET_KEY_RE = /^(.*_)?(token|secret|password|passwd|credential|credentials|apikey|api_key|access_?token|refresh_?token|client_?secret|private_?key|signing_?key|webhook_?secret|auth_?token|bearer)$/i;
5300
+ var SECRET_KEY_ALLOWLIST = /* @__PURE__ */ new Set([
5301
+ "csrfToken",
5302
+ "csrf_token",
5303
+ "publicKey",
5304
+ "public_key",
5305
+ "clientId",
5306
+ "idempotencyKey",
5307
+ "idempotency_key",
5308
+ "requestKey",
5309
+ "request_key",
5310
+ "sessionId",
5311
+ "session_id",
5312
+ "apiKeyId",
5313
+ "api_key_id",
5314
+ "tokenType",
5315
+ "token_type",
5316
+ "tokenExpiry",
5317
+ "token_expiry",
5318
+ "isAccessToken",
5319
+ "hasToken"
5320
+ ]);
5321
+ function scanResponseSecretsInFile(absPath) {
5322
+ const tree = parseSource(absPath);
5323
+ if (!tree) return [];
5324
+ const query = createQuery(`
5325
+ (call_expression
5326
+ function: (member_expression
5327
+ property: (property_identifier) @method
5328
+ (#eq? @method "json"))
5329
+ arguments: (arguments
5330
+ (object) @payload))
5331
+ `);
5332
+ const matches = query.matches(tree.rootNode);
5333
+ const hits = [];
5334
+ for (const m of matches) {
5335
+ const caps = m.captures;
5336
+ const payload = caps.find((c) => c.name === "payload")?.node;
5337
+ if (!payload) continue;
5338
+ walkPairKeys(payload, (keyName, node) => {
5339
+ const trimmed = keyName.replace(/^["']|["']$/g, "");
5340
+ if (SECRET_KEY_ALLOWLIST.has(trimmed)) return;
5341
+ if (!SECRET_KEY_RE.test(trimmed)) return;
5342
+ hits.push({
5343
+ file: absPath,
5344
+ line: node.startPosition.row + 1,
5345
+ keyName: trimmed,
5346
+ callShape: payload.text.slice(0, 40).replace(/\s+/g, " ")
5347
+ });
5348
+ });
5349
+ }
5350
+ return hits;
5351
+ }
5352
+ function walkPairKeys(node, visit) {
5353
+ for (const child of node.children) {
5354
+ if (child.type === "pair") {
5355
+ const keyChild = child.children[0];
5356
+ if (keyChild && (keyChild.type === "property_identifier" || keyChild.type === "string")) {
5357
+ visit(keyChild.text, keyChild);
5358
+ }
5359
+ const valueChild = child.children[child.children.length - 1];
5360
+ if (valueChild && valueChild.type === "object") walkPairKeys(valueChild, visit);
5361
+ } else if (child.type === "shorthand_property_identifier") {
5362
+ visit(child.text, child);
5363
+ } else if (child.type === "object") {
5364
+ walkPairKeys(child, visit);
5365
+ }
5366
+ }
5367
+ }
5368
+ function checkResponseSecretLeak(rootDir, core) {
5369
+ const files = collectSourceFiles(rootDir);
5370
+ if (files === null || files.length === 0) {
5371
+ return core.buildSkipped(
5372
+ "security",
5373
+ "response_secret_leak",
5374
+ `no source files detected \u2014 not a TypeScript project, or src roots aren't configured`
5375
+ );
5376
+ }
5377
+ const findings = [];
5378
+ const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
5379
+ for (const abs of files) {
5380
+ let hits = [];
5381
+ try {
5382
+ hits = scanResponseSecretsInFile(abs);
5383
+ } catch {
5384
+ }
5385
+ for (const h of hits) {
5386
+ const fileId = toNodeId5(rootDir, paths.srcDir, abs);
5387
+ findings.push({
5388
+ id: `secret-leak:${fileId}:${h.line}:${h.keyName}`,
5389
+ severity: "error",
5390
+ category: "response_secret_leak",
5391
+ title: `${h.keyName} in response payload`,
5392
+ detail: `Response body includes a "${h.keyName}" key \u2014 likely leaking a secret to the client. If this is a public identifier or anti-CSRF token, add it to the allow-list in audit-security.ts.`,
5393
+ file: fileId,
5394
+ line: h.line
5395
+ });
5396
+ }
5397
+ }
5398
+ return core.buildReport("security", "response_secret_leak", findings);
5399
+ }
5400
+ function collectDeclaredEnvKeys(rootDir) {
5401
+ const keys = /* @__PURE__ */ new Set();
5402
+ const files = [];
5403
+ let entries = [];
5404
+ try {
5405
+ entries = (0, import_node_fs18.readdirSync)(rootDir);
5406
+ } catch {
5407
+ return { keys, files };
5408
+ }
5409
+ for (const name of entries) {
5410
+ if (!name.startsWith(".env")) continue;
5411
+ const abs = (0, import_node_path20.join)(rootDir, name);
5412
+ if (!(0, import_node_fs18.existsSync)(abs)) continue;
5413
+ files.push(name);
5414
+ let content = "";
5415
+ try {
5416
+ content = (0, import_node_fs18.readFileSync)(abs, "utf-8");
5417
+ } catch {
5418
+ continue;
5419
+ }
5420
+ for (const line of content.split("\n")) {
5421
+ const trimmed = line.trim();
5422
+ if (!trimmed || trimmed.startsWith("#")) continue;
5423
+ const eq = trimmed.indexOf("=");
5424
+ if (eq < 0) continue;
5425
+ const key = trimmed.slice(0, eq).trim();
5426
+ if (/^[A-Z_][A-Z0-9_]*$/.test(key)) keys.add(key);
5427
+ }
5428
+ }
5429
+ return { keys, files };
5430
+ }
5431
+ var FRAMEWORK_ENV_KEYS = /* @__PURE__ */ new Set([
5432
+ "NODE_ENV",
5433
+ "NODE_OPTIONS",
5434
+ "NODE_PATH",
5435
+ "PATH",
5436
+ "HOME",
5437
+ "USER",
5438
+ "PWD",
5439
+ "CI",
5440
+ "GITHUB_ACTIONS",
5441
+ "GITHUB_WORKFLOW",
5442
+ "GITHUB_RUN_ID",
5443
+ "GITHUB_SHA",
5444
+ "VERCEL",
5445
+ "VERCEL_ENV",
5446
+ "VERCEL_URL",
5447
+ "VERCEL_REGION",
5448
+ "VERCEL_GIT_COMMIT_SHA",
5449
+ "VERCEL_GIT_COMMIT_REF",
5450
+ "VERCEL_GIT_REPO_SLUG",
5451
+ "VERCEL_GIT_REPO_OWNER",
5452
+ "PORT",
5453
+ "TZ",
5454
+ "LANG",
5455
+ "LC_ALL",
5456
+ "TERM",
5457
+ "DEBUG",
5458
+ "NEXT_RUNTIME",
5459
+ "NEXT_PUBLIC_VERCEL_URL"
5460
+ ]);
5461
+ function scanEnvRefsInFile(absPath) {
5462
+ const tree = parseSource(absPath);
5463
+ if (!tree) return [];
5464
+ const query = createQuery(`
5465
+ ; process.env.X
5466
+ (member_expression
5467
+ object: (member_expression
5468
+ object: (identifier) @_proc
5469
+ property: (property_identifier) @_env)
5470
+ property: (property_identifier) @env_name
5471
+ (#eq? @_proc "process")
5472
+ (#eq? @_env "env"))
5473
+
5474
+ ; process.env["X"]
5475
+ (subscript_expression
5476
+ object: (member_expression
5477
+ object: (identifier) @_proc2
5478
+ property: (property_identifier) @_env2)
5479
+ index: (string (string_fragment) @env_name_str)
5480
+ (#eq? @_proc2 "process")
5481
+ (#eq? @_env2 "env"))
5482
+ `);
5483
+ const matches = query.matches(tree.rootNode);
5484
+ const refs = [];
5485
+ for (const m of matches) {
5486
+ const caps = m.captures;
5487
+ const key = caps.find((c) => c.name === "env_name" || c.name === "env_name_str");
5488
+ if (!key) continue;
5489
+ refs.push({ file: absPath, line: key.node.startPosition.row + 1, envKey: key.node.text });
5490
+ }
5491
+ return refs;
5492
+ }
5493
+ function checkEnvDeadAlias(rootDir, core) {
5494
+ const { keys: declared, files: envFiles } = collectDeclaredEnvKeys(rootDir);
5495
+ if (envFiles.length === 0) {
5496
+ return core.buildSkipped(
5497
+ "security",
5498
+ "env_dead_alias",
5499
+ `no .env* files in project root \u2014 this check needs a declared-env inventory to compare against`
5500
+ );
5501
+ }
5502
+ const files = collectSourceFiles(rootDir);
5503
+ if (files === null || files.length === 0) {
5504
+ return core.buildSkipped("security", "env_dead_alias", `no source files detected`);
5505
+ }
5506
+ const findings = [];
5507
+ const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
5508
+ const seen = /* @__PURE__ */ new Set();
5509
+ for (const abs of files) {
5510
+ let refs = [];
5511
+ try {
5512
+ refs = scanEnvRefsInFile(abs);
5513
+ } catch {
5514
+ }
5515
+ for (const r of refs) {
5516
+ if (declared.has(r.envKey)) continue;
5517
+ if (FRAMEWORK_ENV_KEYS.has(r.envKey)) continue;
5518
+ if (r.envKey.startsWith("npm_")) continue;
5519
+ const fileId = toNodeId5(rootDir, paths.srcDir, abs);
5520
+ const dedupeKey = `${fileId}:${r.envKey}`;
5521
+ if (seen.has(dedupeKey)) continue;
5522
+ seen.add(dedupeKey);
5523
+ findings.push({
5524
+ id: `dead-env:${fileId}:${r.envKey}`,
5525
+ severity: "warning",
5526
+ category: "env_dead_alias",
5527
+ title: `process.env.${r.envKey} not declared in any .env file`,
5528
+ detail: `Reference to env var "${r.envKey}" but no .env* file declares it (${envFiles.join(", ")}). Likely a rename left behind, a typo, or an env var that's only set in deployment but should be documented in .env.example.`,
5529
+ file: fileId,
5530
+ line: r.line
5531
+ });
5532
+ }
5533
+ }
5534
+ return core.buildReport("security", "env_dead_alias", findings);
5535
+ }
5536
+ function scanUrlFallbacksInFile(absPath) {
5537
+ const tree = parseSource(absPath);
5538
+ if (!tree) return [];
5539
+ const query = createQuery(`
5540
+ ; process.env.X || "https://..." or process.env.X ?? "https://..."
5541
+ (binary_expression
5542
+ left: (member_expression
5543
+ object: (member_expression
5544
+ object: (identifier) @_proc
5545
+ property: (property_identifier) @_env)
5546
+ property: (property_identifier) @env_var)
5547
+ right: (string (string_fragment) @url)
5548
+ (#eq? @_proc "process")
5549
+ (#eq? @_env "env")
5550
+ (#match? @url "^https?://")) @expr
5551
+ `);
5552
+ const matches = query.matches(tree.rootNode);
5553
+ const hits = [];
5554
+ for (const m of matches) {
5555
+ const caps = m.captures;
5556
+ const envVar = caps.find((c) => c.name === "env_var")?.node;
5557
+ const url = caps.find((c) => c.name === "url")?.node;
5558
+ const expr = caps.find((c) => c.name === "expr")?.node;
5559
+ if (!envVar || !url || !expr) continue;
5560
+ const opMatch = / (\|\||\?\?) /.exec(expr.text);
5561
+ const operator = opMatch ? opMatch[1] : "||";
5562
+ hits.push({
5563
+ file: absPath,
5564
+ line: envVar.startPosition.row + 1,
5565
+ envKey: envVar.text,
5566
+ url: url.text,
5567
+ operator
5568
+ });
5569
+ }
5570
+ return hits;
5571
+ }
5572
+ function checkHardcodedUrlFallback(rootDir, core) {
5573
+ const files = collectSourceFiles(rootDir);
5574
+ if (files === null || files.length === 0) {
5575
+ return core.buildSkipped("security", "hardcoded_url_fallback", `no source files detected`);
5576
+ }
5577
+ const findings = [];
5578
+ const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
5579
+ for (const abs of files) {
5580
+ let hits = [];
5581
+ try {
5582
+ hits = scanUrlFallbacksInFile(abs);
5583
+ } catch {
5584
+ }
5585
+ for (const h of hits) {
5586
+ const fileId = toNodeId5(rootDir, paths.srcDir, abs);
5587
+ findings.push({
5588
+ id: `url-fallback:${fileId}:${h.line}:${h.envKey}`,
5589
+ severity: "warning",
5590
+ category: "hardcoded_url_fallback",
5591
+ title: `${h.envKey} ${h.operator} "${h.url}"`,
5592
+ detail: `Hardcoded URL fallback for env var "${h.envKey}". If the env var is unset in prod, requests will silently route to "${h.url}" \u2014 common cause of dev/prod URL leaks and stale localhost ports. Make the env var required, or move the default to a deployment-aware config.`,
5593
+ file: fileId,
5594
+ line: h.line
5595
+ });
5596
+ }
5597
+ }
5598
+ return core.buildReport("security", "hardcoded_url_fallback", findings);
5599
+ }
5600
+
5601
+ // src/server/graph/core/audit-core.ts
4778
5602
  function readGraphFile(rootDir, layer) {
4779
- const filePath = (0, import_node_path18.join)(rootDir, ".launchsecure", "graphs", `${layer}.json`);
4780
- if (!(0, import_node_fs17.existsSync)(filePath)) return null;
5603
+ const filePath = (0, import_node_path21.join)(rootDir, ".launchsecure", "graphs", `${layer}.json`);
5604
+ if (!(0, import_node_fs19.existsSync)(filePath)) return null;
4781
5605
  try {
4782
- return JSON.parse((0, import_node_fs17.readFileSync)(filePath, "utf-8"));
5606
+ return JSON.parse((0, import_node_fs19.readFileSync)(filePath, "utf-8"));
4783
5607
  } catch {
4784
5608
  return null;
4785
5609
  }
@@ -4788,8 +5612,7 @@ function checkSchemaDrift(rootDir) {
4788
5612
  const findings = [];
4789
5613
  const db = readGraphFile(rootDir, "db");
4790
5614
  if (!db) {
4791
- findings.push({ id: "no-db-graph", severity: "info", category: "schema_drift", title: "No DB graph", detail: "Run generate_graph first to populate the DB layer." });
4792
- return buildReport("db", "schema_drift", findings);
5615
+ return buildSkipped("db", "schema_drift", "no db graph \u2014 generate_graph first, or this project has no Prisma schema");
4793
5616
  }
4794
5617
  for (const c of db.contradictions ?? []) {
4795
5618
  const isTableLevel = c.detail.includes("Table ") && (c.detail.includes("has no CREATE TABLE") || c.detail.includes("not in schema.prisma"));
@@ -4806,7 +5629,7 @@ function checkSchemaDrift(rootDir) {
4806
5629
  function checkOrphanFks(rootDir) {
4807
5630
  const findings = [];
4808
5631
  const db = readGraphFile(rootDir, "db");
4809
- if (!db) return buildReport("db", "orphan_fks", findings);
5632
+ if (!db) return buildSkipped("db", "orphan_fks", "no db graph");
4810
5633
  for (const f of db.flagged_edges ?? []) {
4811
5634
  findings.push({
4812
5635
  id: `fk:${f.source}->${f.target}`,
@@ -4821,13 +5644,16 @@ function checkOrphanFks(rootDir) {
4821
5644
  function checkUnprotectedRoutes(rootDir) {
4822
5645
  const findings = [];
4823
5646
  const api = readGraphFile(rootDir, "api");
4824
- const staticGraph = readGraphFile(rootDir, "static");
4825
- if (!api) return buildReport("api", "unprotected_routes", findings);
4826
- const routePermsPath = (0, import_node_path18.join)(rootDir, "src", "config", "route-permissions.ts");
4827
- let routePermsContent = "";
4828
- if ((0, import_node_fs17.existsSync)(routePermsPath)) {
4829
- routePermsContent = (0, import_node_fs17.readFileSync)(routePermsPath, "utf-8");
5647
+ if (!api) return buildSkipped("api", "unprotected_routes", "no api graph");
5648
+ const routePermsPath = (0, import_node_path21.join)(rootDir, "src", "config", "route-permissions.ts");
5649
+ if (!(0, import_node_fs19.existsSync)(routePermsPath)) {
5650
+ return buildSkipped(
5651
+ "api",
5652
+ "unprotected_routes",
5653
+ `no src/config/route-permissions.ts \u2014 this check needs a centralized ROUTE_PERMISSIONS inventory to compare endpoints against`
5654
+ );
4830
5655
  }
5656
+ const routePermsContent = (0, import_node_fs19.readFileSync)(routePermsPath, "utf-8");
4831
5657
  const registeredRoutes = /* @__PURE__ */ new Set();
4832
5658
  const routeEntryRe = /path:\s*'([^']+)'/g;
4833
5659
  let rm;
@@ -4871,7 +5697,7 @@ function routeMatchesPattern(route, pattern) {
4871
5697
  function checkDeadScreens(rootDir) {
4872
5698
  const findings = [];
4873
5699
  const ui = readGraphFile(rootDir, "ui");
4874
- if (!ui) return buildReport("ui", "dead_screens", findings);
5700
+ if (!ui) return buildSkipped("ui", "dead_screens", "no ui graph");
4875
5701
  const pages = ui.nodes.filter((n) => n.type === "page" || n.type === "layout");
4876
5702
  const navTargets = /* @__PURE__ */ new Set();
4877
5703
  for (const e of ui.edges) {
@@ -4903,13 +5729,24 @@ function checkDeadScreens(rootDir) {
4903
5729
  function checkUnenforcedPermissions(rootDir) {
4904
5730
  const findings = [];
4905
5731
  const staticGraph = readGraphFile(rootDir, "static");
4906
- if (!staticGraph) return buildReport("static", "unenforced_permissions", findings);
5732
+ if (!staticGraph) return buildSkipped("static", "unenforced_permissions", "no static graph");
4907
5733
  const permissions = staticGraph.nodes.filter((n) => n.type === "seed_permission").map((n) => ({ id: n.id, key: n.value, name: n.name }));
4908
- const routePermsPath = (0, import_node_path18.join)(rootDir, "src", "config", "route-permissions.ts");
4909
- let routePermsContent = "";
4910
- if ((0, import_node_fs17.existsSync)(routePermsPath)) {
4911
- routePermsContent = (0, import_node_fs17.readFileSync)(routePermsPath, "utf-8");
5734
+ if (permissions.length === 0) {
5735
+ return buildSkipped(
5736
+ "static",
5737
+ "unenforced_permissions",
5738
+ `no seed_permission nodes \u2014 this project either has no seed permissions or hasn't tagged them in seed.ts`
5739
+ );
4912
5740
  }
5741
+ const routePermsPath = (0, import_node_path21.join)(rootDir, "src", "config", "route-permissions.ts");
5742
+ if (!(0, import_node_fs19.existsSync)(routePermsPath)) {
5743
+ return buildSkipped(
5744
+ "static",
5745
+ "unenforced_permissions",
5746
+ `no src/config/route-permissions.ts to compare seed permissions against`
5747
+ );
5748
+ }
5749
+ const routePermsContent = (0, import_node_fs19.readFileSync)(routePermsPath, "utf-8");
4913
5750
  for (const perm of permissions) {
4914
5751
  const regex = new RegExp(`permission:\\s*['"]${perm.key}['"]`);
4915
5752
  if (!regex.test(routePermsContent)) {
@@ -4927,20 +5764,27 @@ function checkUnenforcedPermissions(rootDir) {
4927
5764
  function checkHardcodedValues(rootDir) {
4928
5765
  const findings = [];
4929
5766
  const staticGraph = readGraphFile(rootDir, "static");
4930
- if (!staticGraph) return buildReport("static", "hardcoded_values", findings);
5767
+ if (!staticGraph) return buildSkipped("static", "hardcoded_values", "no static graph");
4931
5768
  const knownValues = /* @__PURE__ */ new Set();
4932
5769
  for (const n of staticGraph.nodes) {
4933
5770
  if (n.type === "enum_value") knownValues.add(n.value);
4934
5771
  }
5772
+ if (knownValues.size === 0) {
5773
+ return buildSkipped(
5774
+ "static",
5775
+ "hardcoded_values",
5776
+ `no enum_value nodes in static graph \u2014 without an inventory of known ALL_CAPS constants, this scan would flag every legitimate constant in your codebase`
5777
+ );
5778
+ }
4935
5779
  const api = readGraphFile(rootDir, "api");
4936
- if (!api) return buildReport("static", "hardcoded_values", findings);
5780
+ if (!api) return buildSkipped("static", "hardcoded_values", "no api graph");
4937
5781
  const allCapsRe = /['"]([A-Z][A-Z_]{2,})['"]/g;
4938
5782
  const seen = /* @__PURE__ */ new Set();
4939
5783
  for (const node of api.nodes) {
4940
5784
  if (node.type !== "endpoint") continue;
4941
- const filePath = (0, import_node_path18.join)(rootDir, "src", node.id);
4942
- if (!(0, import_node_fs17.existsSync)(filePath)) continue;
4943
- const content = (0, import_node_fs17.readFileSync)(filePath, "utf-8");
5785
+ const filePath = (0, import_node_path21.join)(rootDir, "src", node.id);
5786
+ if (!(0, import_node_fs19.existsSync)(filePath)) continue;
5787
+ const content = (0, import_node_fs19.readFileSync)(filePath, "utf-8");
4944
5788
  let m;
4945
5789
  allCapsRe.lastIndex = 0;
4946
5790
  while ((m = allCapsRe.exec(content)) !== null) {
@@ -4972,7 +5816,19 @@ function buildReport(layer, check, findings) {
4972
5816
  warnings: findings.filter((f) => f.severity === "warning").length,
4973
5817
  info: findings.filter((f) => f.severity === "info").length
4974
5818
  },
4975
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
5819
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5820
+ status: "ok"
5821
+ };
5822
+ }
5823
+ function buildSkipped(layer, check, reason) {
5824
+ return {
5825
+ layer,
5826
+ check,
5827
+ findings: [],
5828
+ summary: { errors: 0, warnings: 0, info: 0 },
5829
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5830
+ status: "skipped",
5831
+ skipReason: reason
4976
5832
  };
4977
5833
  }
4978
5834
  var CHECKS = {
@@ -4989,6 +5845,11 @@ var CHECKS = {
4989
5845
  static: {
4990
5846
  unenforced_permissions: checkUnenforcedPermissions,
4991
5847
  hardcoded_values: checkHardcodedValues
5848
+ },
5849
+ security: {
5850
+ response_secret_leak: (rootDir) => checkResponseSecretLeak(rootDir, { buildReport, buildSkipped }),
5851
+ env_dead_alias: (rootDir) => checkEnvDeadAlias(rootDir, { buildReport, buildSkipped }),
5852
+ hardcoded_url_fallback: (rootDir) => checkHardcodedUrlFallback(rootDir, { buildReport, buildSkipped })
4992
5853
  }
4993
5854
  };
4994
5855
  function getAvailableChecks() {
@@ -5035,7 +5896,12 @@ function runAudit(rootDir, layer, check) {
5035
5896
  }
5036
5897
  function formatAsPrompt(reports) {
5037
5898
  const lines = [];
5899
+ const skipped = [];
5038
5900
  for (const report of reports) {
5901
+ if (report.status === "skipped") {
5902
+ skipped.push(report);
5903
+ continue;
5904
+ }
5039
5905
  if (report.findings.length === 0) continue;
5040
5906
  lines.push(`## ${report.layer.toUpperCase()} \u2014 ${report.check} (${report.findings.length} findings)`);
5041
5907
  lines.push("");
@@ -5047,6 +5913,14 @@ function formatAsPrompt(reports) {
5047
5913
  }
5048
5914
  lines.push("");
5049
5915
  }
5916
+ if (skipped.length > 0) {
5917
+ lines.push("## Skipped checks (no comparison target \u2014 NOT passes)");
5918
+ lines.push("");
5919
+ for (const r of skipped) {
5920
+ lines.push(`- ${r.layer}/${r.check}: ${r.skipReason ?? "comparison target missing"}`);
5921
+ }
5922
+ lines.push("");
5923
+ }
5050
5924
  if (lines.length === 0) return "No audit findings.";
5051
5925
  return lines.join("\n");
5052
5926
  }
@@ -5070,16 +5944,16 @@ var MIME_TYPES = {
5070
5944
  function findProjectRoot(startDir) {
5071
5945
  let dir = startDir;
5072
5946
  for (let i = 0; i < 8; i++) {
5073
- const graphsDir2 = import_node_path19.default.join(dir, ".launchsecure", "graphs");
5074
- if (import_node_fs18.default.existsSync(import_node_path19.default.join(graphsDir2, "ui.json")) || import_node_fs18.default.existsSync(import_node_path19.default.join(graphsDir2, "api.json")) || import_node_fs18.default.existsSync(import_node_path19.default.join(graphsDir2, "db.json"))) return dir;
5075
- const parent = import_node_path19.default.dirname(dir);
5947
+ const graphsDir2 = import_node_path22.default.join(dir, ".launchsecure", "graphs");
5948
+ if (import_node_fs20.default.existsSync(import_node_path22.default.join(graphsDir2, "ui.json")) || import_node_fs20.default.existsSync(import_node_path22.default.join(graphsDir2, "api.json")) || import_node_fs20.default.existsSync(import_node_path22.default.join(graphsDir2, "db.json"))) return dir;
5949
+ const parent = import_node_path22.default.dirname(dir);
5076
5950
  if (parent === dir) break;
5077
5951
  dir = parent;
5078
5952
  }
5079
5953
  dir = startDir;
5080
5954
  for (let i = 0; i < 8; i++) {
5081
- if (import_node_fs18.default.existsSync(import_node_path19.default.join(dir, ".git"))) return dir;
5082
- const parent = import_node_path19.default.dirname(dir);
5955
+ if (import_node_fs20.default.existsSync(import_node_path22.default.join(dir, ".git"))) return dir;
5956
+ const parent = import_node_path22.default.dirname(dir);
5083
5957
  if (parent === dir) break;
5084
5958
  dir = parent;
5085
5959
  }
@@ -5088,7 +5962,7 @@ function findProjectRoot(startDir) {
5088
5962
  function resolveRequestRoot(url, monorepoRoot, projects) {
5089
5963
  const projectParam = url.searchParams.get("project");
5090
5964
  if (!projectParam || projects.length === 0) return monorepoRoot;
5091
- const resolved = import_node_path19.default.resolve(monorepoRoot, projectParam);
5965
+ const resolved = import_node_path22.default.resolve(monorepoRoot, projectParam);
5092
5966
  if (!resolved.startsWith(monorepoRoot)) {
5093
5967
  throw new Error("Project path outside monorepo root");
5094
5968
  }
@@ -5139,16 +6013,16 @@ async function buildMergedGraph(root) {
5139
6013
  };
5140
6014
  }
5141
6015
  function serveStatic(res, filePath) {
5142
- if (!import_node_fs18.default.existsSync(filePath) || !import_node_fs18.default.statSync(filePath).isFile()) return false;
5143
- const ext = import_node_path19.default.extname(filePath).toLowerCase();
6016
+ if (!import_node_fs20.default.existsSync(filePath) || !import_node_fs20.default.statSync(filePath).isFile()) return false;
6017
+ const ext = import_node_path22.default.extname(filePath).toLowerCase();
5144
6018
  const mime = MIME_TYPES[ext] ?? "application/octet-stream";
5145
6019
  res.writeHead(200, { "Content-Type": mime, "Cache-Control": "no-cache" });
5146
- import_node_fs18.default.createReadStream(filePath).pipe(res);
6020
+ import_node_fs20.default.createReadStream(filePath).pipe(res);
5147
6021
  return true;
5148
6022
  }
5149
6023
  function serveIndex(res, clientDir) {
5150
- const indexPath = import_node_path19.default.join(clientDir, "index.html");
5151
- if (!import_node_fs18.default.existsSync(indexPath)) {
6024
+ const indexPath = import_node_path22.default.join(clientDir, "index.html");
6025
+ if (!import_node_fs20.default.existsSync(indexPath)) {
5152
6026
  res.writeHead(500, { "Content-Type": "text/plain" });
5153
6027
  res.end(`LaunchChart client bundle not found at ${clientDir}. Run 'npm run build:chart-client'.`);
5154
6028
  return;
@@ -5200,7 +6074,7 @@ async function startChartServer(opts = {}) {
5200
6074
  }
5201
6075
  return { port: existing.port, url: existing.url };
5202
6076
  }
5203
- const clientDir = opts.clientDir ?? import_node_path19.default.join(__dirname, "..", "chart-client");
6077
+ const clientDir = opts.clientDir ?? import_node_path22.default.join(__dirname, "..", "chart-client");
5204
6078
  const rootConfig = loadConfig(projectRoot);
5205
6079
  const projects = rootConfig.projects ?? [];
5206
6080
  const server = import_node_http.default.createServer((req, res) => {
@@ -5216,11 +6090,11 @@ async function startChartServer(opts = {}) {
5216
6090
  }
5217
6091
  if (req.method === "GET" && url2.pathname === "/api/projects") {
5218
6092
  const projectList = projects.length > 0 ? projects.map((p) => {
5219
- const absRoot = import_node_path19.default.resolve(projectRoot, p.root);
5220
- const hasGraphs = import_node_fs18.default.existsSync(import_node_path19.default.join(absRoot, ".launchsecure", "graphs"));
5221
- const hasNextConfig = import_node_fs18.default.existsSync(import_node_path19.default.join(absRoot, "next.config.ts")) || import_node_fs18.default.existsSync(import_node_path19.default.join(absRoot, "next.config.js")) || import_node_fs18.default.existsSync(import_node_path19.default.join(absRoot, "next.config.mjs"));
6093
+ const absRoot = import_node_path22.default.resolve(projectRoot, p.root);
6094
+ const hasGraphs = import_node_fs20.default.existsSync(import_node_path22.default.join(absRoot, ".launchsecure", "graphs"));
6095
+ const hasNextConfig = import_node_fs20.default.existsSync(import_node_path22.default.join(absRoot, "next.config.ts")) || import_node_fs20.default.existsSync(import_node_path22.default.join(absRoot, "next.config.js")) || import_node_fs20.default.existsSync(import_node_path22.default.join(absRoot, "next.config.mjs"));
5222
6096
  return { name: p.name, root: p.root, hasGraphs, hasNextConfig };
5223
- }) : [{ name: import_node_path19.default.basename(projectRoot), root: ".", hasGraphs: import_node_fs18.default.existsSync(import_node_path19.default.join(projectRoot, ".launchsecure", "graphs")), hasNextConfig: true }];
6097
+ }) : [{ name: import_node_path22.default.basename(projectRoot), root: ".", hasGraphs: import_node_fs20.default.existsSync(import_node_path22.default.join(projectRoot, ".launchsecure", "graphs")), hasNextConfig: true }];
5224
6098
  res.writeHead(200, { "Content-Type": "application/json" });
5225
6099
  res.end(JSON.stringify({ projects: projectList, monorepoRoot: projectRoot }));
5226
6100
  return;
@@ -5266,20 +6140,20 @@ async function startChartServer(opts = {}) {
5266
6140
  }
5267
6141
  if (req.method === "GET" && url2.pathname === "/api/file-content") {
5268
6142
  const relPath = url2.searchParams.get("path");
5269
- if (!relPath || relPath.includes("..") || import_node_path19.default.isAbsolute(relPath)) {
6143
+ if (!relPath || relPath.includes("..") || import_node_path22.default.isAbsolute(relPath)) {
5270
6144
  res.writeHead(400, { "Content-Type": "application/json" });
5271
6145
  res.end(JSON.stringify({ error: "Invalid path" }));
5272
6146
  return;
5273
6147
  }
5274
- const filePath = import_node_path19.default.join(reqRoot, relPath);
5275
- if (!filePath.startsWith(reqRoot) || !import_node_fs18.default.existsSync(filePath) || !import_node_fs18.default.statSync(filePath).isFile()) {
6148
+ const filePath = import_node_path22.default.join(reqRoot, relPath);
6149
+ if (!filePath.startsWith(reqRoot) || !import_node_fs20.default.existsSync(filePath) || !import_node_fs20.default.statSync(filePath).isFile()) {
5276
6150
  res.writeHead(404, { "Content-Type": "application/json" });
5277
6151
  res.end(JSON.stringify({ error: "File not found" }));
5278
6152
  return;
5279
6153
  }
5280
- const ext = import_node_path19.default.extname(filePath).toLowerCase();
6154
+ const ext = import_node_path22.default.extname(filePath).toLowerCase();
5281
6155
  const langMap = { ".ts": "typescript", ".tsx": "tsx", ".js": "javascript", ".jsx": "jsx", ".prisma": "prisma", ".json": "json", ".css": "css" };
5282
- const content = import_node_fs18.default.readFileSync(filePath, "utf-8");
6156
+ const content = import_node_fs20.default.readFileSync(filePath, "utf-8");
5283
6157
  res.writeHead(200, { "Content-Type": "application/json" });
5284
6158
  res.end(JSON.stringify({ content, language: langMap[ext] ?? "text", path: relPath }));
5285
6159
  return;
@@ -5321,8 +6195,8 @@ async function startChartServer(opts = {}) {
5321
6195
  req.on("end", () => {
5322
6196
  try {
5323
6197
  const newConfig = JSON.parse(body);
5324
- const configPath = import_node_path19.default.join(reqRoot, ".launchchart.json");
5325
- import_node_fs18.default.writeFileSync(configPath, JSON.stringify(newConfig, null, 2) + "\n", "utf-8");
6198
+ const configPath = import_node_path22.default.join(reqRoot, ".launchchart.json");
6199
+ import_node_fs20.default.writeFileSync(configPath, JSON.stringify(newConfig, null, 2) + "\n", "utf-8");
5326
6200
  res.writeHead(200, { "Content-Type": "application/json" });
5327
6201
  res.end(JSON.stringify({ ok: true }));
5328
6202
  } catch (err) {
@@ -5355,8 +6229,8 @@ async function startChartServer(opts = {}) {
5355
6229
  const taggerConfig = JSON.parse(body);
5356
6230
  const config2 = loadConfig(reqRoot);
5357
6231
  config2.taggers = taggerConfig;
5358
- const configPath = import_node_path19.default.join(reqRoot, ".launchchart.json");
5359
- import_node_fs18.default.writeFileSync(configPath, JSON.stringify(config2, null, 2) + "\n", "utf-8");
6232
+ const configPath = import_node_path22.default.join(reqRoot, ".launchchart.json");
6233
+ import_node_fs20.default.writeFileSync(configPath, JSON.stringify(config2, null, 2) + "\n", "utf-8");
5360
6234
  res.writeHead(200, { "Content-Type": "application/json" });
5361
6235
  res.end(JSON.stringify({ ok: true }));
5362
6236
  } catch (err) {
@@ -5426,7 +6300,7 @@ async function startChartServer(opts = {}) {
5426
6300
  dbDir: !!config2.paths?.dbDir,
5427
6301
  srcRoots: !!(config2.paths?.srcRoots && config2.paths.srcRoots.length > 0)
5428
6302
  };
5429
- const relFromRoot = (abs) => import_node_path19.default.relative(reqRoot, abs) || ".";
6303
+ const relFromRoot = (abs) => import_node_path22.default.relative(reqRoot, abs) || ".";
5430
6304
  res.writeHead(200, { "Content-Type": "application/json" });
5431
6305
  res.end(JSON.stringify({
5432
6306
  projectRoot: reqRoot,
@@ -5448,19 +6322,19 @@ async function startChartServer(opts = {}) {
5448
6322
  }
5449
6323
  if (req.method === "GET" && url2.pathname === "/api/browse-dir") {
5450
6324
  const browsePath = url2.searchParams.get("path") || projectRoot;
5451
- const abs = import_node_path19.default.resolve(browsePath);
5452
- const twoUp = import_node_path19.default.resolve(projectRoot, "..", "..");
6325
+ const abs = import_node_path22.default.resolve(browsePath);
6326
+ const twoUp = import_node_path22.default.resolve(projectRoot, "..", "..");
5453
6327
  if (!abs.startsWith(twoUp)) {
5454
6328
  res.writeHead(403, { "Content-Type": "application/json" });
5455
6329
  res.end(JSON.stringify({ ok: false, error: "Path outside allowed range" }));
5456
6330
  return;
5457
6331
  }
5458
6332
  try {
5459
- const entries = import_node_fs18.default.readdirSync(abs, { withFileTypes: true });
6333
+ const entries = import_node_fs20.default.readdirSync(abs, { withFileTypes: true });
5460
6334
  const dirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules" && e.name !== "dist" && e.name !== ".next").map((e) => e.name).sort();
5461
- const parent = abs !== twoUp ? import_node_path19.default.dirname(abs) : null;
6335
+ const parent = abs !== twoUp ? import_node_path22.default.dirname(abs) : null;
5462
6336
  res.writeHead(200, { "Content-Type": "application/json" });
5463
- res.end(JSON.stringify({ current: abs, parent, dirs, relative: import_node_path19.default.relative(projectRoot, abs) || "." }));
6337
+ res.end(JSON.stringify({ current: abs, parent, dirs, relative: import_node_path22.default.relative(projectRoot, abs) || "." }));
5464
6338
  } catch (err) {
5465
6339
  res.writeHead(400, { "Content-Type": "application/json" });
5466
6340
  res.end(JSON.stringify({ ok: false, error: String(err) }));
@@ -5486,8 +6360,8 @@ async function startChartServer(opts = {}) {
5486
6360
  const { projects: newProjects } = JSON.parse(body);
5487
6361
  const config2 = loadConfig(projectRoot);
5488
6362
  config2.projects = newProjects.length > 0 ? newProjects : void 0;
5489
- const configPath = import_node_path19.default.join(projectRoot, ".launchchart.json");
5490
- import_node_fs18.default.writeFileSync(configPath, JSON.stringify(config2, null, 2) + "\n", "utf-8");
6363
+ const configPath = import_node_path22.default.join(projectRoot, ".launchchart.json");
6364
+ import_node_fs20.default.writeFileSync(configPath, JSON.stringify(config2, null, 2) + "\n", "utf-8");
5491
6365
  projects.length = 0;
5492
6366
  if (config2.projects) projects.push(...config2.projects);
5493
6367
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -5500,7 +6374,7 @@ async function startChartServer(opts = {}) {
5500
6374
  return;
5501
6375
  }
5502
6376
  if (url2.pathname !== "/") {
5503
- const staticPath = import_node_path19.default.join(clientDir, url2.pathname);
6377
+ const staticPath = import_node_path22.default.join(clientDir, url2.pathname);
5504
6378
  if (serveStatic(res, staticPath)) return;
5505
6379
  }
5506
6380
  serveIndex(res, clientDir);