auditor-lambda 0.10.3 → 0.10.7

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 (183) hide show
  1. package/audit-code-wrapper-build.mjs +198 -0
  2. package/audit-code-wrapper-install-hosts.mjs +1140 -0
  3. package/audit-code-wrapper-io.mjs +155 -0
  4. package/audit-code-wrapper-legacy.mjs +125 -0
  5. package/audit-code-wrapper-lib.mjs +17 -1801
  6. package/audit-code-wrapper-opencode.mjs +256 -0
  7. package/dispatch/merge-results.mjs +5 -3
  8. package/dispatch/validate-result.mjs +2 -2
  9. package/dist/adapters/coverageSummary.js +6 -2
  10. package/dist/adapters/normalizeExternal.js +16 -1
  11. package/dist/adapters/npmAudit.js +20 -9
  12. package/dist/adapters/semgrep.js +26 -1
  13. package/dist/cli/advanceAuditCommand.d.ts +1 -0
  14. package/dist/cli/advanceAuditCommand.js +95 -0
  15. package/dist/cli/args.js +1 -2
  16. package/dist/cli/auditStep.js +2 -2
  17. package/dist/cli/cleanup.d.ts +11 -1
  18. package/dist/cli/cleanup.js +25 -5
  19. package/dist/cli/cleanupCommand.d.ts +1 -0
  20. package/dist/cli/cleanupCommand.js +24 -0
  21. package/dist/cli/dispatch.d.ts +55 -31
  22. package/dist/cli/dispatch.js +298 -241
  23. package/dist/cli/dispatchStatusCommand.d.ts +1 -0
  24. package/dist/cli/dispatchStatusCommand.js +68 -0
  25. package/dist/cli/explainTaskCommand.d.ts +1 -0
  26. package/dist/cli/explainTaskCommand.js +33 -0
  27. package/dist/cli/importExternalAnalyzerCommand.d.ts +1 -0
  28. package/dist/cli/importExternalAnalyzerCommand.js +20 -0
  29. package/dist/cli/ingestResultsCommand.d.ts +1 -0
  30. package/dist/cli/ingestResultsCommand.js +34 -0
  31. package/dist/cli/intakeCommand.d.ts +1 -0
  32. package/dist/cli/intakeCommand.js +17 -0
  33. package/dist/cli/lineIndex.js +19 -12
  34. package/dist/cli/nextStepCommand.d.ts +139 -0
  35. package/dist/cli/nextStepCommand.js +281 -232
  36. package/dist/cli/planCommand.d.ts +1 -0
  37. package/dist/cli/planCommand.js +16 -0
  38. package/dist/cli/prepareDispatchCommand.d.ts +1 -0
  39. package/dist/cli/prepareDispatchCommand.js +25 -0
  40. package/dist/cli/quotaCommand.d.ts +1 -0
  41. package/dist/cli/quotaCommand.js +56 -0
  42. package/dist/cli/requeueCommand.d.ts +1 -0
  43. package/dist/cli/requeueCommand.js +10 -0
  44. package/dist/cli/runToCompletion.js +451 -412
  45. package/dist/cli/sampleRunCommand.d.ts +1 -0
  46. package/dist/cli/sampleRunCommand.js +93 -0
  47. package/dist/cli/statusCommand.js +1 -1
  48. package/dist/cli/steps.js +4 -1
  49. package/dist/cli/submitPacketCommand.js +16 -15
  50. package/dist/cli/synthesizeCommand.d.ts +1 -0
  51. package/dist/cli/synthesizeCommand.js +15 -0
  52. package/dist/cli/updateRuntimeValidationCommand.d.ts +1 -0
  53. package/dist/cli/updateRuntimeValidationCommand.js +16 -0
  54. package/dist/cli/validateCommand.d.ts +1 -0
  55. package/dist/cli/validateCommand.js +41 -0
  56. package/dist/cli/validateResultCommand.d.ts +1 -0
  57. package/dist/cli/validateResultCommand.js +63 -0
  58. package/dist/cli/validateResultsCommand.d.ts +1 -0
  59. package/dist/cli/validateResultsCommand.js +31 -0
  60. package/dist/cli/workerRunCommand.d.ts +15 -1
  61. package/dist/cli/workerRunCommand.js +40 -4
  62. package/dist/cli.d.ts +3 -2
  63. package/dist/cli.js +21 -628
  64. package/dist/coverage.js +7 -3
  65. package/dist/extractors/analyzers/css.js +2 -2
  66. package/dist/extractors/analyzers/html.js +2 -2
  67. package/dist/extractors/analyzers/python.js +2 -2
  68. package/dist/extractors/analyzers/registry.js +17 -36
  69. package/dist/extractors/analyzers/treeSitter.d.ts +10 -1
  70. package/dist/extractors/analyzers/treeSitter.js +28 -6
  71. package/dist/extractors/analyzers/typescript.js +104 -85
  72. package/dist/extractors/browserExtension.js +4 -1
  73. package/dist/extractors/designAssessment.js +21 -21
  74. package/dist/extractors/fsIntake.js +34 -10
  75. package/dist/extractors/graph.js +17 -7
  76. package/dist/extractors/graphManifestEdges/cargo.d.ts +4 -0
  77. package/dist/extractors/graphManifestEdges/cargo.js +107 -0
  78. package/dist/extractors/graphManifestEdges/go.d.ts +5 -0
  79. package/dist/extractors/graphManifestEdges/go.js +151 -0
  80. package/dist/extractors/graphManifestEdges/index.d.ts +8 -0
  81. package/dist/extractors/graphManifestEdges/index.js +11 -0
  82. package/dist/extractors/graphManifestEdges/jsonc.d.ts +3 -0
  83. package/dist/extractors/graphManifestEdges/jsonc.js +97 -0
  84. package/dist/extractors/graphManifestEdges/maven.d.ts +3 -0
  85. package/dist/extractors/graphManifestEdges/maven.js +73 -0
  86. package/dist/extractors/graphManifestEdges/packageJson.d.ts +19 -0
  87. package/dist/extractors/graphManifestEdges/packageJson.js +204 -0
  88. package/dist/extractors/graphManifestEdges/pnpm.d.ts +2 -0
  89. package/dist/extractors/graphManifestEdges/pnpm.js +42 -0
  90. package/dist/extractors/graphManifestEdges/pyproject.d.ts +3 -0
  91. package/dist/extractors/graphManifestEdges/pyproject.js +83 -0
  92. package/dist/extractors/graphManifestEdges/toml.d.ts +4 -0
  93. package/dist/extractors/graphManifestEdges/toml.js +68 -0
  94. package/dist/extractors/graphManifestEdges/typescript.d.ts +3 -0
  95. package/dist/extractors/graphManifestEdges/typescript.js +56 -0
  96. package/dist/extractors/graphManifestEdges/workspace.d.ts +10 -0
  97. package/dist/extractors/graphManifestEdges/workspace.js +72 -0
  98. package/dist/extractors/graphManifestEdges/yaml.d.ts +3 -0
  99. package/dist/extractors/graphManifestEdges/yaml.js +59 -0
  100. package/dist/extractors/graphManifestEdges/yamlPaths.d.ts +4 -0
  101. package/dist/extractors/graphManifestEdges/yamlPaths.js +89 -0
  102. package/dist/extractors/graphPythonImports.js +4 -20
  103. package/dist/extractors/pathPatterns.js +3 -13
  104. package/dist/io/artifacts.d.ts +1 -1
  105. package/dist/io/artifacts.js +4 -1
  106. package/dist/io/runArtifacts.d.ts +8 -2
  107. package/dist/io/runArtifacts.js +103 -69
  108. package/dist/io/toolingManifest.js +2 -1
  109. package/dist/orchestrator/advance.js +36 -0
  110. package/dist/orchestrator/artifactFreshness.d.ts +1 -1
  111. package/dist/orchestrator/artifactFreshness.js +1 -1
  112. package/dist/orchestrator/artifactMetadata.js +5 -5
  113. package/dist/orchestrator/auditTaskUtils.d.ts +4 -0
  114. package/dist/orchestrator/auditTaskUtils.js +8 -12
  115. package/dist/orchestrator/autoFixExecutor.js +40 -26
  116. package/dist/orchestrator/dependencyMap.js +1 -1
  117. package/dist/orchestrator/executorResult.d.ts +33 -0
  118. package/dist/orchestrator/executors.d.ts +7 -0
  119. package/dist/orchestrator/executors.js +24 -0
  120. package/dist/orchestrator/fileAnchors.js +42 -29
  121. package/dist/orchestrator/fileIntegrity.js +6 -1
  122. package/dist/orchestrator/flowCoverage.js +1 -2
  123. package/dist/orchestrator/flowPlanning.js +8 -4
  124. package/dist/orchestrator/graphEnrichmentExecutor.js +67 -45
  125. package/dist/orchestrator/ingestionExecutors.js +9 -1
  126. package/dist/orchestrator/intakeExecutors.d.ts +0 -4
  127. package/dist/orchestrator/intakeExecutors.js +24 -14
  128. package/dist/orchestrator/localCommands.d.ts +1 -0
  129. package/dist/orchestrator/localCommands.js +10 -17
  130. package/dist/orchestrator/nextStep.js +3 -1
  131. package/dist/orchestrator/requeueCommand.js +4 -0
  132. package/dist/orchestrator/reviewPacketGraph.js +50 -18
  133. package/dist/orchestrator/reviewPackets.js +10 -8
  134. package/dist/orchestrator/runtimeCommand.js +35 -7
  135. package/dist/orchestrator/runtimeValidationUpdate.js +6 -0
  136. package/dist/orchestrator/selectiveDeepening/highRiskClean.js +3 -2
  137. package/dist/orchestrator/selectiveDeepening/lensVerification.js +44 -18
  138. package/dist/orchestrator/staleness.js +3 -3
  139. package/dist/orchestrator/state.js +1 -1
  140. package/dist/orchestrator/syntaxResolutionExecutor.js +17 -24
  141. package/dist/orchestrator/synthesisExecutors.js +1 -0
  142. package/dist/orchestrator/taskBuilder.js +5 -4
  143. package/dist/providers/claudeCodeProvider.js +4 -1
  144. package/dist/providers/opencodeProvider.js +4 -1
  145. package/dist/quota/discoveredLimits.js +3 -3
  146. package/dist/quota/headerExtraction.js +5 -2
  147. package/dist/quota/headerExtractors/claudeCodeHeaderExtractor.js +3 -0
  148. package/dist/quota/headerExtractors/index.js +3 -3
  149. package/dist/quota/index.d.ts +3 -1
  150. package/dist/quota/index.js +3 -0
  151. package/dist/reporting/findingRanks.d.ts +3 -0
  152. package/dist/reporting/findingRanks.js +24 -0
  153. package/dist/reporting/mergeFindings.js +1 -24
  154. package/dist/reporting/synthesis.d.ts +3 -1
  155. package/dist/reporting/synthesis.js +30 -6
  156. package/dist/reporting/synthesisNarrativePrompt.js +3 -0
  157. package/dist/reporting/workBlocks.js +1 -14
  158. package/dist/supervisor/operatorHandoff.js +2 -6
  159. package/dist/supervisor/runLedger.js +30 -41
  160. package/dist/types/activeDispatch.d.ts +31 -0
  161. package/dist/types/activeDispatch.js +2 -0
  162. package/dist/types.d.ts +21 -4
  163. package/dist/types.js +24 -16
  164. package/dist/validation/artifacts.js +3 -0
  165. package/dist/validation/auditResults.js +8 -2
  166. package/package.json +2 -2
  167. package/schemas/audit_findings.schema.json +5 -1
  168. package/schemas/audit_plan_metrics.schema.json +1 -1
  169. package/schemas/audit_result.schema.json +5 -6
  170. package/schemas/audit_task.schema.json +1 -4
  171. package/schemas/blind_spot_register.schema.json +1 -1
  172. package/schemas/coverage_matrix.schema.json +2 -8
  173. package/schemas/finding.schema.json +1 -16
  174. package/schemas/flow_coverage.schema.json +2 -8
  175. package/schemas/graph_bundle.schema.json +31 -0
  176. package/schemas/lens.schema.json +7 -0
  177. package/schemas/review_packets.schema.json +6 -17
  178. package/schemas/step_contract.schema.json +8 -2
  179. package/schemas/unit_manifest.schema.json +1 -4
  180. package/scripts/postinstall.mjs +3 -1
  181. package/skills/audit-code/audit-code.prompt.md +2 -3
  182. package/dist/extractors/graphManifestEdges.d.ts +0 -12
  183. package/dist/extractors/graphManifestEdges.js +0 -1135
