@harness-engineering/cli 2.4.5 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/agents-md-2JUQ4FE2.js +13 -0
  2. package/dist/analyzer-VY6DJVOU-4AHIJLNS.js +26 -0
  3. package/dist/{architecture-NX7FWQD3.js → architecture-CXAVNB5X.js} +7 -4
  4. package/dist/{assess-project-PLY6ZWMW.js → assess-project-M626SSPN.js} +1 -1
  5. package/dist/bin/harness-mcp.js +20 -17
  6. package/dist/bin/harness.js +38 -35
  7. package/dist/{check-phase-gate-WFIEJKMN.js → check-phase-gate-XMU6LLUQ.js} +8 -5
  8. package/dist/{chunk-OPDIMKUZ.js → chunk-24KCOJJG.js} +2 -2
  9. package/dist/{chunk-72TZ2GZR.js → chunk-43M3RLFQ.js} +7 -7
  10. package/dist/{chunk-7TRTU7JL.js → chunk-5JNSFYZU.js} +2 -2
  11. package/dist/{chunk-PGY57ABC.js → chunk-6AX6R3LM.js} +7 -7
  12. package/dist/{chunk-TC4GQBIK.js → chunk-GWE5LL3R.js} +6 -6
  13. package/dist/{chunk-LJTQVJEP.js → chunk-GWF2OILZ.js} +2 -2
  14. package/dist/{chunk-FPUNXFJG.js → chunk-HFOB2MPQ.js} +2 -2
  15. package/dist/{chunk-Z6PF2YXB.js → chunk-HMW3VKUF.js} +3 -3
  16. package/dist/{chunk-NSLFLGUB.js → chunk-HYTRDAHV.js} +2 -2
  17. package/dist/{chunk-3XFFGAX4.js → chunk-I6K43QQS.js} +3 -3
  18. package/dist/{chunk-YL2N4OZQ.js → chunk-JFZOWO7D.js} +2 -2
  19. package/dist/{chunk-4H2KIQ3I.js → chunk-K246XQ2L.js} +2 -2
  20. package/dist/{chunk-ZEKY6PQQ.js → chunk-L5BCZ5XS.js} +2 -2
  21. package/dist/{chunk-4PO2QAXO.js → chunk-NX6A24DS.js} +2153 -985
  22. package/dist/{chunk-A5GXGRW6.js → chunk-OA6SWTU4.js} +6 -6
  23. package/dist/chunk-OH3NXXUY.js +383 -0
  24. package/dist/{chunk-DUK6C7I7.js → chunk-ORNMAYHB.js} +611 -300
  25. package/dist/{chunk-73ACYXIS.js → chunk-PFZEADQM.js} +1 -1
  26. package/dist/{chunk-M5TJOV3A.js → chunk-Q6ZR2L6Q.js} +2 -2
  27. package/dist/chunk-QCLOIUMX.js +395 -0
  28. package/dist/{chunk-DUCVDDZO.js → chunk-QOKMDDBJ.js} +4 -4
  29. package/dist/chunk-SZEFU6XC.js +3337 -0
  30. package/dist/{chunk-KEF624FQ.js → chunk-VTTNIZJL.js} +27 -6
  31. package/dist/chunk-XARGEOJV.js +21 -0
  32. package/dist/{chunk-HCTXLKAH.js → chunk-YP7I25SL.js} +2 -2
  33. package/dist/{chunk-65M5R3X6.js → chunk-YYRQCQPZ.js} +4204 -7347
  34. package/dist/ci-workflow-3JNDZQYD.js +13 -0
  35. package/dist/dist-E3SAAHCM.js +93 -0
  36. package/dist/{dist-SVZHSOLP.js → dist-OHDQBTSO.js} +119 -48
  37. package/dist/{docs-7LXCR62T.js → docs-5RYMDHN3.js} +8 -5
  38. package/dist/engine-KZ3PPUYE.js +13 -0
  39. package/dist/{entropy-LMOXWMR4.js → entropy-DQTAPSSE.js} +6 -3
  40. package/dist/{feedback-CLBC3IMP.js → feedback-WH6XPQJS.js} +1 -1
  41. package/dist/{generate-agent-definitions-5FDIGEVZ.js → generate-agent-definitions-SZA4QIHN.js} +8 -5
  42. package/dist/index.d.ts +47 -0
  43. package/dist/index.js +49 -46
  44. package/dist/loader-BNKGM23C.js +15 -0
  45. package/dist/{mcp-4W45E6OH.js → mcp-GZH4EYGD.js} +20 -17
  46. package/dist/{performance-WZQIUHLR.js → performance-54TMJOEU.js} +8 -5
  47. package/dist/{review-pipeline-YPRUHKM7.js → review-pipeline-OTDDVPNY.js} +6 -3
  48. package/dist/runtime-W5THSNAL.js +14 -0
  49. package/dist/{security-AZNLX3N6.js → security-27HW7CYT.js} +1 -1
  50. package/dist/timeline-manager-FPYKJRHR-VVX7CSGW.js +24 -0
  51. package/dist/{tool-tiers-QVWKAZX4.js → tool-tiers-B7JC2XC3.js} +9 -2
  52. package/dist/{validate-S7G6H7F4.js → validate-QRVCD4GP.js} +7 -4
  53. package/dist/validate-cross-check-BUAGBAPO.js +13 -0
  54. package/package.json +6 -6
  55. package/dist/agents-md-KJVGWF2S.js +0 -10
  56. package/dist/chunk-O63AAPIZ.js +0 -190
  57. package/dist/ci-workflow-XDIKOFSN.js +0 -10
  58. package/dist/dist-O733ZU6I.js +0 -49
  59. package/dist/engine-MMQF2ZW5.js +0 -10
  60. package/dist/loader-WTWR2CWT.js +0 -12
  61. package/dist/runtime-WC2IWI2X.js +0 -11
  62. package/dist/validate-cross-check-Z73SCHXZ.js +0 -10
