audit-tools 0.30.4 → 0.30.5

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 (73) hide show
  1. package/dist/audit/contracts/schemaEnforcedEmit.d.ts +75 -0
  2. package/dist/audit/contracts/schemaEnforcedEmit.d.ts.map +1 -0
  3. package/dist/audit/contracts/schemaEnforcedEmit.js +131 -0
  4. package/dist/audit/contracts/schemaEnforcedEmit.js.map +1 -0
  5. package/dist/audit/contracts/wrapperResponse.d.ts +6 -6
  6. package/dist/audit/extractors/analyzers/acquisitionEngine.d.ts +140 -0
  7. package/dist/audit/extractors/analyzers/acquisitionEngine.d.ts.map +1 -0
  8. package/dist/audit/extractors/analyzers/acquisitionEngine.js +262 -0
  9. package/dist/audit/extractors/analyzers/acquisitionEngine.js.map +1 -0
  10. package/dist/audit/extractors/analyzers/registry.d.ts +2 -0
  11. package/dist/audit/extractors/analyzers/registry.d.ts.map +1 -1
  12. package/dist/audit/extractors/analyzers/registry.js +5 -0
  13. package/dist/audit/extractors/analyzers/registry.js.map +1 -1
  14. package/dist/audit/extractors/gitHistory.d.ts +46 -0
  15. package/dist/audit/extractors/gitHistory.d.ts.map +1 -0
  16. package/dist/audit/extractors/gitHistory.js +97 -0
  17. package/dist/audit/extractors/gitHistory.js.map +1 -0
  18. package/dist/audit/extractors/graph.d.ts +19 -1
  19. package/dist/audit/extractors/graph.d.ts.map +1 -1
  20. package/dist/audit/extractors/graph.js +31 -0
  21. package/dist/audit/extractors/graph.js.map +1 -1
  22. package/dist/audit/extractors/risk.d.ts +16 -0
  23. package/dist/audit/extractors/risk.d.ts.map +1 -1
  24. package/dist/audit/extractors/risk.js +28 -0
  25. package/dist/audit/extractors/risk.js.map +1 -1
  26. package/dist/audit/io/artifacts.d.ts +3 -1
  27. package/dist/audit/io/artifacts.d.ts.map +1 -1
  28. package/dist/audit/io/artifacts.js +1 -0
  29. package/dist/audit/io/artifacts.js.map +1 -1
  30. package/dist/audit/orchestrator/artifactMetadata.d.ts.map +1 -1
  31. package/dist/audit/orchestrator/artifactMetadata.js +24 -3
  32. package/dist/audit/orchestrator/artifactMetadata.js.map +1 -1
  33. package/dist/audit/orchestrator/dependencyMap.d.ts +7 -0
  34. package/dist/audit/orchestrator/dependencyMap.d.ts.map +1 -1
  35. package/dist/audit/orchestrator/dependencyMap.js +5 -1
  36. package/dist/audit/orchestrator/dependencyMap.js.map +1 -1
  37. package/dist/audit/orchestrator/resultBaseline.d.ts +54 -0
  38. package/dist/audit/orchestrator/resultBaseline.d.ts.map +1 -1
  39. package/dist/audit/orchestrator/resultBaseline.js +59 -0
  40. package/dist/audit/orchestrator/resultBaseline.js.map +1 -1
  41. package/dist/audit/orchestrator/staleness.d.ts.map +1 -1
  42. package/dist/audit/orchestrator/staleness.js +25 -1
  43. package/dist/audit/orchestrator/staleness.js.map +1 -1
  44. package/dist/audit/quota/index.d.ts +20 -20
  45. package/dist/audit/types/artifactMetadata.d.ts +25 -0
  46. package/dist/audit/types/artifactMetadata.d.ts.map +1 -1
  47. package/dist/audit/types/artifactMetadata.js +10 -1
  48. package/dist/audit/types/artifactMetadata.js.map +1 -1
  49. package/dist/shared/git.d.ts +41 -0
  50. package/dist/shared/git.d.ts.map +1 -1
  51. package/dist/shared/git.js +119 -0
  52. package/dist/shared/git.js.map +1 -1
  53. package/dist/shared/index.d.ts +6 -3
  54. package/dist/shared/index.d.ts.map +1 -1
  55. package/dist/shared/index.js +3 -2
  56. package/dist/shared/index.js.map +1 -1
  57. package/dist/shared/providers/providerFactory.d.ts +16 -1
  58. package/dist/shared/providers/providerFactory.d.ts.map +1 -1
  59. package/dist/shared/providers/providerFactory.js +43 -0
  60. package/dist/shared/providers/providerFactory.js.map +1 -1
  61. package/dist/shared/providers/types.d.ts +40 -0
  62. package/dist/shared/providers/types.d.ts.map +1 -1
  63. package/dist/shared/quota/capacity.d.ts +8 -8
  64. package/dist/shared/quota/types.d.ts +4 -4
  65. package/dist/shared/repair/brokeredDispatch.d.ts +147 -0
  66. package/dist/shared/repair/brokeredDispatch.d.ts.map +1 -0
  67. package/dist/shared/repair/brokeredDispatch.js +220 -0
  68. package/dist/shared/repair/brokeredDispatch.js.map +1 -0
  69. package/dist/shared/repair/index.d.ts +2 -0
  70. package/dist/shared/repair/index.d.ts.map +1 -1
  71. package/dist/shared/repair/index.js +1 -0
  72. package/dist/shared/repair/index.js.map +1 -1
  73. package/package.json +1 -1
