@thomasminh1995/depverdict 0.6.0-alpha.1

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 (252) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +518 -0
  3. package/bin/depverdict.js +7 -0
  4. package/bin/upgradelens.js +7 -0
  5. package/docs/GR-01-Semantic-Grounding-Failure-Analysis.md +309 -0
  6. package/docs/GR-02-Versioned-Action-Evaluation-Criteria.md +187 -0
  7. package/docs/GR-03-Extractive-Contract-Safety-Experiment.md +242 -0
  8. package/docs/GR-04-Versioned-Production-Extractive-Contract.md +154 -0
  9. package/docs/IA-01-Repository-Usage-Discovery.md +122 -0
  10. package/docs/IA-02-Repository-Impact-Analysis.md +118 -0
  11. package/docs/IA-03-Repository-Impact-Evidence.md +160 -0
  12. package/docs/IA-04-CLI-Orchestration.md +235 -0
  13. package/docs/IA-05-Real-Provider-Validation.md +235 -0
  14. package/docs/IA-05-VinGrade-Validation.md +339 -0
  15. package/docs/MVP-01.md +48 -0
  16. package/docs/MVP-02-Architecture.md +536 -0
  17. package/docs/MVP-02-CLI-HTTP-Runtime.md +50 -0
  18. package/docs/MVP-02-HTTP-Lifecycle.md +37 -0
  19. package/docs/MVP-02-Knowledge-Manifest-Generation.md +75 -0
  20. package/docs/MVP-02-Knowledge-Manifest.md +279 -0
  21. package/docs/MVP-02-Knowledge-Research-Orchestration.md +62 -0
  22. package/docs/MVP-02-Knowledge-Store.md +87 -0
  23. package/docs/MVP-02-PyPI-Registry-Adapter.md +97 -0
  24. package/docs/MVP-02-Research-Planning.md +196 -0
  25. package/docs/MVP-02-Source-Provenance.md +81 -0
  26. package/docs/MVP-02-npm-Registry-Adapter.md +99 -0
  27. package/docs/RR-01-End-to-End-and-Real-Provider-Validation.md +393 -0
  28. package/docs/RR-01-RERUN-Extractive-Contract-Validation.md +566 -0
  29. package/docs/RR-02-Full-Product-Workflow-and-Developer-CLI-UX-Review.md +35 -0
  30. package/docs/RR02-FIX-01-Persistent-Qualification-Resolution.md +32 -0
  31. package/docs/RR02-FIX-02-Stage-aware-CLI-Progress-and-Heartbeat.md +28 -0
  32. package/docs/RR02-FIX-03-npm-Capture-Evidence-Exclusion.md +33 -0
  33. package/docs/RR02-FIX-03A-Complete-Package-Exclusion-and-Evidence-Commit.md +223 -0
  34. package/docs/RR02-FIX-04-Event-loop-safe-Heartbeat.md +393 -0
  35. package/docs/RR02-FIX-05-Materialize-Persisted-Qualification.md +207 -0
  36. package/docs/RR02-RERUN-CLI-Qualification-Progress-UX-and-Package-Validation.md +342 -0
  37. package/docs/VinGrade-MVP-02-Validation.md +480 -0
  38. package/docs/VinGrade-RC02-Live-Validation.md +273 -0
  39. package/docs/ai-capability-discovery.md +573 -0
  40. package/docs/ai-engineering-production-readiness.md +390 -0
  41. package/docs/ai-engineering-review.md +251 -0
  42. package/docs/ai-runtime-governance-discovery.md +754 -0
  43. package/docs/architecture-overview.md +78 -0
  44. package/docs/cli-progress.md +111 -0
  45. package/docs/decisions/diff-01-brand-distribution-identity.md +450 -0
  46. package/docs/decisions/diff-02-identity-compatibility-contract.md +274 -0
  47. package/docs/decisions/diff-03-repository-docs-community-migration.md +165 -0
  48. package/docs/decisions/diff-04-release-evidence-gap-acceptance.md +110 -0
  49. package/docs/discovery/mvp-05-ai-migration-planning-discovery.md +350 -0
  50. package/docs/gateway-runtime-discovery.md +614 -0
  51. package/docs/live-ai-validation.md +299 -0
  52. package/docs/migration-planning-qualification-resolution.md +99 -0
  53. package/docs/migrations/upgradelens-to-depverdict.md +97 -0
  54. package/docs/mp-r03-deterministic-upgrade-decision-architecture.md +135 -0
  55. package/docs/mp-r04-evidence-bounded-migration-handoff-architecture.md +116 -0
  56. package/docs/mp-r05-product-completion-and-decision-first-cli-architecture.md +162 -0
  57. package/docs/mvp-05-deterministic-context-runtime.md +127 -0
  58. package/docs/mvp-05-migration-checklist-contract.md +166 -0
  59. package/docs/mvp-05-migration-checklist-orchestration.md +105 -0
  60. package/docs/mvp-05-migration-evaluation-and-qualification.md +135 -0
  61. package/docs/mvp-05-provider-neutral-generator.md +79 -0
  62. package/docs/ollama-local-smoke-validation.md +156 -0
  63. package/docs/openai-compatible-runtime-discovery.md +789 -0
  64. package/docs/openrouter-one-dependency-validation.md +205 -0
  65. package/docs/oss-02-package-guard-hardening-architecture.md +131 -0
  66. package/docs/oss-04-public-ci-package-metadata-architecture.md +142 -0
  67. package/docs/package-content-policy.md +98 -0
  68. package/docs/releases/v0.5.0-technical-preview.md +114 -0
  69. package/docs/releases/v0.6.0-alpha.1-depverdict-preview.md +101 -0
  70. package/docs/reviews/diff-02-identity-contract-compatibility.md +413 -0
  71. package/docs/reviews/diff-03-repository-docs-community-migration.md +364 -0
  72. package/docs/reviews/diff-04-depverdict-distribution-identity-readiness-rereview.md +453 -0
  73. package/docs/reviews/diff-04-fix-post-rename-identity-release-remediation.md +310 -0
  74. package/docs/reviews/diff-05-final-preview-distribution-qualification.md +561 -0
  75. package/docs/reviews/mvp-05-final-product-value-workflow-rereview.md +471 -0
  76. package/docs/reviews/mvp-05-product-workflow-review.md +605 -0
  77. package/docs/reviews/oss-01-duplicate-artifact-investigation-cleanup.md +411 -0
  78. package/docs/reviews/oss-02-package-guard-hardening.md +303 -0
  79. package/docs/reviews/oss-03-community-scaffolding.md +378 -0
  80. package/docs/reviews/oss-04-public-ci-package-metadata.md +439 -0
  81. package/docs/reviews/oss-05-technical-preview-qualification.md +482 -0
  82. package/docs/reviews/upgradelens-vs-upgradedepdetective-source-comparison.md +355 -0
  83. package/docs/reviews/v0.5.0-pre-release-smoke.md +263 -0
  84. package/docs/reviews/v0.5.0-version-bump-release-verification.md +306 -0
  85. package/docs/runtime-contract-discovery.md +521 -0
  86. package/docs/structured-output-compatibility-report.md +100 -0
  87. package/docs/ts-fix-01-exact-duplicate-occurrence-target-selection-architecture.md +111 -0
  88. package/docs/version-analysis-architecture.md +827 -0
  89. package/eval/README.md +86 -0
  90. package/eval/datasets/generic/declared-constraint.json +59 -0
  91. package/eval/datasets/generic/evidence-conflict.json +73 -0
  92. package/eval/datasets/generic/major-breaking-release.json +66 -0
  93. package/eval/datasets/generic/missing-evidence.json +46 -0
  94. package/eval/datasets/generic/patch-release-low.json +59 -0
  95. package/eval/datasets/node/axios-patch-low.json +59 -0
  96. package/eval/datasets/node/react-major-breaking.json +66 -0
  97. package/eval/datasets/node/react-minor-compatibility.json +66 -0
  98. package/eval/datasets/python/fastapi-deprecation.json +66 -0
  99. package/eval/datasets/python/pydantic-major-breaking.json +66 -0
  100. package/eval/migration-planning/golden-dataset-v2.json +306 -0
  101. package/eval/migration-planning/golden-dataset.json +214 -0
  102. package/eval/schemas/expected-result.schema.json +88 -0
  103. package/eval/schemas/golden-case.schema.json +181 -0
  104. package/package.json +57 -0
  105. package/schemas/ai-scorecard.schema.json +132 -0
  106. package/schemas/benchmark-report.schema.json +189 -0
  107. package/schemas/benchmark.schema.json +55 -0
  108. package/schemas/capability-profile.schema.json +44 -0
  109. package/schemas/conformance-report.schema.json +150 -0
  110. package/schemas/deployment-profile.schema.json +64 -0
  111. package/schemas/evaluation-report.schema.json +158 -0
  112. package/schemas/knowledge-evidence-bundle.schema.json +150 -0
  113. package/schemas/knowledge-manifest.schema.json +548 -0
  114. package/schemas/metrics.schema.json +178 -0
  115. package/schemas/migration-checklist-extractive-candidate.schema.json +42 -0
  116. package/schemas/migration-checklist.schema.json +706 -0
  117. package/schemas/migration-evaluation-dataset-v2.schema.json +208 -0
  118. package/schemas/migration-evaluation-dataset.schema.json +204 -0
  119. package/schemas/migration-planning-qualification-record.schema.json +234 -0
  120. package/schemas/project-manifest.schema.json +308 -0
  121. package/schemas/qualification-record.schema.json +56 -0
  122. package/schemas/repository-impact-evidence.schema.json +232 -0
  123. package/schemas/repository-impact.schema.json +202 -0
  124. package/schemas/upgrade-decision.schema.json +273 -0
  125. package/schemas/usage-index.schema.json +179 -0
  126. package/schemas/version-analysis.schema.json +449 -0
  127. package/src/ai-runtime-debug.js +325 -0
  128. package/src/ai-runtime-error.js +42 -0
  129. package/src/ai-runtime.js +174 -0
  130. package/src/ai-scorecard.js +204 -0
  131. package/src/ai-version-analysis.js +484 -0
  132. package/src/artifact-root-compatibility.js +91 -0
  133. package/src/benchmark-report.js +111 -0
  134. package/src/benchmark-runner.js +191 -0
  135. package/src/canonical-json.js +69 -0
  136. package/src/cli.js +1299 -0
  137. package/src/conformance-report.js +158 -0
  138. package/src/conformance-runner.js +253 -0
  139. package/src/constants.js +73 -0
  140. package/src/cooperative-scheduler.js +79 -0
  141. package/src/dependencies.js +44 -0
  142. package/src/dependency-ai-context.js +625 -0
  143. package/src/detectors.js +253 -0
  144. package/src/discovery.js +234 -0
  145. package/src/ecosystem-version-adapter.js +294 -0
  146. package/src/environment-compatibility.js +77 -0
  147. package/src/evaluation-comparator.js +158 -0
  148. package/src/evaluation-report.js +76 -0
  149. package/src/evaluation-runner.js +248 -0
  150. package/src/evidence-source-adapter.js +472 -0
  151. package/src/files.js +89 -0
  152. package/src/governance-diagnostics.js +64 -0
  153. package/src/governance-loader.js +63 -0
  154. package/src/governance-metadata.js +346 -0
  155. package/src/governance-validator.js +360 -0
  156. package/src/http/bounded-fetch.js +278 -0
  157. package/src/http/cli-http-runtime.js +44 -0
  158. package/src/impact/input-loader.js +157 -0
  159. package/src/impact/matcher.js +40 -0
  160. package/src/impact/repository-impact.js +199 -0
  161. package/src/impact/runtime.js +24 -0
  162. package/src/impact/status.js +62 -0
  163. package/src/impact/writer.js +30 -0
  164. package/src/impact-evidence/input-loader.js +202 -0
  165. package/src/impact-evidence/repository-impact-evidence.js +234 -0
  166. package/src/impact-evidence/runtime.js +16 -0
  167. package/src/impact-evidence/writer.js +30 -0
  168. package/src/index.js +563 -0
  169. package/src/installed-version-baseline.js +196 -0
  170. package/src/knowledge-cache.js +324 -0
  171. package/src/knowledge-evidence-bundle.js +101 -0
  172. package/src/knowledge-evidence-producer.js +233 -0
  173. package/src/knowledge-manifest-builder.js +188 -0
  174. package/src/knowledge-manifest-writer.js +32 -0
  175. package/src/knowledge-manifest.js +255 -0
  176. package/src/knowledge-research.js +615 -0
  177. package/src/metrics-engine.js +205 -0
  178. package/src/migration-checklist/ai-candidate.js +320 -0
  179. package/src/migration-checklist/assembler.js +37 -0
  180. package/src/migration-checklist/context-runtime.js +828 -0
  181. package/src/migration-checklist/evaluation/action-criteria.js +244 -0
  182. package/src/migration-checklist/evaluation/comparator-v2.js +332 -0
  183. package/src/migration-checklist/evaluation/comparator.js +279 -0
  184. package/src/migration-checklist/evaluation/dataset-v2.js +227 -0
  185. package/src/migration-checklist/evaluation/dataset.js +336 -0
  186. package/src/migration-checklist/evaluation/extractive-fixtures-v2.js +148 -0
  187. package/src/migration-checklist/evaluation/metrics-v2.js +226 -0
  188. package/src/migration-checklist/evaluation/metrics.js +158 -0
  189. package/src/migration-checklist/evaluation/qualification-v2.js +321 -0
  190. package/src/migration-checklist/evaluation/qualification.js +239 -0
  191. package/src/migration-checklist/evaluation/runner-v2.js +294 -0
  192. package/src/migration-checklist/evaluation/runner.js +194 -0
  193. package/src/migration-checklist/evaluation/scorecard-v2.js +106 -0
  194. package/src/migration-checklist/evaluation/scorecard.js +86 -0
  195. package/src/migration-checklist/extractive-candidate.js +166 -0
  196. package/src/migration-checklist/extractive-prompt.js +62 -0
  197. package/src/migration-checklist/generator.js +702 -0
  198. package/src/migration-checklist/grounding-policy.js +117 -0
  199. package/src/migration-checklist/input-loader.js +613 -0
  200. package/src/migration-checklist/migration-checklist.js +635 -0
  201. package/src/migration-checklist/presentation.js +292 -0
  202. package/src/migration-checklist/progress.js +96 -0
  203. package/src/migration-checklist/prompt.js +83 -0
  204. package/src/migration-checklist/qualification-guard.js +462 -0
  205. package/src/migration-checklist/qualification-resolution.js +122 -0
  206. package/src/migration-checklist/qualification-store.js +225 -0
  207. package/src/migration-checklist/runtime.js +205 -0
  208. package/src/migration-checklist/verification.js +134 -0
  209. package/src/migration-checklist/writer.js +35 -0
  210. package/src/openai-compatible-provider.js +451 -0
  211. package/src/orchestration/failure-log.js +32 -0
  212. package/src/orchestration/pipeline.js +200 -0
  213. package/src/orchestration/progress-events.js +337 -0
  214. package/src/orchestration/progress-reporter.js +131 -0
  215. package/src/orchestration/text-writer.js +22 -0
  216. package/src/portable.js +13 -0
  217. package/src/product-completion.js +249 -0
  218. package/src/project-manifest-input.js +90 -0
  219. package/src/project-manifest.js +141 -0
  220. package/src/python-requirements.js +137 -0
  221. package/src/registry/npm-packument.js +256 -0
  222. package/src/registry/npm-registry-adapter.js +262 -0
  223. package/src/registry/pypi-project.js +300 -0
  224. package/src/registry/pypi-registry-adapter.js +235 -0
  225. package/src/registry/sanitize-registry-body.js +51 -0
  226. package/src/renderers/console.js +160 -0
  227. package/src/renderers/impact-presentation.js +278 -0
  228. package/src/renderers/markdown.js +172 -0
  229. package/src/research-plan.js +455 -0
  230. package/src/runtime-conformance.js +275 -0
  231. package/src/source-provenance.js +393 -0
  232. package/src/source-url.js +62 -0
  233. package/src/structured-output-schema.js +66 -0
  234. package/src/target-selector.js +306 -0
  235. package/src/upgrade-decision/input-loader.js +107 -0
  236. package/src/upgrade-decision/presentation.js +43 -0
  237. package/src/upgrade-decision/runtime.js +21 -0
  238. package/src/upgrade-decision/upgrade-decision.js +626 -0
  239. package/src/upgrade-decision/writer.js +30 -0
  240. package/src/usage/analyzer-registry.js +63 -0
  241. package/src/usage/coverage.js +116 -0
  242. package/src/usage/input-loader.js +139 -0
  243. package/src/usage/js/analyzer.js +240 -0
  244. package/src/usage/js/parser.js +21 -0
  245. package/src/usage/runtime.js +187 -0
  246. package/src/usage/scope.js +44 -0
  247. package/src/usage/source-files.js +50 -0
  248. package/src/usage/usage-index.js +217 -0
  249. package/src/usage/writer.js +31 -0
  250. package/src/version-analysis-loader.js +203 -0
  251. package/src/version-analysis-manifest.js +314 -0
  252. package/src/version-analysis-writer.js +30 -0
