@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,203 @@
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 {
8
+ DEFAULT_OUTPUT_DIRECTORY,
9
+ DEFAULT_KNOWLEDGE_MANIFEST_PATH,
10
+ DEFAULT_MANIFEST_PATH,
11
+ KNOWLEDGE_MANIFEST_SCHEMA_VERSION
12
+ } from './constants.js';
13
+ import { validateKnowledgeEvidenceBundle } from './knowledge-evidence-bundle.js';
14
+ import { validateKnowledgeManifestInvariants } from './knowledge-manifest.js';
15
+ import { isPortableRelativePath } from './portable.js';
16
+ import { loadProjectManifestInput } from './project-manifest-input.js';
17
+
18
+ export const DEFAULT_KNOWLEDGE_EVIDENCE_BUNDLE_PATH =
19
+ `${DEFAULT_OUTPUT_DIRECTORY}/knowledge-evidence-bundle.json`;
20
+
21
+ const knowledgeManifestSchema = JSON.parse(await readFile(
22
+ new URL('../schemas/knowledge-manifest.schema.json', import.meta.url),
23
+ 'utf8'
24
+ ));
25
+ const ajv = new Ajv2020({ allErrors: true, strict: true, strictRequired: false });
26
+ addFormats(ajv);
27
+ const validateKnowledgeManifestSchema = ajv.compile(knowledgeManifestSchema);
28
+
29
+ export class VersionAnalysisInputError extends Error {
30
+ constructor(message, code = 'VERSION_ANALYSIS_INPUT_INVALID') {
31
+ super(`Version Analysis input error: ${message}`);
32
+ this.name = 'VersionAnalysisInputError';
33
+ this.code = code;
34
+ }
35
+ }
36
+
37
+ function inputError(message, code) {
38
+ return new VersionAnalysisInputError(message, code);
39
+ }
40
+
41
+ function digest(bytes) {
42
+ return `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
43
+ }
44
+
45
+ function parseJson(bytes, artifactType) {
46
+ try {
47
+ return JSON.parse(Buffer.from(bytes).toString('utf8'));
48
+ } catch {
49
+ throw inputError(`${artifactType} bytes are not valid JSON.`);
50
+ }
51
+ }
52
+
53
+ function artifactFor(source, options, defaultArtifact, artifactType) {
54
+ const artifact = typeof source === 'object' && source !== null && 'bytes' in source
55
+ ? source.artifact
56
+ : options.artifact ?? defaultArtifact;
57
+ if (!isPortableRelativePath(artifact)) {
58
+ throw inputError(`${artifactType} artifact must be a portable repository-relative path.`);
59
+ }
60
+ return artifact;
61
+ }
62
+
63
+ async function readInputBytes(source, artifactType) {
64
+ if (typeof source === 'string' || source instanceof URL) return readFile(source);
65
+ if (source && typeof source === 'object' && 'bytes' in source) {
66
+ if (!(source.bytes instanceof Uint8Array)) {
67
+ throw inputError(`${artifactType} bytes must be a Uint8Array or Buffer.`);
68
+ }
69
+ return Buffer.from(source.bytes);
70
+ }
71
+ throw inputError(`${artifactType} source must be a file path or { bytes, artifact }.`);
72
+ }
73
+
74
+ export async function loadKnowledgeManifestInput(source, options = {}) {
75
+ const artifact = artifactFor(source, options, DEFAULT_KNOWLEDGE_MANIFEST_PATH, 'Knowledge Manifest');
76
+ const bytes = await readInputBytes(source, 'Knowledge Manifest');
77
+ const manifest = parseJson(bytes, 'Knowledge Manifest');
78
+
79
+ if (manifest?.schemaVersion !== KNOWLEDGE_MANIFEST_SCHEMA_VERSION) {
80
+ throw inputError(`unsupported Knowledge Manifest schema version; expected ${KNOWLEDGE_MANIFEST_SCHEMA_VERSION}.`);
81
+ }
82
+ if (!validateKnowledgeManifestSchema(manifest)) {
83
+ throw inputError(
84
+ `Knowledge Manifest schema validation failed: ${ajv.errorsText(validateKnowledgeManifestSchema.errors, { separator: '; ' })}`
85
+ );
86
+ }
87
+ const invariantErrors = validateKnowledgeManifestInvariants(manifest);
88
+ if (invariantErrors.length > 0) {
89
+ throw inputError(`Knowledge Manifest runtime invariants failed: ${invariantErrors.join(' ')}`);
90
+ }
91
+
92
+ return {
93
+ manifest,
94
+ input: {
95
+ knowledgeManifest: {
96
+ schemaVersion: manifest.schemaVersion,
97
+ artifact,
98
+ artifactDigest: digest(bytes),
99
+ researchId: manifest.research.researchId
100
+ }
101
+ }
102
+ };
103
+ }
104
+
105
+ export async function loadKnowledgeEvidenceBundleInput(source, options = {}) {
106
+ const artifact = artifactFor(source, options, DEFAULT_KNOWLEDGE_EVIDENCE_BUNDLE_PATH, 'Knowledge Evidence Bundle');
107
+ const bytes = await readInputBytes(source, 'Knowledge Evidence Bundle');
108
+ const bundle = parseJson(bytes, 'Knowledge Evidence Bundle');
109
+
110
+ if (bundle?.schemaVersion !== '1.0.0') {
111
+ throw inputError('unsupported Knowledge Evidence Bundle schema version; expected 1.0.0.');
112
+ }
113
+ try {
114
+ validateKnowledgeEvidenceBundle(bundle);
115
+ } catch (error) {
116
+ throw inputError(error.message);
117
+ }
118
+
119
+ return {
120
+ bundle,
121
+ input: {
122
+ evidenceArtifact: {
123
+ schemaVersion: bundle.schemaVersion,
124
+ artifact,
125
+ artifactDigest: digest(bytes)
126
+ }
127
+ }
128
+ };
129
+ }
130
+
131
+ function assertProjectKnowledgeLineage(projectInput, knowledgeInput, knowledgeManifest) {
132
+ const expected = knowledgeManifest.input.projectManifest;
133
+ const actual = projectInput.projectManifest;
134
+ const mismatches = [];
135
+ if (expected.schemaVersion !== actual.schemaVersion) mismatches.push('Project Manifest schemaVersion');
136
+ if (expected.artifactDigest !== actual.artifactDigest) mismatches.push('Project Manifest artifactDigest');
137
+ if (expected.repository.name !== actual.repository.name) mismatches.push('Project Manifest repository.name');
138
+ if (expected.repository.root !== actual.repository.root) mismatches.push('Project Manifest repository.root');
139
+ if (mismatches.length > 0) {
140
+ throw inputError(`Knowledge Manifest lineage mismatch: ${mismatches.join(', ')}.`, 'LINEAGE_MISMATCH');
141
+ }
142
+ }
143
+
144
+ function assertEvidenceKnowledgeLineage(knowledgeInput, evidenceBundle) {
145
+ const expected = evidenceBundle.input.knowledgeManifest;
146
+ const actual = knowledgeInput.knowledgeManifest;
147
+ const mismatches = [];
148
+ if (expected.schemaVersion !== actual.schemaVersion) mismatches.push('Knowledge Manifest schemaVersion');
149
+ if (expected.artifactDigest !== actual.artifactDigest) mismatches.push('Knowledge Manifest artifactDigest');
150
+ if (expected.researchId !== actual.researchId) mismatches.push('Knowledge Manifest researchId');
151
+ if (mismatches.length > 0) {
152
+ throw inputError(`Knowledge Evidence Bundle lineage mismatch: ${mismatches.join(', ')}.`, 'LINEAGE_MISMATCH');
153
+ }
154
+ }
155
+
156
+ function assertEvidenceReferences(knowledgeManifest, evidenceBundle) {
157
+ const packageIds = new Set(knowledgeManifest.packages.map((item) => item.id));
158
+ const sourceIds = new Set(knowledgeManifest.sources.map((item) => item.id));
159
+ const errors = [];
160
+ for (const item of evidenceBundle.evidence) {
161
+ if (!packageIds.has(item.packageId)) errors.push(`Evidence ${item.id} references unknown package ${item.packageId}.`);
162
+ if (!sourceIds.has(item.sourceId)) errors.push(`Evidence ${item.id} references unknown source ${item.sourceId}.`);
163
+ }
164
+ for (const warning of evidenceBundle.warnings) {
165
+ if (warning.packageId && !packageIds.has(warning.packageId)) {
166
+ errors.push(`Evidence warning ${warning.code} references unknown package ${warning.packageId}.`);
167
+ }
168
+ if (warning.sourceId && !sourceIds.has(warning.sourceId)) {
169
+ errors.push(`Evidence warning ${warning.code} references unknown source ${warning.sourceId}.`);
170
+ }
171
+ }
172
+ if (errors.length > 0) throw inputError(errors.join(' '));
173
+ }
174
+
175
+ export async function loadVersionAnalysisArtifacts(sources, options = {}) {
176
+ const project = await loadProjectManifestInput(
177
+ sources.projectManifest,
178
+ { artifact: options.projectManifestArtifact ?? DEFAULT_MANIFEST_PATH }
179
+ );
180
+ const knowledge = await loadKnowledgeManifestInput(
181
+ sources.knowledgeManifest,
182
+ { artifact: options.knowledgeManifestArtifact ?? DEFAULT_KNOWLEDGE_MANIFEST_PATH }
183
+ );
184
+ const evidence = await loadKnowledgeEvidenceBundleInput(
185
+ sources.evidenceBundle,
186
+ { artifact: options.evidenceBundleArtifact ?? DEFAULT_KNOWLEDGE_EVIDENCE_BUNDLE_PATH }
187
+ );
188
+
189
+ assertProjectKnowledgeLineage(project.input, knowledge.input, knowledge.manifest);
190
+ assertEvidenceKnowledgeLineage(knowledge.input, evidence.bundle);
191
+ assertEvidenceReferences(knowledge.manifest, evidence.bundle);
192
+
193
+ return {
194
+ projectManifest: project.manifest,
195
+ knowledgeManifest: knowledge.manifest,
196
+ evidenceBundle: evidence.bundle,
197
+ input: {
198
+ projectManifest: project.input.projectManifest,
199
+ knowledgeManifest: knowledge.input.knowledgeManifest,
200
+ evidenceArtifact: evidence.input.evidenceArtifact
201
+ }
202
+ };
203
+ }
@@ -0,0 +1,314 @@
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 { ARTIFACT_GENERATOR_NAME, VERSION } from './constants.js';
9
+ import { DEPENDENCY_AI_CONTEXT_VERSION } from './dependency-ai-context.js';
10
+ import { VERSION_ANALYSIS_PROMPT_VERSION } from './ai-version-analysis.js';
11
+ import { compareText } from './portable.js';
12
+
13
+ export const VERSION_ANALYSIS_SCHEMA_VERSION = '1.0.0';
14
+
15
+ const schema = JSON.parse(await readFile(
16
+ new URL('../schemas/version-analysis.schema.json', import.meta.url),
17
+ 'utf8'
18
+ ));
19
+ const ajv = new Ajv2020({ allErrors: true, strict: true, strictRequired: false });
20
+ addFormats(ajv);
21
+ const validateSchema = ajv.compile(schema);
22
+
23
+ function digest(value) {
24
+ const bytes = value instanceof Uint8Array ? value : canonicalJsonBytes(value);
25
+ return `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
26
+ }
27
+
28
+ function compareBy(...selectors) {
29
+ return (left, right) => {
30
+ for (const selector of selectors) {
31
+ const result = compareText(selector(left), selector(right));
32
+ if (result !== 0) return result;
33
+ }
34
+ return 0;
35
+ };
36
+ }
37
+
38
+ const compareResults = compareBy(
39
+ (item) => item.dependency.projectId,
40
+ (item) => item.dependency.manifest,
41
+ (item) => item.dependency.dependencyType,
42
+ (item) => item.dependency.packageId,
43
+ (item) => item.versions.declaredVersion ?? '',
44
+ (item) => item.versions.targetVersion ?? '',
45
+ (item) => item.contextId
46
+ );
47
+ const compareEvidence = compareBy((item) => item.id);
48
+ const compareFindings = compareBy((item) => item.id);
49
+
50
+ function sorted(values) {
51
+ return [...values].sort(compareText);
52
+ }
53
+
54
+ function isSorted(items, comparator) {
55
+ return items.every((item, index) => index === 0 || comparator(items[index - 1], item) <= 0);
56
+ }
57
+
58
+ function duplicateValues(values) {
59
+ const seen = new Set();
60
+ const duplicates = new Set();
61
+ for (const value of values) {
62
+ if (seen.has(value)) duplicates.add(value);
63
+ seen.add(value);
64
+ }
65
+ return [...duplicates].sort(compareText);
66
+ }
67
+
68
+ function addMismatch(errors, field, actual, expected) {
69
+ if (actual !== expected) errors.push(`${field} is ${actual}; expected ${expected}.`);
70
+ }
71
+
72
+ function resultIdentity(result) {
73
+ return [
74
+ result.dependency.projectId,
75
+ result.dependency.manifest,
76
+ result.dependency.dependencyType,
77
+ result.dependency.packageId,
78
+ result.versions.declaredVersion ?? '',
79
+ result.versions.targetVersion ?? ''
80
+ ].join('\0');
81
+ }
82
+
83
+ function resultIdMaterial(result) {
84
+ return {
85
+ contextId: result.contextId,
86
+ dependency: result.dependency,
87
+ versions: result.versions
88
+ };
89
+ }
90
+
91
+ function refsFor(result) {
92
+ return [
93
+ ...result.summaryEvidenceRefs,
94
+ ...result.riskEvidenceRefs,
95
+ ...result.findings.flatMap((finding) => finding.evidenceRefs)
96
+ ];
97
+ }
98
+
99
+ function manifestSummary(results) {
100
+ return {
101
+ resultCount: results.length,
102
+ analyzedCount: results.filter((item) => item.status === 'analyzed').length,
103
+ skippedCount: results.filter((item) => item.status === 'skipped').length,
104
+ failedCount: results.filter((item) => item.status === 'failed').length,
105
+ requiresHumanReviewCount: results.filter((item) => item.requiresHumanReview).length,
106
+ riskCounts: {
107
+ low: results.filter((item) => item.riskLevel === 'low').length,
108
+ medium: results.filter((item) => item.riskLevel === 'medium').length,
109
+ high: results.filter((item) => item.riskLevel === 'high').length,
110
+ unknown: results.filter((item) => item.riskLevel === 'unknown').length
111
+ }
112
+ };
113
+ }
114
+
115
+ function evidenceFromContext(context) {
116
+ return context.knowledge.evidence
117
+ .map((item) => ({
118
+ id: item.id,
119
+ kind: item.kind,
120
+ sourceId: item.sourceId,
121
+ sourceUrl: item.sourceUrl,
122
+ authority: item.authority,
123
+ trust: item.trust,
124
+ retrievedAt: item.retrievedAt,
125
+ contentDigest: item.contentDigest,
126
+ locator: item.locator,
127
+ releaseVersions: sorted(item.releaseVersions)
128
+ }))
129
+ .sort(compareEvidence);
130
+ }
131
+
132
+ function buildResult(context, analysisResult) {
133
+ const result = {
134
+ id: digest(resultIdMaterial(analysisResult)),
135
+ status: analysisResult.status,
136
+ contextId: analysisResult.contextId,
137
+ dependency: structuredClone(analysisResult.dependency),
138
+ versions: structuredClone(analysisResult.versions),
139
+ summary: analysisResult.summary,
140
+ summaryEvidenceRefs: sorted(analysisResult.summaryEvidenceRefs),
141
+ riskLevel: analysisResult.riskLevel,
142
+ riskEvidenceRefs: sorted(analysisResult.riskEvidenceRefs),
143
+ findings: structuredClone(analysisResult.findings)
144
+ .map((finding) => ({
145
+ ...finding,
146
+ appliesToVersions: sorted(finding.appliesToVersions),
147
+ evidenceRefs: sorted(finding.evidenceRefs)
148
+ }))
149
+ .sort(compareFindings),
150
+ evidence: evidenceFromContext(context),
151
+ evidenceCoverage: analysisResult.evidenceCoverage,
152
+ confidence: analysisResult.confidence ?? {
153
+ evidenceCoverage: analysisResult.evidenceCoverage,
154
+ validationStatus: analysisResult.validation.status,
155
+ requiresHumanReview: analysisResult.requiresHumanReview
156
+ },
157
+ validation: {
158
+ status: analysisResult.validation.status,
159
+ warningCodes: sorted(analysisResult.validation.warningCodes)
160
+ },
161
+ requiresHumanReview: analysisResult.requiresHumanReview,
162
+ humanReviewReasons: sorted(analysisResult.humanReviewReasons),
163
+ nextAction: analysisResult.nextAction,
164
+ limitations: structuredClone(analysisResult.limitations)
165
+ .sort(compareBy((item) => item.code, (item) => item.message))
166
+ };
167
+ return result;
168
+ }
169
+
170
+ function validatePair(context, analysisResult) {
171
+ if (context.contextId !== analysisResult.contextId) {
172
+ throw new Error(`Version Analysis Manifest input error: context/result mismatch for ${analysisResult.contextId}.`);
173
+ }
174
+ const deterministicFields = ['dependency', 'versions'];
175
+ for (const field of deterministicFields) {
176
+ if (JSON.stringify(context[field]) !== JSON.stringify(analysisResult[field])) {
177
+ throw new Error(`Version Analysis Manifest input error: result ${analysisResult.contextId} changed deterministic ${field}.`);
178
+ }
179
+ }
180
+ }
181
+
182
+ export function validateVersionAnalysisManifestInvariants(manifest) {
183
+ const errors = [];
184
+ const results = manifest.results ?? [];
185
+ if (!isSorted(results, compareResults)) errors.push('results must be sorted by dependency and target.');
186
+
187
+ for (const duplicate of duplicateValues(results.map((item) => item.id))) {
188
+ errors.push(`Duplicate result id ${duplicate}.`);
189
+ }
190
+ for (const duplicate of duplicateValues(results.map(resultIdentity))) {
191
+ errors.push(`Duplicate dependency analysis result ${duplicate.replaceAll('\0', ' / ')}.`);
192
+ }
193
+
194
+ for (const result of results) {
195
+ const expectedId = digest(resultIdMaterial(result));
196
+ if (result.id !== expectedId) errors.push(`Result ${result.id} id is not stable for its context/dependency/version facts.`);
197
+ if (!isSorted(result.evidence, compareEvidence)) errors.push(`Result ${result.id} evidence must be sorted by id.`);
198
+ if (!isSorted(result.findings, compareFindings)) errors.push(`Result ${result.id} findings must be sorted by id.`);
199
+ const evidenceIds = new Set(result.evidence.map((item) => item.id));
200
+ for (const duplicate of duplicateValues(result.evidence.map((item) => item.id))) {
201
+ errors.push(`Result ${result.id} has duplicate evidence ${duplicate}.`);
202
+ }
203
+ for (const ref of refsFor(result)) {
204
+ if (!evidenceIds.has(ref)) errors.push(`Result ${result.id} references unknown evidence ${ref}.`);
205
+ }
206
+ if (result.confidence.evidenceCoverage !== result.evidenceCoverage) {
207
+ errors.push(`Result ${result.id} confidence.evidenceCoverage does not match evidenceCoverage.`);
208
+ }
209
+ if (result.confidence.validationStatus !== result.validation.status) {
210
+ errors.push(`Result ${result.id} confidence.validationStatus does not match validation.status.`);
211
+ }
212
+ if (result.confidence.requiresHumanReview !== result.requiresHumanReview) {
213
+ errors.push(`Result ${result.id} confidence.requiresHumanReview does not match requiresHumanReview.`);
214
+ }
215
+ const versions = result.versions;
216
+ const installedFields = [
217
+ 'installedVersion',
218
+ 'installedVersionStatus',
219
+ 'installedVersionSource',
220
+ 'installedVersionReason'
221
+ ];
222
+ const installedFieldCount = installedFields.filter((field) => field in versions).length;
223
+ if (installedFieldCount !== 0 && installedFieldCount !== installedFields.length) {
224
+ errors.push(`Result ${result.id} has an incomplete installed-version baseline.`);
225
+ } else if (versions.installedVersionStatus === 'resolved') {
226
+ if (
227
+ versions.currentVersion !== versions.installedVersion
228
+ || versions.currentVersionSource !== 'resolvedArtifact'
229
+ || versions.installedVersionSource === null
230
+ || versions.installedVersionReason !== null
231
+ ) {
232
+ errors.push(`Result ${result.id} does not preserve its resolved installed-version baseline.`);
233
+ }
234
+ } else if (
235
+ versions.installedVersionStatus === 'unresolved'
236
+ && (
237
+ versions.installedVersion !== null
238
+ || versions.installedVersionSource !== null
239
+ || versions.installedVersionReason === null
240
+ )
241
+ ) {
242
+ errors.push(`Result ${result.id} has inconsistent unresolved installed-version fields.`);
243
+ }
244
+ }
245
+
246
+ addMismatch(errors, 'analysis.resultCount', manifest.analysis?.resultCount, results.length);
247
+ const summary = manifestSummary(results);
248
+ addMismatch(errors, 'summary.resultCount', manifest.summary?.resultCount, summary.resultCount);
249
+ addMismatch(errors, 'summary.analyzedCount', manifest.summary?.analyzedCount, summary.analyzedCount);
250
+ addMismatch(errors, 'summary.skippedCount', manifest.summary?.skippedCount, summary.skippedCount);
251
+ addMismatch(errors, 'summary.failedCount', manifest.summary?.failedCount, summary.failedCount);
252
+ addMismatch(
253
+ errors,
254
+ 'summary.requiresHumanReviewCount',
255
+ manifest.summary?.requiresHumanReviewCount,
256
+ summary.requiresHumanReviewCount
257
+ );
258
+ for (const level of ['low', 'medium', 'high', 'unknown']) {
259
+ addMismatch(errors, `summary.riskCounts.${level}`, manifest.summary?.riskCounts?.[level], summary.riskCounts[level]);
260
+ }
261
+
262
+ return errors.sort(compareText);
263
+ }
264
+
265
+ export function validateVersionAnalysisManifest(manifest) {
266
+ if (manifest?.schemaVersion !== VERSION_ANALYSIS_SCHEMA_VERSION) {
267
+ throw new Error(`Version Analysis Manifest validation error: unsupported schema version; expected ${VERSION_ANALYSIS_SCHEMA_VERSION}.`);
268
+ }
269
+ if (!validateSchema(manifest)) {
270
+ throw new Error(`Version Analysis Manifest validation error: schema validation failed: ${ajv.errorsText(validateSchema.errors, { separator: '; ' })}`);
271
+ }
272
+ const invariantErrors = validateVersionAnalysisManifestInvariants(manifest);
273
+ if (invariantErrors.length > 0) {
274
+ throw new Error(`Version Analysis Manifest validation error: runtime invariants failed: ${invariantErrors.join(' ')}`);
275
+ }
276
+ return manifest;
277
+ }
278
+
279
+ export function buildVersionAnalysisManifest({
280
+ input,
281
+ contexts,
282
+ results,
283
+ generatedAt = new Date(),
284
+ promptVersion = VERSION_ANALYSIS_PROMPT_VERSION,
285
+ contextVersion = DEPENDENCY_AI_CONTEXT_VERSION
286
+ }) {
287
+ if (!Array.isArray(contexts) || !Array.isArray(results) || contexts.length !== results.length) {
288
+ throw new Error('Version Analysis Manifest input error: contexts and results must be arrays of the same length.');
289
+ }
290
+ const records = contexts.map((context, index) => {
291
+ const result = results[index];
292
+ validatePair(context, result);
293
+ return buildResult(context, result);
294
+ }).sort(compareResults);
295
+
296
+ const manifest = {
297
+ schemaVersion: VERSION_ANALYSIS_SCHEMA_VERSION,
298
+ generatedAt: generatedAt instanceof Date ? generatedAt.toISOString() : generatedAt,
299
+ generator: { name: ARTIFACT_GENERATOR_NAME, version: VERSION },
300
+ input: structuredClone(input),
301
+ analysis: {
302
+ promptVersion,
303
+ contextVersion,
304
+ resultCount: records.length
305
+ },
306
+ summary: manifestSummary(records),
307
+ results: records
308
+ };
309
+ return validateVersionAnalysisManifest(manifest);
310
+ }
311
+
312
+ export function versionAnalysisManifestDigest(manifest) {
313
+ return digest(manifest);
314
+ }
@@ -0,0 +1,30 @@
1
+ import { mkdir, open, rename, rm } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ import { validateVersionAnalysisManifest } from './version-analysis-manifest.js';
5
+
6
+ export function serializeVersionAnalysisManifest(manifest) {
7
+ validateVersionAnalysisManifest(manifest);
8
+ return `${JSON.stringify(manifest, null, 2)}\n`;
9
+ }
10
+
11
+ export async function writeVersionAnalysisManifest(outputPath, manifest) {
12
+ const target = path.resolve(outputPath);
13
+ const temporary = `${target}.${process.pid}.tmp`;
14
+ const contents = serializeVersionAnalysisManifest(manifest);
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
+ }