@@ -0,0 +1,262 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { runTracked, } from "audit-tools/shared";
4
+ import { normalizeGenericExternalResults, normalizeGenericExternalEdges, } from "../../adapters/normalizeExternal.js";
5
+ /**
6
+ * F5 — external analyzer acquisition engine.
7
+ *
8
+ * On-demand acquisition + ephemeral execution of mature, ecosystem-native
9
+ * analyzers (eslint, ruff, cargo-clippy, …). Distinct from the in-tree
10
+ * `LanguageAnalyzer` registry (which enriches the regex floor from a resolved
11
+ * npm package directory): F5 *runs an external tool as a subprocess* and
12
+ * normalizes its native output through the EXISTING adapter seam
13
+ * (`normalizeExternal.ts`) into {@link ExternalAnalyzerResults}, which then
14
+ * re-enters the graph/risk artifacts through the shared
15
+ * CCU-analyzer-merge-helper-seam pair.
16
+ *
17
+ * Design invariants (all enforced here, never by host discretion):
18
+ * - **No baked-in language→tool / OS→runner table.** Ecosystem is detected
19
+ * deterministically from repo marker files; the runner (npx/pipx/cargo/bundle)
20
+ * is capability-probed at RUNTIME (`--version`) before any tool spawn.
21
+ * - **Single subprocess-SPAWN admission chokepoint** (`admitSpawn`): EVERY
22
+ * non-DEFAULT candidate is gated on the per-run consent token regardless of
23
+ * its `AnalyzerSetting` (auto|ephemeral|permanent) — even a `permanent`,
24
+ * pre-installed tool cannot spawn without consent (CE-005). The small
25
+ * value-curated DEFAULT set runs without prompting.
26
+ * - **Own-vs-acquire boundary.** git-history / secret-scan are OWNED by F6 and
27
+ * are rejected at registration — never acquired here.
28
+ * - **Run-safety gate written once** (`runSafetyGate`): capability-probe, pin
29
+ * version, read-only/sandboxed argv, degrade-to-empty.
30
+ * - **Degrade-to-empty + report-skipped-never-silently.** Every candidate
31
+ * yields exactly one {@link ExternalAnalyzerToolStatus}; a failure never
32
+ * throws and never silently drops the candidate.
33
+ */
34
+ /** Tool ids OWNED by F6 (git-history mining / secret scanning). Never acquired. */
35
+ export const OWNED_TOOL_IDS = new Set([
36
+ "git-history",
37
+ "git-history-mining",
38
+ "secret-scan",
39
+ "secret-scanner",
40
+ "secrets",
41
+ ]);
42
+ /** Marker-file ecosystem detection — deterministic, no language→tool table. */
43
+ function hasAnyMarker(root, markers) {
44
+ return markers.some((m) => existsSync(join(root, m)));
45
+ }
46
+ export function detectNodeEcosystem(root) {
47
+ return hasAnyMarker(root, ["package.json"]);
48
+ }
49
+ export function detectPythonEcosystem(root) {
50
+ return hasAnyMarker(root, ["pyproject.toml", "setup.py", "setup.cfg", "requirements.txt"]);
51
+ }
52
+ export function detectRustEcosystem(root) {
53
+ return hasAnyMarker(root, ["Cargo.toml"]);
54
+ }
55
+ export function detectRubyEcosystem(root) {
56
+ return hasAnyMarker(root, ["Gemfile"]);
57
+ }
58
+ /**
59
+ * Map a runner to the capability-probe argv (`--version`) and the argv prefix
60
+ * used to run a pinned tool ephemerally. No OS→runner assumption is baked in:
61
+ * the probe is what proves the runner exists on THIS machine.
62
+ */
63
+ function runnerProbeArgv(runner) {
64
+ switch (runner) {
65
+ case "npx": return ["npx", "--version"];
66
+ case "pipx": return ["pipx", "--version"];
67
+ case "cargo": return ["cargo", "--version"];
68
+ case "bundle": return ["bundle", "--version"];
69
+ }
70
+ }
71
+ function runnerPrefix(runner, spec) {
72
+ switch (runner) {
73
+ // `-y` so the ephemeral fetch never blocks on a prompt; pinned spec.
74
+ case "npx": return ["npx", "-y", spec];
75
+ case "pipx": return ["pipx", "run", "--spec", spec];
76
+ case "cargo": return ["cargo", spec];
77
+ case "bundle": return ["bundle", "exec", spec];
78
+ }
79
+ }
80
+ /**
81
+ * Single subprocess-SPAWN admission chokepoint. Returns the reason a spawn is
82
+ * NOT admitted, or `undefined` when admitted. The DEFAULT set is admitted
83
+ * without a token; EVERY other candidate — including `permanent`/`ephemeral`
84
+ * pre-installed tools — requires the per-run consent token (CE-005).
85
+ */
86
+ export function admitSpawn(candidate, setting, consentToken) {
87
+ if (setting === "skip")
88
+ return "setting=skip";
89
+ if (candidate.defaultRun)
90
+ return undefined;
91
+ if (consentToken && consentToken.trim().length > 0)
92
+ return undefined;
93
+ return "non-default tool requires per-run consent token";
94
+ }
95
+ /**
96
+ * The run-safety gate, written once: capability-probe the runner, then (if the
97
+ * probe passes) the caller may spawn the pinned, read-only argv. Returns the
98
+ * probe outcome so a missing runner degrades to an empty result + a status.
99
+ */
100
+ export function runSafetyGate(candidate, run, root) {
101
+ // Pinned version is mandatory for reproducibility — a candidate without a
102
+ // pinned tool spec is never executed (degrades to empty + status).
103
+ if (!candidate.spec || candidate.spec.trim().length === 0) {
104
+ return { ok: false, reason: `tool '${candidate.id}' has no pinned version spec` };
105
+ }
106
+ const probe = run(runnerProbeArgv(candidate.runner), root);
107
+ if (probe.error || probe.status !== 0) {
108
+ return {
109
+ ok: false,
110
+ reason: `runner '${candidate.runner}' not available: ${probe.error?.message ?? `exit ${probe.status}`}`,
111
+ };
112
+ }
113
+ return { ok: true };
114
+ }
115
+ function emptyResults(tool) {
116
+ return { tool, generated_at: new Date().toISOString(), results: [] };
117
+ }
118
+ function settingFor(analyzers, id) {
119
+ return analyzers?.[id] ?? "auto";
120
+ }
121
+ /**
122
+ * Acquire + run ONE external analyzer candidate ephemerally, normalizing its
123
+ * output through the adapter seam. Never throws; always returns exactly one
124
+ * {@link ExternalAnalyzerToolStatus} alongside the (possibly empty) results.
125
+ */
126
+ export function runExternalAnalyzer(candidate, root, options = {}) {
127
+ if (OWNED_TOOL_IDS.has(candidate.id)) {
128
+ // Defence in depth — registration already rejects these.
129
+ return {
130
+ results: emptyResults(candidate.id),
131
+ status: {
132
+ tool: candidate.id,
133
+ resolved: false,
134
+ status: "skipped",
135
+ error: "owned by F6 (git-history/secret-scan); never acquired",
136
+ },
137
+ };
138
+ }
139
+ const run = options.run ?? ((argv, cwd) => runTracked(argv, { cwd }));
140
+ const log = options.log ?? (() => { });
141
+ const setting = settingFor(options.analyzers, candidate.id);
142
+ if (!candidate.detect(root)) {
143
+ return {
144
+ results: emptyResults(candidate.id),
145
+ status: { tool: candidate.id, resolved: false, status: "skipped", error: "ecosystem not detected" },
146
+ };
147
+ }
148
+ // Single spawn-admission chokepoint — consent gating before anything spawns.
149
+ const denied = admitSpawn(candidate, setting, options.consentToken);
150
+ if (denied) {
151
+ log("[f5] %s spawn not admitted: %s", candidate.id, denied);
152
+ return {
153
+ results: emptyResults(candidate.id),
154
+ status: { tool: candidate.id, resolved: false, status: "skipped", error: denied },
155
+ };
156
+ }
157
+ // Run-safety gate: capability-probe the runner before the real spawn.
158
+ const gate = runSafetyGate(candidate, run, root);
159
+ if (!gate.ok) {
160
+ log("[f5] %s safety gate failed: %s", candidate.id, gate.reason);
161
+ return {
162
+ results: emptyResults(candidate.id),
163
+ status: { tool: candidate.id, resolved: false, status: "not_resolved", error: gate.reason },
164
+ };
165
+ }
166
+ const argv = candidate.buildArgv(runnerPrefix(candidate.runner, candidate.spec), root);
167
+ const command = argv.join(" ");
168
+ let result;
169
+ try {
170
+ result = run(argv, root);
171
+ }
172
+ catch (error) {
173
+ return {
174
+ results: emptyResults(candidate.id),
175
+ status: {
176
+ tool: candidate.id,
177
+ command,
178
+ resolved: true,
179
+ status: "spawn_error",
180
+ error: error instanceof Error ? error.message : String(error),
181
+ },
182
+ };
183
+ }
184
+ if (result.error) {
185
+ return {
186
+ results: emptyResults(candidate.id),
187
+ status: {
188
+ tool: candidate.id,
189
+ command,
190
+ resolved: true,
191
+ status: "spawn_error",
192
+ exit_code: result.status,
193
+ error: result.error.message,
194
+ },
195
+ };
196
+ }
197
+ let items;
198
+ try {
199
+ items = candidate.parse(result.stdout);
200
+ }
201
+ catch (error) {
202
+ return {
203
+ results: emptyResults(candidate.id),
204
+ status: {
205
+ tool: candidate.id,
206
+ command,
207
+ resolved: true,
208
+ status: "parse_error",
209
+ exit_code: result.status,
210
+ error: error instanceof Error ? error.message : String(error),
211
+ output_snippet: result.stdout.slice(0, 200),
212
+ },
213
+ };
214
+ }
215
+ const normalized = normalizeGenericExternalResults(candidate.id, items);
216
+ const edges = normalizeGenericExternalEdges(items);
217
+ if (edges.length > 0)
218
+ normalized.graph_edges = edges;
219
+ return {
220
+ results: normalized,
221
+ status: {
222
+ tool: candidate.id,
223
+ command,
224
+ resolved: true,
225
+ status: normalized.results.length > 0 ? "findings" : "success",
226
+ exit_code: result.status,
227
+ },
228
+ };
229
+ }
230
+ /**
231
+ * Register external analyzer candidates, rejecting any whose id is OWNED by F6
232
+ * (git-history/secret-scan). Returns the accepted candidates; the own-vs-acquire
233
+ * boundary is enforced HERE so an owned tool can never enter the engine.
234
+ */
235
+ export function registerExternalAnalyzers(candidates) {
236
+ const accepted = [];
237
+ for (const candidate of candidates) {
238
+ if (OWNED_TOOL_IDS.has(candidate.id))
239
+ continue;
240
+ accepted.push(candidate);
241
+ }
242
+ return accepted;
243
+ }
244
+ /**
245
+ * Drive the whole registered candidate set through the engine for one repo,
246
+ * collecting one status per candidate. Owned tools are rejected at registration;
247
+ * each survivor passes through the spawn-admission chokepoint and run-safety gate.
248
+ */
249
+ export function runAcquisitionEngine(candidates, root, options = {}) {
250
+ const registered = registerExternalAnalyzers(candidates);
251
+ const results = [];
252
+ const statuses = [];
253
+ for (const candidate of registered) {
254
+ const outcome = runExternalAnalyzer(candidate, root, options);
255
+ statuses.push(outcome.status);
256
+ if (outcome.results.results.length > 0 || outcome.results.graph_edges?.length) {
257
+ results.push(outcome.results);
258
+ }
259
+ }
260
+ return { results, statuses };
261
+ }
262
+ //# sourceMappingURL=acquisitionEngine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acquisitionEngine.js","sourceRoot":"","sources":["../../../../src/audit/extractors/analyzers/acquisitionEngine.ts"],"names":[],"mappings":"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"}
@@ -9,6 +9,8 @@ import type { AnalyzerPlanEntry, LanguageAnalyzer } from "./types.js";
9
9
  */