@@ -0,0 +1,234 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFile } from 'node:fs/promises';
3
+
4
+ import Ajv2020 from 'ajv/dist/2020.js';
5
+ import addFormats from 'ajv-formats';
6
+
7
+ import { canonicalJsonBytes } from '../canonical-json.js';
8
+ import {
9
+ ARTIFACT_GENERATOR_NAME,
10
+ REPOSITORY_IMPACT_EVIDENCE_SCHEMA_VERSION,
11
+ VERSION
12
+ } from '../constants.js';
13
+ import { isMatchableUsageSymbol } from '../impact/matcher.js';
14
+ import {
15
+ classifyDependencyImpact,
16
+ classifyFindingImpact
17
+ } from '../impact/status.js';
18
+ import { compareText, isSorted } from '../portable.js';
19
+ import { coverageForProject } from '../usage/coverage.js';
20
+
21
+ export const IMPACT_EVIDENCE_GENERATOR_ID = 'repository-impact-evidence';
22
+ export const IMPACT_EVIDENCE_GENERATOR_VERSION = '1.0.0';
23
+ export const IMPACT_EVIDENCE_REASON_CODES = Object.freeze([
24
+ 'DEPENDENCY_NOT_USED',
25
+ 'EXACT_SYMBOL_USAGE_FOUND',
26
+ 'NO_EXACT_SYMBOL_USAGE_FOUND',
27
+ 'NO_MATCHABLE_SYMBOL_FOUND',
28
+ 'USAGE_NOT_FOUND',
29
+ 'COVERAGE_UNAVAILABLE',
30
+ 'NOT_ANALYZED'
31
+ ]);
32
+
33
+ const schema = JSON.parse(await readFile(
34
+ new URL('../../schemas/repository-impact-evidence.schema.json', import.meta.url),
35
+ 'utf8'
36
+ ));
37
+ const ajv = new Ajv2020({ allErrors: true, strict: true });
38
+ addFormats(ajv);
39
+ const validateSchema = ajv.compile(schema);
40
+
41
+ const compareDependencies = (left, right) => (
42
+ compareText(left.projectId, right.projectId)
43
+ || compareText(left.packageId, right.packageId)
44
+ || compareText(left.analysisResultId, right.analysisResultId)
45
+ );
46
+ const compareFindings = (left, right) => compareText(left.findingId, right.findingId);
47
+ const compareSymbols = (left, right) => compareText(left.symbol, right.symbol);
48
+ const compareUsages = (left, right) => compareText(left.file, right.file);
49
+
50
+ function evidenceId(analysisResultId, findingId) {
51
+ const bytes = canonicalJsonBytes({ analysisResultId, findingId });
52
+ return `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
53
+ }
54
+
55
+ function legacyFindingState(finding, dependencyUsage, coverage) {
56
+ const state = classifyFindingImpact({
57
+ coverage,
58
+ usage: dependencyUsage,
59
+ matches: finding.matches
60
+ });
61
+ if (state.status === 'NOT_IMPACTED'
62
+ && dependencyUsage
63
+ && !dependencyUsage.symbols.some((usage) => isMatchableUsageSymbol(usage.name))) {
64
+ return { ...state, reasonCode: 'NO_MATCHABLE_SYMBOL_FOUND' };
65
+ }
66
+ return state;
67
+ }
68
+
69
+ function evidenceFindingState(state) {
70
+ if (state.status === 'COVERAGE_UNAVAILABLE') {
71
+ return { ...state, reasonCode: 'COVERAGE_UNAVAILABLE' };
72
+ }
73
+ if (state.status === 'NOT_ANALYZED') return { ...state, reasonCode: 'NOT_ANALYZED' };
74
+ if (state.status === 'USAGE_NOT_FOUND') return { ...state, reasonCode: 'USAGE_NOT_FOUND' };
75
+ return state;
76
+ }
77
+
78
+ function buildDependencies(repositoryImpact, usageIndex) {
79
+ const usages = new Map(usageIndex.dependencies.map((usage) => (
80
+ [`${usage.projectId}\0${usage.packageId}`, usage]
81
+ )));
82
+ return repositoryImpact.dependencies.map((dependency) => {
83
+ const usage = usages.get(`${dependency.projectId}\0${dependency.packageId}`) ?? null;
84
+ const coverage = dependency.coverage ?? coverageForProject(usageIndex, dependency.projectId);
85
+ const findings = dependency.findings.map((finding) => {
86
+ const state = evidenceFindingState(finding.status
87
+ ? { status: finding.status, reasonCode: finding.reasonCode }
88
+ : legacyFindingState(finding, usage, coverage));
89
+ return {
90
+ id: evidenceId(dependency.analysisResultId, finding.id),
91
+ findingId: finding.id,
92
+ kind: finding.kind,
93
+ summary: finding.summary,
94
+ impacted: finding.impacted,
95
+ ...state,
96
+ matchedSymbols: finding.matches.map((match) => ({
97
+ symbol: match.symbol,
98
+ usages: match.files.map((file) => ({ file })).sort(compareUsages)
99
+ })).sort(compareSymbols)
100
+ };
101
+ }).sort(compareFindings);
102
+ const state = dependency.status
103
+ ? { status: dependency.status, reasonCode: dependency.reasonCode }
104
+ : classifyDependencyImpact({ coverage, usage, findings });
105
+ return {
106
+ analysisResultId: dependency.analysisResultId,
107
+ projectId: dependency.projectId,
108
+ packageId: dependency.packageId,
109
+ name: dependency.name,
110
+ impacted: dependency.impacted,
111
+ ...state,
112
+ coverage: structuredClone(coverage),
113
+ findings
114
+ };
115
+ }).sort(compareDependencies);
116
+ }
117
+
118
+ function buildSummary(dependencies, reasonCodes = IMPACT_EVIDENCE_REASON_CODES) {
119
+ const findings = dependencies.flatMap((dependency) => dependency.findings);
120
+ const usages = findings.flatMap((finding) => (
121
+ finding.matchedSymbols.flatMap((symbol) => symbol.usages)
122
+ ));
123
+ const reasonCounts = Object.fromEntries(reasonCodes.map((code) => [
124
+ code,
125
+ findings.filter((finding) => finding.reasonCode === code).length
126
+ ]));
127
+ return {
128
+ impacted: dependencies.some((dependency) => dependency.impacted),
129
+ dependencyCount: dependencies.length,
130
+ findingCount: findings.length,
131
+ impactedFindingCount: findings.filter((finding) => finding.impacted).length,
132
+ matchedSymbolCount: findings.reduce((count, finding) => count + finding.matchedSymbols.length, 0),
133
+ usageRecordCount: usages.length,
134
+ affectedFileCount: new Set(usages.map((usage) => usage.file)).size,
135
+ reasonCounts
136
+ };
137
+ }
138
+
139
+ export function validateRepositoryImpactEvidenceInvariants(evidence) {
140
+ const errors = [];
141
+ if (!isSorted(evidence.dependencies, compareDependencies)) errors.push('dependencies must be sorted.');
142
+ if (evidence.analysis.resultCount !== evidence.dependencies.length) {
143
+ errors.push('analysis.resultCount must equal dependencies length.');
144
+ }
145
+ const resultIds = new Set();
146
+ const evidenceIds = new Set();
147
+ for (const dependency of evidence.dependencies) {
148
+ if (resultIds.has(dependency.analysisResultId)) {
149
+ errors.push(`duplicate analysis result ${dependency.analysisResultId}.`);
150
+ }
151
+ resultIds.add(dependency.analysisResultId);
152
+ if (!isSorted(dependency.findings, compareFindings)) {
153
+ errors.push(`findings for ${dependency.analysisResultId} must be sorted.`);
154
+ }
155
+ if (dependency.impacted !== dependency.findings.some((finding) => finding.impacted)) {
156
+ errors.push(`dependency ${dependency.analysisResultId} impacted is inconsistent.`);
157
+ }
158
+ if (dependency.status !== undefined) {
159
+ if ((dependency.status === 'IMPACTED') !== dependency.impacted) {
160
+ errors.push(`dependency ${dependency.analysisResultId} status is inconsistent.`);
161
+ }
162
+ if (!dependency.coverage || typeof dependency.reasonCode !== 'string') {
163
+ errors.push(`dependency ${dependency.analysisResultId} status metadata is incomplete.`);
164
+ }
165
+ }
166
+ for (const finding of dependency.findings) {
167
+ const expectedId = evidenceId(dependency.analysisResultId, finding.findingId);
168
+ if (finding.id !== expectedId) errors.push(`finding evidence ${finding.id} has an unstable id.`);
169
+ if (evidenceIds.has(finding.id)) errors.push(`duplicate finding evidence ${finding.id}.`);
170
+ evidenceIds.add(finding.id);
171
+ if (!isSorted(finding.matchedSymbols, compareSymbols)) {
172
+ errors.push(`matched symbols for ${finding.id} must be sorted.`);
173
+ }
174
+ if (finding.impacted !== (finding.matchedSymbols.length > 0)) {
175
+ errors.push(`finding evidence ${finding.id} impacted is inconsistent.`);
176
+ }
177
+ if (finding.impacted !== (finding.reasonCode === 'EXACT_SYMBOL_USAGE_FOUND')) {
178
+ errors.push(`finding evidence ${finding.id} reasonCode is inconsistent.`);
179
+ }
180
+ if (finding.status !== undefined && (finding.status === 'IMPACTED') !== finding.impacted) {
181
+ errors.push(`finding evidence ${finding.id} status is inconsistent.`);
182
+ }
183
+ if (new Set(finding.matchedSymbols.map((match) => match.symbol)).size !== finding.matchedSymbols.length) {
184
+ errors.push(`matched symbols for ${finding.id} must be unique.`);
185
+ }
186
+ for (const match of finding.matchedSymbols) {
187
+ if (!isSorted(match.usages, compareUsages)) errors.push(`usages for ${finding.id}/${match.symbol} must be sorted.`);
188
+ if (new Set(match.usages.map((usage) => usage.file)).size !== match.usages.length) {
189
+ errors.push(`usages for ${finding.id}/${match.symbol} must be unique.`);
190
+ }
191
+ }
192
+ }
193
+ }
194
+ const expected = buildSummary(evidence.dependencies, Object.keys(evidence.summary.reasonCounts));
195
+ if (JSON.stringify(evidence.summary) !== JSON.stringify(expected)) errors.push('summary is inconsistent.');
196
+ return errors.sort(compareText);
197
+ }
198
+
199
+ export function validateRepositoryImpactEvidence(evidence) {
200
+ if (!validateSchema(evidence)) {
201
+ throw new Error(
202
+ `Repository Impact Evidence validation error: ${ajv.errorsText(validateSchema.errors, { separator: '; ' })}`
203
+ );
204
+ }
205
+ const errors = validateRepositoryImpactEvidenceInvariants(evidence);
206
+ if (errors.length > 0) {
207
+ throw new Error(`Repository Impact Evidence invariant error: ${errors.join(' ')}`);
208
+ }
209
+ return evidence;
210
+ }
211
+
212
+ export function buildRepositoryImpactEvidence({
213
+ input,
214
+ repositoryImpact,
215
+ usageIndex,
216
+ generatedAt = new Date()
217
+ }) {
218
+ const dependencies = buildDependencies(repositoryImpact, usageIndex);
219
+ return validateRepositoryImpactEvidence({
220
+ schemaVersion: REPOSITORY_IMPACT_EVIDENCE_SCHEMA_VERSION,
221
+ generatedAt: generatedAt instanceof Date ? generatedAt.toISOString() : generatedAt,
222
+ generator: { name: ARTIFACT_GENERATOR_NAME, version: VERSION },
223
+ input: structuredClone(input),
224
+ analysis: {
225
+ evidenceGenerator: {
226
+ id: IMPACT_EVIDENCE_GENERATOR_ID,
227
+ version: IMPACT_EVIDENCE_GENERATOR_VERSION
228
+ },
229
+ resultCount: dependencies.length
230
+ },
231
+ summary: buildSummary(dependencies),
232
+ dependencies
233
+ });
234
+ }
@@ -0,0 +1,16 @@
1
+ import { loadImpactEvidenceInputs } from './input-loader.js';
2
+ import { buildRepositoryImpactEvidence } from './repository-impact-evidence.js';
3
+
4
+ export function generateRepositoryImpactEvidence({ repositoryImpact, usageIndex, input, clock }) {
5
+ return buildRepositoryImpactEvidence({
6
+ input,
7
+ repositoryImpact,
8
+ usageIndex,
9
+ generatedAt: clock?.() ?? new Date()
10
+ });
11
+ }
12
+
13
+ export async function runImpactEvidenceGeneration({ sources, ...options }) {
14
+ const artifacts = await loadImpactEvidenceInputs(sources, options);
15
+ return generateRepositoryImpactEvidence({ ...artifacts, ...options });
16
+ }
@@ -0,0 +1,30 @@
1
+ import { mkdir, open, rename, rm } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ import { validateRepositoryImpactEvidence } from './repository-impact-evidence.js';
5
+
6
+ export function serializeRepositoryImpactEvidence(evidence) {
7
+ validateRepositoryImpactEvidence(evidence);
8
+ return `${JSON.stringify(evidence, null, 2)}\n`;
9
+ }
10
+
11
+ export async function writeRepositoryImpactEvidence(outputPath, evidence) {
12
+ const target = path.resolve(outputPath);
13
+ const temporary = `${target}.${process.pid}.tmp`;
14
+ const contents = serializeRepositoryImpactEvidence(evidence);
15
+ await mkdir(path.dirname(target), { recursive: true });
16
+ let handle;
17
+ try {
18
+ handle = await open(temporary, 'w', 0o600);
19
+ await handle.writeFile(contents, 'utf8');
20
+ await handle.sync();
21
+ await handle.close();
22
+ handle = null;
23
+ await rename(temporary, target);
24
+ } catch (error) {
25
+ await handle?.close().catch(() => {});
26
+ await rm(temporary, { force: true }).catch(() => {});
27
+ throw error;
28
+ }
29
+ return target;
30
+ }