@@ -0,0 +1,3337 @@
1
+ import {
2
+ Err,
3
+ Ok,
4
+ init_dist
5
+ } from "./chunk-OH3NXXUY.js";
6
+ import {
7
+ DEFAULT_SKIP_DIRS,
8
+ init_dist as init_dist2,
9
+ skipDirGlobs
10
+ } from "./chunk-BCFDGOMA.js";
11
+ import {
12
+ __esm
13
+ } from "./chunk-KFQGP6VL.js";
14
+
15
+ // ../core/dist/chunk-MUWJHO2S.mjs
16
+ import { parse } from "@typescript-eslint/typescript-estree";
17
+ import { access, constants, readFile } from "fs";
18
+ import { promisify } from "util";
19
+ import { relative } from "path";
20
+ import { glob } from "glob";
21
+ import Parser from "web-tree-sitter";
22
+ import { minimatch } from "minimatch";
23
+ import { dirname, resolve, extname } from "path";
24
+ import { readFile as readFile2 } from "fs/promises";
25
+ function createError(code, message, details = {}, suggestions = []) {
26
+ return { code, message, details, suggestions };
27
+ }
28
+ function createEntropyError(code, message, details = {}, suggestions = []) {
29
+ return { code, message, details, suggestions };
30
+ }
31
+ function createParseError(code, message, details = {}, suggestions = []) {
32
+ return { code, message, details, suggestions };
33
+ }
34
+ async function fileExists(path) {
35
+ try {
36
+ await accessAsync(path, constants.F_OK);
37
+ return true;
38
+ } catch {
39
+ return false;
40
+ }
41
+ }
42
+ async function readFileContent(path) {
43
+ try {
44
+ const content = await readFileAsync(path, "utf-8");
45
+ return Ok(content);
46
+ } catch (error) {
47
+ return Err(error);
48
+ }
49
+ }
50
+ async function findFiles(pattern, cwd = process.cwd(), extraIgnore = []) {
51
+ return glob(pattern, {
52
+ cwd,
53
+ absolute: true,
54
+ ignore: [...DEFAULT_FIND_FILES_IGNORE, ...extraIgnore]
55
+ });
56
+ }
57
+ function relativePosix(from, to) {
58
+ return relative(from, to).replaceAll("\\", "/");
59
+ }
60
+ function walk(node, visitor) {
61
+ if (!node || typeof node !== "object") return;
62
+ if ("type" in node) {
63
+ visitor(node);
64
+ }
65
+ for (const value of Object.values(node)) {
66
+ if (Array.isArray(value)) {
67
+ value.forEach((v) => walk(v, visitor));
68
+ } else {
69
+ walk(value, visitor);
70
+ }
71
+ }
72
+ }
73
+ function makeLocation(node) {
74
+ return {
75
+ file: "",
76
+ line: node.loc?.start.line ?? 0,
77
+ column: node.loc?.start.column ?? 0
78
+ };
79
+ }
80
+ function processImportSpecifiers(importDecl, imp) {
81
+ for (const spec of importDecl.specifiers) {
82
+ if (spec.type === "ImportDefaultSpecifier") {
83
+ imp.default = spec.local.name;
84
+ } else if (spec.type === "ImportNamespaceSpecifier") {
85
+ imp.namespace = spec.local.name;
86
+ } else if (spec.type === "ImportSpecifier") {
87
+ imp.specifiers.push(spec.local.name);
88
+ if (spec.importKind === "type") {
89
+ imp.kind = "type";
90
+ }
91
+ }
92
+ }
93
+ }
94
+ function getExportedName(exported) {
95
+ return exported.type === "Identifier" ? exported.name : String(exported.value);
96
+ }
97
+ function processReExportSpecifiers(exportDecl, exports) {
98
+ for (const spec of exportDecl.specifiers) {
99
+ if (spec.type !== "ExportSpecifier") continue;
100
+ exports.push({
101
+ name: getExportedName(spec.exported),
102
+ type: "named",
103
+ location: makeLocation(exportDecl),
104
+ isReExport: true,
105
+ source: exportDecl.source.value
106
+ });
107
+ }
108
+ }
109
+ function processExportDeclaration(exportDecl, exports) {
110
+ const decl = exportDecl.declaration;
111
+ if (!decl) return;
112
+ if (decl.type === "VariableDeclaration") {
113
+ for (const declarator of decl.declarations) {
114
+ if (declarator.id.type === "Identifier") {
115
+ exports.push({
116
+ name: declarator.id.name,
117
+ type: "named",
118
+ location: makeLocation(decl),
119
+ isReExport: false
120
+ });
121
+ }
122
+ }
123
+ } else if ((decl.type === "FunctionDeclaration" || decl.type === "ClassDeclaration") && decl.id) {
124
+ exports.push({
125
+ name: decl.id.name,
126
+ type: "named",
127
+ location: makeLocation(decl),
128
+ isReExport: false
129
+ });
130
+ }
131
+ }
132
+ function processExportListSpecifiers(exportDecl, exports) {
133
+ for (const spec of exportDecl.specifiers) {
134
+ if (spec.type !== "ExportSpecifier") continue;
135
+ exports.push({
136
+ name: getExportedName(spec.exported),
137
+ type: "named",
138
+ location: makeLocation(exportDecl),
139
+ isReExport: false
140
+ });
141
+ }
142
+ }
143
+ function detectLanguage(filePath) {
144
+ const ext = filePath.slice(filePath.lastIndexOf("."));
145
+ return EXTENSION_MAP[ext] ?? null;
146
+ }
147
+ async function ensureInit() {
148
+ if (!initialized) {
149
+ await Parser.init();
150
+ initialized = true;
151
+ }
152
+ }
153
+ async function resolveWasmPath(grammarName) {
154
+ const { createRequire } = await import("module");
155
+ const require2 = createRequire(import.meta.url ?? __filename);
156
+ const pkgPath = require2.resolve("tree-sitter-wasms/package.json");
157
+ const path = await import("path");
158
+ const pkgDir = path.dirname(pkgPath);
159
+ return path.join(pkgDir, "out", `${grammarName}.wasm`);
160
+ }
161
+ async function loadLanguage(lang) {
162
+ const grammarName = GRAMMAR_MAP[lang];
163
+ const wasmPath = await resolveWasmPath(grammarName);
164
+ return Parser.Language.load(wasmPath);
165
+ }
166
+ async function getParser(lang) {
167
+ const cached = parserCache.get(lang);
168
+ if (cached) return cached;
169
+ await ensureInit();
170
+ const parser = new Parser();
171
+ const language = await loadLanguage(lang);
172
+ parser.setLanguage(language);
173
+ parserCache.set(lang, parser);
174
+ return parser;
175
+ }
176
+ async function parseFile(filePath) {
177
+ const lang = detectLanguage(filePath);
178
+ if (!lang) {
179
+ return Err({
180
+ code: "UNSUPPORTED_LANGUAGE",
181
+ message: `Unsupported file extension: ${filePath}`
182
+ });
183
+ }
184
+ const contentResult = await readFileContent(filePath);
185
+ if (!contentResult.ok) {
186
+ return Err({
187
+ code: "FILE_NOT_FOUND",
188
+ message: `Cannot read file: ${filePath}`
189
+ });
190
+ }
191
+ try {
192
+ const parser = await getParser(lang);
193
+ const tree = parser.parse(contentResult.value);
194
+ return Ok({ tree, language: lang, source: contentResult.value, filePath });
195
+ } catch (e) {
196
+ return Err({
197
+ code: "PARSE_FAILED",
198
+ message: `Tree-sitter parse failed for ${filePath}: ${e.message}`
199
+ });
200
+ }
201
+ }
202
+ function resetParserCache() {
203
+ parserCache.clear();
204
+ initialized = false;
205
+ }
206
+ function findIdentifier(node) {
207
+ return node.childForFieldName("name") ?? node.children.find((c) => IDENTIFIER_TYPES.has(c.type)) ?? null;
208
+ }
209
+ function getVariableDeclarationName(node) {
210
+ const declarator = node.children.find((c) => c.type === "variable_declarator");
211
+ if (!declarator) return null;
212
+ const id = findIdentifier(declarator);
213
+ return id?.text ?? null;
214
+ }
215
+ function getExportName(node, source) {
216
+ const decl = node.children.find(
217
+ (c) => c.type !== "export" && c.type !== "default" && c.type !== "comment"
218
+ );
219
+ return decl ? getNodeName(decl, source) : "<anonymous>";
220
+ }
221
+ function getAssignmentName(node) {
222
+ const left = node.childForFieldName("left") ?? node.children[0];
223
+ return left?.text ?? "<anonymous>";
224
+ }
225
+ function getGoTypeSpecName(node) {
226
+ const typeSpec = node.children.find((c) => c.type === "type_spec");
227
+ if (typeSpec) {
228
+ const id = findIdentifier(typeSpec);
229
+ if (id) return id.text;
230
+ }
231
+ return "<anonymous>";
232
+ }
233
+ function getRustImplName(node) {
234
+ const typeId = node.childForFieldName("type");
235
+ if (typeId) return typeId.text;
236
+ const id = node.children.find((c) => IDENTIFIER_TYPES.has(c.type));
237
+ return id?.text ?? "<anonymous>";
238
+ }
239
+ function getGoVarName(node) {
240
+ const spec = node.children.find((c) => c.type === "var_spec" || c.type === "const_spec");
241
+ if (spec) {
242
+ const id = findIdentifier(spec);
243
+ if (id) return id.text;
244
+ }
245
+ return "<anonymous>";
246
+ }
247
+ function getNodeName(node, source) {
248
+ const id = findIdentifier(node);
249
+ if (id) return id.text;
250
+ const extractor = NAME_EXTRACTORS[node.type];
251
+ if (extractor) return extractor(node, source) ?? "<anonymous>";
252
+ return "<anonymous>";
253
+ }
254
+ function getSignature(node, source) {
255
+ const startLine = node.startPosition.row;
256
+ const lines = source.split("\n");
257
+ return (lines[startLine] ?? "").trim();
258
+ }
259
+ function extractMethods(classNode, language, source, filePath) {
260
+ const methodTypes = METHOD_TYPES[language] ?? [];
261
+ const body = classNode.childForFieldName("body") ?? classNode.children.find(
262
+ (c) => c.type === "class_body" || c.type === "block" || c.type === "declaration_list" || c.type === "field_declaration_list"
263
+ );
264
+ if (!body) return [];
265
+ return body.children.filter((child) => methodTypes.includes(child.type)).map((child) => ({
266
+ name: getNodeName(child, source),
267
+ kind: "method",
268
+ file: filePath,
269
+ line: child.startPosition.row + 1,
270
+ endLine: child.endPosition.row + 1,
271
+ signature: getSignature(child, source)
272
+ }));
273
+ }
274
+ function nodeToSymbol(node, kind, source, filePath) {
275
+ return {
276
+ name: getNodeName(node, source),
277
+ kind,
278
+ file: filePath,
279
+ line: node.startPosition.row + 1,
280
+ endLine: node.endPosition.row + 1,
281
+ signature: getSignature(node, source)
282
+ };
283
+ }
284
+ function processExportStatement(child, topLevelTypes, lang, source, filePath) {
285
+ const declaration = child.children.find(
286
+ (c) => c.type !== "export" && c.type !== "default" && c.type !== ";" && c.type !== "comment"
287
+ );
288
+ const kind = declaration ? topLevelTypes[declaration.type] : void 0;
289
+ if (declaration && kind) {
290
+ const sym = nodeToSymbol(child, kind, source, filePath);
291
+ sym.name = getNodeName(declaration, source);
292
+ if (kind === "class") {
293
+ sym.children = extractMethods(declaration, lang, source, filePath);
294
+ }
295
+ return sym;
296
+ }
297
+ return nodeToSymbol(child, "export", source, filePath);
298
+ }
299
+ function extractSymbols(rootNode, lang, source, filePath) {
300
+ const symbols = [];
301
+ const topLevelTypes = TOP_LEVEL_TYPES[lang] ?? {};
302
+ for (const child of rootNode.children) {
303
+ if (child.type === "export_statement") {
304
+ symbols.push(processExportStatement(child, topLevelTypes, lang, source, filePath));
305
+ continue;
306
+ }
307
+ const kind = topLevelTypes[child.type];
308
+ if (!kind || kind === "import") continue;
309
+ const sym = nodeToSymbol(child, kind, source, filePath);
310
+ if (kind === "class") {
311
+ sym.children = extractMethods(child, lang, source, filePath);
312
+ }
313
+ symbols.push(sym);
314
+ }
315
+ return symbols;
316
+ }
317
+ function buildFailedResult(filePath, lang) {
318
+ return { file: filePath, language: lang, totalLines: 0, symbols: [], error: "[parse-failed]" };
319
+ }
320
+ function extractOutlineFromTree(rootNode, lang, source, filePath) {
321
+ const totalLines = source.split("\n").length;
322
+ const symbols = extractSymbols(rootNode, lang, source, filePath);
323
+ return { file: filePath, language: lang, totalLines, symbols };
324
+ }
325
+ async function getOutline(filePath) {
326
+ const lang = detectLanguage(filePath);
327
+ if (!lang) return buildFailedResult(filePath, "unknown");
328
+ const result = await parseFile(filePath);
329
+ if (!result.ok) return buildFailedResult(filePath, lang);
330
+ const { tree, source } = result.value;
331
+ const totalLines = source.split("\n").length;
332
+ const symbols = extractSymbols(tree.rootNode, lang, source, filePath);
333
+ return { file: filePath, language: lang, totalLines, symbols };
334
+ }
335
+ function formatOutline(outline) {
336
+ if (outline.error) {
337
+ return `${outline.file} ${outline.error}`;
338
+ }
339
+ const lines = [`${outline.file} (${outline.totalLines} lines)`];
340
+ const last = outline.symbols.length - 1;
341
+ outline.symbols.forEach((sym, i) => {
342
+ const prefix = i === last ? "\u2514\u2500\u2500" : "\u251C\u2500\u2500";
343
+ lines.push(`${prefix} ${sym.signature} :${sym.line}`);
344
+ if (sym.children) {
345
+ const childLast = sym.children.length - 1;
346
+ sym.children.forEach((child, j) => {
347
+ const childConnector = i === last ? " " : "\u2502 ";
348
+ const childPrefix = j === childLast ? "\u2514\u2500\u2500" : "\u251C\u2500\u2500";
349
+ lines.push(`${childConnector}${childPrefix} ${child.signature} :${child.line}`);
350
+ });
351
+ }
352
+ });
353
+ return lines.join("\n");
354
+ }
355
+ function defineLayer(name, patterns, allowedDependencies) {
356
+ return {
357
+ name,
358
+ patterns,
359
+ allowedDependencies
360
+ };
361
+ }
362
+ function resolveFileToLayer(file, layers) {
363
+ for (const layer of layers) {
364
+ for (const pattern of layer.patterns) {
365
+ if (minimatch(file, pattern)) {
366
+ return layer;
367
+ }
368
+ }
369
+ }
370
+ return void 0;
371
+ }
372
+ function detectLangFromExt(ext) {
373
+ for (const [lang, exts] of Object.entries(EXTENSION_BY_LANG)) {
374
+ if (exts.includes(ext)) return lang;
375
+ }
376
+ return null;
377
+ }
378
+ function getExtensionsForLang(lang) {
379
+ switch (lang) {
380
+ case "typescript":
381
+ return [".ts", ".tsx"];
382
+ case "javascript":
383
+ return [".js", ".jsx", ".mjs", ".cjs"];
384
+ case "python":
385
+ return [".py"];
386
+ case "go":
387
+ return [".go"];
388
+ case "rust":
389
+ return [".rs"];
390
+ case "java":
391
+ return [".java"];
392
+ default:
393
+ return [".ts", ".tsx", ".js", ".jsx"];
394
+ }
395
+ }
396
+ async function resolveImportPath(importSource, fromFile, _rootDir) {
397
+ if (!importSource.startsWith(".") && !importSource.startsWith("/")) {
398
+ return null;
399
+ }
400
+ const fromDir = dirname(fromFile);
401
+ const resolved = resolve(fromDir, importSource);
402
+ const sourceExt = extname(resolved);
403
+ const fromLang = detectLangFromExt(extname(fromFile));
404
+ const fallbacks = JS_EXT_FALLBACKS[sourceExt];
405
+ if (fallbacks) {
406
+ const base = resolved.slice(0, -sourceExt.length);
407
+ for (const ext of fallbacks) {
408
+ const candidate = base + ext;
409
+ if (await fileExists(candidate)) return candidate.replace(/\\/g, "/");
410
+ }
411
+ for (const indexExt of [".ts", ".tsx", ".jsx"]) {
412
+ const indexPath = resolve(base, "index" + indexExt);
413
+ if (await fileExists(indexPath)) return indexPath.replace(/\\/g, "/");
414
+ }
415
+ }
416
+ const hasKnownExt = Object.values(EXTENSION_BY_LANG).flat().some((e) => resolved.endsWith(e));
417
+ if (hasKnownExt) {
418
+ return resolved.replace(/\\/g, "/");
419
+ }
420
+ const extensions = getExtensionsForLang(fromLang);
421
+ return (resolved + extensions[0]).replace(/\\/g, "/");
422
+ }
423
+ function getImportType(imp) {
424
+ if (imp.kind === "type") return "type-only";
425
+ return "static";
426
+ }
427
+ async function buildDependencyGraph(files, parser, graphDependencyData) {
428
+ if (graphDependencyData) {
429
+ return Ok({
430
+ nodes: graphDependencyData.nodes,
431
+ edges: graphDependencyData.edges
432
+ });
433
+ }
434
+ const isLookup = "getForFile" in parser;
435
+ const nodes = files.map((f) => f.replace(/\\/g, "/"));
436
+ const edges = [];
437
+ for (const file of files) {
438
+ const normalizedFile = file.replace(/\\/g, "/");
439
+ const fileParser = isLookup ? parser.getForFile(file) : parser;
440
+ if (!fileParser) continue;
441
+ const parseResult = await fileParser.parseFile(file);
442
+ if (!parseResult.ok) {
443
+ continue;
444
+ }
445
+ const importsResult = fileParser.extractImports(parseResult.value);
446
+ if (!importsResult.ok) {
447
+ continue;
448
+ }
449
+ for (const imp of importsResult.value) {
450
+ const resolvedPath = await resolveImportPath(imp.source, file, "");
451
+ if (resolvedPath) {
452
+ edges.push({
453
+ from: normalizedFile,
454
+ to: resolvedPath,
455
+ importType: getImportType(imp),
456
+ line: imp.location.line
457
+ });
458
+ }
459
+ }
460
+ }
461
+ return Ok({ nodes, edges });
462
+ }
463
+ function checkLayerViolations(graph, layers, rootDir) {
464
+ const violations = [];
465
+ for (const edge of graph.edges) {
466
+ const fromRelative = relativePosix(rootDir, edge.from);
467
+ const toRelative = relativePosix(rootDir, edge.to);
468
+ const fromLayer = resolveFileToLayer(fromRelative, layers);
469
+ const toLayer = resolveFileToLayer(toRelative, layers);
470
+ if (!fromLayer || !toLayer) continue;
471
+ if (fromLayer.name === toLayer.name) continue;
472
+ if (!fromLayer.allowedDependencies.includes(toLayer.name)) {
473
+ violations.push({
474
+ file: edge.from,
475
+ imports: edge.to,
476
+ fromLayer: fromLayer.name,
477
+ toLayer: toLayer.name,
478
+ reason: "WRONG_LAYER",
479
+ line: edge.line,
480
+ suggestion: `Move the dependency to an allowed layer (${fromLayer.allowedDependencies.join(", ") || "none"}) or update layer rules`
481
+ });
482
+ }
483
+ }
484
+ return violations;
485
+ }
486
+ async function validateDependencies(config) {
487
+ const { layers, rootDir, parser, fallbackBehavior = "error", graphDependencyData } = config;
488
+ if (graphDependencyData) {
489
+ const graphResult2 = await buildDependencyGraph([], parser, graphDependencyData);
490
+ if (!graphResult2.ok) {
491
+ return Err(graphResult2.error);
492
+ }
493
+ const violations2 = checkLayerViolations(graphResult2.value, layers, rootDir);
494
+ return Ok({
495
+ valid: violations2.length === 0,
496
+ violations: violations2,
497
+ graph: graphResult2.value
498
+ });
499
+ }
500
+ const healthResult = await parser.health();
501
+ if (!healthResult.ok || !healthResult.value.available) {
502
+ if (fallbackBehavior === "skip") {
503
+ return Ok({
504
+ valid: true,
505
+ violations: [],
506
+ graph: { nodes: [], edges: [] },
507
+ skipped: true,
508
+ reason: "Parser unavailable"
509
+ });
510
+ }
511
+ if (fallbackBehavior === "warn") {
512
+ console.warn(`Parser ${parser.name} unavailable, skipping validation`);
513
+ return Ok({
514
+ valid: true,
515
+ violations: [],
516
+ graph: { nodes: [], edges: [] },
517
+ skipped: true,
518
+ reason: "Parser unavailable"
519
+ });
520
+ }
521
+ return Err(
522
+ createError(
523
+ "PARSER_UNAVAILABLE",
524
+ `Parser ${parser.name} is not available`,
525
+ { parser: parser.name },
526
+ ["Install required runtime", "Use different parser", 'Set fallbackBehavior: "skip"']
527
+ )
528
+ );
529
+ }
530
+ const allFiles = [];
531
+ for (const layer of layers) {
532
+ for (const pattern of layer.patterns) {
533
+ const files = await findFiles(pattern, rootDir);
534
+ allFiles.push(...files);
535
+ }
536
+ }
537
+ const uniqueFiles = [...new Set(allFiles)];
538
+ const graphResult = await buildDependencyGraph(uniqueFiles, parser);
539
+ if (!graphResult.ok) {
540
+ return Err(graphResult.error);
541
+ }
542
+ const violations = checkLayerViolations(graphResult.value, layers, rootDir);
543
+ return Ok({
544
+ valid: violations.length === 0,
545
+ violations,
546
+ graph: graphResult.value
547
+ });
548
+ }
549
+ function extractFunctions(content) {
550
+ const functions = [];
551
+ const lines = content.split("\n");
552
+ for (let i = 0; i < lines.length; i++) {
553
+ const line = lines[i];
554
+ for (const pattern of FUNCTION_PATTERNS) {
555
+ const match = line.match(pattern);
556
+ if (match) {
557
+ const name = match[1] ?? "anonymous";
558
+ const paramsStr = match[2] || "";
559
+ const params = paramsStr.trim() === "" ? 0 : paramsStr.split(",").length;
560
+ const endLine = findFunctionEnd(lines, i);
561
+ const body = lines.slice(i, endLine + 1).join("\n");
562
+ functions.push({
563
+ name,
564
+ line: i + 1,
565
+ params,
566
+ startLine: i + 1,
567
+ endLine: endLine + 1,
568
+ body
569
+ });
570
+ break;
571
+ }
572
+ }
573
+ }
574
+ return functions;
575
+ }
576
+ function findFunctionEnd(lines, startIdx) {
577
+ let depth = 0;
578
+ let foundOpen = false;
579
+ for (let i = startIdx; i < lines.length; i++) {
580
+ const line = lines[i];
581
+ for (const ch of line) {
582
+ if (ch === "{") {
583
+ depth++;
584
+ foundOpen = true;
585
+ } else if (ch === "}") {
586
+ depth--;
587
+ if (foundOpen && depth === 0) {
588
+ return i;
589
+ }
590
+ }
591
+ }
592
+ }
593
+ return lines.length - 1;
594
+ }
595
+ function countDecisionPoints(body) {
596
+ let count = 0;
597
+ for (const pattern of DECISION_PATTERNS) {
598
+ const matches = body.match(pattern);
599
+ if (matches) count += matches.length;
600
+ }
601
+ return count;
602
+ }
603
+ function computeCyclomaticComplexity(body) {
604
+ let complexity = 1 + countDecisionPoints(body);
605
+ const elseIfMatches = body.match(/\belse\s+if\s*\(/g);
606
+ if (elseIfMatches) {
607
+ complexity -= elseIfMatches.length;
608
+ }
609
+ return complexity;
610
+ }
611
+ function computeNestingDepth(body) {
612
+ let maxDepth = 0;
613
+ let currentDepth = 0;
614
+ let functionBodyStarted = false;
615
+ for (const ch of body) {
616
+ if (ch === "{") {
617
+ if (!functionBodyStarted) {
618
+ functionBodyStarted = true;
619
+ continue;
620
+ }
621
+ currentDepth++;
622
+ if (currentDepth > maxDepth) {
623
+ maxDepth = currentDepth;
624
+ }
625
+ } else if (ch === "}") {
626
+ if (currentDepth > 0) {
627
+ currentDepth--;
628
+ }
629
+ }
630
+ }
631
+ return maxDepth;
632
+ }
633
+ function resolveThresholds(config) {
634
+ const userThresholds = config?.thresholds;
635
+ if (!userThresholds) return { ...DEFAULT_THRESHOLDS };
636
+ return {
637
+ cyclomaticComplexity: {
638
+ ...DEFAULT_THRESHOLDS.cyclomaticComplexity,
639
+ ...stripUndefined(userThresholds.cyclomaticComplexity)
640
+ },
641
+ nestingDepth: {
642
+ ...DEFAULT_THRESHOLDS.nestingDepth,
643
+ ...stripUndefined(userThresholds.nestingDepth)
644
+ },
645
+ functionLength: {
646
+ ...DEFAULT_THRESHOLDS.functionLength,
647
+ ...stripUndefined(userThresholds.functionLength)
648
+ },
649
+ parameterCount: {
650
+ ...DEFAULT_THRESHOLDS.parameterCount,
651
+ ...stripUndefined(userThresholds.parameterCount)
652
+ },
653
+ fileLength: { ...DEFAULT_THRESHOLDS.fileLength, ...stripUndefined(userThresholds.fileLength) }
654
+ };
655
+ }
656
+ function stripUndefined(obj) {
657
+ if (!obj) return {};
658
+ const result = {};
659
+ for (const [key, val] of Object.entries(obj)) {
660
+ if (val !== void 0) result[key] = val;
661
+ }
662
+ return result;
663
+ }
664
+ function checkFileLengthViolation(filePath, lineCount, threshold) {
665
+ if (lineCount <= threshold) return null;
666
+ return {
667
+ file: filePath,
668
+ function: "<file>",
669
+ line: 1,
670
+ metric: "fileLength",
671
+ value: lineCount,
672
+ threshold,
673
+ tier: 3,
674
+ severity: "info",
675
+ message: `File has ${lineCount} lines (threshold: ${threshold})`
676
+ };
677
+ }
678
+ function checkCyclomaticComplexity(filePath, fn, thresholds) {
679
+ const complexity = computeCyclomaticComplexity(fn.body);
680
+ if (complexity > thresholds.error) {
681
+ return {
682
+ file: filePath,
683
+ function: fn.name,
684
+ line: fn.line,
685
+ metric: "cyclomaticComplexity",
686
+ value: complexity,
687
+ threshold: thresholds.error,
688
+ tier: 1,
689
+ severity: "error",
690
+ message: `Function "${fn.name}" has cyclomatic complexity of ${complexity} (error threshold: ${thresholds.error})`
691
+ };
692
+ }
693
+ if (complexity > thresholds.warn) {
694
+ return {
695
+ file: filePath,
696
+ function: fn.name,
697
+ line: fn.line,
698
+ metric: "cyclomaticComplexity",
699
+ value: complexity,
700
+ threshold: thresholds.warn,
701
+ tier: 2,
702
+ severity: "warning",
703
+ message: `Function "${fn.name}" has cyclomatic complexity of ${complexity} (warning threshold: ${thresholds.warn})`
704
+ };
705
+ }
706
+ return null;
707
+ }
708
+ function checkNestingDepth(filePath, fn, threshold) {
709
+ const depth = computeNestingDepth(fn.body);
710
+ if (depth <= threshold) return null;
711
+ return {
712
+ file: filePath,
713
+ function: fn.name,
714
+ line: fn.line,
715
+ metric: "nestingDepth",
716
+ value: depth,
717
+ threshold,
718
+ tier: 2,
719
+ severity: "warning",
720
+ message: `Function "${fn.name}" has nesting depth of ${depth} (threshold: ${threshold})`
721
+ };
722
+ }
723
+ function checkFunctionLength(filePath, fn, threshold) {
724
+ const fnLength = fn.endLine - fn.startLine + 1;
725
+ if (fnLength <= threshold) return null;
726
+ return {
727
+ file: filePath,
728
+ function: fn.name,
729
+ line: fn.line,
730
+ metric: "functionLength",
731
+ value: fnLength,
732
+ threshold,
733
+ tier: 2,
734
+ severity: "warning",
735
+ message: `Function "${fn.name}" is ${fnLength} lines long (threshold: ${threshold})`
736
+ };
737
+ }
738
+ function checkParameterCount(filePath, fn, threshold) {
739
+ if (fn.params <= threshold) return null;
740
+ return {
741
+ file: filePath,
742
+ function: fn.name,
743
+ line: fn.line,
744
+ metric: "parameterCount",
745
+ value: fn.params,
746
+ threshold,
747
+ tier: 2,
748
+ severity: "warning",
749
+ message: `Function "${fn.name}" has ${fn.params} parameters (threshold: ${threshold})`
750
+ };
751
+ }
752
+ function checkHotspot(filePath, fn, graphData) {
753
+ const hotspot = graphData.hotspots.find((h) => h.file === filePath && h.function === fn.name);
754
+ if (!hotspot || hotspot.hotspotScore <= graphData.percentile95Score) return null;
755
+ return {
756
+ file: filePath,
757
+ function: fn.name,
758
+ line: fn.line,
759
+ metric: "hotspotScore",
760
+ value: hotspot.hotspotScore,
761
+ threshold: graphData.percentile95Score,
762
+ tier: 1,
763
+ severity: "error",
764
+ message: `Function "${fn.name}" is a complexity hotspot (score: ${hotspot.hotspotScore}, p95: ${graphData.percentile95Score})`
765
+ };
766
+ }
767
+ function collectFunctionViolations(filePath, fn, thresholds, graphData) {
768
+ const checks = [
769
+ checkCyclomaticComplexity(filePath, fn, thresholds.cyclomaticComplexity),
770
+ checkNestingDepth(filePath, fn, thresholds.nestingDepth.warn),
771
+ checkFunctionLength(filePath, fn, thresholds.functionLength.warn),
772
+ checkParameterCount(filePath, fn, thresholds.parameterCount.warn)
773
+ ];
774
+ if (graphData) {
775
+ checks.push(checkHotspot(filePath, fn, graphData));
776
+ }
777
+ return checks.filter((v) => v !== null);
778
+ }
779
+ async function detectComplexityViolations(snapshot, config, graphData) {
780
+ const violations = [];
781
+ const thresholds = resolveThresholds(config);
782
+ let totalFunctions = 0;
783
+ for (const file of snapshot.files) {
784
+ let content;
785
+ try {
786
+ content = await readFile2(file.path, "utf-8");
787
+ } catch {
788
+ continue;
789
+ }
790
+ const lines = content.split("\n");
791
+ const fileLenViolation = checkFileLengthViolation(
792
+ file.path,
793
+ lines.length,
794
+ thresholds.fileLength.info
795
+ );
796
+ if (fileLenViolation) violations.push(fileLenViolation);
797
+ const functions = extractFunctions(content);
798
+ totalFunctions += functions.length;
799
+ for (const fn of functions) {
800
+ violations.push(...collectFunctionViolations(file.path, fn, thresholds, graphData));
801
+ }
802
+ }
803
+ const errorCount = violations.filter((v) => v.severity === "error").length;
804
+ const warningCount = violations.filter((v) => v.severity === "warning").length;
805
+ const infoCount = violations.filter((v) => v.severity === "info").length;
806
+ return Ok({
807
+ violations,
808
+ stats: {
809
+ filesAnalyzed: snapshot.files.length,
810
+ functionsAnalyzed: totalFunctions,
811
+ violationCount: violations.length,
812
+ errorCount,
813
+ warningCount,
814
+ infoCount
815
+ }
816
+ });
817
+ }
818
+ function computeMetricsFromSnapshot(snapshot) {
819
+ const fanInMap = /* @__PURE__ */ new Map();
820
+ for (const file of snapshot.files) {
821
+ for (const imp of file.imports) {
822
+ const resolved = resolveImportSource(imp.source, file.path, snapshot);
823
+ if (resolved) {
824
+ fanInMap.set(resolved, (fanInMap.get(resolved) || 0) + 1);
825
+ }
826
+ }
827
+ }
828
+ return snapshot.files.map((file) => {
829
+ const fanOut = file.imports.length;
830
+ const fanIn = fanInMap.get(file.path) || 0;
831
+ const total = fanIn + fanOut;
832
+ const couplingRatio = total > 0 ? fanOut / total : 0;
833
+ return {
834
+ file: file.path,
835
+ fanIn,
836
+ fanOut,
837
+ couplingRatio,
838
+ transitiveDepth: 0
839
+ };
840
+ });
841
+ }
842
+ function resolveRelativePath(from, source) {
843
+ const dir = from.includes("/") ? from.substring(0, from.lastIndexOf("/")) : ".";
844
+ const parts = dir.split("/");
845
+ for (const segment of source.split("/")) {
846
+ if (segment === ".") continue;
847
+ if (segment === "..") {
848
+ parts.pop();
849
+ } else {
850
+ parts.push(segment);
851
+ }
852
+ }
853
+ return parts.join("/");
854
+ }
855
+ function resolveImportSource(source, fromFile, snapshot) {
856
+ if (!source.startsWith(".") && !source.startsWith("/")) {
857
+ return void 0;
858
+ }
859
+ const resolved = resolveRelativePath(fromFile, source);
860
+ const filePaths = snapshot.files.map((f) => f.path);
861
+ const candidates = [
862
+ resolved,
863
+ `${resolved}.ts`,
864
+ `${resolved}.tsx`,
865
+ `${resolved}/index.ts`,
866
+ `${resolved}/index.tsx`
867
+ ];
868
+ for (const candidate of candidates) {
869
+ const match = filePaths.find((fp) => fp === candidate);
870
+ if (match) return match;
871
+ }
872
+ return void 0;
873
+ }
874
+ function resolveThresholds2(config) {
875
+ return {
876
+ fanOut: { ...DEFAULT_THRESHOLDS2.fanOut, ...config?.thresholds?.fanOut },
877
+ fanIn: { ...DEFAULT_THRESHOLDS2.fanIn, ...config?.thresholds?.fanIn },
878
+ couplingRatio: { ...DEFAULT_THRESHOLDS2.couplingRatio, ...config?.thresholds?.couplingRatio },
879
+ transitiveDependencyDepth: {
880
+ ...DEFAULT_THRESHOLDS2.transitiveDependencyDepth,
881
+ ...config?.thresholds?.transitiveDependencyDepth
882
+ }
883
+ };
884
+ }
885
+ function checkFanOut(m, threshold) {
886
+ if (m.fanOut <= threshold) return null;
887
+ return {
888
+ file: m.file,
889
+ metric: "fanOut",
890
+ value: m.fanOut,
891
+ threshold,
892
+ tier: 2,
893
+ severity: "warning",
894
+ message: `File has ${m.fanOut} imports (threshold: ${threshold})`
895
+ };
896
+ }
897
+ function checkFanIn(m, threshold) {
898
+ if (m.fanIn <= threshold) return null;
899
+ return {
900
+ file: m.file,
901
+ metric: "fanIn",
902
+ value: m.fanIn,
903
+ threshold,
904
+ tier: 3,
905
+ severity: "info",
906
+ message: `File is imported by ${m.fanIn} files (threshold: ${threshold})`
907
+ };
908
+ }
909
+ function checkCouplingRatio(m, threshold) {
910
+ const totalConnections = m.fanIn + m.fanOut;
911
+ if (totalConnections <= 5 || m.couplingRatio <= threshold) return null;
912
+ return {
913
+ file: m.file,
914
+ metric: "couplingRatio",
915
+ value: m.couplingRatio,
916
+ threshold,
917
+ tier: 2,
918
+ severity: "warning",
919
+ message: `Coupling ratio is ${m.couplingRatio.toFixed(2)} (threshold: ${threshold})`
920
+ };
921
+ }
922
+ function checkTransitiveDepth(m, threshold) {
923
+ if (m.transitiveDepth <= threshold) return null;
924
+ return {
925
+ file: m.file,
926
+ metric: "transitiveDependencyDepth",
927
+ value: m.transitiveDepth,
928
+ threshold,
929
+ tier: 3,
930
+ severity: "info",
931
+ message: `Transitive dependency depth is ${m.transitiveDepth} (threshold: ${threshold})`
932
+ };
933
+ }
934
+ function checkMetricViolations(m, thresholds) {
935
+ const candidates = [
936
+ checkFanOut(m, thresholds.fanOut.warn),
937
+ checkFanIn(m, thresholds.fanIn.info),
938
+ checkCouplingRatio(m, thresholds.couplingRatio.warn),
939
+ checkTransitiveDepth(m, thresholds.transitiveDependencyDepth.info)
940
+ ];
941
+ return candidates.filter((v) => v !== null);
942
+ }
943
+ function checkViolations(metrics, config) {
944
+ const thresholds = resolveThresholds2(config);
945
+ const violations = [];
946
+ for (const m of metrics) {
947
+ violations.push(...checkMetricViolations(m, thresholds));
948
+ }
949
+ return violations;
950
+ }
951
+ async function detectCouplingViolations(snapshot, config, graphData) {
952
+ let metrics;
953
+ if (graphData) {
954
+ metrics = graphData.files.map((f) => ({
955
+ file: f.file,
956
+ fanIn: f.fanIn,
957
+ fanOut: f.fanOut,
958
+ couplingRatio: f.couplingRatio,
959
+ transitiveDepth: f.transitiveDepth
960
+ }));
961
+ } else {
962
+ metrics = computeMetricsFromSnapshot(snapshot);
963
+ }
964
+ const violations = checkViolations(metrics, config);
965
+ const warningCount = violations.filter((v) => v.severity === "warning").length;
966
+ const infoCount = violations.filter((v) => v.severity === "info").length;
967
+ return Ok({
968
+ violations,
969
+ stats: {
970
+ filesAnalyzed: metrics.length,
971
+ violationCount: violations.length,
972
+ warningCount,
973
+ infoCount
974
+ }
975
+ });
976
+ }
977
+ function findSymbolByName(symbols, name) {
978
+ for (const sym of symbols) {
979
+ if (sym.name === name) return { line: sym.line, endLine: sym.endLine };
980
+ if (sym.children) {
981
+ const found = findSymbolByName(sym.children, name);
982
+ if (found) return found;
983
+ }
984
+ }
985
+ return null;
986
+ }
987
+ function makeLocation2(node) {
988
+ return {
989
+ file: "",
990
+ line: node.startPosition.row + 1,
991
+ column: node.startPosition.column
992
+ };
993
+ }
994
+ function makeNamedExport(node) {
995
+ const name = node.childForFieldName("name");
996
+ if (!name) return null;
997
+ return { name: name.text, type: "named", location: makeLocation2(node), isReExport: false };
998
+ }
999
+ function makeImport(node, source) {
1000
+ return { source, specifiers: [], location: makeLocation2(node), kind: "value" };
1001
+ }
1002
+ function extractPythonImport(child) {
1003
+ const name = child.childForFieldName("name");
1004
+ if (!name) return null;
1005
+ return makeImport(child, name.text);
1006
+ }
1007
+ function extractPythonFromImport(child) {
1008
+ const moduleName = child.childForFieldName("module_name");
1009
+ const specifiers = [];
1010
+ for (const c of child.children) {
1011
+ if (c.type === "dotted_name" && c !== moduleName) specifiers.push(c.text);
1012
+ if (c.type === "aliased_import") {
1013
+ const n = c.childForFieldName("name");
1014
+ if (n) specifiers.push(n.text);
1015
+ }
1016
+ }
1017
+ return {
1018
+ source: moduleName?.text ?? "",
1019
+ specifiers,
1020
+ location: makeLocation2(child),
1021
+ kind: "value"
1022
+ };
1023
+ }
1024
+ function extractPythonExport(child) {
1025
+ if (child.type === "function_definition" || child.type === "class_definition") {
1026
+ return makeNamedExport(child);
1027
+ }
1028
+ if (child.type === "assignment") {
1029
+ const left = child.childForFieldName("left") ?? child.children[0];
1030
+ if (left && !left.text.startsWith("_")) {
1031
+ return { name: left.text, type: "named", location: makeLocation2(child), isReExport: false };
1032
+ }
1033
+ }
1034
+ return null;
1035
+ }
1036
+ function extractGoImportPath(spec) {
1037
+ const pathNode = spec.childForFieldName("path") ?? spec.children.find((c) => c.type === "interpreted_string_literal");
1038
+ return pathNode ? pathNode.text.replace(/"/g, "") : null;
1039
+ }
1040
+ function extractGoImportsFromDecl(child) {
1041
+ const imports = [];
1042
+ for (const spec of child.children.filter((c) => c.type === "import_spec")) {
1043
+ const source = extractGoImportPath(spec);
1044
+ if (source) imports.push(makeImport(child, source));
1045
+ }
1046
+ const specList = child.children.find((c) => c.type === "import_spec_list");
1047
+ if (specList) {
1048
+ for (const spec of specList.children.filter((c) => c.type === "import_spec")) {
1049
+ const source = extractGoImportPath(spec);
1050
+ if (source) imports.push(makeImport(spec, source));
1051
+ }
1052
+ }
1053
+ return imports;
1054
+ }
1055
+ function isGoExported(name) {
1056
+ return /^[A-Z]/.test(name);
1057
+ }
1058
+ function extractGoExport(child) {
1059
+ if (child.type === "function_declaration" || child.type === "method_declaration") {
1060
+ const name = child.childForFieldName("name");
1061
+ if (name && isGoExported(name.text)) return makeNamedExport(child);
1062
+ }
1063
+ if (child.type === "type_declaration") {
1064
+ const typeSpec = child.children.find((c) => c.type === "type_spec");
1065
+ if (typeSpec) {
1066
+ const name = typeSpec.childForFieldName("name");
1067
+ if (name && isGoExported(name.text)) {
1068
+ return { name: name.text, type: "named", location: makeLocation2(child), isReExport: false };
1069
+ }
1070
+ }
1071
+ }
1072
+ return null;
1073
+ }
1074
+ function createTreeSitterParser(lang) {
1075
+ const strategy = STRATEGIES[lang];
1076
+ if (!strategy) return null;
1077
+ const extensionMap = {
1078
+ python: [".py"],
1079
+ go: [".go"],
1080
+ rust: [".rs"],
1081
+ java: [".java"]
1082
+ };
1083
+ const extensions = extensionMap[lang];
1084
+ if (!extensions) return null;
1085
+ return new TreeSitterParser(lang, extensions, strategy);
1086
+ }
1087
+ function getDefaultRegistry() {
1088
+ if (defaultRegistry) return defaultRegistry;
1089
+ const registry = new ParserRegistry();
1090
+ const tsParser = new TypeScriptParser();
1091
+ registry.register(tsParser);
1092
+ registry.register({
1093
+ name: "javascript",
1094
+ extensions: [".js", ".jsx", ".mjs", ".cjs"],
1095
+ parseFile: tsParser.parseFile.bind(tsParser),
1096
+ extractImports: tsParser.extractImports.bind(tsParser),
1097
+ extractExports: tsParser.extractExports.bind(tsParser),
1098
+ health: tsParser.health.bind(tsParser)
1099
+ });
1100
+ const treeSitterLanguages = ["python", "go", "rust", "java"];
1101
+ for (const lang of treeSitterLanguages) {
1102
+ const parser = createTreeSitterParser(lang);
1103
+ if (parser) {
1104
+ registry.register(parser);
1105
+ }
1106
+ }
1107
+ defaultRegistry = registry;
1108
+ return registry;
1109
+ }
1110
+ var accessAsync, readFileAsync, DEFAULT_FIND_FILES_IGNORE, TypeScriptParser, EXTENSION_MAP, parserCache, initialized, GRAMMAR_MAP, TOP_LEVEL_TYPES, METHOD_TYPES, IDENTIFIER_TYPES, NAME_EXTRACTORS, EXTENSION_BY_LANG, JS_EXT_FALLBACKS, DEFAULT_THRESHOLDS, FUNCTION_PATTERNS, DECISION_PATTERNS, DEFAULT_THRESHOLDS2, pythonStrategy, goStrategy, RUST_USE_ARG_TYPES, RUST_PUB_ITEM_TYPES, rustStrategy, JAVA_IMPORT_TYPES, JAVA_EXPORT_TYPES, javaStrategy, STRATEGIES, TreeSitterParser, ParserRegistry, defaultRegistry;
1111
+ var init_chunk_MUWJHO2S = __esm({
1112
+ "../core/dist/chunk-MUWJHO2S.mjs"() {
1113
+ "use strict";
1114
+ init_dist();
1115
+ accessAsync = promisify(access);
1116
+ readFileAsync = promisify(readFile);
1117
+ DEFAULT_FIND_FILES_IGNORE = [
1118
+ "**/node_modules/**",
1119
+ "**/dist/**",
1120
+ "**/build/**",
1121
+ "**/coverage/**"
1122
+ ];
1123
+ TypeScriptParser = class {
1124
+ name = "typescript";
1125
+ extensions = [".ts", ".tsx", ".mts", ".cts"];
1126
+ async parseFile(path) {
1127
+ const contentResult = await readFileContent(path);
1128
+ if (!contentResult.ok) {
1129
+ return Err(
1130
+ createParseError("NOT_FOUND", `File not found: ${path}`, { path }, [
1131
+ "Check that the file exists",
1132
+ "Verify the path is correct"
1133
+ ])
1134
+ );
1135
+ }
1136
+ try {
1137
+ const ast = parse(contentResult.value, {
1138
+ loc: true,
1139
+ range: true,
1140
+ jsx: path.endsWith(".tsx"),
1141
+ errorOnUnknownASTType: false
1142
+ });
1143
+ return Ok({
1144
+ type: "Program",
1145
+ body: ast,
1146
+ language: "typescript"
1147
+ });
1148
+ } catch (e) {
1149
+ const error = e;
1150
+ return Err(
1151
+ createParseError("SYNTAX_ERROR", `Failed to parse ${path}: ${error.message}`, { path }, [
1152
+ "Check for syntax errors in the file",
1153
+ "Ensure valid TypeScript syntax"
1154
+ ])
1155
+ );
1156
+ }
1157
+ }
1158
+ extractImports(ast) {
1159
+ const imports = [];
1160
+ const program = ast.body;
1161
+ walk(program, (node) => {
1162
+ if (node.type === "ImportDeclaration") {
1163
+ const importDecl = node;
1164
+ const imp = {
1165
+ source: importDecl.source.value,
1166
+ specifiers: [],
1167
+ location: makeLocation(importDecl),
1168
+ kind: importDecl.importKind === "type" ? "type" : "value"
1169
+ };
1170
+ processImportSpecifiers(importDecl, imp);
1171
+ imports.push(imp);
1172
+ return;
1173
+ }
1174
+ if (node.type === "ImportExpression") {
1175
+ const importExpr = node;
1176
+ if (importExpr.source.type === "Literal" && typeof importExpr.source.value === "string") {
1177
+ imports.push({
1178
+ source: importExpr.source.value,
1179
+ specifiers: [],
1180
+ location: makeLocation(importExpr),
1181
+ kind: "value"
1182
+ });
1183
+ }
1184
+ }
1185
+ });
1186
+ return Ok(imports);
1187
+ }
1188
+ extractExports(ast) {
1189
+ const exports = [];
1190
+ const program = ast.body;
1191
+ walk(program, (node) => {
1192
+ if (node.type === "ExportNamedDeclaration") {
1193
+ const exportDecl = node;
1194
+ if (exportDecl.source) {
1195
+ processReExportSpecifiers(exportDecl, exports);
1196
+ return;
1197
+ }
1198
+ processExportDeclaration(exportDecl, exports);
1199
+ processExportListSpecifiers(exportDecl, exports);
1200
+ return;
1201
+ }
1202
+ if (node.type === "ExportDefaultDeclaration") {
1203
+ const exportDecl = node;
1204
+ exports.push({
1205
+ name: "default",
1206
+ type: "default",
1207
+ location: makeLocation(exportDecl),
1208
+ isReExport: false
1209
+ });
1210
+ return;
1211
+ }
1212
+ if (node.type === "ExportAllDeclaration") {
1213
+ const exportDecl = node;
1214
+ exports.push({
1215
+ name: exportDecl.exported?.name ?? "*",
1216
+ type: "namespace",
1217
+ location: makeLocation(exportDecl),
1218
+ isReExport: true,
1219
+ source: exportDecl.source.value
1220
+ });
1221
+ }
1222
+ });
1223
+ return Ok(exports);
1224
+ }
1225
+ async health() {
1226
+ return Ok({ available: true, version: "7.0.0" });
1227
+ }
1228
+ };
1229
+ EXTENSION_MAP = {
1230
+ ".ts": "typescript",
1231
+ ".tsx": "typescript",
1232
+ ".mts": "typescript",
1233
+ ".cts": "typescript",
1234
+ ".js": "javascript",
1235
+ ".jsx": "javascript",
1236
+ ".mjs": "javascript",
1237
+ ".cjs": "javascript",
1238
+ ".py": "python",
1239
+ ".go": "go",
1240
+ ".rs": "rust",
1241
+ ".java": "java"
1242
+ };
1243
+ parserCache = /* @__PURE__ */ new Map();
1244
+ initialized = false;
1245
+ GRAMMAR_MAP = {
1246
+ typescript: "tree-sitter-typescript",
1247
+ javascript: "tree-sitter-javascript",
1248
+ python: "tree-sitter-python",
1249
+ go: "tree-sitter-go",
1250
+ rust: "tree-sitter-rust",
1251
+ java: "tree-sitter-java"
1252
+ };
1253
+ TOP_LEVEL_TYPES = {
1254
+ typescript: {
1255
+ function_declaration: "function",
1256
+ class_declaration: "class",
1257
+ interface_declaration: "interface",
1258
+ type_alias_declaration: "type",
1259
+ lexical_declaration: "variable",
1260
+ variable_declaration: "variable",
1261
+ export_statement: "export",
1262
+ import_statement: "import",
1263
+ enum_declaration: "type"
1264
+ },
1265
+ javascript: {
1266
+ function_declaration: "function",
1267
+ class_declaration: "class",
1268
+ lexical_declaration: "variable",
1269
+ variable_declaration: "variable",
1270
+ export_statement: "export",
1271
+ import_statement: "import"
1272
+ },
1273
+ python: {
1274
+ function_definition: "function",
1275
+ class_definition: "class",
1276
+ assignment: "variable",
1277
+ import_statement: "import",
1278
+ import_from_statement: "import"
1279
+ },
1280
+ go: {
1281
+ function_declaration: "function",
1282
+ method_declaration: "method",
1283
+ type_declaration: "type",
1284
+ var_declaration: "variable",
1285
+ const_declaration: "variable",
1286
+ import_declaration: "import",
1287
+ short_var_declaration: "variable"
1288
+ },
1289
+ rust: {
1290
+ function_item: "function",
1291
+ struct_item: "class",
1292
+ enum_item: "type",
1293
+ trait_item: "interface",
1294
+ impl_item: "class",
1295
+ mod_item: "variable",
1296
+ const_item: "variable",
1297
+ static_item: "variable",
1298
+ type_item: "type",
1299
+ use_declaration: "import"
1300
+ },
1301
+ java: {
1302
+ class_declaration: "class",
1303
+ interface_declaration: "interface",
1304
+ method_declaration: "function",
1305
+ field_declaration: "variable",
1306
+ import_declaration: "import",
1307
+ enum_declaration: "type",
1308
+ record_declaration: "class",
1309
+ annotation_type_declaration: "interface"
1310
+ }
1311
+ };
1312
+ METHOD_TYPES = {
1313
+ typescript: ["method_definition", "public_field_definition"],
1314
+ javascript: ["method_definition"],
1315
+ python: ["function_definition"],
1316
+ go: ["method_declaration"],
1317
+ rust: ["function_item"],
1318
+ java: ["method_declaration", "constructor_declaration"]
1319
+ };
1320
+ IDENTIFIER_TYPES = /* @__PURE__ */ new Set(["identifier", "property_identifier", "type_identifier"]);
1321
+ NAME_EXTRACTORS = {
1322
+ lexical_declaration: (n) => getVariableDeclarationName(n),
1323
+ variable_declaration: (n) => getVariableDeclarationName(n),
1324
+ export_statement: (n, s) => getExportName(n, s),
1325
+ assignment: (n) => getAssignmentName(n),
1326
+ type_declaration: (n) => getGoTypeSpecName(n),
1327
+ impl_item: (n) => getRustImplName(n),
1328
+ var_declaration: (n) => getGoVarName(n),
1329
+ const_declaration: (n) => getGoVarName(n)
1330
+ };
1331
+ EXTENSION_BY_LANG = {
1332
+ typescript: [".ts", ".tsx"],
1333
+ javascript: [".js", ".jsx", ".mjs", ".cjs"],
1334
+ python: [".py"],
1335
+ go: [".go"],
1336
+ rust: [".rs"],
1337
+ java: [".java"]
1338
+ };
1339
+ JS_EXT_FALLBACKS = {
1340
+ ".js": [".ts", ".tsx", ".jsx"],
1341
+ ".jsx": [".tsx"],
1342
+ ".mjs": [".mts"],
1343
+ ".cjs": [".cts"]
1344
+ };
1345
+ DEFAULT_THRESHOLDS = {
1346
+ cyclomaticComplexity: { error: 15, warn: 10 },
1347
+ nestingDepth: { warn: 4 },
1348
+ functionLength: { warn: 50 },
1349
+ parameterCount: { warn: 5 },
1350
+ fileLength: { info: 300 },
1351
+ hotspotPercentile: { error: 95 }
1352
+ };
1353
+ FUNCTION_PATTERNS = [
1354
+ // function declarations: function name(params) {
1355
+ /^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/,
1356
+ // method declarations: name(params) {
1357
+ /^\s*(?:async\s+)?(\w+)\s*\(([^)]*)\)\s*(?::\s*[^{]+)?\s*\{/,
1358
+ // arrow functions assigned to const/let/var: const name = (params) =>
1359
+ /^\s*(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\(([^)]*)\)\s*(?::\s*[^=]+)?\s*=>/,
1360
+ // arrow functions assigned to const/let/var with single param: const name = param =>
1361
+ /^\s*(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(\w+)\s*=>/
1362
+ ];
1363
+ DECISION_PATTERNS = [
1364
+ /\bif\s*\(/g,
1365
+ /\belse\s+if\s*\(/g,
1366
+ /\bwhile\s*\(/g,
1367
+ /\bfor\s*\(/g,
1368
+ /\bcase\s+/g,
1369
+ /&&/g,
1370
+ /\|\|/g,
1371
+ /\?(?!=)/g,
1372
+ // Ternary ? but not ?. or ??
1373
+ /\bcatch\s*\(/g
1374
+ ];
1375
+ DEFAULT_THRESHOLDS2 = {
1376
+ fanOut: { warn: 15 },
1377
+ fanIn: { info: 20 },
1378
+ couplingRatio: { warn: 0.7 },
1379
+ transitiveDependencyDepth: { info: 30 }
1380
+ };
1381
+ pythonStrategy = {
1382
+ extractImports(root) {
1383
+ const imports = [];
1384
+ for (const child of root.children) {
1385
+ if (child.type === "import_statement") {
1386
+ const imp = extractPythonImport(child);
1387
+ if (imp) imports.push(imp);
1388
+ } else if (child.type === "import_from_statement") {
1389
+ imports.push(extractPythonFromImport(child));
1390
+ }
1391
+ }
1392
+ return imports;
1393
+ },
1394
+ extractExports(root) {
1395
+ const exports = [];
1396
+ for (const child of root.children) {
1397
+ const exp = extractPythonExport(child);
1398
+ if (exp) exports.push(exp);
1399
+ }
1400
+ return exports;
1401
+ }
1402
+ };
1403
+ goStrategy = {
1404
+ extractImports(root) {
1405
+ const imports = [];
1406
+ for (const child of root.children) {
1407
+ if (child.type === "import_declaration") imports.push(...extractGoImportsFromDecl(child));
1408
+ }
1409
+ return imports;
1410
+ },
1411
+ extractExports(root) {
1412
+ const exports = [];
1413
+ for (const child of root.children) {
1414
+ const exp = extractGoExport(child);
1415
+ if (exp) exports.push(exp);
1416
+ }
1417
+ return exports;
1418
+ }
1419
+ };
1420
+ RUST_USE_ARG_TYPES = /* @__PURE__ */ new Set([
1421
+ "scoped_identifier",
1422
+ "use_wildcard",
1423
+ "scoped_use_list",
1424
+ "identifier"
1425
+ ]);
1426
+ RUST_PUB_ITEM_TYPES = /* @__PURE__ */ new Set([
1427
+ "function_item",
1428
+ "struct_item",
1429
+ "enum_item",
1430
+ "trait_item",
1431
+ "type_item",
1432
+ "const_item",
1433
+ "static_item"
1434
+ ]);
1435
+ rustStrategy = {
1436
+ extractImports(root) {
1437
+ const imports = [];
1438
+ for (const child of root.children) {
1439
+ if (child.type !== "use_declaration") continue;
1440
+ const arg = child.childForFieldName("argument") ?? child.children.find((c) => RUST_USE_ARG_TYPES.has(c.type));
1441
+ if (arg) imports.push(makeImport(child, arg.text));
1442
+ }
1443
+ return imports;
1444
+ },
1445
+ extractExports(root, source) {
1446
+ const exports = [];
1447
+ const lines = source.split("\n");
1448
+ for (const child of root.children) {
1449
+ const line = lines[child.startPosition.row] ?? "";
1450
+ if (!/^\s*pub\b/.test(line)) continue;
1451
+ if (RUST_PUB_ITEM_TYPES.has(child.type)) {
1452
+ const exp = makeNamedExport(child);
1453
+ if (exp) exports.push(exp);
1454
+ } else if (child.type === "mod_item") {
1455
+ const name = child.childForFieldName("name");
1456
+ if (name) {
1457
+ exports.push({
1458
+ name: name.text,
1459
+ type: "namespace",
1460
+ location: makeLocation2(child),
1461
+ isReExport: false
1462
+ });
1463
+ }
1464
+ }
1465
+ }
1466
+ return exports;
1467
+ }
1468
+ };
1469
+ JAVA_IMPORT_TYPES = /* @__PURE__ */ new Set(["scoped_identifier", "scoped_absolute_identifier"]);
1470
+ JAVA_EXPORT_TYPES = /* @__PURE__ */ new Set([
1471
+ "class_declaration",
1472
+ "interface_declaration",
1473
+ "enum_declaration",
1474
+ "record_declaration"
1475
+ ]);
1476
+ javaStrategy = {
1477
+ extractImports(root) {
1478
+ const imports = [];
1479
+ for (const child of root.children) {
1480
+ if (child.type !== "import_declaration") continue;
1481
+ const scoped = child.children.find((c) => JAVA_IMPORT_TYPES.has(c.type));
1482
+ if (scoped) imports.push(makeImport(child, scoped.text));
1483
+ }
1484
+ return imports;
1485
+ },
1486
+ extractExports(root, source) {
1487
+ const exports = [];
1488
+ const lines = source.split("\n");
1489
+ for (const child of root.children) {
1490
+ if (!JAVA_EXPORT_TYPES.has(child.type)) continue;
1491
+ const line = lines[child.startPosition.row] ?? "";
1492
+ if (!/\bpublic\b/.test(line)) continue;
1493
+ const exp = makeNamedExport(child);
1494
+ if (exp) exports.push(exp);
1495
+ }
1496
+ return exports;
1497
+ }
1498
+ };
1499
+ STRATEGIES = {
1500
+ python: pythonStrategy,
1501
+ go: goStrategy,
1502
+ rust: rustStrategy,
1503
+ java: javaStrategy
1504
+ };
1505
+ TreeSitterParser = class {
1506
+ name;
1507
+ extensions;
1508
+ lang;
1509
+ strategy;
1510
+ constructor(lang, extensions, strategy) {
1511
+ this.name = lang;
1512
+ this.lang = lang;
1513
+ this.extensions = extensions;
1514
+ this.strategy = strategy;
1515
+ }
1516
+ async parseFile(path) {
1517
+ const contentResult = await readFileContent(path);
1518
+ if (!contentResult.ok) {
1519
+ return Err(
1520
+ createParseError("NOT_FOUND", `File not found: ${path}`, { path }, [
1521
+ "Check that the file exists"
1522
+ ])
1523
+ );
1524
+ }
1525
+ try {
1526
+ const parser = await getParser(this.lang);
1527
+ const tree = parser.parse(contentResult.value);
1528
+ return Ok({
1529
+ type: "Program",
1530
+ body: { tree, source: contentResult.value },
1531
+ language: this.lang
1532
+ });
1533
+ } catch (e) {
1534
+ const error = e;
1535
+ return Err(
1536
+ createParseError("SYNTAX_ERROR", `Failed to parse ${path}: ${error.message}`, { path }, [
1537
+ "Check for syntax errors in the file"
1538
+ ])
1539
+ );
1540
+ }
1541
+ }
1542
+ extractImports(ast) {
1543
+ const { tree, source } = ast.body;
1544
+ return Ok(this.strategy.extractImports(tree.rootNode, source));
1545
+ }
1546
+ extractExports(ast) {
1547
+ const { tree, source } = ast.body;
1548
+ return Ok(this.strategy.extractExports(tree.rootNode, source));
1549
+ }
1550
+ async health() {
1551
+ try {
1552
+ await getParser(this.lang);
1553
+ return Ok({ available: true, message: `tree-sitter ${this.lang} grammar loaded` });
1554
+ } catch {
1555
+ return Ok({ available: false, message: `tree-sitter ${this.lang} grammar not available` });
1556
+ }
1557
+ }
1558
+ outline(filePath, ast) {
1559
+ const { tree, source } = ast.body;
1560
+ return extractOutlineFromTree(tree.rootNode, this.lang, source, filePath);
1561
+ }
1562
+ unfold(filePath, ast, symbolName) {
1563
+ const outlineResult = this.outline(filePath, ast);
1564
+ if (outlineResult.error || !outlineResult.symbols.length) return null;
1565
+ const { source } = ast.body;
1566
+ const match = findSymbolByName(outlineResult.symbols, symbolName);
1567
+ if (!match) return null;
1568
+ const lines = source.split("\n");
1569
+ const content = lines.slice(match.line - 1, match.endLine).join("\n");
1570
+ return {
1571
+ file: filePath,
1572
+ symbolName,
1573
+ startLine: match.line,
1574
+ endLine: match.endLine,
1575
+ content,
1576
+ language: this.lang,
1577
+ fallback: false
1578
+ };
1579
+ }
1580
+ };
1581
+ ParserRegistry = class {
1582
+ parsers = /* @__PURE__ */ new Map();
1583
+ register(parser) {
1584
+ this.parsers.set(parser.name, parser);
1585
+ }
1586
+ getByLanguage(lang) {
1587
+ return this.parsers.get(lang) ?? null;
1588
+ }
1589
+ getForFile(filePath) {
1590
+ const lang = detectLanguage(filePath);
1591
+ if (!lang) return null;
1592
+ return this.getByLanguage(lang);
1593
+ }
1594
+ getSupportedExtensions() {
1595
+ return Object.keys(EXTENSION_MAP);
1596
+ }
1597
+ getSupportedLanguages() {
1598
+ return Array.from(this.parsers.keys());
1599
+ }
1600
+ isSupportedExtension(ext) {
1601
+ return ext in EXTENSION_MAP;
1602
+ }
1603
+ };
1604
+ defaultRegistry = null;
1605
+ }
1606
+ });
1607
+
1608
+ // ../core/dist/chunk-BTUDWWB4.mjs
1609
+ import { resolve as resolve2 } from "path";
1610
+ import { minimatch as minimatch2 } from "minimatch";
1611
+ import { join, resolve as resolve3 } from "path";
1612
+ import { dirname as dirname2, resolve as resolve32 } from "path";
1613
+ import { dirname as dirname22, extname as extname2, resolve as resolve4 } from "path";
1614
+ import { minimatch as minimatch22 } from "minimatch";
1615
+ import { readdirSync, statSync } from "fs";
1616
+ import { join as join2 } from "path";
1617
+ function collectFieldEntries(rootDir, field) {
1618
+ if (typeof field === "string") return [resolve3(rootDir, field)];
1619
+ if (typeof field === "object" && field !== null) {
1620
+ return Object.values(field).filter((v) => typeof v === "string").map((v) => resolve3(rootDir, v));
1621
+ }
1622
+ return [];
1623
+ }
1624
+ function extractPackageEntries(rootDir, pkg) {
1625
+ const entries = [];
1626
+ entries.push(...collectFieldEntries(rootDir, pkg["exports"]));
1627
+ if (entries.length === 0 && typeof pkg["main"] === "string") {
1628
+ entries.push(resolve3(rootDir, pkg["main"]));
1629
+ }
1630
+ if (pkg["bin"]) entries.push(...collectFieldEntries(rootDir, pkg["bin"]));
1631
+ return entries;
1632
+ }
1633
+ async function readPackageJsonEntries(rootDir, pkgPath) {
1634
+ const content = await readFileContent(pkgPath);
1635
+ if (!content.ok) return [];
1636
+ try {
1637
+ const pkg = JSON.parse(content.value);
1638
+ return extractPackageEntries(rootDir, pkg);
1639
+ } catch {
1640
+ return [];
1641
+ }
1642
+ }
1643
+ async function resolveTypeScript(rootDir) {
1644
+ const pkgPath = join(rootDir, "package.json");
1645
+ const detected = await fileExists(pkgPath);
1646
+ if (detected) {
1647
+ const entries = await readPackageJsonEntries(rootDir, pkgPath);
1648
+ if (entries.length > 0) {
1649
+ return { language: "typescript", detected: true, entries, hints: TS_HINTS };
1650
+ }
1651
+ }
1652
+ for (const conv of TS_CONVENTIONS) {
1653
+ const p = join(rootDir, conv);
1654
+ if (await fileExists(p)) {
1655
+ return { language: "typescript", detected: true, entries: [p], hints: TS_HINTS };
1656
+ }
1657
+ }
1658
+ return { language: "typescript", detected, entries: [], hints: TS_HINTS };
1659
+ }
1660
+ async function detectPython(rootDir) {
1661
+ return await fileExists(join(rootDir, "pyproject.toml")) || await fileExists(join(rootDir, "setup.py")) || await fileExists(join(rootDir, "requirements.txt"));
1662
+ }
1663
+ async function readPyProject(rootDir) {
1664
+ const pyproject = join(rootDir, "pyproject.toml");
1665
+ if (!await fileExists(pyproject)) return { scriptTargets: [] };
1666
+ const content = await readFileContent(pyproject);
1667
+ if (!content.ok) return { scriptTargets: [] };
1668
+ return parsePyProject(content.value);
1669
+ }
1670
+ async function resolveScriptTargetEntry(rootDir, target) {
1671
+ const mod = target.split(":")[0];
1672
+ if (!mod) return void 0;
1673
+ const relPath = mod.replaceAll(".", "/") + ".py";
1674
+ for (const candidate of [join(rootDir, relPath), join(rootDir, "src", relPath)]) {
1675
+ if (await fileExists(candidate)) return candidate;
1676
+ }
1677
+ return void 0;
1678
+ }
1679
+ async function resolvePythonFromScripts(rootDir, targets) {
1680
+ const entries = [];
1681
+ for (const target of targets) {
1682
+ const entry = await resolveScriptTargetEntry(rootDir, target);
1683
+ if (entry) entries.push(entry);
1684
+ }
1685
+ return entries;
1686
+ }
1687
+ async function resolvePythonFromProjectName(rootDir, projectName) {
1688
+ if (!projectName) return [];
1689
+ const normalized = projectName.replaceAll("-", "_");
1690
+ const candidates = [
1691
+ join(rootDir, normalized, "__init__.py"),
1692
+ join(rootDir, normalized, "__main__.py"),
1693
+ join(rootDir, "src", normalized, "__init__.py"),
1694
+ join(rootDir, "src", normalized, "__main__.py")
1695
+ ];
1696
+ const entries = [];
1697
+ for (const c of candidates) {
1698
+ if (await fileExists(c)) entries.push(c);
1699
+ }
1700
+ return entries;
1701
+ }
1702
+ async function resolvePythonConventions(rootDir) {
1703
+ const entries = [];
1704
+ for (const conv of PYTHON_CONVENTIONS) {
1705
+ const p = join(rootDir, conv);
1706
+ if (await fileExists(p)) entries.push(p);
1707
+ }
1708
+ return entries;
1709
+ }
1710
+ async function findPythonTopLevelPackages(rootDir) {
1711
+ const found = await findFiles("*/__init__.py", rootDir);
1712
+ if (found.length > 0) return found;
1713
+ return findFiles("src/*/__init__.py", rootDir);
1714
+ }
1715
+ async function resolvePython(rootDir) {
1716
+ if (!await detectPython(rootDir)) {
1717
+ return { language: "python", detected: false, entries: [], hints: PYTHON_HINTS };
1718
+ }
1719
+ const info = await readPyProject(rootDir);
1720
+ const strategies = [
1721
+ () => resolvePythonFromScripts(rootDir, info.scriptTargets),
1722
+ () => resolvePythonFromProjectName(rootDir, info.projectName),
1723
+ () => resolvePythonConventions(rootDir),
1724
+ () => findPythonTopLevelPackages(rootDir)
1725
+ ];
1726
+ for (const strategy of strategies) {
1727
+ const entries = await strategy();
1728
+ if (entries.length > 0) {
1729
+ return { language: "python", detected: true, entries, hints: PYTHON_HINTS };
1730
+ }
1731
+ }
1732
+ return { language: "python", detected: true, entries: [], hints: PYTHON_HINTS };
1733
+ }
1734
+ async function resolveGo(rootDir) {
1735
+ const hints = ["Create main.go at the project root, or use the cmd/<name>/main.go layout"];
1736
+ const detected = await fileExists(join(rootDir, "go.mod"));
1737
+ if (!detected) return { language: "go", detected: false, entries: [], hints };
1738
+ const entries = [];
1739
+ const mainGo = join(rootDir, "main.go");
1740
+ if (await fileExists(mainGo)) entries.push(mainGo);
1741
+ entries.push(...await findFiles("cmd/*/main.go", rootDir));
1742
+ return { language: "go", detected: true, entries, hints };
1743
+ }
1744
+ async function resolveRust(rootDir) {
1745
+ const hints = [
1746
+ "Create src/main.rs or src/lib.rs",
1747
+ "Declare [[bin]] entries with a `path` in Cargo.toml"
1748
+ ];
1749
+ const cargoPath = join(rootDir, "Cargo.toml");
1750
+ const detected = await fileExists(cargoPath);
1751
+ if (!detected) return { language: "rust", detected: false, entries: [], hints };
1752
+ const entries = [];
1753
+ const content = await readFileContent(cargoPath);
1754
+ if (content.ok) {
1755
+ for (const bp of parseCargoBinPaths(content.value)) {
1756
+ const abs = resolve3(rootDir, bp);
1757
+ if (await fileExists(abs)) entries.push(abs);
1758
+ }
1759
+ }
1760
+ if (entries.length === 0) {
1761
+ for (const conv of ["src/main.rs", "src/lib.rs"]) {
1762
+ const p = join(rootDir, conv);
1763
+ if (await fileExists(p)) entries.push(p);
1764
+ }
1765
+ entries.push(...await findFiles("src/bin/*.rs", rootDir));
1766
+ }
1767
+ return { language: "rust", detected: true, entries, hints };
1768
+ }
1769
+ async function resolveJava(rootDir) {
1770
+ const hints = [
1771
+ "Place an entry class at src/main/java/**/Main.java (or *Application.java for Spring Boot)"
1772
+ ];
1773
+ const detected = await fileExists(join(rootDir, "pom.xml")) || await fileExists(join(rootDir, "build.gradle")) || await fileExists(join(rootDir, "build.gradle.kts"));
1774
+ if (!detected) return { language: "java", detected: false, entries: [], hints };
1775
+ const entries = [];
1776
+ entries.push(...await findFiles("src/main/java/**/Main.java", rootDir));
1777
+ entries.push(...await findFiles("src/main/java/**/*Application.java", rootDir));
1778
+ return { language: "java", detected: true, entries, hints };
1779
+ }
1780
+ function parseTomlLine(raw) {
1781
+ const line = raw.replace(/(^|\s)#.*$/, "").trim();
1782
+ if (!line) return {};
1783
+ const sectionMatch = /^\[([^\]]+)\]$/.exec(line);
1784
+ if (sectionMatch) return { section: sectionMatch[1] ?? "" };
1785
+ const eq = line.indexOf("=");
1786
+ if (eq <= 0) return {};
1787
+ return {
1788
+ key: line.slice(0, eq).trim(),
1789
+ value: stripTomlString(line.slice(eq + 1).trim())
1790
+ };
1791
+ }
1792
+ function parsePyProject(content) {
1793
+ const result = { scriptTargets: [] };
1794
+ let section = null;
1795
+ for (const raw of content.split(/\r?\n/)) {
1796
+ const parsed = parseTomlLine(raw);
1797
+ if (parsed.section !== void 0) {
1798
+ section = parsed.section;
1799
+ continue;
1800
+ }
1801
+ if (parsed.key === void 0 || parsed.value === void 0) continue;
1802
+ if (section === "project" && parsed.key === "name") result.projectName = parsed.value;
1803
+ else if (section === "project.scripts") result.scriptTargets.push(parsed.value);
1804
+ }
1805
+ return result;
1806
+ }
1807
+ function parseCargoBinPaths(content) {
1808
+ const paths = [];
1809
+ let inBin = false;
1810
+ for (const raw of content.split(/\r?\n/)) {
1811
+ const line = raw.replace(/(^|\s)#.*$/, "").trim();
1812
+ if (!line) continue;
1813
+ if (line === "[[bin]]") {
1814
+ inBin = true;
1815
+ continue;
1816
+ }
1817
+ if (line.startsWith("[")) {
1818
+ inBin = false;
1819
+ continue;
1820
+ }
1821
+ if (!inBin) continue;
1822
+ const eq = line.indexOf("=");
1823
+ if (eq <= 0) continue;
1824
+ const key = line.slice(0, eq).trim();
1825
+ if (key === "path") paths.push(stripTomlString(line.slice(eq + 1).trim()));
1826
+ }
1827
+ return paths;
1828
+ }
1829
+ function stripTomlString(value) {
1830
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1831
+ return value.slice(1, -1);
1832
+ }
1833
+ return value;
1834
+ }
1835
+ async function resolveEntryPoints(rootDir, explicitEntries) {
1836
+ if (explicitEntries && explicitEntries.length > 0) {
1837
+ return Ok(explicitEntries.map((e) => resolve3(rootDir, e)));
1838
+ }
1839
+ const resolvers = [resolveTypeScript, resolvePython, resolveGo, resolveRust, resolveJava];
1840
+ const resolutions = [];
1841
+ for (const resolver of resolvers) {
1842
+ const res = await resolver(rootDir);
1843
+ resolutions.push(res);
1844
+ if (res.entries.length > 0) return Ok(res.entries);
1845
+ }
1846
+ const detectedLangs = resolutions.filter((r) => r.detected);
1847
+ const suggestions = detectedLangs.length > 0 ? detectedLangs.flatMap((r) => r.hints) : resolutions.flatMap((r) => r.hints);
1848
+ suggestions.push("Specify entryPoints in config");
1849
+ const reason = detectedLangs.length > 0 ? `Detected ${detectedLangs.map((r) => r.language).join(", ")} project but found no entry points` : "No language manifest (package.json, pyproject.toml, go.mod, Cargo.toml, pom.xml) and no conventional entry files found";
1850
+ return Err(
1851
+ createEntropyError(
1852
+ "ENTRY_POINT_NOT_FOUND",
1853
+ "Could not resolve entry points",
1854
+ { reason },
1855
+ suggestions
1856
+ )
1857
+ );
1858
+ }
1859
+ function extractCodeBlocks(content) {
1860
+ const blocks = [];
1861
+ const lines = content.split("\n");
1862
+ for (let i = 0; i < lines.length; i++) {
1863
+ const line = lines[i];
1864
+ if (line !== void 0 && line.startsWith("```")) {
1865
+ const langMatch = line.match(/```(\w*)/);
1866
+ const language = langMatch?.[1] || "text";
1867
+ let codeContent = "";
1868
+ let j = i + 1;
1869
+ let currentLine = lines[j];
1870
+ while (j < lines.length && currentLine !== void 0 && !currentLine.startsWith("```")) {
1871
+ codeContent += currentLine + "\n";
1872
+ j++;
1873
+ currentLine = lines[j];
1874
+ }
1875
+ blocks.push({
1876
+ language,
1877
+ content: codeContent.trim(),
1878
+ line: i + 1
1879
+ });
1880
+ i = j;
1881
+ }
1882
+ }
1883
+ return blocks;
1884
+ }
1885
+ function extractInlineRefs(content) {
1886
+ const refs = [];
1887
+ const lines = content.split("\n");
1888
+ for (let i = 0; i < lines.length; i++) {
1889
+ const line = lines[i];
1890
+ if (line === void 0) continue;
1891
+ const regex = /`([^`]+)`/g;
1892
+ let match;
1893
+ while ((match = regex.exec(line)) !== null) {
1894
+ const reference = match[1];
1895
+ if (reference === void 0) continue;
1896
+ if (reference.match(/^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*(\(.*\))?$/)) {
1897
+ refs.push({
1898
+ reference: reference.replace(/\(.*\)$/, ""),
1899
+ // Remove function parens
1900
+ line: i + 1,
1901
+ column: match.index
1902
+ });
1903
+ }
1904
+ }
1905
+ }
1906
+ return refs;
1907
+ }
1908
+ async function parseDocumentationFile(path) {
1909
+ const contentResult = await readFileContent(path);
1910
+ if (!contentResult.ok) {
1911
+ return Err(
1912
+ createEntropyError(
1913
+ "PARSE_ERROR",
1914
+ `Failed to read documentation file: ${path}`,
1915
+ { file: path },
1916
+ ["Check that the file exists"]
1917
+ )
1918
+ );
1919
+ }
1920
+ const content = contentResult.value;
1921
+ const type = path.endsWith(".md") ? "markdown" : "text";
1922
+ return Ok({
1923
+ path,
1924
+ type,
1925
+ content,
1926
+ codeBlocks: extractCodeBlocks(content),
1927
+ inlineRefs: extractInlineRefs(content)
1928
+ });
1929
+ }
1930
+ function makeInternalSymbol(name, type, line) {
1931
+ return { name, type, line, references: 0, calledBy: [] };
1932
+ }
1933
+ function extractFunctionSymbol(node, line) {
1934
+ if (node.id?.name) return [makeInternalSymbol(node.id.name, "function", line)];
1935
+ return [];
1936
+ }
1937
+ function extractVariableSymbols(node, line) {
1938
+ return (node.declarations || []).filter((decl) => decl.id?.name).map((decl) => makeInternalSymbol(decl.id.name, "variable", line));
1939
+ }
1940
+ function extractClassSymbol(node, line) {
1941
+ if (node.id?.name) return [makeInternalSymbol(node.id.name, "class", line)];
1942
+ return [];
1943
+ }
1944
+ function extractSymbolsFromNode(node) {
1945
+ const line = node.loc?.start?.line || 0;
1946
+ if (node.type === "FunctionDeclaration") return extractFunctionSymbol(node, line);
1947
+ if (node.type === "VariableDeclaration") return extractVariableSymbols(node, line);
1948
+ if (node.type === "ClassDeclaration") return extractClassSymbol(node, line);
1949
+ return [];
1950
+ }
1951
+ function extractInternalSymbols(ast) {
1952
+ if (ast.language !== "typescript" && ast.language !== "javascript") return [];
1953
+ const body = ast.body;
1954
+ if (!body?.body) return [];
1955
+ const nodes = body.body;
1956
+ return nodes.flatMap(extractSymbolsFromNode);
1957
+ }
1958
+ function toJSDocComment(comment) {
1959
+ if (comment.type !== "Block" || !comment.value?.startsWith("*")) return null;
1960
+ return { content: comment.value, line: comment.loc?.start?.line || 0 };
1961
+ }
1962
+ function extractJSDocComments(ast) {
1963
+ if (ast.language !== "typescript" && ast.language !== "javascript") return [];
1964
+ const body = ast.body;
1965
+ if (!body?.comments) return [];
1966
+ return body.comments.flatMap((c) => {
1967
+ const doc = toJSDocComment(c);
1968
+ return doc ? [doc] : [];
1969
+ });
1970
+ }
1971
+ function buildExportMap(files) {
1972
+ const byFile = /* @__PURE__ */ new Map();
1973
+ const byName = /* @__PURE__ */ new Map();
1974
+ for (const file of files) {
1975
+ byFile.set(file.path, file.exports);
1976
+ for (const exp of file.exports) {
1977
+ const existing = byName.get(exp.name) || [];
1978
+ existing.push({ file: file.path, export: exp });
1979
+ byName.set(exp.name, existing);
1980
+ }
1981
+ }
1982
+ return { byFile, byName };
1983
+ }
1984
+ function refsFromInlineRefs(doc) {
1985
+ return doc.inlineRefs.map((inlineRef) => ({
1986
+ docFile: doc.path,
1987
+ line: inlineRef.line,
1988
+ column: inlineRef.column,
1989
+ reference: inlineRef.reference,
1990
+ context: "inline"
1991
+ }));
1992
+ }
1993
+ function refsFromCodeBlock(docPath, block) {
1994
+ if (!CODE_BLOCK_LANGUAGES.has(block.language)) return [];
1995
+ const refs = [];
1996
+ const importRegex = /import\s+\{([^}]+)\}\s+from/g;
1997
+ let match;
1998
+ while ((match = importRegex.exec(block.content)) !== null) {
1999
+ const group = match[1];
2000
+ if (group === void 0) continue;
2001
+ for (const name of group.split(",").map((n) => n.trim())) {
2002
+ refs.push({
2003
+ docFile: docPath,
2004
+ line: block.line,
2005
+ column: 0,
2006
+ reference: name,
2007
+ context: "code-block"
2008
+ });
2009
+ }
2010
+ }
2011
+ return refs;
2012
+ }
2013
+ function refsFromCodeBlocks(doc) {
2014
+ return doc.codeBlocks.flatMap((block) => refsFromCodeBlock(doc.path, block));
2015
+ }
2016
+ function extractAllCodeReferences(docs) {
2017
+ return docs.flatMap((doc) => [...refsFromInlineRefs(doc), ...refsFromCodeBlocks(doc)]);
2018
+ }
2019
+ async function buildSnapshot(config) {
2020
+ const startTime = Date.now();
2021
+ const rootDir = resolve2(config.rootDir);
2022
+ const entryPointsResult = await resolveEntryPoints(rootDir, config.entryPoints);
2023
+ if (!entryPointsResult.ok) {
2024
+ return Err(entryPointsResult.error);
2025
+ }
2026
+ const registry = getDefaultRegistry();
2027
+ const singleParser = config.parser;
2028
+ const parserForFile = (filePath) => singleParser ?? registry.getForFile(filePath);
2029
+ const includePatterns = config.include || DEFAULT_INCLUDE_PATTERNS;
2030
+ const excludePatterns = config.exclude || [...skipDirGlobs(), "**/*.test.ts", "**/*.spec.ts"];
2031
+ let sourceFilePaths = [];
2032
+ for (const pattern of includePatterns) {
2033
+ const files2 = await findFiles(pattern, rootDir);
2034
+ sourceFilePaths.push(...files2);
2035
+ }
2036
+ sourceFilePaths = sourceFilePaths.filter((f) => {
2037
+ const rel = relativePosix(rootDir, f);
2038
+ return !excludePatterns.some((p) => minimatch2(rel, p));
2039
+ });
2040
+ const files = [];
2041
+ for (const filePath of sourceFilePaths) {
2042
+ const fileParser = parserForFile(filePath);
2043
+ if (!fileParser) continue;
2044
+ const parseResult = await fileParser.parseFile(filePath);
2045
+ if (!parseResult.ok) continue;
2046
+ const importsResult = fileParser.extractImports(parseResult.value);
2047
+ const exportsResult = fileParser.extractExports(parseResult.value);
2048
+ const internalSymbols = extractInternalSymbols(parseResult.value);
2049
+ const jsDocComments = extractJSDocComments(parseResult.value);
2050
+ files.push({
2051
+ path: filePath,
2052
+ ast: parseResult.value,
2053
+ imports: importsResult.ok ? importsResult.value : [],
2054
+ exports: exportsResult.ok ? exportsResult.value : [],
2055
+ internalSymbols,
2056
+ jsDocComments
2057
+ });
2058
+ }
2059
+ const graphResult = await buildDependencyGraph(sourceFilePaths, singleParser ?? registry);
2060
+ const dependencyGraph = graphResult.ok ? graphResult.value : { nodes: [], edges: [] };
2061
+ const docPatterns = config.docPaths || ["docs/**/*.md", "README.md", "**/README.md"];
2062
+ let docFilePaths = [];
2063
+ for (const pattern of docPatterns) {
2064
+ const docFiles = await findFiles(pattern, rootDir);
2065
+ docFilePaths.push(...docFiles);
2066
+ }
2067
+ docFilePaths = [...new Set(docFilePaths)];
2068
+ const docs = [];
2069
+ for (const docPath of docFilePaths) {
2070
+ const docResult = await parseDocumentationFile(docPath);
2071
+ if (docResult.ok) {
2072
+ docs.push(docResult.value);
2073
+ }
2074
+ }
2075
+ const exportMap = buildExportMap(files);
2076
+ const codeReferences = extractAllCodeReferences(docs);
2077
+ const buildTime = Date.now() - startTime;
2078
+ return Ok({
2079
+ files,
2080
+ dependencyGraph,
2081
+ exportMap,
2082
+ docs,
2083
+ codeReferences,
2084
+ entryPoints: entryPointsResult.value,
2085
+ rootDir,
2086
+ config,
2087
+ buildTime
2088
+ });
2089
+ }
2090
+ function initLevenshteinMatrix(aLen, bLen) {
2091
+ const matrix = [];
2092
+ for (let i = 0; i <= bLen; i++) {
2093
+ matrix[i] = [i];
2094
+ }
2095
+ const firstRow = matrix[0];
2096
+ if (firstRow) {
2097
+ for (let j = 0; j <= aLen; j++) {
2098
+ firstRow[j] = j;
2099
+ }
2100
+ }
2101
+ return matrix;
2102
+ }
2103
+ function computeLevenshteinCell(row, prevRow, j, charsMatch) {
2104
+ if (charsMatch) {
2105
+ row[j] = prevRow[j - 1] ?? 0;
2106
+ } else {
2107
+ row[j] = Math.min((prevRow[j - 1] ?? 0) + 1, (row[j - 1] ?? 0) + 1, (prevRow[j] ?? 0) + 1);
2108
+ }
2109
+ }
2110
+ function levenshteinDistance(a, b) {
2111
+ const matrix = initLevenshteinMatrix(a.length, b.length);
2112
+ for (let i = 1; i <= b.length; i++) {
2113
+ for (let j = 1; j <= a.length; j++) {
2114
+ const row = matrix[i];
2115
+ const prevRow = matrix[i - 1];
2116
+ if (!row || !prevRow) continue;
2117
+ computeLevenshteinCell(row, prevRow, j, b.charAt(i - 1) === a.charAt(j - 1));
2118
+ }
2119
+ }
2120
+ const lastRow = matrix[b.length];
2121
+ return lastRow?.[a.length] ?? 0;
2122
+ }
2123
+ function findPossibleMatches(reference, exportNames, maxDistance = 5) {
2124
+ const matches = [];
2125
+ const refLower = reference.toLowerCase();
2126
+ for (const name of exportNames) {
2127
+ const nameLower = name.toLowerCase();
2128
+ if (nameLower === refLower) {
2129
+ matches.push({ name, score: 0 });
2130
+ continue;
2131
+ }
2132
+ if (nameLower.includes(refLower) || refLower.includes(nameLower)) {
2133
+ matches.push({ name, score: 1 });
2134
+ continue;
2135
+ }
2136
+ const distance = levenshteinDistance(refLower, nameLower);
2137
+ if (distance <= maxDistance) {
2138
+ matches.push({ name, score: distance });
2139
+ }
2140
+ }
2141
+ return matches.sort((a, b) => a.score - b.score).slice(0, 3).map((m) => m.name);
2142
+ }
2143
+ function checkApiSignatureDrift(snapshot, config) {
2144
+ const drifts = [];
2145
+ const exportNames = Array.from(snapshot.exportMap.byName.keys());
2146
+ for (const ref of snapshot.codeReferences) {
2147
+ if (config.ignorePatterns.some((p) => ref.reference.match(new RegExp(p)))) {
2148
+ continue;
2149
+ }
2150
+ if (!snapshot.exportMap.byName.has(ref.reference)) {
2151
+ const possibleMatches = findPossibleMatches(ref.reference, exportNames);
2152
+ const confidence = possibleMatches.length > 0 ? "high" : "medium";
2153
+ const drift = {
2154
+ type: "api-signature",
2155
+ docFile: ref.docFile,
2156
+ line: ref.line,
2157
+ reference: ref.reference,
2158
+ context: ref.context,
2159
+ issue: possibleMatches.length > 0 ? "RENAMED" : "NOT_FOUND",
2160
+ details: possibleMatches.length > 0 ? `Symbol "${ref.reference}" not found. Similar: ${possibleMatches.join(", ")}` : `Symbol "${ref.reference}" not found in codebase`,
2161
+ suggestion: possibleMatches.length > 0 ? `Did you mean "${possibleMatches[0]}"?` : "Remove reference or add the missing export",
2162
+ confidence
2163
+ };
2164
+ if (possibleMatches.length > 0) {
2165
+ drift.possibleMatches = possibleMatches;
2166
+ }
2167
+ drifts.push(drift);
2168
+ }
2169
+ }
2170
+ return drifts;
2171
+ }
2172
+ function extractFileLinks(content) {
2173
+ const links = [];
2174
+ const lines = content.split("\n");
2175
+ for (let i = 0; i < lines.length; i++) {
2176
+ const line = lines[i];
2177
+ if (!line) continue;
2178
+ const linkRegex = /\[([^\]]*)\]\(([^)]+)\)/g;
2179
+ let match;
2180
+ while ((match = linkRegex.exec(line)) !== null) {
2181
+ const linkPath = match[2];
2182
+ if (linkPath && !linkPath.startsWith("http") && !linkPath.startsWith("#") && (linkPath.includes(".") || linkPath.startsWith(".."))) {
2183
+ links.push({ link: linkPath, line: i + 1 });
2184
+ }
2185
+ }
2186
+ }
2187
+ return links;
2188
+ }
2189
+ async function checkStructureDrift(snapshot, _config) {
2190
+ const drifts = [];
2191
+ for (const doc of snapshot.docs) {
2192
+ const fileLinks = extractFileLinks(doc.content);
2193
+ for (const { link, line } of fileLinks) {
2194
+ const resolvedPath = resolve32(dirname2(doc.path), link);
2195
+ const exists = await fileExists(resolvedPath);
2196
+ if (!exists) {
2197
+ drifts.push({
2198
+ type: "structure",
2199
+ docFile: doc.path,
2200
+ line,
2201
+ reference: link,
2202
+ context: "link",
2203
+ issue: "NOT_FOUND",
2204
+ details: `File "${link}" referenced in documentation does not exist`,
2205
+ suggestion: "Update the link or remove the reference",
2206
+ confidence: "high"
2207
+ });
2208
+ }
2209
+ }
2210
+ }
2211
+ return drifts;
2212
+ }
2213
+ function computeDriftSeverity(driftCount) {
2214
+ if (driftCount === 0) return "none";
2215
+ if (driftCount <= 3) return "low";
2216
+ if (driftCount <= 10) return "medium";
2217
+ return "high";
2218
+ }
2219
+ function buildGraphDriftReport(graphDriftData) {
2220
+ const drifts = [];
2221
+ for (const target of graphDriftData.missingTargets) {
2222
+ drifts.push({
2223
+ type: "api-signature",
2224
+ docFile: target,
2225
+ line: 0,
2226
+ reference: target,
2227
+ context: "graph-missing-target",
2228
+ issue: "NOT_FOUND",
2229
+ details: `Graph node "${target}" has no matching code target`,
2230
+ confidence: "high"
2231
+ });
2232
+ }
2233
+ for (const edge of graphDriftData.staleEdges) {
2234
+ drifts.push({
2235
+ type: "api-signature",
2236
+ docFile: edge.docNodeId,
2237
+ line: 0,
2238
+ reference: edge.codeNodeId,
2239
+ context: `graph-stale-edge:${edge.edgeType}`,
2240
+ issue: "NOT_FOUND",
2241
+ details: `Stale edge from doc "${edge.docNodeId}" to code "${edge.codeNodeId}" (${edge.edgeType})`,
2242
+ confidence: "medium"
2243
+ });
2244
+ }
2245
+ return Ok({
2246
+ drifts,
2247
+ stats: {
2248
+ docsScanned: graphDriftData.staleEdges.length,
2249
+ referencesChecked: graphDriftData.staleEdges.length + graphDriftData.missingTargets.length,
2250
+ driftsFound: drifts.length,
2251
+ byType: { api: drifts.length, example: 0, structure: 0 }
2252
+ },
2253
+ severity: computeDriftSeverity(drifts.length)
2254
+ });
2255
+ }
2256
+ async function detectDocDrift(snapshot, config, graphDriftData) {
2257
+ if (graphDriftData) {
2258
+ return buildGraphDriftReport(graphDriftData);
2259
+ }
2260
+ const fullConfig = { ...DEFAULT_DRIFT_CONFIG, ...config };
2261
+ const drifts = [];
2262
+ if (fullConfig.checkApiSignatures) {
2263
+ drifts.push(...checkApiSignatureDrift(snapshot, fullConfig));
2264
+ }
2265
+ if (fullConfig.checkStructure) {
2266
+ drifts.push(...await checkStructureDrift(snapshot, fullConfig));
2267
+ }
2268
+ const apiDrifts = drifts.filter((d) => d.type === "api-signature").length;
2269
+ const exampleDrifts = drifts.filter((d) => d.type === "example-code").length;
2270
+ const structureDrifts = drifts.filter((d) => d.type === "structure").length;
2271
+ const severity = drifts.length === 0 ? "none" : drifts.length <= 3 ? "low" : drifts.length <= 10 ? "medium" : "high";
2272
+ return Ok({
2273
+ drifts,
2274
+ stats: {
2275
+ docsScanned: snapshot.docs.length,
2276
+ referencesChecked: snapshot.codeReferences.length,
2277
+ driftsFound: drifts.length,
2278
+ byType: { api: apiDrifts, example: exampleDrifts, structure: structureDrifts }
2279
+ },
2280
+ severity
2281
+ });
2282
+ }
2283
+ function buildFileIndex(snapshot) {
2284
+ const index = /* @__PURE__ */ new Map();
2285
+ for (const file of snapshot.files) {
2286
+ index.set(file.path, file);
2287
+ }
2288
+ return index;
2289
+ }
2290
+ function resolveImportToFile(importSource, fromFile, snapshot, fileIndex) {
2291
+ if (!importSource.startsWith(".")) {
2292
+ return null;
2293
+ }
2294
+ const hasFile = fileIndex ? (p) => fileIndex.has(p) : (p) => snapshot.files.some((f) => f.path === p);
2295
+ const fromDir = dirname22(fromFile);
2296
+ const resolved = resolve4(fromDir, importSource);
2297
+ const sourceExt = extname2(resolved);
2298
+ const fallbacks = JS_EXT_FALLBACKS2[sourceExt];
2299
+ if (fallbacks) {
2300
+ const base = resolved.slice(0, -sourceExt.length);
2301
+ for (const ext of fallbacks) {
2302
+ const candidate = base + ext;
2303
+ if (hasFile(candidate)) return candidate;
2304
+ }
2305
+ for (const indexExt of [".ts", ".tsx", ".jsx"]) {
2306
+ const indexPath = resolve4(base, "index" + indexExt);
2307
+ if (hasFile(indexPath)) return indexPath;
2308
+ }
2309
+ }
2310
+ if (hasFile(resolved)) return resolved;
2311
+ if (!sourceExt) {
2312
+ for (const ext of [".ts", ".tsx"]) {
2313
+ const candidate = resolved + ext;
2314
+ if (hasFile(candidate)) return candidate;
2315
+ }
2316
+ for (const indexExt of [".ts", ".tsx"]) {
2317
+ const indexPath = resolve4(resolved, "index" + indexExt);
2318
+ if (hasFile(indexPath)) return indexPath;
2319
+ }
2320
+ }
2321
+ return null;
2322
+ }
2323
+ function enqueueResolved(sources, current, snapshot, visited, queue, fileIndex) {
2324
+ for (const item of sources) {
2325
+ if (!item.source) continue;
2326
+ const resolved = resolveImportToFile(item.source, current, snapshot, fileIndex);
2327
+ if (resolved && !visited.has(resolved)) {
2328
+ queue.push(resolved);
2329
+ }
2330
+ }
2331
+ }
2332
+ function processReachabilityNode(current, snapshot, reachability, visited, queue, fileIndex) {
2333
+ reachability.set(current, true);
2334
+ const sourceFile = fileIndex ? fileIndex.get(current) : snapshot.files.find((f) => f.path === current);
2335
+ if (!sourceFile) return;
2336
+ enqueueResolved(sourceFile.imports, current, snapshot, visited, queue, fileIndex);
2337
+ const reExports = sourceFile.exports.filter((e) => e.isReExport);
2338
+ enqueueResolved(reExports, current, snapshot, visited, queue, fileIndex);
2339
+ }
2340
+ function buildReachabilityMap(snapshot) {
2341
+ const fileIndex = buildFileIndex(snapshot);
2342
+ const reachability = /* @__PURE__ */ new Map();
2343
+ for (const file of snapshot.files) {
2344
+ reachability.set(file.path, false);
2345
+ }
2346
+ const queue = [...snapshot.entryPoints];
2347
+ const visited = /* @__PURE__ */ new Set();
2348
+ while (queue.length > 0) {
2349
+ const current = queue.shift();
2350
+ if (visited.has(current)) continue;
2351
+ visited.add(current);
2352
+ processReachabilityNode(current, snapshot, reachability, visited, queue, fileIndex);
2353
+ }
2354
+ return reachability;
2355
+ }
2356
+ function buildExportUsageMap(snapshot) {
2357
+ const fileIndex = buildFileIndex(snapshot);
2358
+ const usageMap = /* @__PURE__ */ new Map();
2359
+ for (const file of snapshot.files) {
2360
+ for (const exp of file.exports) {
2361
+ const key = `${file.path}:${exp.name}`;
2362
+ usageMap.set(key, { importers: [], isReExported: exp.isReExport });
2363
+ }
2364
+ }
2365
+ for (const file of snapshot.files) {
2366
+ for (const imp of file.imports) {
2367
+ const resolvedFile = resolveImportToFile(imp.source, file.path, snapshot, fileIndex);
2368
+ if (!resolvedFile) continue;
2369
+ const sourceFile = fileIndex.get(resolvedFile);
2370
+ if (!sourceFile) continue;
2371
+ for (const specifier of imp.specifiers) {
2372
+ const matchingExport = sourceFile.exports.find(
2373
+ (e) => e.name === specifier || specifier === "default" && e.type === "default"
2374
+ );
2375
+ if (matchingExport) {
2376
+ const key = `${resolvedFile}:${matchingExport.name}`;
2377
+ const usage = usageMap.get(key);
2378
+ if (usage) {
2379
+ usage.importers.push(file.path);
2380
+ }
2381
+ }
2382
+ }
2383
+ }
2384
+ }
2385
+ return usageMap;
2386
+ }
2387
+ function findDeadExports(snapshot, usageMap, reachability) {
2388
+ const deadExports = [];
2389
+ for (const file of snapshot.files) {
2390
+ if (snapshot.entryPoints.includes(file.path)) continue;
2391
+ for (const exp of file.exports) {
2392
+ if (exp.isReExport) continue;
2393
+ const key = `${file.path}:${exp.name}`;
2394
+ const usage = usageMap.get(key);
2395
+ if (!usage || usage.importers.length === 0) {
2396
+ deadExports.push({
2397
+ file: file.path,
2398
+ name: exp.name,
2399
+ line: exp.location.line,
2400
+ type: "variable",
2401
+ // Default type since Export doesn't track declaration kind
2402
+ isDefault: exp.type === "default",
2403
+ reason: "NO_IMPORTERS"
2404
+ });
2405
+ } else {
2406
+ const allImportersDead = usage.importers.every((importer) => !reachability.get(importer));
2407
+ if (allImportersDead) {
2408
+ deadExports.push({
2409
+ file: file.path,
2410
+ name: exp.name,
2411
+ line: exp.location.line,
2412
+ type: "variable",
2413
+ // Default type since Export doesn't track declaration kind
2414
+ isDefault: exp.type === "default",
2415
+ reason: "IMPORTERS_ALSO_DEAD"
2416
+ });
2417
+ }
2418
+ }
2419
+ }
2420
+ }
2421
+ return deadExports;
2422
+ }
2423
+ function maxLineOfValue(value) {
2424
+ if (Array.isArray(value)) {
2425
+ return value.reduce((m, item) => Math.max(m, findMaxLineInNode(item)), 0);
2426
+ }
2427
+ if (value && typeof value === "object") {
2428
+ return findMaxLineInNode(value);
2429
+ }
2430
+ return 0;
2431
+ }
2432
+ function maxLineOfNodeKeys(node) {
2433
+ let max = 0;
2434
+ for (const key of Object.keys(node)) {
2435
+ max = Math.max(max, maxLineOfValue(node[key]));
2436
+ }
2437
+ return max;
2438
+ }
2439
+ function findMaxLineInNode(node) {
2440
+ if (!node || typeof node !== "object") return 0;
2441
+ const n = node;
2442
+ const locLine = n.loc?.end?.line ?? 0;
2443
+ return Math.max(locLine, maxLineOfNodeKeys(node));
2444
+ }
2445
+ function countLinesFromAST(ast) {
2446
+ if (!ast.body || !Array.isArray(ast.body)) return 1;
2447
+ const maxLine = findMaxLineInNode(ast);
2448
+ if (maxLine > 0) return maxLine;
2449
+ return Math.max(ast.body.length * 3, 1);
2450
+ }
2451
+ function findDeadFiles(snapshot, reachability) {
2452
+ const deadFiles = [];
2453
+ for (const file of snapshot.files) {
2454
+ const isReachable = reachability.get(file.path) ?? false;
2455
+ if (!isReachable) {
2456
+ deadFiles.push({
2457
+ path: file.path,
2458
+ reason: "NO_IMPORTERS",
2459
+ exportCount: file.exports.filter((e) => !e.isReExport).length,
2460
+ lineCount: countLinesFromAST(file.ast)
2461
+ });
2462
+ }
2463
+ }
2464
+ return deadFiles;
2465
+ }
2466
+ function isIdentifierUsedInAST(ast, identifier, skipImportDeclaration = true) {
2467
+ const astString = JSON.stringify(
2468
+ ast,
2469
+ (_key, value) => typeof value === "bigint" ? value.toString() : value
2470
+ );
2471
+ const identifierPattern = new RegExp(`"name"\\s*:\\s*"${identifier}"`, "g");
2472
+ const matches = astString.match(identifierPattern);
2473
+ if (!matches) return false;
2474
+ if (skipImportDeclaration) {
2475
+ return matches.length > 2;
2476
+ }
2477
+ return matches.length > 0;
2478
+ }
2479
+ function findUnusedImports(snapshot) {
2480
+ const unusedImports = [];
2481
+ for (const file of snapshot.files) {
2482
+ for (const imp of file.imports) {
2483
+ const unusedSpecifiers = [];
2484
+ for (const specifier of imp.specifiers) {
2485
+ if (!isIdentifierUsedInAST(file.ast, specifier, true)) {
2486
+ unusedSpecifiers.push(specifier);
2487
+ }
2488
+ }
2489
+ if (unusedSpecifiers.length > 0) {
2490
+ unusedImports.push({
2491
+ file: file.path,
2492
+ line: imp.location.line,
2493
+ source: imp.source,
2494
+ specifiers: unusedSpecifiers,
2495
+ isFullyUnused: unusedSpecifiers.length === imp.specifiers.length
2496
+ });
2497
+ }
2498
+ }
2499
+ }
2500
+ return unusedImports;
2501
+ }
2502
+ function findDeadInternals(snapshot, _reachability) {
2503
+ const deadInternals = [];
2504
+ for (const file of snapshot.files) {
2505
+ for (const symbol of file.internalSymbols) {
2506
+ if (symbol.type === "type") continue;
2507
+ if (symbol.references === 0 && symbol.calledBy.length === 0) {
2508
+ deadInternals.push({
2509
+ file: file.path,
2510
+ name: symbol.name,
2511
+ line: symbol.line,
2512
+ type: symbol.type,
2513
+ reason: "NEVER_CALLED"
2514
+ });
2515
+ }
2516
+ }
2517
+ }
2518
+ return deadInternals;
2519
+ }
2520
+ function classifyUnreachableNode(node, deadFiles, deadExports) {
2521
+ if (FILE_TYPES.has(node.type)) {
2522
+ deadFiles.push({
2523
+ path: node.path || node.id,
2524
+ reason: "NO_IMPORTERS",
2525
+ exportCount: 0,
2526
+ lineCount: 0
2527
+ });
2528
+ } else if (EXPORT_TYPES.has(node.type)) {
2529
+ const exportType = node.type === "method" ? "function" : node.type;
2530
+ deadExports.push({
2531
+ file: node.path || node.id,
2532
+ name: node.name,
2533
+ line: 0,
2534
+ type: exportType,
2535
+ isDefault: false,
2536
+ reason: "NO_IMPORTERS"
2537
+ });
2538
+ }
2539
+ }
2540
+ function computeGraphReportStats(data, deadFiles, deadExports) {
2541
+ const reachableCount = data.reachableNodeIds instanceof Set ? data.reachableNodeIds.size : data.reachableNodeIds.length;
2542
+ const fileNodes = data.unreachableNodes.filter((n) => FILE_TYPES.has(n.type));
2543
+ const exportNodes = data.unreachableNodes.filter((n) => EXPORT_TYPES.has(n.type));
2544
+ const totalFiles = reachableCount + fileNodes.length;
2545
+ const totalExports = exportNodes.length + (reachableCount > 0 ? reachableCount : 0);
2546
+ return {
2547
+ filesAnalyzed: totalFiles,
2548
+ entryPointsUsed: [],
2549
+ totalExports,
2550
+ deadExportCount: deadExports.length,
2551
+ totalFiles,
2552
+ deadFileCount: deadFiles.length,
2553
+ estimatedDeadLines: 0
2554
+ };
2555
+ }
2556
+ function buildReportFromGraph(data) {
2557
+ const deadFiles = [];
2558
+ const deadExports = [];
2559
+ for (const node of data.unreachableNodes) {
2560
+ classifyUnreachableNode(node, deadFiles, deadExports);
2561
+ }
2562
+ return {
2563
+ deadExports,
2564
+ deadFiles,
2565
+ deadInternals: [],
2566
+ unusedImports: [],
2567
+ stats: computeGraphReportStats(data, deadFiles, deadExports)
2568
+ };
2569
+ }
2570
+ function buildReportFromSnapshot(snapshot) {
2571
+ const reachability = buildReachabilityMap(snapshot);
2572
+ const usageMap = buildExportUsageMap(snapshot);
2573
+ const deadExports = findDeadExports(snapshot, usageMap, reachability);
2574
+ const deadFiles = findDeadFiles(snapshot, reachability);
2575
+ const unusedImports = findUnusedImports(snapshot);
2576
+ const deadInternals = findDeadInternals(snapshot, reachability);
2577
+ const totalExports = snapshot.files.reduce(
2578
+ (acc, file) => acc + file.exports.filter((e) => !e.isReExport).length,
2579
+ 0
2580
+ );
2581
+ const estimatedDeadLines = deadFiles.reduce((acc, file) => acc + file.lineCount, 0);
2582
+ return {
2583
+ deadExports,
2584
+ deadFiles,
2585
+ deadInternals,
2586
+ unusedImports,
2587
+ stats: {
2588
+ filesAnalyzed: snapshot.files.length,
2589
+ entryPointsUsed: snapshot.entryPoints,
2590
+ totalExports,
2591
+ deadExportCount: deadExports.length,
2592
+ totalFiles: snapshot.files.length,
2593
+ deadFileCount: deadFiles.length,
2594
+ estimatedDeadLines
2595
+ }
2596
+ };
2597
+ }
2598
+ function filterProtectedFindings(report, regions) {
2599
+ const deadExports = report.deadExports.filter(
2600
+ (e) => !regions.isProtected(e.file, e.line, "entropy")
2601
+ );
2602
+ const deadFiles = report.deadFiles.filter((f) => regions.getRegions(f.path).length === 0);
2603
+ const unusedImports = report.unusedImports.filter(
2604
+ (i) => !regions.isProtected(i.file, i.line, "entropy")
2605
+ );
2606
+ const deadInternals = report.deadInternals.filter(
2607
+ (i) => !regions.isProtected(i.file, i.line, "entropy")
2608
+ );
2609
+ const estimatedDeadLines = deadFiles.reduce((acc, f) => acc + f.lineCount, 0);
2610
+ return {
2611
+ deadExports,
2612
+ deadFiles,
2613
+ unusedImports,
2614
+ deadInternals,
2615
+ stats: {
2616
+ ...report.stats,
2617
+ deadExportCount: deadExports.length,
2618
+ deadFileCount: deadFiles.length,
2619
+ estimatedDeadLines
2620
+ }
2621
+ };
2622
+ }
2623
+ async function detectDeadCode(snapshot, graphDeadCodeData, protectedRegions) {
2624
+ let report = graphDeadCodeData ? buildReportFromGraph(graphDeadCodeData) : buildReportFromSnapshot(snapshot);
2625
+ if (protectedRegions) {
2626
+ report = filterProtectedFindings(report, protectedRegions);
2627
+ }
2628
+ return Ok(report);
2629
+ }
2630
+ function fileMatchesPattern(filePath, pattern, rootDir) {
2631
+ const relativePath = relativePosix(rootDir, filePath);
2632
+ return minimatch22(relativePath, pattern);
2633
+ }
2634
+ function checkMustExport(rule, file, message) {
2635
+ if (rule.type !== "must-export") return [];
2636
+ const matches = [];
2637
+ for (const name of rule.names) {
2638
+ if (!file.exports.some((e) => e.name === name)) {
2639
+ matches.push({
2640
+ line: 1,
2641
+ message: message || `Missing required export: "${name}"`,
2642
+ suggestion: `Add export for "${name}"`
2643
+ });
2644
+ }
2645
+ }
2646
+ return matches;
2647
+ }
2648
+ function checkMustExportDefault(_rule, file, message) {
2649
+ if (!file.exports.some((e) => e.type === "default")) {
2650
+ return [
2651
+ {
2652
+ line: 1,
2653
+ message: message || "File must have a default export",
2654
+ suggestion: "Add a default export"
2655
+ }
2656
+ ];
2657
+ }
2658
+ return [];
2659
+ }
2660
+ function checkNoExport(rule, file, message) {
2661
+ if (rule.type !== "no-export") return [];
2662
+ const matches = [];
2663
+ for (const name of rule.names) {
2664
+ const exp = file.exports.find((e) => e.name === name);
2665
+ if (exp) {
2666
+ matches.push({
2667
+ line: exp.location.line,
2668
+ message: message || `Forbidden export: "${name}"`,
2669
+ suggestion: `Remove export "${name}"`
2670
+ });
2671
+ }
2672
+ }
2673
+ return matches;
2674
+ }
2675
+ function checkMustImport(rule, file, message) {
2676
+ if (rule.type !== "must-import") return [];
2677
+ const hasImport = file.imports.some(
2678
+ (i) => i.source === rule.from || i.source.endsWith(rule.from)
2679
+ );
2680
+ if (!hasImport) {
2681
+ return [
2682
+ {
2683
+ line: 1,
2684
+ message: message || `Missing required import from "${rule.from}"`,
2685
+ suggestion: `Add import from "${rule.from}"`
2686
+ }
2687
+ ];
2688
+ }
2689
+ return [];
2690
+ }
2691
+ function checkNoImport(rule, file, message) {
2692
+ if (rule.type !== "no-import") return [];
2693
+ const forbiddenImport = file.imports.find(
2694
+ (i) => i.source === rule.from || i.source.endsWith(rule.from)
2695
+ );
2696
+ if (forbiddenImport) {
2697
+ return [
2698
+ {
2699
+ line: forbiddenImport.location.line,
2700
+ message: message || `Forbidden import from "${rule.from}"`,
2701
+ suggestion: `Remove import from "${rule.from}"`
2702
+ }
2703
+ ];
2704
+ }
2705
+ return [];
2706
+ }
2707
+ function checkNaming(rule, file, message) {
2708
+ if (rule.type !== "naming") return [];
2709
+ const regex = new RegExp(rule.match);
2710
+ const matches = [];
2711
+ for (const exp of file.exports) {
2712
+ if (!regex.test(exp.name)) {
2713
+ const expected = CONVENTION_DESCRIPTIONS[rule.convention] ?? rule.convention;
2714
+ matches.push({
2715
+ line: exp.location.line,
2716
+ message: message || `"${exp.name}" does not follow ${rule.convention} convention`,
2717
+ suggestion: `Rename to follow ${expected}`
2718
+ });
2719
+ }
2720
+ }
2721
+ return matches;
2722
+ }
2723
+ function checkMaxExports(rule, file, message) {
2724
+ if (rule.type !== "max-exports") return [];
2725
+ if (file.exports.length > rule.count) {
2726
+ return [
2727
+ {
2728
+ line: 1,
2729
+ message: message || `File has ${file.exports.length} exports, max is ${rule.count}`,
2730
+ suggestion: `Split into multiple files or reduce exports to ${rule.count}`
2731
+ }
2732
+ ];
2733
+ }
2734
+ return [];
2735
+ }
2736
+ function checkMaxLines(_rule, _file, _message) {
2737
+ return [];
2738
+ }
2739
+ function checkRequireJsdoc(_rule, file, message) {
2740
+ if (file.jsDocComments.length === 0 && file.exports.length > 0) {
2741
+ return [
2742
+ {
2743
+ line: 1,
2744
+ message: message || "Exported symbols require JSDoc documentation",
2745
+ suggestion: "Add JSDoc comments to exports"
2746
+ }
2747
+ ];
2748
+ }
2749
+ return [];
2750
+ }
2751
+ function checkConfigPattern(pattern, file, rootDir) {
2752
+ const fileMatches = pattern.files.some((glob2) => fileMatchesPattern(file.path, glob2, rootDir));
2753
+ if (!fileMatches) return [];
2754
+ const checker = RULE_CHECKERS[pattern.rule.type];
2755
+ if (!checker) return [];
2756
+ return checker(pattern.rule, file, pattern.message);
2757
+ }
2758
+ async function detectPatternViolations(snapshot, config) {
2759
+ const violations = [];
2760
+ const patterns = config?.patterns || [];
2761
+ for (const file of snapshot.files) {
2762
+ for (const pattern of patterns) {
2763
+ const matches = checkConfigPattern(pattern, file, snapshot.rootDir);
2764
+ for (const match of matches) {
2765
+ violations.push({
2766
+ pattern: pattern.name,
2767
+ file: file.path,
2768
+ line: match.line,
2769
+ message: match.message,
2770
+ suggestion: match.suggestion || "Review and fix this pattern violation",
2771
+ severity: pattern.severity
2772
+ });
2773
+ }
2774
+ }
2775
+ }
2776
+ if (config?.customPatterns) {
2777
+ for (const file of snapshot.files) {
2778
+ for (const custom of config.customPatterns) {
2779
+ const matches = custom.check(file, snapshot);
2780
+ for (const match of matches) {
2781
+ violations.push({
2782
+ pattern: custom.name,
2783
+ file: file.path,
2784
+ line: match.line,
2785
+ message: match.message,
2786
+ suggestion: match.suggestion || "Review and fix this pattern violation",
2787
+ severity: custom.severity
2788
+ });
2789
+ }
2790
+ }
2791
+ }
2792
+ }
2793
+ const errorCount = violations.filter((v) => v.severity === "error").length;
2794
+ const warningCount = violations.filter((v) => v.severity === "warning").length;
2795
+ const customCount = config?.customPatterns?.length ?? 0;
2796
+ const allPatternsCount = patterns.length + customCount;
2797
+ const totalChecks = snapshot.files.length * allPatternsCount;
2798
+ const passRate = totalChecks > 0 ? Math.max(0, (totalChecks - violations.length) / totalChecks) : 1;
2799
+ return Ok({
2800
+ violations,
2801
+ stats: {
2802
+ filesChecked: snapshot.files.length,
2803
+ patternsApplied: allPatternsCount,
2804
+ violationCount: violations.length,
2805
+ errorCount,
2806
+ warningCount
2807
+ },
2808
+ passRate
2809
+ });
2810
+ }
2811
+ function parseSize(size) {
2812
+ const match = size.trim().match(/^(\d+(?:\.\d+)?)\s*(KB|MB|GB|B)?$/i);
2813
+ if (!match) return 0;
2814
+ const value = parseFloat(match[1]);
2815
+ const unit = (match[2] || "B").toUpperCase();
2816
+ switch (unit) {
2817
+ case "KB":
2818
+ return Math.round(value * 1024);
2819
+ case "MB":
2820
+ return Math.round(value * 1024 * 1024);
2821
+ case "GB":
2822
+ return Math.round(value * 1024 * 1024 * 1024);
2823
+ default:
2824
+ return Math.round(value);
2825
+ }
2826
+ }
2827
+ function dirSize(dirPath) {
2828
+ let total = 0;
2829
+ let entries;
2830
+ try {
2831
+ entries = readdirSync(dirPath);
2832
+ } catch {
2833
+ return 0;
2834
+ }
2835
+ for (const entry of entries) {
2836
+ if (DEFAULT_SKIP_DIRS.has(entry)) continue;
2837
+ const fullPath = join2(dirPath, entry);
2838
+ try {
2839
+ const stat = statSync(fullPath);
2840
+ if (stat.isDirectory()) {
2841
+ total += dirSize(fullPath);
2842
+ } else if (stat.isFile()) {
2843
+ total += stat.size;
2844
+ }
2845
+ } catch {
2846
+ continue;
2847
+ }
2848
+ }
2849
+ return total;
2850
+ }
2851
+ async function detectSizeBudgetViolations(rootDir, config) {
2852
+ const budgets = config?.budgets ?? {};
2853
+ const violations = [];
2854
+ let packagesChecked = 0;
2855
+ for (const [pkgPath, budget] of Object.entries(budgets)) {
2856
+ packagesChecked++;
2857
+ const distPath = join2(rootDir, pkgPath, "dist");
2858
+ const currentSize = dirSize(distPath);
2859
+ if (budget.warn) {
2860
+ const budgetBytes = parseSize(budget.warn);
2861
+ if (budgetBytes > 0 && currentSize > budgetBytes) {
2862
+ violations.push({
2863
+ package: pkgPath,
2864
+ currentSize,
2865
+ budgetSize: budgetBytes,
2866
+ unit: "bytes",
2867
+ tier: 2,
2868
+ severity: "warning"
2869
+ });
2870
+ }
2871
+ }
2872
+ }
2873
+ const warningCount = violations.filter((v) => v.severity === "warning").length;
2874
+ const infoCount = violations.filter((v) => v.severity === "info").length;
2875
+ return Ok({
2876
+ violations,
2877
+ stats: {
2878
+ packagesChecked,
2879
+ violationCount: violations.length,
2880
+ warningCount,
2881
+ infoCount
2882
+ }
2883
+ });
2884
+ }
2885
+ function deadFileSuggestion(file) {
2886
+ return {
2887
+ type: "delete",
2888
+ priority: "high",
2889
+ source: "dead-code",
2890
+ relatedIssues: [`dead-file:${file.path}`],
2891
+ title: `Remove dead file: ${file.path.split("/").pop()}`,
2892
+ description: `This file is not imported by any other file and can be safely removed.`,
2893
+ files: [file.path],
2894
+ steps: [`Delete ${file.path}`, "Run tests to verify no regressions"],
2895
+ whyManual: "File deletion requires verification that no dynamic imports exist"
2896
+ };
2897
+ }
2898
+ function deadExportSuggestion(exp) {
2899
+ return {
2900
+ type: "refactor",
2901
+ priority: "medium",
2902
+ source: "dead-code",
2903
+ relatedIssues: [`dead-export:${exp.file}:${exp.name}`],
2904
+ title: `Remove unused export: ${exp.name}`,
2905
+ description: `The export "${exp.name}" is not used anywhere. Consider removing it.`,
2906
+ files: [exp.file],
2907
+ steps: [`Remove export "${exp.name}" from ${exp.file}`, "Run tests to verify no regressions"],
2908
+ whyManual: "Export removal may affect external consumers not in scope"
2909
+ };
2910
+ }
2911
+ function unusedImportSuggestion(imp) {
2912
+ const plural = imp.specifiers.length > 1;
2913
+ return {
2914
+ type: "delete",
2915
+ priority: "medium",
2916
+ source: "dead-code",
2917
+ relatedIssues: [`unused-import:${imp.file}:${imp.specifiers.join(",")}`],
2918
+ title: `Remove unused import${plural ? "s" : ""}: ${imp.specifiers.join(", ")}`,
2919
+ description: `The import${plural ? "s" : ""} from "${imp.source}" ${plural ? "are" : "is"} not used.`,
2920
+ files: [imp.file],
2921
+ steps: imp.isFullyUnused ? [`Remove entire import line from ${imp.file}`] : [`Remove unused specifiers (${imp.specifiers.join(", ")}) from import statement`],
2922
+ whyManual: "Import removal can be auto-fixed"
2923
+ };
2924
+ }
2925
+ function generateDeadCodeSuggestions(report) {
2926
+ return [
2927
+ ...report.deadFiles.map(deadFileSuggestion),
2928
+ ...report.deadExports.map(deadExportSuggestion),
2929
+ ...report.unusedImports.map(unusedImportSuggestion)
2930
+ ];
2931
+ }
2932
+ function generateDriftSuggestions(report) {
2933
+ const suggestions = [];
2934
+ for (const drift of report.drifts) {
2935
+ const priority = drift.confidence === "high" ? "high" : "medium";
2936
+ suggestions.push({
2937
+ type: "update-docs",
2938
+ priority,
2939
+ source: "drift",
2940
+ relatedIssues: [`drift:${drift.docFile}:${drift.reference}`],
2941
+ title: `Fix documentation drift: ${drift.reference}`,
2942
+ description: drift.details,
2943
+ files: [drift.docFile],
2944
+ steps: [
2945
+ drift.suggestion || "Review and update documentation",
2946
+ "Review documentation for accuracy"
2947
+ ],
2948
+ whyManual: "Documentation updates require human judgment for accuracy"
2949
+ });
2950
+ }
2951
+ return suggestions;
2952
+ }
2953
+ function generatePatternSuggestions(report) {
2954
+ const suggestions = [];
2955
+ for (const violation of report.violations) {
2956
+ suggestions.push({
2957
+ type: "refactor",
2958
+ priority: violation.severity === "error" ? "high" : "low",
2959
+ source: "pattern",
2960
+ relatedIssues: [`pattern:${violation.pattern}:${violation.file}`],
2961
+ title: `Fix pattern violation: ${violation.pattern}`,
2962
+ description: violation.message,
2963
+ files: [violation.file],
2964
+ steps: [violation.suggestion || "Follow pattern guidelines"],
2965
+ whyManual: "Pattern violations often require architectural decisions"
2966
+ });
2967
+ }
2968
+ return suggestions;
2969
+ }
2970
+ function generateSuggestions(deadCode, drift, patterns) {
2971
+ const suggestions = [];
2972
+ if (deadCode) {
2973
+ suggestions.push(...generateDeadCodeSuggestions(deadCode));
2974
+ }
2975
+ if (drift) {
2976
+ suggestions.push(...generateDriftSuggestions(drift));
2977
+ }
2978
+ if (patterns) {
2979
+ suggestions.push(...generatePatternSuggestions(patterns));
2980
+ }
2981
+ const priorityOrder = { high: 0, medium: 1, low: 2 };
2982
+ suggestions.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
2983
+ const byPriority = {
2984
+ high: suggestions.filter((s) => s.priority === "high"),
2985
+ medium: suggestions.filter((s) => s.priority === "medium"),
2986
+ low: suggestions.filter((s) => s.priority === "low")
2987
+ };
2988
+ let estimatedEffort;
2989
+ if (suggestions.length === 0) {
2990
+ estimatedEffort = "trivial";
2991
+ } else if (suggestions.length <= 5) {
2992
+ estimatedEffort = "small";
2993
+ } else if (suggestions.length <= 20) {
2994
+ estimatedEffort = "medium";
2995
+ } else {
2996
+ estimatedEffort = "large";
2997
+ }
2998
+ return {
2999
+ suggestions,
3000
+ byPriority,
3001
+ estimatedEffort
3002
+ };
3003
+ }
3004
+ var TS_HINTS, TS_CONVENTIONS, PYTHON_HINTS, PYTHON_CONVENTIONS, DEFAULT_INCLUDE_PATTERNS, CODE_BLOCK_LANGUAGES, DEFAULT_DRIFT_CONFIG, JS_EXT_FALLBACKS2, FILE_TYPES, EXPORT_TYPES, CONVENTION_DESCRIPTIONS, RULE_CHECKERS, EntropyAnalyzer;
3005
+ var init_chunk_BTUDWWB4 = __esm({
3006
+ "../core/dist/chunk-BTUDWWB4.mjs"() {
3007
+ "use strict";
3008
+ init_chunk_MUWJHO2S();
3009
+ init_dist2();
3010
+ init_dist2();
3011
+ TS_HINTS = ['Add "exports" or "main" to package.json', "Create src/index.ts"];
3012
+ TS_CONVENTIONS = ["src/index.ts", "src/main.ts", "src/index.tsx", "index.ts", "main.ts"];
3013
+ PYTHON_HINTS = [
3014
+ "Add an entry to [project.scripts] in pyproject.toml",
3015
+ "Create main.py or <package>/__main__.py"
3016
+ ];
3017
+ PYTHON_CONVENTIONS = [
3018
+ "__main__.py",
3019
+ "main.py",
3020
+ "app.py",
3021
+ "src/__main__.py",
3022
+ "src/main.py",
3023
+ "src/app.py"
3024
+ ];
3025
+ DEFAULT_INCLUDE_PATTERNS = [
3026
+ "**/*.ts",
3027
+ "**/*.tsx",
3028
+ "**/*.js",
3029
+ "**/*.jsx",
3030
+ "**/*.mjs",
3031
+ "**/*.cjs",
3032
+ "**/*.py",
3033
+ "**/*.go",
3034
+ "**/*.rs",
3035
+ "**/*.java"
3036
+ ];
3037
+ CODE_BLOCK_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "ts", "javascript", "js"]);
3038
+ DEFAULT_DRIFT_CONFIG = {
3039
+ docPaths: [],
3040
+ checkApiSignatures: true,
3041
+ checkExamples: true,
3042
+ checkStructure: true,
3043
+ ignorePatterns: []
3044
+ };
3045
+ JS_EXT_FALLBACKS2 = {
3046
+ ".js": [".ts", ".tsx", ".jsx"],
3047
+ ".jsx": [".tsx"],
3048
+ ".mjs": [".mts"],
3049
+ ".cjs": [".cts"]
3050
+ };
3051
+ FILE_TYPES = /* @__PURE__ */ new Set(["file", "module"]);
3052
+ EXPORT_TYPES = /* @__PURE__ */ new Set(["function", "class", "method", "interface", "variable"]);
3053
+ CONVENTION_DESCRIPTIONS = {
3054
+ camelCase: "camelCase (e.g., myFunction)",
3055
+ PascalCase: "PascalCase (e.g., MyClass)",
3056
+ UPPER_SNAKE: "UPPER_SNAKE_CASE (e.g., MY_CONSTANT)",
3057
+ "kebab-case": "kebab-case (e.g., my-component)"
3058
+ };
3059
+ RULE_CHECKERS = {
3060
+ "must-export": checkMustExport,
3061
+ "must-export-default": checkMustExportDefault,
3062
+ "no-export": checkNoExport,
3063
+ "must-import": checkMustImport,
3064
+ "no-import": checkNoImport,
3065
+ naming: checkNaming,
3066
+ "max-exports": checkMaxExports,
3067
+ "max-lines": checkMaxLines,
3068
+ "require-jsdoc": checkRequireJsdoc
3069
+ };
3070
+ EntropyAnalyzer = class {
3071
+ config;
3072
+ snapshot;
3073
+ report;
3074
+ constructor(config) {
3075
+ this.config = { ...config };
3076
+ }
3077
+ /**
3078
+ * Run full entropy analysis.
3079
+ * When graphOptions is provided, passes graph data to drift and dead code detectors
3080
+ * for graph-enhanced analysis instead of snapshot-based analysis.
3081
+ */
3082
+ async analyze(graphOptions) {
3083
+ const startTime = Date.now();
3084
+ const needsSnapshot = !graphOptions || !graphOptions.graphDriftData || !graphOptions.graphDeadCodeData;
3085
+ if (needsSnapshot) {
3086
+ const snapshotResult = await buildSnapshot(this.config);
3087
+ if (!snapshotResult.ok) {
3088
+ return Err(snapshotResult.error);
3089
+ }
3090
+ this.snapshot = snapshotResult.value;
3091
+ } else {
3092
+ this.snapshot = {
3093
+ files: [],
3094
+ dependencyGraph: { nodes: [], edges: [] },
3095
+ exportMap: { byFile: /* @__PURE__ */ new Map(), byName: /* @__PURE__ */ new Map() },
3096
+ docs: [],
3097
+ codeReferences: [],
3098
+ entryPoints: [],
3099
+ rootDir: this.config.rootDir,
3100
+ config: this.config,
3101
+ buildTime: 0
3102
+ };
3103
+ }
3104
+ let driftReport;
3105
+ let deadCodeReport;
3106
+ let patternReport;
3107
+ const analysisErrors = [];
3108
+ if (this.config.analyze.drift) {
3109
+ const driftConfig = typeof this.config.analyze.drift === "object" ? this.config.analyze.drift : {};
3110
+ const result = await detectDocDrift(this.snapshot, driftConfig, graphOptions?.graphDriftData);
3111
+ if (result.ok) {
3112
+ driftReport = result.value;
3113
+ } else {
3114
+ analysisErrors.push({ analyzer: "drift", error: result.error });
3115
+ }
3116
+ }
3117
+ if (this.config.analyze.deadCode) {
3118
+ const result = await detectDeadCode(
3119
+ this.snapshot,
3120
+ graphOptions?.graphDeadCodeData,
3121
+ this.config.protectedRegions
3122
+ );
3123
+ if (result.ok) {
3124
+ deadCodeReport = result.value;
3125
+ } else {
3126
+ analysisErrors.push({ analyzer: "deadCode", error: result.error });
3127
+ }
3128
+ }
3129
+ if (this.config.analyze.patterns) {
3130
+ const patternConfig = typeof this.config.analyze.patterns === "object" ? this.config.analyze.patterns : { patterns: [] };
3131
+ const result = await detectPatternViolations(this.snapshot, patternConfig);
3132
+ if (result.ok) {
3133
+ patternReport = result.value;
3134
+ } else {
3135
+ analysisErrors.push({ analyzer: "patterns", error: result.error });
3136
+ }
3137
+ }
3138
+ let complexityReport;
3139
+ if (this.config.analyze.complexity) {
3140
+ const complexityConfig = typeof this.config.analyze.complexity === "object" ? this.config.analyze.complexity : {};
3141
+ const result = await detectComplexityViolations(
3142
+ this.snapshot,
3143
+ complexityConfig,
3144
+ graphOptions?.graphComplexityData
3145
+ );
3146
+ if (result.ok) {
3147
+ complexityReport = result.value;
3148
+ } else {
3149
+ analysisErrors.push({ analyzer: "complexity", error: result.error });
3150
+ }
3151
+ }
3152
+ let couplingReport;
3153
+ if (this.config.analyze.coupling) {
3154
+ const couplingConfig = typeof this.config.analyze.coupling === "object" ? this.config.analyze.coupling : {};
3155
+ const result = await detectCouplingViolations(
3156
+ this.snapshot,
3157
+ couplingConfig,
3158
+ graphOptions?.graphCouplingData
3159
+ );
3160
+ if (result.ok) {
3161
+ couplingReport = result.value;
3162
+ } else {
3163
+ analysisErrors.push({ analyzer: "coupling", error: result.error });
3164
+ }
3165
+ }
3166
+ let sizeBudgetReport;
3167
+ if (this.config.analyze.sizeBudget) {
3168
+ const sizeBudgetConfig = typeof this.config.analyze.sizeBudget === "object" ? this.config.analyze.sizeBudget : {};
3169
+ const result = await detectSizeBudgetViolations(this.config.rootDir, sizeBudgetConfig);
3170
+ if (result.ok) {
3171
+ sizeBudgetReport = result.value;
3172
+ } else {
3173
+ analysisErrors.push({ analyzer: "sizeBudget", error: result.error });
3174
+ }
3175
+ }
3176
+ const driftIssues = driftReport?.drifts.length || 0;
3177
+ const deadCodeIssues = (deadCodeReport?.deadExports.length || 0) + (deadCodeReport?.deadFiles.length || 0) + (deadCodeReport?.unusedImports.length || 0);
3178
+ const patternIssues = patternReport?.violations.length || 0;
3179
+ const patternErrors = patternReport?.stats.errorCount || 0;
3180
+ const patternWarnings = patternReport?.stats.warningCount || 0;
3181
+ const complexityIssues = complexityReport?.violations.length || 0;
3182
+ const couplingIssues = couplingReport?.violations.length || 0;
3183
+ const sizeBudgetIssues = sizeBudgetReport?.violations.length || 0;
3184
+ const complexityErrors = complexityReport?.stats.errorCount || 0;
3185
+ const complexityWarnings = complexityReport?.stats.warningCount || 0;
3186
+ const couplingWarnings = couplingReport?.stats.warningCount || 0;
3187
+ const sizeBudgetWarnings = sizeBudgetReport?.stats.warningCount || 0;
3188
+ const totalIssues = driftIssues + deadCodeIssues + patternIssues + complexityIssues + couplingIssues + sizeBudgetIssues;
3189
+ const fixableCount = (deadCodeReport?.deadFiles.length || 0) + (deadCodeReport?.unusedImports.length || 0);
3190
+ const suggestions = generateSuggestions(deadCodeReport, driftReport, patternReport);
3191
+ const duration = Date.now() - startTime;
3192
+ const report = {
3193
+ snapshot: this.snapshot,
3194
+ analysisErrors,
3195
+ summary: {
3196
+ totalIssues,
3197
+ errors: patternErrors + complexityErrors,
3198
+ warnings: patternWarnings + driftIssues + complexityWarnings + couplingWarnings + sizeBudgetWarnings,
3199
+ fixableCount,
3200
+ suggestionCount: suggestions.suggestions.length
3201
+ },
3202
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3203
+ duration
3204
+ };
3205
+ if (driftReport) {
3206
+ report.drift = driftReport;
3207
+ }
3208
+ if (deadCodeReport) {
3209
+ report.deadCode = deadCodeReport;
3210
+ }
3211
+ if (patternReport) {
3212
+ report.patterns = patternReport;
3213
+ }
3214
+ if (complexityReport) {
3215
+ report.complexity = complexityReport;
3216
+ }
3217
+ if (couplingReport) {
3218
+ report.coupling = couplingReport;
3219
+ }
3220
+ if (sizeBudgetReport) {
3221
+ report.sizeBudget = sizeBudgetReport;
3222
+ }
3223
+ this.report = report;
3224
+ return Ok(report);
3225
+ }
3226
+ /**
3227
+ * Get the built snapshot (must call analyze first)
3228
+ */
3229
+ getSnapshot() {
3230
+ return this.snapshot;
3231
+ }
3232
+ /**
3233
+ * Get the last report (must call analyze first)
3234
+ */
3235
+ getReport() {
3236
+ return this.report;
3237
+ }
3238
+ /**
3239
+ * Generate suggestions from the last analysis
3240
+ */
3241
+ getSuggestions() {
3242
+ if (!this.report) {
3243
+ return {
3244
+ suggestions: [],
3245
+ byPriority: { high: [], medium: [], low: [] },
3246
+ estimatedEffort: "trivial"
3247
+ };
3248
+ }
3249
+ return generateSuggestions(this.report.deadCode, this.report.drift, this.report.patterns);
3250
+ }
3251
+ /**
3252
+ * Build snapshot without running analysis
3253
+ */
3254
+ async buildSnapshot() {
3255
+ const result = await buildSnapshot(this.config);
3256
+ if (result.ok) {
3257
+ this.snapshot = result.value;
3258
+ }
3259
+ return result;
3260
+ }
3261
+ /**
3262
+ * Ensure snapshot is built, returning the snapshot or an error
3263
+ */
3264
+ async ensureSnapshot() {
3265
+ if (this.snapshot) {
3266
+ return Ok(this.snapshot);
3267
+ }
3268
+ return this.buildSnapshot();
3269
+ }
3270
+ /**
3271
+ * Run drift detection only (snapshot must be built first)
3272
+ */
3273
+ async detectDrift(config, graphDriftData) {
3274
+ const snapshotResult = await this.ensureSnapshot();
3275
+ if (!snapshotResult.ok) {
3276
+ return Err(snapshotResult.error);
3277
+ }
3278
+ return detectDocDrift(snapshotResult.value, config || {}, graphDriftData);
3279
+ }
3280
+ /**
3281
+ * Run dead code detection only (snapshot must be built first)
3282
+ */
3283
+ async detectDeadCode(graphDeadCodeData) {
3284
+ const snapshotResult = await this.ensureSnapshot();
3285
+ if (!snapshotResult.ok) {
3286
+ return Err(snapshotResult.error);
3287
+ }
3288
+ return detectDeadCode(snapshotResult.value, graphDeadCodeData, this.config.protectedRegions);
3289
+ }
3290
+ /**
3291
+ * Run pattern detection only (snapshot must be built first)
3292
+ */
3293
+ async detectPatterns(config) {
3294
+ const snapshotResult = await this.ensureSnapshot();
3295
+ if (!snapshotResult.ok) {
3296
+ return Err(snapshotResult.error);
3297
+ }
3298
+ return detectPatternViolations(snapshotResult.value, config);
3299
+ }
3300
+ };
3301
+ }
3302
+ });
3303
+
3304
+ export {
3305
+ createError,
3306
+ createEntropyError,
3307
+ createParseError,
3308
+ fileExists,
3309
+ readFileContent,
3310
+ findFiles,
3311
+ relativePosix,
3312
+ TypeScriptParser,
3313
+ EXTENSION_MAP,
3314
+ detectLanguage,
3315
+ getParser,
3316
+ parseFile,
3317
+ resetParserCache,
3318
+ getOutline,
3319
+ formatOutline,
3320
+ defineLayer,
3321
+ resolveFileToLayer,
3322
+ buildDependencyGraph,
3323
+ validateDependencies,
3324
+ detectComplexityViolations,
3325
+ detectCouplingViolations,
3326
+ getDefaultRegistry,
3327
+ init_chunk_MUWJHO2S,
3328
+ buildSnapshot,
3329
+ detectDocDrift,
3330
+ detectDeadCode,
3331
+ detectPatternViolations,
3332
+ parseSize,
3333
+ detectSizeBudgetViolations,
3334
+ generateSuggestions,
3335
+ EntropyAnalyzer,
3336
+ init_chunk_BTUDWWB4
3337
+ };