10
10
  export declare const ANALYZER_REGISTRY: LanguageAnalyzer[];
11
11
  export declare function getAnalyzerById(id: string): LanguageAnalyzer | undefined;
12
+ export { OWNED_TOOL_IDS, admitSpawn, runSafetyGate, runExternalAnalyzer, registerExternalAnalyzers, runAcquisitionEngine, detectNodeEcosystem, detectPythonEcosystem, detectRustEcosystem, detectRubyEcosystem, } from "./acquisitionEngine.js";
13
+ export type { EcosystemRunner, ExternalAnalyzerCandidate, AcquisitionRunner, AcquisitionEngineOptions, AcquisitionOutcome, RunAllOutcome, } from "./acquisitionEngine.js";
12
14
  /**
13
15
  * Deterministically resolve, without installing anything, how each registered
14
16
  * analyzer would run for this repo. The conversation-first CLI uses this to
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../../src/audit/extractors/analyzers/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAOtE;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,EAAE,gBAAgB,EAM/C,CAAC;AAEF,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAExE;AA2BD;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,SAAS,EACtD,aAAa,EAAE,MAAM,EAAE,EACvB,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACnC,iBAAiB,EAAE,CAyBrB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAEtE"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../../src/audit/extractors/analyzers/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAOtE;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,EAAE,gBAAgB,EAM/C,CAAC;AAEF,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAExE;AAMD,OAAO,EACL,cAAc,EACd,UAAU,EACV,aAAa,EACb,mBAAmB,EACnB,yBAAyB,EACzB,oBAAoB,EACpB,mBAAmB,EACnB,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,eAAe,EACf,yBAAyB,EACzB,iBAAiB,EACjB,wBAAwB,EACxB,kBAAkB,EAClB,aAAa,GACd,MAAM,wBAAwB,CAAC;AA2BhC;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,SAAS,EACtD,aAAa,EAAE,MAAM,EAAE,EACvB,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACnC,iBAAiB,EAAE,CAyBrB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAEtE"}
@@ -21,6 +21,11 @@ export const ANALYZER_REGISTRY = [
21
21
  export function getAnalyzerById(id) {
22
22
  return ANALYZER_REGISTRY.find((analyzer) => analyzer.id === id);
23
23
  }
24
+ // F5 external analyzer acquisition engine (on-demand ecosystem-native tools run
25
+ // ephemerally + normalized through the adapter seam). Re-exported here so the
26
+ // analyzer registry is the single entry point for both the in-tree
27
+ // `LanguageAnalyzer` set and the acquired external set.
28
+ export { OWNED_TOOL_IDS, admitSpawn, runSafetyGate, runExternalAnalyzer, registerExternalAnalyzers, runAcquisitionEngine, detectNodeEcosystem, detectPythonEcosystem, detectRustEcosystem, detectRubyEcosystem, } from "./acquisitionEngine.js";
24
29
  function settingFor(analyzers, id) {
25
30
  return analyzers?.[id] ?? "auto";
26
31
  }
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../../src/audit/extractors/analyzers/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,GAEnB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEvC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAuB;IACnD,kBAAkB;IAClB,cAAc;IACd,YAAY;IACZ,WAAW;IACX,WAAW;CACZ,CAAC;AAEF,MAAM,UAAU,eAAe,CAAC,EAAU;IACxC,OAAO,iBAAiB,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AAClE,CAAC;AAED,SAAS,UAAU,CACjB,SAAsD,EACtD,EAAU;IAEV,OAAO,SAAS,EAAE,CAAC,EAAE,CAAC,IAAI,MAAM,CAAC;AACnC,CAAC;AAED,SAAS,SAAS,CAChB,QAA0B,EAC1B,OAAwB,EACxB,cAAsB,EACtB,UAA2C,EAC3C,IAAa;IAEb,MAAM,KAAK,GAAsB;QAC/B,EAAE,EAAE,QAAQ,CAAC,EAAE;QACf,UAAU,EAAE,QAAQ,CAAC,UAAU;QAC/B,OAAO;QACP,UAAU;QACV,cAAc;KACf,CAAC;IACF,IAAI,IAAI,KAAK,SAAS;QAAE,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC;IAC1C,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAAY,EACZ,SAAsD,EACtD,aAAuB,EACvB,UAAkC,EAAE;IAEpC,MAAM,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7E,OAAO,iBAAiB,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE;QACxC,MAAM,OAAO,GAAG,UAAU,CAAC,SAAS,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;QACnD,MAAM,cAAc,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CACnD,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CACxB,CAAC,MAAM,CAAC;QAET,IAAI,cAAc,KAAK,CAAC,EAAE,CAAC;YACzB,OAAO,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,cAAc,EAAE,gBAAgB,CAAC,CAAC;QACxE,CAAC;QACD,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;YACvB,OAAO,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC;QAC9D,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;YACzB,4CAA4C;YAC5C,OAAO,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC;QAC9D,CAAC;QAED,MAAM,QAAQ,GAAG,kBAAkB,CAAC,QAAQ,CAAC,UAAU,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;QAC3E,IAAI,QAAQ,CAAC,GAAG,KAAK,MAAM,IAAI,QAAQ,CAAC,GAAG,KAAK,OAAO,EAAE,CAAC;YACxD,OAAO,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,cAAc,EAAE,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;QACnF,CAAC;QACD,OAAO,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,cAAc,EAAE,QAAQ,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAwB;IAC3D,OAAO,KAAK,CAAC,UAAU,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,KAAK,MAAM,CAAC;AACnE,CAAC"}
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../../src/audit/extractors/analyzers/registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import type { GitHistory, GraphEdge, RiskRegister, RiskItem } from "audit-tools/shared";
2
+ import type { RepoManifest, UnitManifest } from "../types.js";
3
+ import type { FileDisposition } from "audit-tools/shared";
4
+ /**
5
+ * F6 — git-history mining.
6
+ *
7
+ * Deterministic extractor that turns the repository's commit log into the
8
+ * language-neutral `git_history.json` artifact: co-change (temporal coupling),
9
+ * churn (change frequency), and authorship breadth (bus-factor). All mining
10
+ * runs through `audit-tools/shared`'s `mineGitHistory`, which itself degrades to
11
+ * empty when git is unavailable / the log fails — so this extractor never
12
+ * throws and is safe to run on any tree (including a fresh, history-less repo).
13
+ *
14
+ * Declared upstream deps: `{repo_manifest, file_disposition}`. Both are used to
15
+ * scope mined paths to the audited file set (excluded / non-manifest paths are
16
+ * dropped) so downstream consumers see only in-scope signals.
17
+ */
18
+ declare const GIT_CO_CHANGE_EDGE_KIND = "git-co-change";
19
+ /**
20
+ * Mine `git_history.json` for `root`, keeping only paths present (and not
21
+ * audit-excluded) in the manifest/disposition. Output is deterministic: the
22
+ * shared miner already sorts every list by a total order, and the in-scope
23
+ * filter preserves that order.
24
+ */
25
+ export declare function mineGitHistoryArtifact(root: string, repoManifest: RepoManifest, disposition?: FileDisposition): GitHistory;
26
+ /**
27
+ * Project mined co-change pairs into language-neutral, undirected graph edges
28
+ * suitable for merging into the graph bundle via
29
+ * `mergeAnalyzerGraphContribution`. Confidence scales with co-change count
30
+ * (more shared commits → stronger coupling) up to a cap. Degrades to an empty
31
+ * list when there is no co-change history.
32
+ */
33
+ export declare function gitHistoryGraphEdges(history: GitHistory): GraphEdge[];
34
+ /**
35
+ * Derive per-unit git-history risk signals (`change_hotspot`, `broad_authorship`)
36
+ * keyed by `unit_id`, suitable for merging into the risk register via
37
+ * `mergeAnalyzerRiskSignals`. A unit inherits a signal when ANY of its files is
38
+ * so flagged. These are informational signals (they widen the picture, they do
39
+ * not by themselves saturate risk). Degrades to empty when history is empty.
40
+ */
41
+ export declare function gitHistoryRiskSignals(history: GitHistory, unitManifest: UnitManifest): Map<string, string[]>;
42
+ export type { GitHistory } from "audit-tools/shared";
43
+ export { GIT_CO_CHANGE_EDGE_KIND };
44
+ export type RiskRegisterForMerge = RiskRegister;
45
+ export type RiskItemForMerge = RiskItem;
46
+ //# sourceMappingURL=gitHistory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitHistory.d.ts","sourceRoot":"","sources":["../../../src/audit/extractors/gitHistory.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,UAAU,EACV,SAAS,EACT,YAAY,EACZ,QAAQ,EACT,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAI1D;;;;;;;;;;;;;GAaG;AAEH,QAAA,MAAM,uBAAuB,kBAAkB,CAAC;AAchD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,YAAY,EAC1B,WAAW,CAAC,EAAE,eAAe,GAC5B,UAAU,CAoBZ;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,UAAU,GAAG,SAAS,EAAE,CAcrE;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,UAAU,EACnB,YAAY,EAAE,YAAY,GACzB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAuBvB;AAED,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,uBAAuB,EAAE,CAAC;AACnC,MAAM,MAAM,oBAAoB,GAAG,YAAY,CAAC;AAChD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC"}
@@ -0,0 +1,97 @@
1
+ import { mineGitHistory } from "audit-tools/shared";
2
+ import { buildDispositionMap, isAuditExcludedStatus } from "./disposition.js";
3
+ import { graphEdge, graphLookupKey } from "./graphPathUtils.js";
4
+ /**
5
+ * F6 — git-history mining.
6
+ *
7
+ * Deterministic extractor that turns the repository's commit log into the
8
+ * language-neutral `git_history.json` artifact: co-change (temporal coupling),
9
+ * churn (change frequency), and authorship breadth (bus-factor). All mining
10
+ * runs through `audit-tools/shared`'s `mineGitHistory`, which itself degrades to
11
+ * empty when git is unavailable / the log fails — so this extractor never
12
+ * throws and is safe to run on any tree (including a fresh, history-less repo).
13
+ *
14
+ * Declared upstream deps: `{repo_manifest, file_disposition}`. Both are used to
15
+ * scope mined paths to the audited file set (excluded / non-manifest paths are
16
+ * dropped) so downstream consumers see only in-scope signals.
17
+ */
18
+ const GIT_CO_CHANGE_EDGE_KIND = "git-co-change";
19
+ /**
20
+ * Co-change is a soft, historical signal (files that *happened* to change
21
+ * together) — deliberately below the structural reference confidences so it
22
+ * never outweighs a resolved import/reference edge.
23
+ */
24
+ const CO_CHANGE_BASE_CONFIDENCE = 0.4;
25
+ const CO_CHANGE_CONFIDENCE_CAP = 0.75;
26
+ /** Authorship floor at/above which a unit is flagged broadly-owned. */
27
+ const BROAD_AUTHORSHIP_FLOOR = 4;
28
+ /** Churn floor at/above which a unit is flagged a change hotspot. */
29
+ const CHURN_HOTSPOT_FLOOR = 8;
30
+ /**
31
+ * Mine `git_history.json` for `root`, keeping only paths present (and not
32
+ * audit-excluded) in the manifest/disposition. Output is deterministic: the
33
+ * shared miner already sorts every list by a total order, and the in-scope
34
+ * filter preserves that order.
35
+ */
36
+ export function mineGitHistoryArtifact(root, repoManifest, disposition) {
37
+ const history = mineGitHistory(root);
38
+ const dispositionMap = buildDispositionMap(disposition);
39
+ const inScope = new Set();
40
+ for (const file of repoManifest.files) {
41
+ if (file.excluded)
42
+ continue;
43
+ const status = dispositionMap.get(file.path);
44
+ if (status && isAuditExcludedStatus(status))
45
+ continue;
46
+ inScope.add(graphLookupKey(file.path));
47
+ }
48
+ const known = (path) => inScope.has(graphLookupKey(path));
49
+ return {
50
+ co_change: history.co_change.filter((pair) => known(pair.a) && known(pair.b)),
51
+ churn: history.churn.filter((entry) => known(entry.path)),
52
+ authorship: history.authorship.filter((entry) => known(entry.path)),
53
+ };
54
+ }
55
+ /**
56
+ * Project mined co-change pairs into language-neutral, undirected graph edges
57
+ * suitable for merging into the graph bundle via
58
+ * `mergeAnalyzerGraphContribution`. Confidence scales with co-change count
59
+ * (more shared commits → stronger coupling) up to a cap. Degrades to an empty
60
+ * list when there is no co-change history.
61
+ */
62
+ export function gitHistoryGraphEdges(history) {
63
+ return history.co_change.map((pair) => graphEdge({
64
+ from: pair.a,
65
+ to: pair.b,
66
+ kind: GIT_CO_CHANGE_EDGE_KIND,
67
+ direction: "undirected",
68
+ confidence: Math.min(CO_CHANGE_CONFIDENCE_CAP, CO_CHANGE_BASE_CONFIDENCE + 0.05 * (pair.commits - 1)),
69
+ reason: `Files changed together in ${pair.commits} commit(s) (temporal coupling).`,
70
+ }));
71
+ }
72
+ /**
73
+ * Derive per-unit git-history risk signals (`change_hotspot`, `broad_authorship`)
74
+ * keyed by `unit_id`, suitable for merging into the risk register via
75
+ * `mergeAnalyzerRiskSignals`. A unit inherits a signal when ANY of its files is
76
+ * so flagged. These are informational signals (they widen the picture, they do
77
+ * not by themselves saturate risk). Degrades to empty when history is empty.
78
+ */
79
+ export function gitHistoryRiskSignals(history, unitManifest) {
80
+ const churnByPath = new Map(history.churn.map((e) => [e.path, e.commits]));
81
+ const authorsByPath = new Map(history.authorship.map((e) => [e.path, e.authors]));
82
+ const signalsByUnit = new Map();
83
+ for (const unit of unitManifest.units) {
84
+ const signals = [];
85
+ if (unit.files.some((path) => (churnByPath.get(path) ?? 0) >= CHURN_HOTSPOT_FLOOR)) {
86
+ signals.push("change_hotspot");
87
+ }
88
+ if (unit.files.some((path) => (authorsByPath.get(path) ?? 0) >= BROAD_AUTHORSHIP_FLOOR)) {
89
+ signals.push("broad_authorship");
90
+ }
91
+ if (signals.length > 0)
92
+ signalsByUnit.set(unit.unit_id, signals);
93
+ }
94
+ return signalsByUnit;
95
+ }
96
+ export { GIT_CO_CHANGE_EDGE_KIND };
97
+ //# sourceMappingURL=gitHistory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitHistory.js","sourceRoot":"","sources":["../../../src/audit/extractors/gitHistory.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import type { RepoManifest } from "../types.js";
2
- import type { FileDisposition, GraphBundle } from "audit-tools/shared";
2
+ import type { FileDisposition, GraphBundle, GraphEdge } from "audit-tools/shared";
3
3
  import type { ExternalAnalyzerResults } from "../types/externalAnalyzer.js";
