@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,625 @@
1
+ import { createHash } from 'node:crypto';
2
+
3
+ import { canonicalJson, canonicalJsonBytes } from './canonical-json.js';
4
+ import {
5
+ createDefaultEcosystemVersionAdapterRegistry,
6
+ getEcosystemVersionAdapter
7
+ } from './ecosystem-version-adapter.js';
8
+ import { compareText } from './portable.js';
9
+ import { VersionAnalysisInputError } from './version-analysis-loader.js';
10
+
11
+ export const DEPENDENCY_AI_CONTEXT_VERSION = '1';
12
+ export const BASELINE_UNSUPPORTED_WARNING_CODE = 'BASELINE_UNSUPPORTED';
13
+ export const MISSING_TARGET_WARNING_CODE = 'TARGET_MISSING';
14
+
15
+ const KIND_PRIORITY = new Map([
16
+ ['breakingChanges', 0],
17
+ ['migrationGuide', 1],
18
+ ['compatibility', 2],
19
+ ['deprecations', 3],
20
+ ['releaseNotes', 4],
21
+ ['changelog', 5],
22
+ ['registryFact', 6]
23
+ ]);
24
+ const AUTHORITY_PRIORITY = new Map([
25
+ ['officialProject', 0],
26
+ ['publisherProvided', 1],
27
+ ['registryAuthoritative', 2],
28
+ ['community', 9]
29
+ ]);
30
+ const TRUST_PRIORITY = new Map([
31
+ ['official', 0],
32
+ ['publisher', 1],
33
+ ['verified', 2],
34
+ ['unknown', 8],
35
+ ['community', 9]
36
+ ]);
37
+ const UNVERSIONED_TARGET_KINDS = new Set(['migrationGuide', 'compatibility', 'changelog']);
38
+
39
+ function digestBytes(bytes) {
40
+ return `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
41
+ }
42
+
43
+ function digestText(value) {
44
+ return digestBytes(Buffer.from(value, 'utf8'));
45
+ }
46
+
47
+ function inputError(message, code) {
48
+ return new VersionAnalysisInputError(message, code);
49
+ }
50
+
51
+ function occurrenceKey(occurrence) {
52
+ return [
53
+ occurrence.projectId,
54
+ occurrence.manifest,
55
+ occurrence.dependencyType,
56
+ occurrence.declaredName,
57
+ occurrence.declaredVersion ?? ''
58
+ ].join('\0');
59
+ }
60
+
61
+ function projectDependencyKey(project, dependency) {
62
+ return [
63
+ project.id,
64
+ dependency.manifest,
65
+ dependency.type,
66
+ dependency.name,
67
+ dependency.declaredVersion ?? ''
68
+ ].join('\0');
69
+ }
70
+
71
+ function dependencyMatchesSelector(project, dependency, selector = {}) {
72
+ if (selector.projectId && selector.projectId !== project.id) return false;
73
+ if (selector.manifest && selector.manifest !== dependency.manifest) return false;
74
+ if (selector.dependencyType && selector.dependencyType !== dependency.type) return false;
75
+ if (selector.declaredName && selector.declaredName !== dependency.name) return false;
76
+ if ('declaredVersion' in selector && selector.declaredVersion !== dependency.declaredVersion) return false;
77
+ return true;
78
+ }
79
+
80
+ function packageForDependency(knowledgeManifest, project, dependency) {
81
+ const key = projectDependencyKey(project, dependency);
82
+ const matches = knowledgeManifest.packages.filter((packageRecord) =>
83
+ packageRecord.ecosystem === project.ecosystem
84
+ && packageRecord.occurrences.some((occurrence) => occurrenceKey(occurrence) === key)
85
+ );
86
+ if (matches.length !== 1) {
87
+ throw inputError(
88
+ `Expected exactly one Knowledge package for dependency ${dependency.name} in ${project.id}; found ${matches.length}.`
89
+ );
90
+ }
91
+ return matches[0];
92
+ }
93
+
94
+ export function resolveDependencyAnalysisInput(artifacts, selector = {}) {
95
+ const matches = [];
96
+ for (const project of artifacts.projectManifest.projects) {
97
+ if (project.dependencySummary.status !== 'parsed') continue;
98
+ for (const dependency of project.dependencies) {
99
+ if (dependencyMatchesSelector(project, dependency, selector)) {
100
+ matches.push({ project, dependency });
101
+ }
102
+ }
103
+ }
104
+ if (matches.length !== 1) {
105
+ throw inputError(`Expected exactly one dependency occurrence; found ${matches.length}.`);
106
+ }
107
+ const { project, dependency } = matches[0];
108
+ const packageRecord = packageForDependency(artifacts.knowledgeManifest, project, dependency);
109
+ return { project, dependency, packageRecord };
110
+ }
111
+
112
+ export function resolveDependencyAnalysisInputs(artifacts) {
113
+ return artifacts.projectManifest.projects
114
+ .filter((project) => project.dependencySummary.status === 'parsed')
115
+ .flatMap((project) => project.dependencies.map((dependency) => ({
116
+ project,
117
+ dependency,
118
+ packageRecord: packageForDependency(artifacts.knowledgeManifest, project, dependency)
119
+ })))
120
+ .sort((left, right) => compareText(projectDependencyKey(left.project, left.dependency), projectDependencyKey(right.project, right.dependency)));
121
+ }
122
+
123
+ function installedVersionFacts(adapter, dependency) {
124
+ if (
125
+ dependency.installedVersionStatus === 'resolved'
126
+ && typeof dependency.installedVersion === 'string'
127
+ && dependency.installedVersionSource
128
+ ) {
129
+ const normalized = adapter.normalizeVersion(dependency.installedVersion);
130
+ if (normalized.ok) {
131
+ return {
132
+ installedVersion: normalized.value,
133
+ installedVersionStatus: 'resolved',
134
+ installedVersionSource: structuredClone(dependency.installedVersionSource),
135
+ installedVersionReason: null
136
+ };
137
+ }
138
+ }
139
+ return {
140
+ installedVersion: null,
141
+ installedVersionStatus: 'unresolved',
142
+ installedVersionSource: null,
143
+ installedVersionReason: dependency.installedVersionStatus === 'unresolved'
144
+ && typeof dependency.installedVersionReason === 'string'
145
+ ? dependency.installedVersionReason
146
+ : 'RESOLVED_VERSION_UNAVAILABLE'
147
+ };
148
+ }
149
+
150
+ export function resolveVersionBaseline(adapter, dependency, options = {}) {
151
+ const installed = installedVersionFacts(adapter, dependency);
152
+ if (installed.installedVersionStatus === 'resolved') {
153
+ return {
154
+ analysisMode: 'exactBaseline',
155
+ currentVersion: installed.installedVersion,
156
+ currentVersionSource: 'resolvedArtifact',
157
+ declaredConstraint: null,
158
+ ...installed
159
+ };
160
+ }
161
+
162
+ if (options.currentVersion !== undefined && options.currentVersion !== null) {
163
+ const normalized = adapter.normalizeVersion(options.currentVersion);
164
+ if (!normalized.ok) throw inputError(`Explicit current version is invalid: ${normalized.reason}.`, 'BASELINE_UNSUPPORTED');
165
+ return {
166
+ analysisMode: 'exactBaseline',
167
+ currentVersion: normalized.value,
168
+ currentVersionSource: 'explicit',
169
+ declaredConstraint: null,
170
+ ...installed
171
+ };
172
+ }
173
+
174
+ const baseline = adapter.resolveDeclaredBaseline(dependency.declaredVersion);
175
+ if (baseline.kind === 'exactVersion') {
176
+ return {
177
+ analysisMode: 'exactBaseline',
178
+ currentVersion: baseline.version,
179
+ currentVersionSource: 'exactDeclaration',
180
+ declaredConstraint: null,
181
+ ...installed
182
+ };
183
+ }
184
+ if (baseline.kind === 'declaredConstraint') {
185
+ return {
186
+ analysisMode: 'declaredConstraint',
187
+ currentVersion: null,
188
+ currentVersionSource: null,
189
+ declaredConstraint: baseline.constraint,
190
+ ...installed
191
+ };
192
+ }
193
+ throw inputError(`Dependency ${dependency.name} has unsupported baseline: ${baseline.reason}.`, BASELINE_UNSUPPORTED_WARNING_CODE);
194
+ }
195
+
196
+ export function resolveTargetVersion(adapter, packageRecord, request = {}) {
197
+ const policy = request.policy ?? (request.version ? 'explicit' : 'registryLatest');
198
+ if (policy === 'explicit') {
199
+ const normalized = adapter.normalizeVersion(request.version);
200
+ if (!normalized.ok) throw inputError(`Explicit target version is invalid: ${normalized.reason}.`, 'TARGET_INVALID');
201
+ return { targetVersion: normalized.value, targetPolicy: 'explicit' };
202
+ }
203
+ if (policy === 'registryLatest') {
204
+ if (!packageRecord.latest?.version) {
205
+ throw inputError(`Package ${packageRecord.id} has no registry latest target.`, MISSING_TARGET_WARNING_CODE);
206
+ }
207
+ const normalized = adapter.normalizeVersion(packageRecord.latest.version);
208
+ if (!normalized.ok) throw inputError(`Registry latest version is invalid: ${normalized.reason}.`, 'TARGET_INVALID');
209
+ return { targetVersion: normalized.value, targetPolicy: 'registryLatest' };
210
+ }
211
+ throw inputError(`Unsupported target policy ${policy}.`, 'TARGET_INVALID');
212
+ }
213
+
214
+ function isMissingTargetError(error) {
215
+ return error instanceof VersionAnalysisInputError && error.code === MISSING_TARGET_WARNING_CODE;
216
+ }
217
+
218
+ function isUnsupportedBaselineError(error) {
219
+ return error instanceof VersionAnalysisInputError && error.code === BASELINE_UNSUPPORTED_WARNING_CODE;
220
+ }
221
+
222
+ function sourceRank(source) {
223
+ return [
224
+ AUTHORITY_PRIORITY.get(source.authority) ?? 8,
225
+ TRUST_PRIORITY.get(source.trust) ?? 8
226
+ ];
227
+ }
228
+
229
+ function relevantVersionSet(relevantReleases, targetVersion) {
230
+ return new Set([
231
+ targetVersion,
232
+ ...relevantReleases.map((release) => release.version).filter(Boolean).reverse()
233
+ ]);
234
+ }
235
+
236
+ function itemIsRelevant(item, versions, mode) {
237
+ if (item.releaseVersions.length > 0) {
238
+ return item.releaseVersions.some((version) => versions.has(version));
239
+ }
240
+ return mode === 'declaredConstraint' && UNVERSIONED_TARGET_KINDS.has(item.kind);
241
+ }
242
+
243
+ function compareSelectedEvidence(left, right) {
244
+ const leftRank = left.rank;
245
+ const rightRank = right.rank;
246
+ for (let index = 0; index < leftRank.length; index += 1) {
247
+ if (leftRank[index] !== rightRank[index]) return leftRank[index] < rightRank[index] ? -1 : 1;
248
+ }
249
+ return compareText(left.item.id, right.item.id);
250
+ }
251
+
252
+ function compareWarnings(left, right) {
253
+ return compareText(left.packageId ?? '', right.packageId ?? '')
254
+ || compareText(left.sourceId ?? '', right.sourceId ?? '')
255
+ || compareText(left.code, right.code)
256
+ || compareText((left.conflictSourceIds ?? []).join('\0'), (right.conflictSourceIds ?? []).join('\0'))
257
+ || compareText(left.message, right.message);
258
+ }
259
+
260
+ function warningKey(warning) {
261
+ return [
262
+ warning.code,
263
+ warning.packageId ?? '',
264
+ warning.sourceId ?? '',
265
+ (warning.conflictSourceIds ?? []).join('\0'),
266
+ warning.message
267
+ ].join('\0');
268
+ }
269
+
270
+ function addWarning(warnings, warning) {
271
+ const normalized = {
272
+ ...warning,
273
+ ...(warning.conflictSourceIds
274
+ ? { conflictSourceIds: [...warning.conflictSourceIds].sort(compareText) }
275
+ : {})
276
+ };
277
+ if (!warnings.some((item) => warningKey(item) === warningKey(normalized))) {
278
+ warnings.push(normalized);
279
+ }
280
+ }
281
+
282
+ function addSelectedSourceConflictWarnings(warnings, input, bounded) {
283
+ for (const { source } of bounded) {
284
+ if ((source.conflictsWith ?? []).length === 0) continue;
285
+ addWarning(warnings, {
286
+ code: 'SOURCE_CONFLICT',
287
+ packageId: input.packageRecord.id,
288
+ sourceId: source.id,
289
+ conflictSourceIds: source.conflictsWith,
290
+ message: `Source ${source.id} conflicts with another source for ${input.packageRecord.id}.`
291
+ });
292
+ }
293
+ }
294
+
295
+ function addArtifactSourceConflictWarnings(warnings, artifacts, input, bounded) {
296
+ const selectedSourceIds = new Set(bounded.map(({ source }) => source.id));
297
+ const packageHasConflict = input.packageRecord.warningCodes?.includes('SOURCE_CONFLICT') ?? false;
298
+ const conflictWarnings = [
299
+ ...(artifacts.knowledgeManifest.warnings ?? []),
300
+ ...(artifacts.evidenceBundle.warnings ?? [])
301
+ ].filter((warning) =>
302
+ warning.code === 'SOURCE_CONFLICT'
303
+ && warning.packageId === input.packageRecord.id
304
+ && (
305
+ packageHasConflict
306
+ || !warning.sourceId
307
+ || selectedSourceIds.has(warning.sourceId)
308
+ )
309
+ );
310
+
311
+ for (const warning of conflictWarnings) {
312
+ addWarning(warnings, {
313
+ code: 'SOURCE_CONFLICT',
314
+ packageId: input.packageRecord.id,
315
+ ...(warning.sourceId ? { sourceId: warning.sourceId } : {}),
316
+ message: warning.message
317
+ });
318
+ }
319
+
320
+ if (packageHasConflict && conflictWarnings.length === 0) {
321
+ addWarning(warnings, {
322
+ code: 'SOURCE_CONFLICT',
323
+ packageId: input.packageRecord.id,
324
+ message: `Package ${input.packageRecord.id} has unresolved source conflicts.`
325
+ });
326
+ }
327
+ }
328
+
329
+ export function selectEvidence(artifacts, input, selection, options = {}) {
330
+ const maxItems = options.maxEvidenceItems ?? 8;
331
+ const maxCharacters = options.maxEvidenceCharacters ?? 12000;
332
+ const sourcesById = new Map(artifacts.knowledgeManifest.sources.map((source) => [source.id, source]));
333
+ const versions = relevantVersionSet(selection.relevantReleases, selection.targetVersion);
334
+ const warnings = [];
335
+ const selected = [];
336
+ const seenContent = new Set();
337
+
338
+ for (const item of artifacts.evidenceBundle.evidence) {
339
+ if (item.packageId !== input.packageRecord.id) continue;
340
+ const source = sourcesById.get(item.sourceId);
341
+ if (!source) continue;
342
+ if (source.authority === 'community' || source.trust === 'community') continue;
343
+ if (!['available', 'stale'].includes(source.status)) continue;
344
+ if (item.contentDigest !== digestText(item.content)) continue;
345
+ if (!itemIsRelevant(item, versions, selection.analysisMode)) continue;
346
+ if (seenContent.has(item.contentDigest)) continue;
347
+ seenContent.add(item.contentDigest);
348
+ if (source.status === 'stale') {
349
+ warnings.push({ code: 'SOURCE_STALE', sourceId: source.id, message: `Source ${source.id} is stale.` });
350
+ }
351
+ const [authorityRank, trustRank] = sourceRank(source);
352
+ const releaseRank = item.releaseVersions.length === 0
353
+ ? Number.MAX_SAFE_INTEGER
354
+ : Math.min(...item.releaseVersions.map((version) => [...versions].indexOf(version)).filter((index) => index >= 0));
355
+ selected.push({
356
+ item,
357
+ source,
358
+ rank: [
359
+ releaseRank,
360
+ KIND_PRIORITY.get(item.kind) ?? 99,
361
+ authorityRank,
362
+ trustRank
363
+ ]
364
+ });
365
+ }
366
+
367
+ selected.sort(compareSelectedEvidence);
368
+ const bounded = [];
369
+ let characters = 0;
370
+ for (const record of selected) {
371
+ if (bounded.length >= maxItems) break;
372
+ if (characters + record.item.content.length > maxCharacters) continue;
373
+ characters += record.item.content.length;
374
+ bounded.push(record);
375
+ }
376
+
377
+ addSelectedSourceConflictWarnings(warnings, input, bounded);
378
+ addArtifactSourceConflictWarnings(warnings, artifacts, input, bounded);
379
+
380
+ if (bounded.length === 0) {
381
+ warnings.push({
382
+ code: 'EVIDENCE_MISSING',
383
+ packageId: input.packageRecord.id,
384
+ message: `No selected evidence for ${input.packageRecord.id}.`
385
+ });
386
+ }
387
+
388
+ return {
389
+ evidence: bounded.map(({ item, source }) => ({
390
+ id: item.id,
391
+ kind: item.kind,
392
+ sourceId: item.sourceId,
393
+ sourceUrl: source.url,
394
+ authority: source.authority,
395
+ trust: source.trust,
396
+ retrievedAt: item.retrievedAt,
397
+ contentDigest: item.contentDigest,
398
+ locator: item.locator,
399
+ releaseVersions: [...item.releaseVersions],
400
+ content: item.content
401
+ })),
402
+ warnings: warnings.sort(compareWarnings)
403
+ };
404
+ }
405
+
406
+ function normalizeRelevantReleases(releases) {
407
+ return releases.map((release) => release.version).filter(Boolean);
408
+ }
409
+
410
+ function buildContextWithoutId(artifacts, input, versions, knowledge, metadata) {
411
+ return {
412
+ contextVersion: DEPENDENCY_AI_CONTEXT_VERSION,
413
+ lineage: {
414
+ projectManifestDigest: artifacts.input.projectManifest.artifactDigest,
415
+ knowledgeManifestDigest: artifacts.input.knowledgeManifest.artifactDigest,
416
+ knowledgeResearchId: artifacts.input.knowledgeManifest.researchId,
417
+ evidenceArtifactDigest: artifacts.input.evidenceArtifact.artifactDigest
418
+ },
419
+ dependency: dependencyFacts(input),
420
+ versions,
421
+ knowledge,
422
+ metadata
423
+ };
424
+ }
425
+
426
+ function dependencyFacts(input) {
427
+ return {
428
+ projectId: input.project.id,
429
+ packageId: input.packageRecord.id,
430
+ declaredName: input.dependency.name,
431
+ normalizedName: input.packageRecord.identity.normalizedName,
432
+ ecosystem: input.project.ecosystem,
433
+ registry: input.packageRecord.identity.registry,
434
+ packageManager: input.project.packageManager?.name ?? null,
435
+ dependencyType: input.dependency.type,
436
+ manifest: input.dependency.manifest
437
+ };
438
+ }
439
+
440
+ function contextWithDigest(artifacts, input, versions, knowledge, metadata) {
441
+ const contextWithoutSize = buildContextWithoutId(artifacts, input, versions, knowledge, metadata);
442
+ const characters = canonicalJson(contextWithoutSize).length;
443
+ const contextMaterial = buildContextWithoutId(artifacts, input, versions, knowledge, {
444
+ ...metadata,
445
+ size: { ...metadata.size, characters }
446
+ });
447
+ const contextId = digestBytes(canonicalJsonBytes(contextMaterial));
448
+ return { contextId, ...contextMaterial };
449
+ }
450
+
451
+ function buildMissingTargetContext(artifacts, input, baseline, targetPolicy, message) {
452
+ const versions = {
453
+ analysisMode: baseline.analysisMode,
454
+ declaredVersion: input.dependency.declaredVersion,
455
+ installedVersion: baseline.installedVersion,
456
+ installedVersionStatus: baseline.installedVersionStatus,
457
+ installedVersionSource: baseline.installedVersionSource,
458
+ installedVersionReason: baseline.installedVersionReason,
459
+ currentVersion: baseline.currentVersion,
460
+ currentVersionSource: baseline.currentVersionSource,
461
+ targetVersion: null,
462
+ targetPolicy,
463
+ delta: { direction: 'unknown', classification: 'unknown' }
464
+ };
465
+ const knowledge = {
466
+ relevantReleases: [],
467
+ evidence: []
468
+ };
469
+ const metadata = {
470
+ selectedEvidenceIds: [],
471
+ missingInformation: ['targetVersion'],
472
+ warnings: [
473
+ {
474
+ code: MISSING_TARGET_WARNING_CODE,
475
+ packageId: input.packageRecord.id,
476
+ message
477
+ }
478
+ ],
479
+ size: {
480
+ characters: 0,
481
+ evidenceItems: 0
482
+ }
483
+ };
484
+ return contextWithDigest(artifacts, input, versions, knowledge, metadata);
485
+ }
486
+
487
+ function buildUnsupportedBaselineContext(
488
+ artifacts,
489
+ input,
490
+ installed,
491
+ target,
492
+ targetPolicy,
493
+ baselineMessage,
494
+ targetMessage
495
+ ) {
496
+ const warnings = [
497
+ {
498
+ code: BASELINE_UNSUPPORTED_WARNING_CODE,
499
+ packageId: input.packageRecord.id,
500
+ message: baselineMessage
501
+ }
502
+ ];
503
+ const missingInformation = ['baseline'];
504
+ if (!target) {
505
+ missingInformation.push('targetVersion');
506
+ warnings.push({
507
+ code: MISSING_TARGET_WARNING_CODE,
508
+ packageId: input.packageRecord.id,
509
+ message: targetMessage
510
+ });
511
+ }
512
+ const versions = {
513
+ analysisMode: 'unsupportedBaseline',
514
+ declaredVersion: input.dependency.declaredVersion,
515
+ ...installed,
516
+ currentVersion: null,
517
+ currentVersionSource: null,
518
+ targetVersion: target?.targetVersion ?? null,
519
+ targetPolicy: target?.targetPolicy ?? targetPolicy,
520
+ delta: { direction: 'unknown', classification: 'unknown' }
521
+ };
522
+ const knowledge = {
523
+ relevantReleases: [],
524
+ evidence: []
525
+ };
526
+ const metadata = {
527
+ selectedEvidenceIds: [],
528
+ missingInformation,
529
+ warnings,
530
+ size: {
531
+ characters: 0,
532
+ evidenceItems: 0
533
+ }
534
+ };
535
+ return contextWithDigest(artifacts, input, versions, knowledge, metadata);
536
+ }
537
+
538
+ export function buildDependencyAiContext(artifacts, request = {}) {
539
+ const registry = request.adapterRegistry ?? createDefaultEcosystemVersionAdapterRegistry();
540
+ const input = request.input ?? resolveDependencyAnalysisInput(artifacts, request.dependency ?? {});
541
+ const adapter = getEcosystemVersionAdapter(input.project.ecosystem, registry);
542
+ const targetRequest = request.target ?? { policy: 'explicit', version: request.targetVersion };
543
+ const requestedTargetPolicy = targetRequest.policy ?? (targetRequest.version ? 'explicit' : 'registryLatest');
544
+ let baseline;
545
+ try {
546
+ baseline = resolveVersionBaseline(adapter, input.dependency, { currentVersion: request.currentVersion });
547
+ } catch (error) {
548
+ if (!isUnsupportedBaselineError(error)) throw error;
549
+ const installed = installedVersionFacts(adapter, input.dependency);
550
+ let target = null;
551
+ let targetMessage = null;
552
+ try {
553
+ target = resolveTargetVersion(adapter, input.packageRecord, targetRequest);
554
+ } catch (targetError) {
555
+ if (!isMissingTargetError(targetError)) throw targetError;
556
+ targetMessage = targetError.message;
557
+ }
558
+ return buildUnsupportedBaselineContext(
559
+ artifacts,
560
+ input,
561
+ installed,
562
+ target,
563
+ requestedTargetPolicy,
564
+ error.message,
565
+ targetMessage
566
+ );
567
+ }
568
+ let target;
569
+ try {
570
+ target = resolveTargetVersion(adapter, input.packageRecord, targetRequest);
571
+ } catch (error) {
572
+ if (!isMissingTargetError(error)) throw error;
573
+ return buildMissingTargetContext(artifacts, input, baseline, requestedTargetPolicy, error.message);
574
+ }
575
+ const delta = baseline.analysisMode === 'exactBaseline'
576
+ ? adapter.compareVersions(baseline.currentVersion, target.targetVersion)
577
+ : { direction: 'unknown', classification: 'unknown' };
578
+ const relevantReleases = adapter.selectRelevantReleases(input.packageRecord.releaseIndex, {
579
+ mode: baseline.analysisMode,
580
+ current: baseline.currentVersion,
581
+ target: target.targetVersion
582
+ });
583
+ const selected = selectEvidence(artifacts, input, {
584
+ analysisMode: baseline.analysisMode,
585
+ relevantReleases,
586
+ targetVersion: target.targetVersion
587
+ }, request.evidencePolicy);
588
+ const missingInformation = selected.evidence.length === 0 ? ['evidence'] : [];
589
+ const versions = {
590
+ analysisMode: baseline.analysisMode,
591
+ declaredVersion: input.dependency.declaredVersion,
592
+ installedVersion: baseline.installedVersion,
593
+ installedVersionStatus: baseline.installedVersionStatus,
594
+ installedVersionSource: baseline.installedVersionSource,
595
+ installedVersionReason: baseline.installedVersionReason,
596
+ currentVersion: baseline.currentVersion,
597
+ currentVersionSource: baseline.currentVersionSource,
598
+ targetVersion: target.targetVersion,
599
+ targetPolicy: target.targetPolicy,
600
+ delta
601
+ };
602
+ const knowledge = {
603
+ relevantReleases: normalizeRelevantReleases(relevantReleases),
604
+ evidence: selected.evidence
605
+ };
606
+ const metadata = {
607
+ selectedEvidenceIds: selected.evidence.map((item) => item.id).sort(compareText),
608
+ missingInformation,
609
+ warnings: selected.warnings,
610
+ size: {
611
+ characters: 0,
612
+ evidenceItems: selected.evidence.length
613
+ }
614
+ };
615
+ return contextWithDigest(artifacts, input, versions, knowledge, metadata);
616
+ }
617
+
618
+ export function dependencyAiContextDigest(context) {
619
+ const { contextId, ...material } = context;
620
+ return digestBytes(canonicalJsonBytes(material));
621
+ }
622
+
623
+ export function dependencyAiContextsEqual(left, right) {
624
+ return canonicalJson(left) === canonicalJson(right);
625
+ }