@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,828 @@
1
+ import { createHash } from 'node:crypto';
2
+
3
+ import { canonicalJsonBytes } from '../canonical-json.js';
4
+ import { compareText } from '../portable.js';
5
+ import { coverageForProject } from '../usage/coverage.js';
6
+ import { loadPersistedUpgradeDecision } from '../upgrade-decision/input-loader.js';
7
+ import { migrationChecklistEligibility } from './grounding-policy.js';
8
+ import { loadMigrationChecklistInputs } from './input-loader.js';
9
+ import {
10
+ extractProjectVerification,
11
+ unavailableProjectVerification
12
+ } from './verification.js';
13
+
14
+ export const MIGRATION_TASK_CONTEXT_VERSION = '1';
15
+ export const DEFAULT_MIGRATION_CONTEXT_MAX_EVIDENCE_ITEMS = 6;
16
+ export const DEFAULT_MIGRATION_CONTEXT_MAX_EVIDENCE_CHARACTERS = 24_000;
17
+ export const DEFAULT_MIGRATION_CONTEXT_MAX_FINDING_SUMMARY_CHARACTERS = 2_000;
18
+
19
+ export const MIGRATION_LOCATION_REASON_CODES = Object.freeze([
20
+ 'POSITIVE_USAGE_MATCH',
21
+ 'NO_POSITIVE_USAGE_MATCH',
22
+ 'UNSUPPORTED_USAGE_COVERAGE',
23
+ 'NOT_ANALYZED'
24
+ ]);
25
+
26
+ const ACTION_EVIDENCE_KINDS = new Set([
27
+ 'migrationGuide',
28
+ 'breakingChanges',
29
+ 'deprecations',
30
+ 'releaseNotes',
31
+ 'changelog'
32
+ ]);
33
+
34
+ const EVIDENCE_KIND_PRIORITY = Object.freeze({
35
+ migrationGuide: 0,
36
+ breakingChanges: 1,
37
+ deprecations: 2,
38
+ releaseNotes: 3,
39
+ changelog: 4
40
+ });
41
+
42
+ const compareLimitations = (left, right) => (
43
+ compareText(left.code, right.code) || compareText(left.message, right.message)
44
+ );
45
+ const compareLocations = (left, right) => (
46
+ compareText(left.impactEvidenceId, right.impactEvidenceId)
47
+ || compareText(left.symbol, right.symbol)
48
+ || compareText(left.file, right.file)
49
+ );
50
+ const compareAffectedAreas = (left, right) => (
51
+ compareText(left.impactEvidenceId, right.impactEvidenceId)
52
+ || compareText(left.findingId, right.findingId)
53
+ || compareText(left.symbol, right.symbol)
54
+ || compareText(left.file, right.file)
55
+ );
56
+ const compareContexts = (left, right) => (
57
+ compareText(left.dependency.projectId, right.dependency.projectId)
58
+ || compareText(left.dependency.manifest, right.dependency.manifest)
59
+ || compareText(left.dependency.dependencyType, right.dependency.dependencyType)
60
+ || compareText(left.dependency.packageId, right.dependency.packageId)
61
+ || compareText(left.finding.id, right.finding.id)
62
+ || compareText(left.contextId, right.contextId)
63
+ );
64
+ const compareFallbackRecords = (left, right) => (
65
+ compareText(left.dependency.projectId, right.dependency.projectId)
66
+ || compareText(left.dependency.manifest, right.dependency.manifest)
67
+ || compareText(left.dependency.dependencyType, right.dependency.dependencyType)
68
+ || compareText(left.dependency.packageId, right.dependency.packageId)
69
+ || compareText(left.analysisResultId, right.analysisResultId)
70
+ );
71
+
72
+ function digest(value) {
73
+ return `sha256:${createHash('sha256').update(canonicalJsonBytes(value)).digest('hex')}`;
74
+ }
75
+
76
+ function deepFreeze(value) {
77
+ if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
78
+ for (const child of Object.values(value)) deepFreeze(child);
79
+ return Object.freeze(value);
80
+ }
81
+
82
+ function sortedUniqueText(values = []) {
83
+ return [...new Set(values)].sort(compareText);
84
+ }
85
+
86
+ function sortedUniqueLimitations(values = []) {
87
+ const unique = new Map();
88
+ for (const value of values) unique.set(`${value.code}\0${value.message}`, structuredClone(value));
89
+ return [...unique.values()].sort(compareLimitations);
90
+ }
91
+
92
+ function limitation(code, message) {
93
+ return { code, message };
94
+ }
95
+
96
+ function recommendationDriver(decision) {
97
+ if (decision.reasonCodes.includes('USER_SELECTED_TARGET')) return 'USER_SELECTED_TARGET';
98
+ if (decision.reasonCodes.includes('STRUCTURED_URGENCY')) return 'STRUCTURED_URGENCY';
99
+ return null;
100
+ }
101
+
102
+ function decisionProjection(decision) {
103
+ return {
104
+ status: decision.decision,
105
+ targetOrigin: decision.versions.targetPolicy ?? 'unknown',
106
+ recommendationDriver: recommendationDriver(decision),
107
+ primaryReasonCode: decision.primaryReasonCode,
108
+ reasonCodes: [...decision.reasonCodes]
109
+ };
110
+ }
111
+
112
+ function officialEvidenceFor(artifacts, refs) {
113
+ const bundle = new Map(artifacts.knowledgeEvidenceBundle.evidence.map((item) => [item.id, item]));
114
+ const sources = new Map(artifacts.knowledgeManifest.sources.map((item) => [item.id, item]));
115
+ return refs.flatMap((ref) => {
116
+ const evidence = bundle.get(ref);
117
+ const source = evidence ? sources.get(evidence.sourceId) : null;
118
+ if (!evidence || !source) return [];
119
+ return [{
120
+ id: evidence.id,
121
+ sourceId: evidence.sourceId,
122
+ kind: evidence.kind,
123
+ authority: source.authority,
124
+ trust: source.trust,
125
+ contentDigest: evidence.contentDigest,
126
+ locator: evidence.locator,
127
+ releaseVersions: [...evidence.releaseVersions]
128
+ }];
129
+ }).sort((left, right) => compareText(left.id, right.id));
130
+ }
131
+
132
+ function preconditionsFor(decision) {
133
+ if (!['PLAN_UPGRADE', 'UPGRADE_NOW'].includes(decision.decision)) return [];
134
+ return [
135
+ {
136
+ code: 'EXPLICIT_TARGET_SELECTED',
137
+ message: 'The target was selected through the structured explicit-target boundary.'
138
+ },
139
+ {
140
+ code: 'TARGET_SCOPED_EVIDENCE_VALID',
141
+ message: 'Target-scoped official or publisher evidence passed Upgrade Decision validation.'
142
+ },
143
+ {
144
+ code: 'HUMAN_APPROVAL_REQUIRED',
145
+ message: 'A human reviewer must approve migration scope before source changes.'
146
+ }
147
+ ];
148
+ }
149
+
150
+ function reviewQuestionsFor(decision) {
151
+ if (decision.decision !== 'INVESTIGATE') return [];
152
+ const questionByReason = {
153
+ EVIDENCE_CONFLICT: 'Which conflicting evidence should govern the selected dependency occurrence?',
154
+ NON_REGISTRY_DEPENDENCY: 'How is this non-registry dependency resolved and versioned in the project?',
155
+ UNSUPPORTED_ECOSYSTEM: 'Which supported analyzer and version comparator can validate this ecosystem?',
156
+ UPGRADE_AVAILABLE_NO_RECOMMENDATION_DRIVER:
157
+ 'Has a human or structured policy selected this target for migration?',
158
+ USAGE_COVERAGE_PARTIAL: 'Which repository areas were not covered by usage analysis?',
159
+ USAGE_COVERAGE_UNAVAILABLE: 'How will dependency usage be inspected without supported analyzer coverage?',
160
+ VERSION_INCOMPARABLE: 'Which ecosystem-aware comparison can establish installed-to-target direction?'
161
+ };
162
+ return [questionByReason[decision.primaryReasonCode]
163
+ ?? 'Which missing or uncertain fact must be resolved before migration planning?'];
164
+ }
165
+
166
+ function missingInformationFor(decision) {
167
+ if (decision.decision !== 'INSUFFICIENT_EVIDENCE') return [];
168
+ const messageByReason = {
169
+ EVIDENCE_INSUFFICIENT: 'Valid target-scoped official migration evidence is unavailable.',
170
+ INSTALLED_VERSION_UNAVAILABLE: 'The installed version baseline is unavailable.',
171
+ TARGET_VERSION_UNAVAILABLE: 'A validated target version is unavailable.'
172
+ };
173
+ return [{
174
+ code: decision.primaryReasonCode,
175
+ message: messageByReason[decision.primaryReasonCode]
176
+ ?? 'Required deterministic upgrade evidence is unavailable.'
177
+ }];
178
+ }
179
+
180
+ function nextStepFor(decision) {
181
+ const values = {
182
+ EVIDENCE_INSUFFICIENT: {
183
+ code: 'COLLECT_TARGET_EVIDENCE',
184
+ message: 'Collect valid target-scoped official or publisher evidence and rerun Upgrade Decision.'
185
+ },
186
+ INSTALLED_VERSION_UNAVAILABLE: {
187
+ code: 'RESOLVE_INSTALLED_BASELINE',
188
+ message: 'Resolve the installed dependency version and rerun the upstream analysis.'
189
+ },
190
+ TARGET_VERSION_UNAVAILABLE: {
191
+ code: 'SELECT_OR_DISCOVER_TARGET',
192
+ message: 'Provide or discover a validated target version and rerun the upstream analysis.'
193
+ },
194
+ VERSION_ANALYSIS_FAILED: {
195
+ code: 'RERUN_VERSION_ANALYSIS',
196
+ message: 'Resolve the recorded failure and rerun Version Analysis.'
197
+ },
198
+ VERSION_ANALYSIS_MISSING: {
199
+ code: 'RERUN_VERSION_ANALYSIS',
200
+ message: 'Run Version Analysis for this dependency occurrence.'
201
+ },
202
+ VERSION_ANALYSIS_SKIPPED: {
203
+ code: 'RERUN_VERSION_ANALYSIS',
204
+ message: 'Resolve the recorded skip condition and rerun Version Analysis.'
205
+ }
206
+ };
207
+ if (values[decision.primaryReasonCode]) return values[decision.primaryReasonCode];
208
+ if (decision.decision === 'INVESTIGATE') {
209
+ return {
210
+ code: 'COMPLETE_HUMAN_INVESTIGATION',
211
+ message: 'Resolve the deterministic investigation questions before selecting migration work.'
212
+ };
213
+ }
214
+ if (['PLAN_UPGRADE', 'UPGRADE_NOW'].includes(decision.decision)) {
215
+ return {
216
+ code: 'REVIEW_MIGRATION_HANDOFF',
217
+ message: 'Review evidence-bounded actions, affected areas, and verification state.'
218
+ };
219
+ }
220
+ return { code: 'NONE', message: 'No migration handoff step is required.' };
221
+ }
222
+
223
+ function handoffBasis(decision, affectedAreas, verification, officialEvidence) {
224
+ return {
225
+ decisionId: decision.id,
226
+ decision: decisionProjection(decision),
227
+ affectedAreas: structuredClone(affectedAreas).sort(compareAffectedAreas),
228
+ coverage: structuredClone(decision.impact.coverage),
229
+ verification: structuredClone(verification),
230
+ officialEvidence: structuredClone(officialEvidence),
231
+ preconditions: preconditionsFor(decision),
232
+ recovery: { status: 'RECOVERY_PLAN_NOT_PROVIDED', evidenceRefs: [] },
233
+ reviewQuestions: reviewQuestionsFor(decision),
234
+ missingInformation: missingInformationFor(decision),
235
+ nextStep: nextStepFor(decision),
236
+ humanReviewRequired: decision.requiresHumanReview
237
+ };
238
+ }
239
+
240
+ function humanReviewLimitation(result) {
241
+ if (!result.requiresHumanReview && result.humanReviewReasons.length === 0) return [];
242
+ const reasons = sortedUniqueText(result.humanReviewReasons);
243
+ return [limitation(
244
+ 'UPSTREAM_HUMAN_REVIEW_REQUIRED',
245
+ reasons.length > 0
246
+ ? `Version Analysis requires human review: ${reasons.join(', ')}.`
247
+ : 'Version Analysis requires human review.'
248
+ )];
249
+ }
250
+
251
+ function validateBounds(options) {
252
+ const bounds = {
253
+ maxEvidenceItems: options.maxEvidenceItems ?? DEFAULT_MIGRATION_CONTEXT_MAX_EVIDENCE_ITEMS,
254
+ maxEvidenceCharacters: options.maxEvidenceCharacters
255
+ ?? DEFAULT_MIGRATION_CONTEXT_MAX_EVIDENCE_CHARACTERS,
256
+ maxFindingSummaryCharacters: options.maxFindingSummaryCharacters
257
+ ?? DEFAULT_MIGRATION_CONTEXT_MAX_FINDING_SUMMARY_CHARACTERS
258
+ };
259
+ for (const [field, value] of Object.entries(bounds)) {
260
+ if (!Number.isSafeInteger(value) || value <= 0) {
261
+ throw new Error(`Migration Context input error: ${field} must be a positive safe integer.`);
262
+ }
263
+ }
264
+ return bounds;
265
+ }
266
+
267
+ function isOfficialActionSource(source) {
268
+ return ['official', 'publisher'].includes(source.trust)
269
+ && ['officialProject', 'publisherProvided'].includes(source.authority)
270
+ && source.kind !== 'registry'
271
+ && source.kind !== 'community';
272
+ }
273
+
274
+ function sourceIsStale(source) {
275
+ return source.status === 'stale' || source.snapshot?.freshness === 'stale';
276
+ }
277
+
278
+ function sourceHasConflict(source, packageId, knowledgeManifest, result) {
279
+ if ((source.conflictsWith?.length ?? 0) > 0) return true;
280
+ if (result.validation.warningCodes.includes('SOURCE_CONFLICT')) return true;
281
+ if (result.limitations.some((item) => item.code === 'SOURCE_CONFLICT')) return true;
282
+ return knowledgeManifest.warnings.some((warning) => (
283
+ warning.code === 'SOURCE_CONFLICT'
284
+ && (!warning.packageId || warning.packageId === packageId)
285
+ && (!warning.sourceId || warning.sourceId === source.id)
286
+ ));
287
+ }
288
+
289
+ function sourceHasStaleWarning(source, packageId, knowledgeManifest, result) {
290
+ if (sourceIsStale(source)) return true;
291
+ if (result.validation.warningCodes.includes('CACHE_EXPIRED')) return true;
292
+ if (result.limitations.some((item) => item.code === 'CACHE_EXPIRED')) return true;
293
+ return knowledgeManifest.warnings.some((warning) => (
294
+ warning.code === 'CACHE_EXPIRED'
295
+ && (!warning.packageId || warning.packageId === packageId)
296
+ && (!warning.sourceId || warning.sourceId === source.id)
297
+ ));
298
+ }
299
+
300
+ function evidenceRecord(bundleItem, source) {
301
+ return {
302
+ id: bundleItem.id,
303
+ sourceId: bundleItem.sourceId,
304
+ sourceUrl: source.url,
305
+ kind: bundleItem.kind,
306
+ authority: source.authority,
307
+ trust: source.trust,
308
+ retrievedAt: bundleItem.retrievedAt,
309
+ contentDigest: bundleItem.contentDigest,
310
+ locator: bundleItem.locator,
311
+ releaseVersions: [...bundleItem.releaseVersions],
312
+ content: bundleItem.content
313
+ };
314
+ }
315
+
316
+ function compareEvidence(left, right) {
317
+ return (EVIDENCE_KIND_PRIORITY[left.record.kind] ?? 99)
318
+ - (EVIDENCE_KIND_PRIORITY[right.record.kind] ?? 99)
319
+ || compareText(left.record.id, right.record.id);
320
+ }
321
+
322
+ function evidenceScopeMatches(bundleItem, targetVersion) {
323
+ return targetVersion !== null && bundleItem.releaseVersions.includes(targetVersion);
324
+ }
325
+
326
+ function resolveFindingEvidence(artifacts, result, finding, bounds) {
327
+ const bundledById = new Map(artifacts.knowledgeEvidenceBundle.evidence.map((item) => [item.id, item]));
328
+ const sourcesById = new Map(artifacts.knowledgeManifest.sources.map((item) => [item.id, item]));
329
+ const resolved = finding.evidenceRefs.map((ref) => {
330
+ const bundleItem = bundledById.get(ref);
331
+ const source = sourcesById.get(bundleItem.sourceId);
332
+ return { bundleItem, source, record: evidenceRecord(bundleItem, source) };
333
+ });
334
+ const invalid = resolved.filter(({ source }) => (
335
+ sourceHasConflict(source, result.dependency.packageId, artifacts.knowledgeManifest, result)
336
+ || sourceHasStaleWarning(source, result.dependency.packageId, artifacts.knowledgeManifest, result)
337
+ ));
338
+ const usefulOfficialRefs = resolved
339
+ .filter(({ source }) => isOfficialActionSource(source))
340
+ .map(({ record }) => record.id)
341
+ .sort(compareText);
342
+ const candidates = resolved
343
+ .filter(({ bundleItem, source }) => (
344
+ isOfficialActionSource(source)
345
+ && source.status === 'available'
346
+ && !sourceIsStale(source)
347
+ && ACTION_EVIDENCE_KINDS.has(bundleItem.kind)
348
+ && evidenceScopeMatches(bundleItem, result.versions.targetVersion)
349
+ ))
350
+ .sort(compareEvidence);
351
+
352
+ const selected = [];
353
+ const contentDigests = new Set();
354
+ let characters = 0;
355
+ let boundsApplied = false;
356
+ for (const candidate of candidates) {
357
+ if (contentDigests.has(candidate.record.contentDigest)) continue;
358
+ if (selected.length >= bounds.maxEvidenceItems
359
+ || characters + candidate.record.content.length > bounds.maxEvidenceCharacters) {
360
+ boundsApplied = true;
361
+ continue;
362
+ }
363
+ selected.push(candidate.record);
364
+ contentDigests.add(candidate.record.contentDigest);
365
+ characters += candidate.record.content.length;
366
+ }
367
+ return {
368
+ selected,
369
+ usefulOfficialRefs,
370
+ hasInvalidEvidence: invalid.length > 0,
371
+ invalidReasons: sortedUniqueText(invalid.flatMap(({ source }) => [
372
+ ...(sourceHasConflict(source, result.dependency.packageId, artifacts.knowledgeManifest, result)
373
+ ? ['SOURCE_CONFLICT'] : []),
374
+ ...(sourceHasStaleWarning(source, result.dependency.packageId, artifacts.knowledgeManifest, result)
375
+ ? ['STALE_EVIDENCE'] : [])
376
+ ])),
377
+ boundsApplied
378
+ };
379
+ }
380
+
381
+ /** Classify action-generation eligibility without considering repository location coverage. */
382
+ export function classifyMigrationEligibility({
383
+ analysisStatus,
384
+ targetVersion,
385
+ selectedEvidence = [],
386
+ hasInvalidEvidence = false,
387
+ summaryWithinBounds = true
388
+ }) {
389
+ if (analysisStatus !== 'analyzed') return migrationChecklistEligibility('NOT_ANALYZED');
390
+ if (hasInvalidEvidence) return migrationChecklistEligibility('INVALID_OR_CONFLICTED_EVIDENCE');
391
+ if (targetVersion === null || selectedEvidence.length === 0 || !summaryWithinBounds) {
392
+ return migrationChecklistEligibility('NO_GROUNDED_ACTION');
393
+ }
394
+ return migrationChecklistEligibility('ELIGIBLE');
395
+ }
396
+
397
+ function positiveLocations(impactEvidenceFinding) {
398
+ if (!impactEvidenceFinding?.impacted) return [];
399
+ return impactEvidenceFinding.matchedSymbols.flatMap((match) => (
400
+ match.usages.map((usage) => ({
401
+ impactEvidenceId: impactEvidenceFinding.id,
402
+ symbol: match.symbol,
403
+ file: usage.file
404
+ }))
405
+ )).sort(compareLocations);
406
+ }
407
+
408
+ function affectedAreasFor(artifacts, decision) {
409
+ if (decision.analysisResultId === null) return [];
410
+ const dependency = artifacts.repositoryImpactEvidence.dependencies
411
+ .find((item) => item.analysisResultId === decision.analysisResultId);
412
+ if (!dependency) return [];
413
+ return dependency.findings.flatMap((finding) => (
414
+ positiveLocations(finding).map((location) => ({
415
+ ...location,
416
+ findingId: finding.findingId,
417
+ coverageStatus: dependency.coverage?.status ?? 'unavailable'
418
+ }))
419
+ )).sort(compareAffectedAreas);
420
+ }
421
+
422
+ function projectUsageCoverageSupported(artifacts, result) {
423
+ return coverageForProject(
424
+ artifacts.usageIndex,
425
+ result.dependency.projectId,
426
+ result.dependency.ecosystem
427
+ ).status === 'complete';
428
+ }
429
+
430
+ function locationEligibility(artifacts, result, locations, impactEvidenceFinding) {
431
+ if (result.status !== 'analyzed') {
432
+ return { status: 'INELIGIBLE', reasonCode: 'NOT_ANALYZED' };
433
+ }
434
+ if (locations.length > 0) {
435
+ return { status: 'ELIGIBLE', reasonCode: 'POSITIVE_USAGE_MATCH' };
436
+ }
437
+ if (impactEvidenceFinding?.status === 'COVERAGE_UNAVAILABLE') {
438
+ return { status: 'REVIEW_REQUIRED', reasonCode: 'UNSUPPORTED_USAGE_COVERAGE' };
439
+ }
440
+ if (projectUsageCoverageSupported(artifacts, result)) {
441
+ return { status: 'REVIEW_REQUIRED', reasonCode: 'NO_POSITIVE_USAGE_MATCH' };
442
+ }
443
+ return { status: 'REVIEW_REQUIRED', reasonCode: 'UNSUPPORTED_USAGE_COVERAGE' };
444
+ }
445
+
446
+ function locationLimitations(eligibility) {
447
+ if (eligibility.reasonCode === 'UNSUPPORTED_USAGE_COVERAGE') {
448
+ return [limitation(
449
+ 'UNSUPPORTED_USAGE_COVERAGE',
450
+ 'Usage coverage cannot be proven for this project; no repository location or unused/safe conclusion is available.'
451
+ )];
452
+ }
453
+ if (eligibility.reasonCode === 'NO_POSITIVE_USAGE_MATCH') {
454
+ return [limitation(
455
+ 'NO_POSITIVE_USAGE_MATCH',
456
+ 'No positive exact usage match was recorded; this is not evidence that the dependency is unused or safe to upgrade.'
457
+ )];
458
+ }
459
+ return [];
460
+ }
461
+
462
+ function classificationLimitations(result, evidence, eligibility, summaryWithinBounds) {
463
+ const values = [
464
+ ...result.limitations,
465
+ ...humanReviewLimitation(result)
466
+ ];
467
+ if (eligibility.reasonCode === 'INVALID_OR_CONFLICTED_EVIDENCE') {
468
+ values.push(limitation(
469
+ 'INVALID_OR_CONFLICTED_EVIDENCE',
470
+ `Selected evidence cannot ground an action: ${evidence.invalidReasons.join(', ') || 'invalid evidence state'}.`
471
+ ));
472
+ } else if (eligibility.reasonCode === 'NO_GROUNDED_ACTION') {
473
+ values.push(limitation(
474
+ 'NO_GROUNDED_ACTION',
475
+ 'No bounded, target-scoped official or publisher evidence is available for action generation.'
476
+ ));
477
+ }
478
+ if (!summaryWithinBounds) {
479
+ values.push(limitation(
480
+ 'FINDING_SUMMARY_EXCEEDS_BOUND',
481
+ 'The finding summary exceeds the deterministic model-context bound and was not sent for generation.'
482
+ ));
483
+ }
484
+ if (evidence.boundsApplied) {
485
+ values.push(limitation(
486
+ 'EVIDENCE_BOUNDS_APPLIED',
487
+ 'Additional eligible evidence was excluded by deterministic item or character bounds.'
488
+ ));
489
+ }
490
+ if (result.versions.targetPolicy === 'registryLatest') {
491
+ values.push(limitation(
492
+ 'REGISTRY_LATEST_IS_NOT_RECOMMENDATION',
493
+ 'The target is a registry-latest fact and is not a recommended migration target.'
494
+ ));
495
+ }
496
+ return values;
497
+ }
498
+
499
+ function contextId(input, payload) {
500
+ return digest({
501
+ contextVersion: MIGRATION_TASK_CONTEXT_VERSION,
502
+ inputDigests: Object.fromEntries(Object.entries(input).map(([key, value]) => [key, value.artifactDigest])),
503
+ payload
504
+ });
505
+ }
506
+
507
+ function buildEligibleContext({
508
+ artifacts,
509
+ result,
510
+ decision,
511
+ handoff,
512
+ finding,
513
+ evidence,
514
+ locations,
515
+ locationState,
516
+ limitations
517
+ }) {
518
+ const selectedRefs = evidence.selected.map((item) => item.id).sort(compareText);
519
+ const payload = {
520
+ dependency: structuredClone(result.dependency),
521
+ versions: structuredClone(result.versions),
522
+ analysisResultId: result.id,
523
+ ...structuredClone(handoff),
524
+ finding: {
525
+ id: finding.id,
526
+ kind: finding.kind,
527
+ summary: finding.summary,
528
+ appliesToVersions: [...finding.appliesToVersions],
529
+ evidenceRefs: selectedRefs
530
+ },
531
+ evidence: structuredClone(evidence.selected),
532
+ evidenceAllowlist: selectedRefs,
533
+ positiveCandidateLocations: structuredClone(locations),
534
+ eligibility: migrationChecklistEligibility('ELIGIBLE'),
535
+ locationEligibility: locationState,
536
+ requiresHumanReview: true,
537
+ humanReviewReasons: sortedUniqueText([
538
+ ...result.humanReviewReasons,
539
+ 'MIGRATION_CHECKLIST_DRAFT_REVIEW_REQUIRED'
540
+ ]),
541
+ limitations: sortedUniqueLimitations(limitations)
542
+ };
543
+ return {
544
+ contextVersion: MIGRATION_TASK_CONTEXT_VERSION,
545
+ contextId: contextId(artifacts.input, payload),
546
+ ...payload
547
+ };
548
+ }
549
+
550
+ function fallbackInstruction(reasonCode) {
551
+ if (reasonCode === 'INVALID_OR_CONFLICTED_EVIDENCE') {
552
+ return 'Manual review is required because selected official evidence is stale or conflicted.';
553
+ }
554
+ return 'Manual review is required because no bounded official migration action is available.';
555
+ }
556
+
557
+ function fallbackFinding(finding, eligibility, evidenceRefs) {
558
+ return {
559
+ id: finding.id,
560
+ kind: finding.kind,
561
+ summary: finding.summary,
562
+ eligibilityReasonCode: eligibility.reasonCode,
563
+ evidenceRefs: sortedUniqueText(evidenceRefs),
564
+ positiveImpactLocations: [],
565
+ items: [{
566
+ kind: 'MANUAL_REVIEW_REQUIRED',
567
+ basis: 'DETERMINISTIC',
568
+ instruction: fallbackInstruction(eligibility.reasonCode),
569
+ findingId: finding.id,
570
+ evidenceRefs: sortedUniqueText(evidenceRefs),
571
+ candidateLocations: [],
572
+ requiresHumanReview: true
573
+ }]
574
+ };
575
+ }
576
+
577
+ function baseFallbackRecord(result, handoff) {
578
+ return {
579
+ analysisResultId: result.id,
580
+ ...structuredClone(handoff),
581
+ dependency: structuredClone(result.dependency),
582
+ versions: structuredClone(result.versions),
583
+ analysisStatus: result.status,
584
+ selectedEvidenceRefs: [],
585
+ findings: [],
586
+ limitations: []
587
+ };
588
+ }
589
+
590
+ function notAnalyzedFallback(result, handoff) {
591
+ const record = baseFallbackRecord(result, handoff);
592
+ record.limitations = sortedUniqueLimitations([
593
+ ...result.limitations,
594
+ ...humanReviewLimitation(result),
595
+ limitation(
596
+ 'NOT_ANALYZED',
597
+ `Version Analysis status is ${result.status}; next action is ${result.nextAction}.`
598
+ )
599
+ ]);
600
+ return record;
601
+ }
602
+
603
+ function noFindingFallback(result, handoff) {
604
+ const record = baseFallbackRecord(result, handoff);
605
+ record.limitations = sortedUniqueLimitations([
606
+ ...result.limitations,
607
+ ...humanReviewLimitation(result),
608
+ limitation('NO_GROUNDED_ACTION', 'Version Analysis contains no breaking-change finding to ground a migration action.')
609
+ ]);
610
+ return record;
611
+ }
612
+
613
+ function upgradeDecisionFallback(result, decision, handoff) {
614
+ const record = baseFallbackRecord(result, handoff);
615
+ record.selectedEvidenceRefs = [...decision.evidence.targetScopedRefs];
616
+ const manualReview = ['INVESTIGATE', 'INSUFFICIENT_EVIDENCE', 'NOT_ANALYZED']
617
+ .includes(decision.decision);
618
+ record.limitations = sortedUniqueLimitations([
619
+ ...result.limitations,
620
+ ...decision.limitations,
621
+ ...humanReviewLimitation(result),
622
+ ...(decision.decision === 'NOT_ANALYZED' ? [limitation(
623
+ 'NOT_ANALYZED',
624
+ `Version Analysis status is ${result.status}; next action is ${result.nextAction}.`
625
+ )] : []),
626
+ limitation(
627
+ `UPGRADE_DECISION_${decision.decision}`,
628
+ manualReview
629
+ ? `Deterministic Upgrade Decision is ${decision.decision}; migration action generation is blocked pending human review or additional evidence.`
630
+ : 'Deterministic Upgrade Decision is KEEP_CURRENT; no version-change action is permitted.'
631
+ )
632
+ ]);
633
+ return record;
634
+ }
635
+
636
+ function missingAnalysisFallback(decision, handoff) {
637
+ return {
638
+ analysisResultId: null,
639
+ ...structuredClone(handoff),
640
+ dependency: structuredClone(decision.occurrence),
641
+ versions: {
642
+ analysisMode: 'unsupportedBaseline',
643
+ declaredVersion: decision.versions.declaredVersion,
644
+ installedVersion: null,
645
+ installedVersionStatus: 'legacyMissing',
646
+ installedVersionSource: null,
647
+ installedVersionReason: null,
648
+ currentVersion: null,
649
+ currentVersionSource: null,
650
+ targetVersion: decision.versions.targetVersion,
651
+ targetPolicy: decision.versions.targetPolicy,
652
+ delta: { direction: 'unknown', classification: 'unknown' }
653
+ },
654
+ analysisStatus: 'missing',
655
+ selectedEvidenceRefs: [...decision.evidence.targetScopedRefs],
656
+ findings: [],
657
+ limitations: sortedUniqueLimitations([
658
+ ...decision.limitations,
659
+ limitation(
660
+ 'VERSION_ANALYSIS_MISSING',
661
+ 'No Version Analysis result exists for this dependency occurrence.'
662
+ )
663
+ ])
664
+ };
665
+ }
666
+
667
+ function impactEvidenceByResult(artifacts) {
668
+ return new Map(artifacts.repositoryImpactEvidence.dependencies.map((dependency) => [
669
+ dependency.analysisResultId,
670
+ new Map(dependency.findings.map((finding) => [finding.findingId, finding]))
671
+ ]));
672
+ }
673
+
674
+ /** Build immutable, minimal MP-03 contexts and MP-01-compatible deterministic fallbacks. */
675
+ export function buildMigrationTaskContexts(artifacts, options = {}) {
676
+ if (!artifacts.upgradeDecision) {
677
+ if (options.allowLegacyWithoutDecision === true) {
678
+ // Historical evaluation-only behavior. V2 assembly requires decision lineage.
679
+ } else {
680
+ const error = new Error(
681
+ 'Migration Checklist input error: persisted authoritative Upgrade Decision is required.'
682
+ );
683
+ error.code = 'UPGRADE_DECISION_REQUIRED';
684
+ throw error;
685
+ }
686
+ }
687
+ const bounds = validateBounds(options);
688
+ const evidenceByResult = impactEvidenceByResult(artifacts);
689
+ const eligibleContexts = [];
690
+ const fallbackRecords = [];
691
+ const summary = {
692
+ totalFindings: 0,
693
+ eligible: 0,
694
+ notAnalyzed: 0,
695
+ noGroundedAction: 0,
696
+ unsupportedUsageCoverage: 0,
697
+ conflictedEvidence: 0
698
+ };
699
+ const decisionsByResult = new Map((artifacts.upgradeDecision?.decisions ?? []).map((decision) => [
700
+ decision.analysisResultId,
701
+ decision
702
+ ]));
703
+ const verificationByProject = options.verificationByProject ?? new Map();
704
+
705
+ for (const result of artifacts.versionAnalysis.results) {
706
+ const upgradeDecision = decisionsByResult.get(result.id);
707
+ if (!upgradeDecision && artifacts.upgradeDecision) {
708
+ throw new Error(`Migration Checklist input error: no Upgrade Decision for ${result.id}.`);
709
+ }
710
+ const handoff = upgradeDecision
711
+ ? handoffBasis(
712
+ upgradeDecision,
713
+ affectedAreasFor(artifacts, upgradeDecision),
714
+ verificationByProject.get(result.dependency.projectId) ?? unavailableProjectVerification(),
715
+ officialEvidenceFor(artifacts, upgradeDecision.evidence.targetScopedRefs)
716
+ )
717
+ : null;
718
+ if (upgradeDecision && !['PLAN_UPGRADE', 'UPGRADE_NOW'].includes(upgradeDecision.decision)) {
719
+ fallbackRecords.push(upgradeDecisionFallback(result, upgradeDecision, handoff));
720
+ if (upgradeDecision.decision === 'NOT_ANALYZED') summary.notAnalyzed += 1;
721
+ else summary.noGroundedAction += 1;
722
+ continue;
723
+ }
724
+ if (result.status !== 'analyzed') {
725
+ fallbackRecords.push(notAnalyzedFallback(result, handoff));
726
+ summary.notAnalyzed += 1;
727
+ continue;
728
+ }
729
+ const breakingFindings = result.findings.filter((finding) => finding.kind === 'breakingChange');
730
+ if (breakingFindings.length === 0) {
731
+ fallbackRecords.push(noFindingFallback(result, handoff));
732
+ summary.noGroundedAction += 1;
733
+ continue;
734
+ }
735
+ const fallback = baseFallbackRecord(result, handoff);
736
+ for (const finding of breakingFindings) {
737
+ summary.totalFindings += 1;
738
+ const resolvedEvidence = resolveFindingEvidence(artifacts, result, finding, bounds);
739
+ const summaryWithinBounds = finding.summary.length <= bounds.maxFindingSummaryCharacters;
740
+ const eligibility = classifyMigrationEligibility({
741
+ analysisStatus: result.status,
742
+ targetVersion: result.versions.targetVersion,
743
+ selectedEvidence: resolvedEvidence.selected,
744
+ hasInvalidEvidence: resolvedEvidence.hasInvalidEvidence,
745
+ summaryWithinBounds
746
+ });
747
+ const impactFinding = evidenceByResult.get(result.id)?.get(finding.id);
748
+ const locations = positiveLocations(impactFinding);
749
+ const locationState = locationEligibility(artifacts, result, locations, impactFinding);
750
+ const limitations = sortedUniqueLimitations([
751
+ ...upgradeDecision.limitations,
752
+ ...classificationLimitations(result, resolvedEvidence, eligibility, summaryWithinBounds),
753
+ ...locationLimitations(locationState)
754
+ ]);
755
+ if (locationState.reasonCode === 'UNSUPPORTED_USAGE_COVERAGE') {
756
+ summary.unsupportedUsageCoverage += 1;
757
+ }
758
+ if (eligibility.reasonCode === 'ELIGIBLE') {
759
+ eligibleContexts.push(buildEligibleContext({
760
+ artifacts,
761
+ result,
762
+ decision: upgradeDecision,
763
+ handoff,
764
+ finding,
765
+ evidence: resolvedEvidence,
766
+ locations,
767
+ locationState,
768
+ limitations
769
+ }));
770
+ summary.eligible += 1;
771
+ continue;
772
+ }
773
+ if (eligibility.reasonCode === 'INVALID_OR_CONFLICTED_EVIDENCE') {
774
+ summary.conflictedEvidence += 1;
775
+ } else {
776
+ summary.noGroundedAction += 1;
777
+ }
778
+ const refs = resolvedEvidence.usefulOfficialRefs;
779
+ fallback.findings.push(fallbackFinding(finding, eligibility, refs));
780
+ fallback.selectedEvidenceRefs.push(...refs);
781
+ fallback.limitations.push(...limitations);
782
+ }
783
+ if (fallback.findings.length > 0) {
784
+ fallback.findings.sort((left, right) => compareText(left.id, right.id));
785
+ fallback.selectedEvidenceRefs = sortedUniqueText(fallback.selectedEvidenceRefs);
786
+ fallback.limitations = sortedUniqueLimitations(fallback.limitations);
787
+ fallbackRecords.push(fallback);
788
+ }
789
+ }
790
+
791
+ for (const decision of artifacts.upgradeDecision?.decisions ?? []) {
792
+ if (decision.analysisResultId !== null) continue;
793
+ const handoff = handoffBasis(
794
+ decision,
795
+ [],
796
+ verificationByProject.get(decision.occurrence.projectId) ?? unavailableProjectVerification(),
797
+ officialEvidenceFor(artifacts, decision.evidence.targetScopedRefs)
798
+ );
799
+ fallbackRecords.push(missingAnalysisFallback(decision, handoff));
800
+ summary.notAnalyzed += 1;
801
+ }
802
+
803
+ const output = {
804
+ contextVersion: MIGRATION_TASK_CONTEXT_VERSION,
805
+ input: structuredClone(artifacts.input),
806
+ eligibleContexts: eligibleContexts.sort(compareContexts),
807
+ fallbackRecords: fallbackRecords.sort(compareFallbackRecords),
808
+ summary
809
+ };
810
+ return deepFreeze(structuredClone(output));
811
+ }
812
+
813
+ /**
814
+ * Load the seven legacy inputs plus the optional persisted Upgrade Decision, then
815
+ * prepare contexts without AI or writes. Production analyze always supplies the decision.
816
+ */
817
+ export async function prepareMigrationChecklistContexts(input, options = {}) {
818
+ const artifacts = await loadMigrationChecklistInputs(input, options);
819
+ const withDecision = await loadPersistedUpgradeDecision(input, artifacts, options);
820
+ const repositoryRoot = typeof input === 'string'
821
+ ? input
822
+ : typeof input?.repositoryRoot === 'string'
823
+ ? input.repositoryRoot
824
+ : null;
825
+ const verificationByProject = options.verificationByProject
826
+ ?? await extractProjectVerification(repositoryRoot, withDecision.projectManifest, options);
827
+ return buildMigrationTaskContexts(withDecision, { ...options, verificationByProject });
828
+ }