4
4
  export interface BuildGraphBundleOptions {
5
5
  fileContents?: Record<string, string>;
@@ -7,5 +7,23 @@ export interface BuildGraphBundleOptions {
7
7
  }
8
8
  export declare function buildPathLookup(repoManifest: RepoManifest, dispositionMap: Map<string, FileDisposition["files"][number]["status"]>): Map<string, string>;
9
9
  export declare function buildGraphBundleFromFs(repoManifest: RepoManifest, root: string, disposition?: FileDisposition, options?: Pick<BuildGraphBundleOptions, "externalAnalyzerResults">): Promise<GraphBundle>;
10
+ /**
11
+ * CCU-analyzer-merge-helper-seam (graph half).
12
+ *
13
+ * Append an analyzer's contributed edges into a graph bundle's edge categories,
14
+ * returning a NEW bundle (the input is never mutated). This is the single,
15
+ * pre-shipped seam through which any post-build analyzer contribution —
16
+ * git-history co-change (F6), and any later F5+ analyzer — re-enters the graph,
17
+ * so contributions can never drift in how they merge.
18
+ *
19
+ * Merged into `graphs.references` by default (the open-ended cross-file edge
20
+ * category) under one `category` key, then deduped + sorted by the same
21
+ * `uniqueSortedEdges` the build uses, so the result is deterministic and an edge
22
+ * already present (same from/to/kind) is idempotent. Degrades to the original
23
+ * bundle (structurally cloned) when `edges` is empty / not an array.
24
+ */
25
+ export declare function mergeAnalyzerGraphContribution(bundle: GraphBundle, edges: GraphEdge[] | undefined, options?: {
26
+ category?: string;
27
+ }): GraphBundle;
10
28
  export declare function buildGraphBundle(repoManifest: RepoManifest, disposition?: FileDisposition, options?: BuildGraphBundleOptions): GraphBundle;
11
29
  //# sourceMappingURL=graph.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../../src/audit/extractors/graph.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EACV,eAAe,EACf,WAAW,EAIZ,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAgD5E,MAAM,WAAW,uBAAuB;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,uBAAuB,CAAC,EAAE,uBAAuB,CAAC;CACnD;AAmED,wBAAgB,eAAe,CAC7B,YAAY,EAAE,YAAY,EAC1B,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC,GACtE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CASrB;AAmTD,wBAAsB,sBAAsB,CAC1C,YAAY,EAAE,YAAY,EAC1B,IAAI,EAAE,MAAM,EACZ,WAAW,CAAC,EAAE,eAAe,EAC7B,OAAO,GAAE,IAAI,CAAC,uBAAuB,EAAE,yBAAyB,CAAM,GACrE,OAAO,CAAC,WAAW,CAAC,CA4CtB;AAsND,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,YAAY,EAC1B,WAAW,CAAC,EAAE,eAAe,EAC7B,OAAO,GAAE,uBAA4B,GACpC,WAAW,CA8Db"}
1
+ {"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../../src/audit/extractors/graph.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EACV,eAAe,EACf,WAAW,EACX,SAAS,EAGV,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAgD5E,MAAM,WAAW,uBAAuB;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,uBAAuB,CAAC,EAAE,uBAAuB,CAAC;CACnD;AAmED,wBAAgB,eAAe,CAC7B,YAAY,EAAE,YAAY,EAC1B,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC,GACtE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CASrB;AAmTD,wBAAsB,sBAAsB,CAC1C,YAAY,EAAE,YAAY,EAC1B,IAAI,EAAE,MAAM,EACZ,WAAW,CAAC,EAAE,eAAe,EAC7B,OAAO,GAAE,IAAI,CAAC,uBAAuB,EAAE,yBAAyB,CAAM,GACrE,OAAO,CAAC,WAAW,CAAC,CA4CtB;AAsND;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,SAAS,EAAE,GAAG,SAAS,EAC9B,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,WAAW,CAeb;AAED,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,YAAY,EAC1B,WAAW,CAAC,EAAE,eAAe,EAC7B,OAAO,GAAE,uBAA4B,GACpC,WAAW,CA8Db"}
@@ -474,6 +474,37 @@ function accumulateCrossFileEdges(acc, pathLookup, options) {
474
474
  acc.references.push(...extractPytestConftestLinks(pathLookup));
475
475
  acc.references.push(...extractBoundedSuiteEdges(pathLookup, options.fileContents ?? {}, acc.references));
476
476
  }
477
+ /**
478
+ * CCU-analyzer-merge-helper-seam (graph half).
479
+ *
480
+ * Append an analyzer's contributed edges into a graph bundle's edge categories,
481
+ * returning a NEW bundle (the input is never mutated). This is the single,
482
+ * pre-shipped seam through which any post-build analyzer contribution —
483
+ * git-history co-change (F6), and any later F5+ analyzer — re-enters the graph,
484
+ * so contributions can never drift in how they merge.
485
+ *
486
+ * Merged into `graphs.references` by default (the open-ended cross-file edge
487
+ * category) under one `category` key, then deduped + sorted by the same
488
+ * `uniqueSortedEdges` the build uses, so the result is deterministic and an edge
489
+ * already present (same from/to/kind) is idempotent. Degrades to the original
490
+ * bundle (structurally cloned) when `edges` is empty / not an array.
491
+ */
492
+ export function mergeAnalyzerGraphContribution(bundle, edges, options = {}) {
493
+ const category = options.category ?? "references";
494
+ const existing = bundle.graphs;
495
+ const existingEdges = Array.isArray(existing[category])
496
+ ? existing[category]
497
+ : [];
498
+ const contributed = Array.isArray(edges) ? edges : [];
499
+ const merged = uniqueSortedEdges([...existingEdges, ...contributed]);
500
+ return {
501
+ ...bundle,
502
+ graphs: {
503
+ ...bundle.graphs,
504
+ [category]: merged,
505
+ },
506
+ };
507
+ }
477
508
  export function buildGraphBundle(repoManifest, disposition, options = {}) {
478
509
  const acc = {
479
510
  imports: [],