package/dist/coverage.js CHANGED
@@ -1,3 +1,6 @@
1
+ function buildFileIndex(matrix) {
2
+ return new Map(matrix.files.map((f) => [f.path, f]));
3
+ }
1
4
  export function createCoverageMatrix(paths) {
2
5
  return {
3
6
  files: paths.map((path) => ({
@@ -11,7 +14,7 @@ export function createCoverageMatrix(paths) {
11
14
  };
12
15
  }
13
16
  export function markExcludedPath(matrix, path, classificationStatus) {
14
- const record = matrix.files.find((file) => file.path === path);
17
+ const record = buildFileIndex(matrix).get(path);
15
18
  if (!record)
16
19
  return;
17
20
  record.classification_status = classificationStatus;
@@ -21,7 +24,7 @@ export function markExcludedPath(matrix, path, classificationStatus) {
21
24
  record.unit_ids = [];
22
25
  }
23
26
  export function applyUnitCoverage(matrix, path, unitId, requiredLenses) {
24
- const record = matrix.files.find((file) => file.path === path);
27
+ const record = buildFileIndex(matrix).get(path);
25
28
  if (!record || record.audit_status === "excluded")
26
29
  return;
27
30
  if (!record.unit_ids.includes(unitId)) {
@@ -33,8 +36,9 @@ export function applyUnitCoverage(matrix, path, unitId, requiredLenses) {
33
36
  ];
34
37
  }
35
38
  export function applyFileCoverage(matrix, fileCoverage) {
39
+ const index = buildFileIndex(matrix);
36
40
  for (const coverage of fileCoverage) {
37
- const record = matrix.files.find((file) => file.path === coverage.path);
41
+ const record = index.get(coverage.path);
38
42
  if (!record || record.audit_status === "excluded")
39
43
  continue;
40
44
  if (coverage.lens &&
@@ -87,8 +87,8 @@ async function analyze(files, context) {
87
87
  const tree = parser.parse(content);
88
88
  collectFileEdges(normalizeGraphPath(file), tree.rootNode, context.pathLookup, edges);
89
89
  }
90
- catch {
91
- // Degrade to the floor for this file.
90
+ catch (e) {
91
+ process.stderr.write(`[audit-code] css-analyzer: parse failed for '${file}': ${e.message ?? String(e)}\n`);
92
92
  }
93
93
  }
94
94
  return { edges };
@@ -78,8 +78,8 @@ async function analyze(files, context) {
78
78
  const tree = parser.parse(content);
79
79
  collectFileEdges(normalizeGraphPath(file), tree.rootNode, context.pathLookup, edges);
80
80
  }
81
- catch {
82
- // Degrade to the floor for this file.
81
+ catch (e) {
82
+ process.stderr.write(`[audit-code] html-analyzer: parse failed for '${file}': ${e.message ?? String(e)}\n`);
83
83
  }
84
84
  }
85
85
  return { edges };
@@ -90,8 +90,8 @@ async function analyze(files, context) {
90
90
  const tree = parser.parse(content);
91
91
  collectFileEdges(normalizeGraphPath(file), tree.rootNode, context.pathLookup, edges);
92
92
  }
93
- catch {
94
- // A parse failure on one file degrades to the floor for that file.
93
+ catch (e) {
94
+ process.stderr.write(`[audit-code] python-analyzer: parse failed for '${file}': ${e.message ?? String(e)}\n`);
95
95
  }
96
96
  }
97
97
  return { edges };
@@ -24,6 +24,18 @@ export function getAnalyzerById(id) {
24
24
  function settingFor(analyzers, id) {
25
25
  return analyzers?.[id] ?? "auto";
26
26
  }
27
+ function makeEntry(analyzer, setting, supportedCount, resolution, path) {
28
+ const entry = {
29
+ id: analyzer.id,
30
+ dependency: analyzer.dependency,
31
+ setting,
32
+ resolution,
33
+ supportedCount,
34
+ };
35
+ if (path !== undefined)
36
+ entry.path = path;
37
+ return entry;
38
+ }
27
39
  /**
28
40
  * Deterministically resolve, without installing anything, how each registered
29
41
  * analyzer would run for this repo. The conversation-first CLI uses this to
@@ -42,51 +54,20 @@ export function resolveAnalyzerPlan(root, analyzers, includedFiles, options = {}
42
54
  const setting = settingFor(analyzers, analyzer.id);
43
55
  const supportedCount = includedFiles.filter((file) => analyzer.supports(file)).length;
44
56
  if (supportedCount === 0) {
45
- return {
46
- id: analyzer.id,
47
- dependency: analyzer.dependency,
48
- setting,
49
- resolution: "not_applicable",
50
- supportedCount,
51
- };
57
+ return makeEntry(analyzer, setting, supportedCount, "not_applicable");
52
58
  }
53
59
  if (setting === "skip") {
54
- return {
55
- id: analyzer.id,
56
- dependency: analyzer.dependency,
57
- setting,
58
- resolution: "skip",
59
- supportedCount,
60
- };
60
+ return makeEntry(analyzer, setting, supportedCount, "skip");
61
61
  }
62
62
  if (!analyzer.dependency) {
63
63
  // No dependency required: always available.
64
- return {
65
- id: analyzer.id,
66
- dependency: analyzer.dependency,
67
- setting,
68
- resolution: "repo",
69
- supportedCount,
70
- };
64
+ return makeEntry(analyzer, setting, supportedCount, "repo");
71
65
  }
72
66
  const resolved = resolveAnalyzerDep(analyzer.dependency, root, depOptions);
73
67
  if (resolved.via === "repo" || resolved.via === "cache") {
74
- return {
75
- id: analyzer.id,
76
- dependency: analyzer.dependency,
77
- setting,
78
- resolution: resolved.via,
79
- path: resolved.path,
80
- supportedCount,
81
- };
68
+ return makeEntry(analyzer, setting, supportedCount, resolved.via, resolved.path);
82
69
  }
83
- return {
84
- id: analyzer.id,
85
- dependency: analyzer.dependency,
86
- setting,
87
- resolution: "absent",
88
- supportedCount,
89
- };
70
+ return makeEntry(analyzer, setting, supportedCount, "absent");
90
71
  });
91
72
  }
92
73
  /**
@@ -25,10 +25,19 @@ export interface TsParser {
25
25
  setLanguage(language: TsLanguage): void;
26
26
  parse(source: string): TsTree;
27
27
  }
28
+ /**
29
+ * Return the number of times tree-sitter fell back to the regex floor since
30
+ * process start (or since the last `__resetTreeSitterForTests` call). Each
31
+ * distinct failure path (import failure, init failure, grammar load failure,
32
+ * parser instantiation failure) increments the counter once. A caller can
33
+ * snapshot this before and after a batch parse pass to detect systemic
34
+ * degradation without scraping stderr.
35
+ */
36
+ export declare function getTreeSitterDegradationCount(): number;
28
37
  /**
29
38
  * Obtain a parser bound to `grammar` (e.g. "python", "html", "css"), or
30
39
  * `undefined` if web-tree-sitter or the grammar wasm cannot be loaded.
31
40
  */
32
41
  export declare function getTreeSitterParser(grammar: string, dependencyPath?: string): Promise<TsParser | undefined>;
33
- /** Test seam: reset the memoised runtime/grammar caches. */
42
+ /** Test seam: reset the memoised runtime/grammar caches and degradation counter. */
34
43
  export declare function __resetTreeSitterForTests(): void;
@@ -2,6 +2,18 @@ import { createRequire } from "node:module";
2
2
  import { dirname, join } from "node:path";
3
3
  import { pathToFileURL } from "node:url";
4
4
  const requireFromHere = createRequire(import.meta.url);
5
+ let _degradationCount = 0;
6
+ /**
7
+ * Return the number of times tree-sitter fell back to the regex floor since
8
+ * process start (or since the last `__resetTreeSitterForTests` call). Each
9
+ * distinct failure path (import failure, init failure, grammar load failure,
10
+ * parser instantiation failure) increments the counter once. A caller can
11
+ * snapshot this before and after a batch parse pass to detect systemic
12
+ * degradation without scraping stderr.
13
+ */
14
+ export function getTreeSitterDegradationCount() {
15
+ return _degradationCount;
16
+ }
5
17
  // The parser module is resolved per `dependencyPath`: a call with a different
6
18
  // dependencyPath must resolve its own module rather than reusing the first
7
19
  // resolution. Keyed by `dependencyPath ?? ""` so the bare-specifier path
@@ -31,6 +43,7 @@ async function importParserModule(dependencyPath) {
31
43
  }
32
44
  }
33
45
  catch (e) {
46
+ _degradationCount += 1;
34
47
  process.stderr.write(`[audit-code] tree-sitter: failed to import '${specifier}': ${e.message ?? String(e)}\n`);
35
48
  }
36
49
  }
@@ -51,6 +64,7 @@ async function ensureInit(parserModule) {
51
64
  cached = parserModule.Parser.init()
52
65
  .then(() => true)
53
66
  .catch((e) => {
67
+ _degradationCount += 1;
54
68
  process.stderr.write(`[audit-code] tree-sitter: Parser.init() failed: ${e.message ?? String(e)}\n`);
55
69
  return false;
56
70
  });
@@ -75,22 +89,28 @@ function resolveGrammarPath(grammar) {
75
89
  }
76
90
  }
77
91
  async function loadLanguage(parserModule, grammar) {
78
- if (languageCache.has(grammar)) {
79
- return languageCache.get(grammar) ?? undefined;
92
+ let moduleMap = languageCache.get(parserModule);
93
+ if (!moduleMap) {
94
+ moduleMap = new Map();
95
+ languageCache.set(parserModule, moduleMap);
96
+ }
97
+ if (moduleMap.has(grammar)) {
98
+ return moduleMap.get(grammar) ?? undefined;
80
99
  }
81
100
  const grammarPath = resolveGrammarPath(grammar);
82
101
  if (!grammarPath) {
83
- languageCache.set(grammar, null);
102
+ moduleMap.set(grammar, null);
84
103
  return undefined;
85
104
  }
86
105
  try {
87
106
  const language = await parserModule.Language.load(grammarPath);
88
- languageCache.set(grammar, language);
107
+ moduleMap.set(grammar, language);
89
108
  return language;
90
109
  }
91
110
  catch (e) {
111
+ _degradationCount += 1;
92
112
  process.stderr.write(`[audit-code] tree-sitter: failed to load grammar '${grammar}' from '${grammarPath}': ${e.message ?? String(e)}\n`);
93
- languageCache.set(grammar, null);
113
+ moduleMap.set(grammar, null);
94
114
  return undefined;
95
115
  }
96
116
  }
@@ -113,13 +133,15 @@ export async function getTreeSitterParser(grammar, dependencyPath) {
113
133
  return parser;
114
134
  }
115
135
  catch (e) {
136
+ _degradationCount += 1;
116
137
  process.stderr.write(`[audit-code] tree-sitter: failed to instantiate parser for grammar '${grammar}': ${e.message ?? String(e)}\n`);
117
138
  return undefined;
118
139
  }
119
140
  }
120
- /** Test seam: reset the memoised runtime/grammar caches. */
141
+ /** Test seam: reset the memoised runtime/grammar caches and degradation counter. */
121
142
  export function __resetTreeSitterForTests() {
122
143
  moduleCache.clear();
123
144
  initCache.clear();
124
145
  languageCache.clear();
146
+ _degradationCount = 0;
125
147
  }
@@ -112,104 +112,123 @@ function resolveSymbolToIncluded(state, symbol) {
112
112
  }
113
113
  return undefined;
114
114
  }
115
- function collectFileEdges(state, sourceFile, fromPath, imports, references, calls) {
115
+ function recordCallEdge(calls, callTargets, fromPath, target) {
116
+ if (!target || target === fromPath || callTargets.has(target))
117
+ return;
118
+ callTargets.add(target);
119
+ calls.push(graphEdge({
120
+ from: fromPath,
121
+ to: target,
122
+ kind: "ts-call",
123
+ confidence: TS_CALL_EDGE_CONFIDENCE,
124
+ reason: `TypeScript checker resolved a cross-file call into '${target}'.`,
125
+ }));
126
+ }
127
+ function visitHeritageNode(state, references, fromPath, node) {
116
128
  const ts = state.ts;
117
- const callTargets = new Set();
118
- const recordCall = (target) => {
119
- if (!target || target === fromPath || callTargets.has(target))
120
- return;
121
- callTargets.add(target);
122
- calls.push(graphEdge({
129
+ for (const clause of node.heritageClauses ?? []) {
130
+ const isExtends = clause.token === ts.SyntaxKind.ExtendsKeyword;
131
+ for (const typeNode of clause.types) {
132
+ const target = resolveSymbolToIncluded(state, state.checker.getSymbolAtLocation(typeNode.expression));
133
+ if (!target || target === fromPath)
134
+ continue;
135
+ references.push(graphEdge({
136
+ from: fromPath,
137
+ to: target,
138
+ kind: isExtends ? "ts-extends" : "ts-implements",
139
+ confidence: isExtends
140
+ ? TS_EXTENDS_EDGE_CONFIDENCE
141
+ : TS_IMPLEMENTS_EDGE_CONFIDENCE,
142
+ reason: `TypeScript ${isExtends ? "extends" : "implements"} heritage resolves to '${target}'.`,
143
+ }));
144
+ }
145
+ }
146
+ }
147
+ function visitImportDeclaration(state, imports, fromPath, node) {
148
+ if (!state.ts.isStringLiteral(node.moduleSpecifier))
149
+ return;
150
+ const target = resolveSpecifierTarget(state, node.moduleSpecifier.text, node.getSourceFile().fileName);
151
+ if (target && target !== fromPath) {
152
+ imports.push(graphEdge({
123
153
  from: fromPath,
124
154
  to: target,
125
- kind: "ts-call",
126
- confidence: TS_CALL_EDGE_CONFIDENCE,
127
- reason: `TypeScript checker resolved a cross-file call into '${target}'.`,
155
+ kind: "ts-import",
156
+ confidence: TS_IMPORT_EDGE_CONFIDENCE,
157
+ reason: `TypeScript resolved import '${node.moduleSpecifier.text}' to '${target}'.`,
128
158
  }));
129
- };
130
- const visitHeritage = (node) => {
131
- for (const clause of node.heritageClauses ?? []) {
132
- const isExtends = clause.token === ts.SyntaxKind.ExtendsKeyword;
133
- for (const typeNode of clause.types) {
134
- const target = resolveSymbolToIncluded(state, state.checker.getSymbolAtLocation(typeNode.expression));
135
- if (!target || target === fromPath)
136
- continue;
137
- references.push(graphEdge({
138
- from: fromPath,
139
- to: target,
140
- kind: isExtends ? "ts-extends" : "ts-implements",
141
- confidence: isExtends
142
- ? TS_EXTENDS_EDGE_CONFIDENCE
143
- : TS_IMPLEMENTS_EDGE_CONFIDENCE,
144
- reason: `TypeScript ${isExtends ? "extends" : "implements"} heritage resolves to '${target}'.`,
145
- }));
146
- }
159
+ }
160
+ }
161
+ function visitExportDeclaration(state, imports, fromPath, node) {
162
+ if (!node.moduleSpecifier || !state.ts.isStringLiteral(node.moduleSpecifier))
163
+ return;
164
+ const target = resolveSpecifierTarget(state, node.moduleSpecifier.text, node.getSourceFile().fileName);
165
+ if (target && target !== fromPath) {
166
+ imports.push(graphEdge({
167
+ from: fromPath,
168
+ to: target,
169
+ kind: "ts-reexport",
170
+ confidence: TS_REEXPORT_EDGE_CONFIDENCE,
171
+ reason: `TypeScript resolved re-export '${node.moduleSpecifier.text}' to '${target}'.`,
172
+ }));
173
+ }
174
+ }
175
+ function visitImportEqualsDeclaration(state, imports, fromPath, node) {
176
+ const ts = state.ts;
177
+ if (!ts.isExternalModuleReference(node.moduleReference) ||
178
+ !ts.isStringLiteral(node.moduleReference.expression)) {
179
+ return;
180
+ }
181
+ const target = resolveSpecifierTarget(state, node.moduleReference.expression.text, node.getSourceFile().fileName);
182
+ if (target && target !== fromPath) {
183
+ imports.push(graphEdge({
184
+ from: fromPath,
185
+ to: target,
186
+ kind: "ts-import",
187
+ confidence: TS_IMPORT_EDGE_CONFIDENCE,
188
+ reason: `TypeScript resolved import-equals to '${target}'.`,
189
+ }));
190
+ }
191
+ }
192
+ function visitCallExpression(state, imports, calls, callTargets, fromPath, node) {
193
+ const ts = state.ts;
194
+ if (node.expression.kind === ts.SyntaxKind.ImportKeyword &&
195
+ node.arguments[0] &&
196
+ ts.isStringLiteral(node.arguments[0])) {
197
+ const target = resolveSpecifierTarget(state, node.arguments[0].text, node.getSourceFile().fileName);
198
+ if (target && target !== fromPath) {
199
+ imports.push(graphEdge({
200
+ from: fromPath,
201
+ to: target,
202
+ kind: "ts-import",
203
+ confidence: TS_IMPORT_EDGE_CONFIDENCE,
204
+ reason: `TypeScript resolved dynamic import to '${target}'.`,
205
+ }));
147
206
  }
148
- };
207
+ }
208
+ else {
209
+ recordCallEdge(calls, callTargets, fromPath, resolveSymbolToIncluded(state, state.checker.getSymbolAtLocation(node.expression)));
210
+ }
211
+ }
212
+ function collectFileEdges(state, sourceFile, fromPath, imports, references, calls) {
213
+ const ts = state.ts;
214
+ const callTargets = new Set();
149
215
  const visit = (node) => {
150
- if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
151
- const target = resolveSpecifierTarget(state, node.moduleSpecifier.text, sourceFile.fileName);
152
- if (target && target !== fromPath) {
153
- imports.push(graphEdge({
154
- from: fromPath,
155
- to: target,
156
- kind: "ts-import",
157
- confidence: TS_IMPORT_EDGE_CONFIDENCE,
158
- reason: `TypeScript resolved import '${node.moduleSpecifier.text}' to '${target}'.`,
159
- }));
160
- }
216
+ if (ts.isImportDeclaration(node)) {
217
+ visitImportDeclaration(state, imports, fromPath, node);
161
218
  }
162
- else if (ts.isExportDeclaration(node) &&
163
- node.moduleSpecifier &&
164
- ts.isStringLiteral(node.moduleSpecifier)) {
165
- const target = resolveSpecifierTarget(state, node.moduleSpecifier.text, sourceFile.fileName);
166
- if (target && target !== fromPath) {
167
- imports.push(graphEdge({
168
- from: fromPath,
169
- to: target,
170
- kind: "ts-reexport",
171
- confidence: TS_REEXPORT_EDGE_CONFIDENCE,
172
- reason: `TypeScript resolved re-export '${node.moduleSpecifier.text}' to '${target}'.`,
173
- }));
174
- }
219
+ else if (ts.isExportDeclaration(node)) {
220
+ visitExportDeclaration(state, imports, fromPath, node);
175
221
  }
176
- else if (ts.isImportEqualsDeclaration(node) &&
177
- ts.isExternalModuleReference(node.moduleReference) &&
178
- ts.isStringLiteral(node.moduleReference.expression)) {
179
- const target = resolveSpecifierTarget(state, node.moduleReference.expression.text, sourceFile.fileName);
180
- if (target && target !== fromPath) {
181
- imports.push(graphEdge({
182
- from: fromPath,
183
- to: target,
184
- kind: "ts-import",
185
- confidence: TS_IMPORT_EDGE_CONFIDENCE,
186
- reason: `TypeScript resolved import-equals to '${target}'.`,
187
- }));
188
- }
222
+ else if (ts.isImportEqualsDeclaration(node)) {
223
+ visitImportEqualsDeclaration(state, imports, fromPath, node);
189
224
  }
190
225
  else if (ts.isCallExpression(node)) {
191
- if (node.expression.kind === ts.SyntaxKind.ImportKeyword &&
192
- node.arguments[0] &&
193
- ts.isStringLiteral(node.arguments[0])) {
194
- const target = resolveSpecifierTarget(state, node.arguments[0].text, sourceFile.fileName);
195
- if (target && target !== fromPath) {
196
- imports.push(graphEdge({
197
- from: fromPath,
198
- to: target,
199
- kind: "ts-import",
200
- confidence: TS_IMPORT_EDGE_CONFIDENCE,
201
- reason: `TypeScript resolved dynamic import to '${target}'.`,
202
- }));
203
- }
204
- }
205
- else {
206
- recordCall(resolveSymbolToIncluded(state, state.checker.getSymbolAtLocation(node.expression)));
207
- }
226
+ visitCallExpression(state, imports, calls, callTargets, fromPath, node);
208
227
  }
209
228
  else if (ts.isClassDeclaration(node) ||
210
229
  ts.isClassExpression(node) ||
211
230
  ts.isInterfaceDeclaration(node)) {
212
- visitHeritage(node);
231
+ visitHeritageNode(state, references, fromPath, node);
213
232
  }
214
233
  ts.forEachChild(node, visit);
215
234
  };
@@ -247,7 +266,7 @@ async function analyze(files, context) {
247
266
  return { edges: [...imports, ...references, ...calls] };
248
267
  }
249
268
  catch (e) {
250
- process.stderr.write(`[audit-code] typescript-analyzer: program analysis failed for ${files.length} file(s) under '${context.root}', degrading to regex floor: ${e.message ?? String(e)}\n`);
269
+ process.stderr.write(`[audit-code] typescript-analyzer: program analysis failed for ${files.length} file(s) under '${context.root}' — returning 0 edges (all ${files.length} file(s) edges lost), degrading to regex floor: ${e.message ?? String(e)}\n`);
251
270
  return { edges: [] };
252
271
  }
253
272
  }
@@ -279,7 +279,10 @@ export function extractHtmlResourceEdges(fromPath, content, pathLookup) {
279
279
  return edges;
280
280
  }
281
281
  export function hasBrowserExtensionManifestFile(repoManifest) {
282
- return repoManifest.files.some((file) => normalizeGraphPath(file.path).toLowerCase() === "manifest.json");
282
+ // Detect a `manifest.json` anywhere in the tree (root or a subdirectory),
283
+ // using the same basename match as isBrowserExtensionManifestPath so the
284
+ // repo-level gate and the per-file classifier agree.
285
+ return repoManifest.files.some((file) => isBrowserExtensionManifestPath(file.path));
283
286
  }
284
287
  export function deriveBrowserExtensionLensesForPath(path) {
285
288
  const normalized = normalizeGraphPath(path).toLowerCase();
@@ -18,6 +18,25 @@ function allEdges(graphBundle) {
18
18
  }
19
19
  return edges;
20
20
  }
21
+ function dfsVisit(node, path, adjacency, visited, stack, cycles) {
22
+ if (stack.has(node)) {
23
+ const cycleStart = path.indexOf(node);
24
+ if (cycleStart >= 0) {
25
+ cycles.push(path.slice(cycleStart));
26
+ }
27
+ return;
28
+ }
29
+ if (visited.has(node))
30
+ return;
31
+ visited.add(node);
32
+ stack.add(node);
33
+ path.push(node);
34
+ for (const neighbor of adjacency.get(node) ?? []) {
35
+ dfsVisit(neighbor, path, adjacency, visited, stack, cycles);
36
+ }
37
+ path.pop();
38
+ stack.delete(node);
39
+ }
21
40
  function detectCycles(edges) {
22
41
  const adjacency = new Map();
23
42
  for (const edge of edges) {
@@ -28,27 +47,8 @@ function detectCycles(edges) {
28
47
  const cycles = [];
29
48
  const visited = new Set();
30
49
  const stack = new Set();
31
- function dfs(node, path) {
32
- if (stack.has(node)) {
33
- const cycleStart = path.indexOf(node);
34
- if (cycleStart >= 0) {
35
- cycles.push(path.slice(cycleStart));
36
- }
37
- return;
38
- }
39
- if (visited.has(node))
40
- return;
41
- visited.add(node);
42
- stack.add(node);
43
- path.push(node);
44
- for (const neighbor of adjacency.get(node) ?? []) {
45
- dfs(neighbor, path);
46
- }
47
- path.pop();
48
- stack.delete(node);
49
- }
50
50
  for (const node of adjacency.keys()) {
51
- dfs(node, []);
51
+ dfsVisit(node, [], adjacency, visited, stack, cycles);
52
52
  }
53
53
  return cycles;
54
54
  }
@@ -195,7 +195,7 @@ function detectUnitSprawl(unitManifest, nextId) {
195
195
  return [];
196
196
  const fileCounts = unitManifest.units.map((u) => u.files.length);
197
197
  const totalFiles = fileCounts.reduce((a, b) => a + b, 0);
198
- const maxFiles = Math.max(...fileCounts);
198
+ const maxFiles = fileCounts.reduce((max, n) => n > max ? n : max, 0);
199
199
  const findings = [];
200
200
  const dominantUnit = unitManifest.units.find((u) => u.files.length === maxFiles);
201
201
  if (dominantUnit && maxFiles > totalFiles * 0.5 && totalFiles > 10) {
@@ -35,25 +35,43 @@ async function maybeHashFile(path, enabled) {
35
35
  const content = await readFile(path);
36
36
  return createHash("sha256").update(content).digest("hex");
37
37
  }
38
- async function walk(root, current, ignores, hashFiles, maxFileSizeBytes, results) {
39
- const entries = await readdir(current, { withFileTypes: true });
38
+ async function walk(ctx, current, results) {
39
+ let entries;
40
+ try {
41
+ entries = await readdir(current, { withFileTypes: true });
42
+ }
43
+ catch (err) {
44
+ console.warn(`[fsIntake] skipping unreadable directory: ${current} (${err.message})`);
45
+ return;
46
+ }
40
47
  for (const entry of entries) {
41
48
  const absolutePath = join(current, entry.name);
42
- const relativePath = normalizePath(relative(root, absolutePath));
43
- if (!relativePath || shouldIgnore(relativePath, ignores)) {
49
+ const relativePath = normalizePath(relative(ctx.root, absolutePath));
50
+ if (!relativePath || shouldIgnore(relativePath, ctx.ignores)) {
44
51
  continue;
45
52
  }
46
53
  if (entry.isDirectory()) {
47
- await walk(root, absolutePath, ignores, hashFiles, maxFileSizeBytes, results);
54
+ await walk(ctx, absolutePath, results);
48
55
  continue;
49
56
  }
50
57
  if (!entry.isFile()) {
51
58
  continue;
52
59
  }
53
- const info = await stat(absolutePath);
54
- const hash = info.size <= maxFileSizeBytes
55
- ? await maybeHashFile(absolutePath, hashFiles)
56
- : undefined;
60
+ let info;
61
+ try {
62
+ info = await stat(absolutePath);
63
+ }
64
+ catch (err) {
65
+ console.warn(`[fsIntake] skipping unreadable file: ${relativePath} (${err.message})`);
66
+ continue;
67
+ }
68
+ let hash;
69
+ if (info.size <= ctx.maxFileSizeBytes) {
70
+ hash = await maybeHashFile(absolutePath, ctx.hashFiles);
71
+ }
72
+ else {
73
+ console.warn(`[fsIntake] skipping oversized file: ${relativePath} (${info.size} bytes > ${ctx.maxFileSizeBytes} limit)`);
74
+ }
57
75
  results.push({
58
76
  path: relativePath,
59
77
  size_bytes: info.size,
@@ -65,6 +83,12 @@ export async function buildRepoManifestFromFs(options) {
65
83
  const root = resolve(options.root);
66
84
  const ignore = [...DEFAULT_IGNORES, ...(options.ignore ?? [])];
67
85
  const files = [];
68
- await walk(root, root, ignore, options.hash_files ?? false, options.max_file_size_bytes ?? 1024 * 1024, files);
86
+ const ctx = {
87
+ root,
88
+ ignores: ignore,
89
+ hashFiles: options.hash_files ?? false,
90
+ maxFileSizeBytes: options.max_file_size_bytes ?? 1024 * 1024,
91
+ };
92
+ await walk(ctx, root, files);
69
93
  return buildRepoManifest(root.split(/[\\/]/).pop() ?? "repo", files);
70
94
  }