@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,635 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFile } from 'node:fs/promises';
3
+
4
+ import Ajv2020 from 'ajv/dist/2020.js';
5
+ import addFormats from 'ajv-formats';
6
+
7
+ import { canonicalJsonBytes } from '../canonical-json.js';
8
+ import {
9
+ MIGRATION_CHECKLIST_SCHEMA_VERSION,
10
+ ARTIFACT_GENERATOR_NAME,
11
+ VERSION
12
+ } from '../constants.js';
13
+ import { compareText, isSorted } from '../portable.js';
14
+ import {
15
+ MIGRATION_CHECKLIST_STATUSES,
16
+ isActionableMigrationChecklistItem,
17
+ migrationChecklistEligibility,
18
+ migrationChecklistStatusForEligibility,
19
+ validateMigrationChecklistInstructionContent
20
+ } from './grounding-policy.js';
21
+
22
+ const schema = JSON.parse(await readFile(
23
+ new URL('../../schemas/migration-checklist.schema.json', import.meta.url),
24
+ 'utf8'
25
+ ));
26
+ const ajv = new Ajv2020({ allErrors: true, strict: true });
27
+ addFormats(ajv);
28
+ const validateSchema = ajv.compile(schema);
29
+
30
+ const compareDependencies = (left, right) => (
31
+ compareText(left.dependency.projectId, right.dependency.projectId)
32
+ || compareText(left.dependency.manifest, right.dependency.manifest)
33
+ || compareText(left.dependency.dependencyType, right.dependency.dependencyType)
34
+ || compareText(left.dependency.packageId, right.dependency.packageId)
35
+ || compareText(left.versions.declaredVersion ?? '', right.versions.declaredVersion ?? '')
36
+ || compareText(left.versions.targetVersion ?? '', right.versions.targetVersion ?? '')
37
+ || compareText(left.analysisResultId ?? '', right.analysisResultId ?? '')
38
+ || compareText(left.decisionId ?? '', right.decisionId ?? '')
39
+ );
40
+ const compareFindings = (left, right) => compareText(left.id, right.id);
41
+ const compareItems = (left, right) => compareText(left.id, right.id);
42
+ const compareLocations = (left, right) => (
43
+ compareText(left.impactEvidenceId, right.impactEvidenceId)
44
+ || compareText(left.symbol, right.symbol)
45
+ || compareText(left.file, right.file)
46
+ );
47
+ const compareLimitations = (left, right) => (
48
+ compareText(left.code, right.code) || compareText(left.message, right.message)
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 compareVerificationCommands = (left, right) => (
57
+ compareText(left.role, right.role)
58
+ || compareText(left.workingDirectory, right.workingDirectory)
59
+ || compareText(left.command, right.command)
60
+ || compareText(left.id, right.id)
61
+ );
62
+
63
+ function digest(value) {
64
+ return `sha256:${createHash('sha256').update(canonicalJsonBytes(value)).digest('hex')}`;
65
+ }
66
+
67
+ function sortedText(values = []) {
68
+ return [...values].sort(compareText);
69
+ }
70
+
71
+ function sortedLocations(locations = []) {
72
+ return structuredClone(locations).sort(compareLocations);
73
+ }
74
+
75
+ function duplicateValues(values) {
76
+ const seen = new Set();
77
+ const duplicates = new Set();
78
+ for (const value of values) {
79
+ if (seen.has(value)) duplicates.add(value);
80
+ seen.add(value);
81
+ }
82
+ return [...duplicates].sort(compareText);
83
+ }
84
+
85
+ function locationKey(location) {
86
+ return `${location.impactEvidenceId}\0${location.symbol}\0${location.file}`;
87
+ }
88
+
89
+ function sameJson(left, right) {
90
+ return JSON.stringify(left) === JSON.stringify(right);
91
+ }
92
+
93
+ function expectedFindingState(reasonCode) {
94
+ return {
95
+ status: migrationChecklistStatusForEligibility(reasonCode),
96
+ eligibility: migrationChecklistEligibility(reasonCode)
97
+ };
98
+ }
99
+
100
+ function expectedDependencyState(analysisStatus, findings) {
101
+ if (analysisStatus !== 'analyzed') {
102
+ return {
103
+ status: 'NOT_ANALYZED',
104
+ eligibility: migrationChecklistEligibility('NOT_ANALYZED')
105
+ };
106
+ }
107
+ if (findings.length === 0 || findings.every((finding) => finding.status === 'NO_GROUNDED_ACTION')) {
108
+ return {
109
+ status: 'NO_GROUNDED_ACTION',
110
+ eligibility: migrationChecklistEligibility('NO_GROUNDED_ACTION')
111
+ };
112
+ }
113
+ if (findings.every((finding) => finding.status === 'COMPLETE')) {
114
+ return {
115
+ status: 'COMPLETE',
116
+ eligibility: migrationChecklistEligibility('ELIGIBLE')
117
+ };
118
+ }
119
+ const reasons = new Set(findings.map((finding) => finding.eligibility.reasonCode));
120
+ const reasonCode = reasons.has('INVALID_OR_CONFLICTED_EVIDENCE')
121
+ ? 'INVALID_OR_CONFLICTED_EVIDENCE'
122
+ : reasons.has('UNSUPPORTED_USAGE_COVERAGE')
123
+ ? 'UNSUPPORTED_USAGE_COVERAGE'
124
+ : 'MANUAL_REVIEW_REQUIRED';
125
+ return {
126
+ status: 'INCOMPLETE',
127
+ eligibility: migrationChecklistEligibility(reasonCode)
128
+ };
129
+ }
130
+
131
+ function expectedOverallStatus(dependencies) {
132
+ if (dependencies.length === 0) return 'NO_GROUNDED_ACTION';
133
+ if (dependencies.every((dependency) => dependency.status === 'COMPLETE')) return 'COMPLETE';
134
+ if (dependencies.every((dependency) => dependency.status === 'NOT_ANALYZED')) return 'NOT_ANALYZED';
135
+ if (dependencies.every((dependency) => dependency.status === 'NO_GROUNDED_ACTION')) {
136
+ return 'NO_GROUNDED_ACTION';
137
+ }
138
+ return 'INCOMPLETE';
139
+ }
140
+
141
+ function checklistSummary(dependencies, limitations) {
142
+ const findings = dependencies.flatMap((dependency) => dependency.findings);
143
+ const items = findings.flatMap((finding) => finding.items);
144
+ const statusCounts = Object.fromEntries(MIGRATION_CHECKLIST_STATUSES.map((status) => [
145
+ status,
146
+ dependencies.filter((dependency) => dependency.status === status).length
147
+ ]));
148
+ const handoffStatusCounts = Object.fromEntries([
149
+ 'NO_VERSION_CHANGE_REQUIRED',
150
+ 'ACTIONABLE_WITH_REVIEW',
151
+ 'INVESTIGATION_REQUIRED',
152
+ 'INSUFFICIENT_EVIDENCE',
153
+ 'NOT_ANALYZED',
154
+ 'ACTION_GENERATION_FAILED',
155
+ 'NO_GROUNDED_ACTION'
156
+ ].map((status) => [
157
+ status,
158
+ dependencies.filter((dependency) => dependency.handoff.status === status).length
159
+ ]));
160
+ return {
161
+ dependencyCount: dependencies.length,
162
+ findingCount: findings.length,
163
+ itemCount: items.length,
164
+ groundedActionCount: items.filter((item) => isActionableMigrationChecklistItem(item.kind)).length,
165
+ aiAuthoredItemCount: items.filter((item) => item.basis === 'AI_AUTHORED').length,
166
+ candidateLocationCount: items.reduce((count, item) => count + item.candidateLocations.length, 0),
167
+ requiresHumanReviewItemCount: items.filter((item) => item.requiresHumanReview).length,
168
+ limitationCount: limitations.length
169
+ + dependencies.reduce((count, dependency) => count + dependency.limitations.length, 0),
170
+ statusCounts,
171
+ handoffStatusCounts
172
+ };
173
+ }
174
+
175
+ function itemIdMaterial(analysisResultId, findingId, item) {
176
+ return {
177
+ analysisResultId,
178
+ findingId,
179
+ kind: item.kind,
180
+ basis: item.basis,
181
+ instruction: item.instruction,
182
+ evidenceRefs: sortedText(item.evidenceRefs),
183
+ candidateLocations: sortedLocations(item.candidateLocations),
184
+ requiresHumanReview: item.requiresHumanReview
185
+ };
186
+ }
187
+
188
+ export function migrationChecklistItemId(analysisResultId, findingId, item) {
189
+ return digest(itemIdMaterial(analysisResultId, findingId, item));
190
+ }
191
+
192
+ function normalizeItem(analysisResultId, findingId, item) {
193
+ const normalized = {
194
+ kind: item.kind,
195
+ basis: item.basis,
196
+ instruction: item.instruction,
197
+ findingId: item.findingId,
198
+ evidenceRefs: sortedText(item.evidenceRefs),
199
+ candidateLocations: sortedLocations(item.candidateLocations),
200
+ requiresHumanReview: item.requiresHumanReview
201
+ };
202
+ return {
203
+ id: migrationChecklistItemId(analysisResultId, findingId, normalized),
204
+ ...normalized
205
+ };
206
+ }
207
+
208
+ function normalizeFinding(analysisResultId, finding) {
209
+ const reasonCode = finding.eligibilityReasonCode ?? finding.eligibility?.reasonCode;
210
+ const state = expectedFindingState(reasonCode);
211
+ return {
212
+ id: finding.id,
213
+ kind: finding.kind,
214
+ summary: finding.summary,
215
+ ...state,
216
+ evidenceRefs: sortedText(finding.evidenceRefs),
217
+ positiveImpactLocations: sortedLocations(finding.positiveImpactLocations),
218
+ items: (finding.items ?? [])
219
+ .map((item) => normalizeItem(analysisResultId, finding.id, item))
220
+ .sort(compareItems)
221
+ };
222
+ }
223
+
224
+ const GENERATION_FAILURE_CODES = new Set([
225
+ 'AI_RUNTIME_FAILED',
226
+ 'EVIDENCE_REFERENCE_INVALID',
227
+ 'OUTPUT_JSON_INVALID',
228
+ 'OUTPUT_SCHEMA_INVALID',
229
+ 'OUTPUT_SEMANTICS_INVALID',
230
+ 'PROHIBITED_CAPABILITY',
231
+ 'SUPPORTING_EXCERPT_INVALID',
232
+ 'TRUST_VALIDATION_REJECTED'
233
+ ]);
234
+
235
+ function handoffStatus(record, findings) {
236
+ const decision = record.decision.status;
237
+ if (decision === 'KEEP_CURRENT') return 'NO_VERSION_CHANGE_REQUIRED';
238
+ if (decision === 'INVESTIGATE') return 'INVESTIGATION_REQUIRED';
239
+ if (decision === 'INSUFFICIENT_EVIDENCE') return 'INSUFFICIENT_EVIDENCE';
240
+ if (decision === 'NOT_ANALYZED') return 'NOT_ANALYZED';
241
+ const hasFailure = record.limitations.some((item) => GENERATION_FAILURE_CODES.has(item.code));
242
+ if (hasFailure) return 'ACTION_GENERATION_FAILED';
243
+ const hasAction = findings.some((finding) => finding.items.some((item) => (
244
+ item.kind === 'REVIEW_MIGRATION_INSTRUCTION' && item.basis === 'AI_AUTHORED'
245
+ )));
246
+ return hasAction ? 'ACTIONABLE_WITH_REVIEW' : 'NO_GROUNDED_ACTION';
247
+ }
248
+
249
+ function normalizeVerification(record) {
250
+ if (!['PLAN_UPGRADE', 'UPGRADE_NOW'].includes(record.decision.status)) {
251
+ return { status: 'NOT_APPLICABLE', commands: [], limitation: null };
252
+ }
253
+ const verification = structuredClone(record.verification);
254
+ verification.commands.sort(compareVerificationCommands);
255
+ return verification;
256
+ }
257
+
258
+ function normalizeHandoff(record, findings) {
259
+ return {
260
+ status: handoffStatus(record, findings),
261
+ affectedAreas: structuredClone(record.affectedAreas).sort(compareAffectedAreas),
262
+ coverage: structuredClone(record.coverage),
263
+ verification: normalizeVerification(record),
264
+ officialEvidence: structuredClone(record.officialEvidence)
265
+ .sort((left, right) => compareText(left.id, right.id)),
266
+ preconditions: structuredClone(record.preconditions)
267
+ .sort((left, right) => compareText(left.code, right.code)),
268
+ recovery: structuredClone(record.recovery),
269
+ reviewQuestions: [...record.reviewQuestions].sort(compareText),
270
+ missingInformation: structuredClone(record.missingInformation)
271
+ .sort((left, right) => compareText(left.code, right.code)),
272
+ nextStep: structuredClone(record.nextStep),
273
+ humanReviewRequired: record.humanReviewRequired
274
+ };
275
+ }
276
+
277
+ function normalizeDependency(record) {
278
+ const findings = (record.findings ?? [])
279
+ .map((finding) => normalizeFinding(record.analysisResultId, finding))
280
+ .sort(compareFindings);
281
+ const state = expectedDependencyState(record.analysisStatus, findings);
282
+ return {
283
+ analysisResultId: record.analysisResultId,
284
+ decisionId: record.decisionId,
285
+ decision: {
286
+ ...structuredClone(record.decision),
287
+ reasonCodes: sortedText(record.decision.reasonCodes)
288
+ },
289
+ handoff: normalizeHandoff(record, findings),
290
+ dependency: structuredClone(record.dependency),
291
+ versions: structuredClone(record.versions),
292
+ analysisStatus: record.analysisStatus,
293
+ ...state,
294
+ selectedEvidenceRefs: sortedText(record.selectedEvidenceRefs),
295
+ findings,
296
+ limitations: structuredClone(record.limitations ?? []).sort(compareLimitations)
297
+ };
298
+ }
299
+
300
+ function validateVersionSemantics(record, errors) {
301
+ const versions = record.versions;
302
+ const installedFields = [
303
+ 'installedVersion',
304
+ 'installedVersionStatus',
305
+ 'installedVersionSource',
306
+ 'installedVersionReason'
307
+ ];
308
+ const installedFieldCount = installedFields.filter((field) => field in versions).length;
309
+ if (installedFieldCount !== 0 && installedFieldCount !== installedFields.length) {
310
+ errors.push(`Dependency ${record.analysisResultId} has an incomplete installed-version baseline.`);
311
+ }
312
+ if (versions.analysisMode === 'exactBaseline') {
313
+ if (versions.currentVersion === null || versions.currentVersionSource === null) {
314
+ errors.push(`Dependency ${record.analysisResultId} exact baseline requires current version and source.`);
315
+ }
316
+ } else if (versions.currentVersion !== null || versions.currentVersionSource !== null) {
317
+ errors.push(`Dependency ${record.analysisResultId} uncertain baseline cannot contain an exact current version.`);
318
+ }
319
+ if (versions.installedVersionStatus === 'resolved') {
320
+ if (
321
+ versions.currentVersion !== versions.installedVersion
322
+ || versions.currentVersionSource !== 'resolvedArtifact'
323
+ || versions.installedVersionSource === null
324
+ || versions.installedVersionReason !== null
325
+ ) {
326
+ errors.push(`Dependency ${record.analysisResultId} does not preserve its resolved installed-version baseline.`);
327
+ }
328
+ } else if (
329
+ versions.installedVersionStatus === 'unresolved'
330
+ && (
331
+ versions.installedVersion !== null
332
+ || versions.installedVersionSource !== null
333
+ || versions.installedVersionReason === null
334
+ )
335
+ ) {
336
+ errors.push(`Dependency ${record.analysisResultId} has inconsistent unresolved installed-version fields.`);
337
+ } else if (
338
+ versions.installedVersionStatus === 'legacyMissing'
339
+ && (
340
+ versions.installedVersion !== null
341
+ || versions.installedVersionSource !== null
342
+ || versions.installedVersionReason !== null
343
+ || record.analysisResultId !== null
344
+ )
345
+ ) {
346
+ errors.push(`Dependency ${record.decisionId} has inconsistent legacy installed-version fields.`);
347
+ }
348
+ }
349
+
350
+ function verificationCommandId(command) {
351
+ const { id, ...material } = command;
352
+ return digest(material);
353
+ }
354
+
355
+ function validateHandoff(record, errors) {
356
+ const expectedStatus = handoffStatus({
357
+ ...record,
358
+ limitations: record.limitations
359
+ }, record.findings);
360
+ if (record.handoff.status !== expectedStatus) {
361
+ errors.push(`Dependency ${record.decisionId} handoff status is inconsistent.`);
362
+ }
363
+ if (!isSorted(record.decision.reasonCodes, compareText)) {
364
+ errors.push(`Dependency ${record.decisionId} decision reason codes must be sorted.`);
365
+ }
366
+ if (!record.decision.reasonCodes.includes(record.decision.primaryReasonCode)) {
367
+ errors.push(`Dependency ${record.decisionId} decision primary reason is not present.`);
368
+ }
369
+ if (record.decision.targetOrigin !== record.versions.targetPolicy) {
370
+ errors.push(`Dependency ${record.decisionId} target origin differs from version facts.`);
371
+ }
372
+ const migrationDecision = ['PLAN_UPGRADE', 'UPGRADE_NOW'].includes(record.decision.status);
373
+ const aiActions = record.findings.flatMap((finding) => finding.items)
374
+ .filter((item) => item.basis === 'AI_AUTHORED');
375
+ if (!migrationDecision && aiActions.length > 0) {
376
+ errors.push(`Dependency ${record.decisionId} has AI action under a non-actionable decision.`);
377
+ }
378
+ if (record.decision.status === 'PLAN_UPGRADE'
379
+ && (record.decision.targetOrigin !== 'explicit'
380
+ || record.decision.recommendationDriver !== 'USER_SELECTED_TARGET')) {
381
+ errors.push(`Dependency ${record.decisionId} PLAN_UPGRADE lacks explicit target intent.`);
382
+ }
383
+ if (record.decision.status !== 'PLAN_UPGRADE'
384
+ && record.decision.status !== 'UPGRADE_NOW'
385
+ && record.decision.recommendationDriver !== null) {
386
+ errors.push(`Dependency ${record.decisionId} has a recommendation driver on a non-actionable decision.`);
387
+ }
388
+ if (!isSorted(record.handoff.affectedAreas, compareAffectedAreas)) {
389
+ errors.push(`Dependency ${record.decisionId} affected areas must be sorted.`);
390
+ }
391
+ if (!isSorted(record.handoff.verification.commands, compareVerificationCommands)) {
392
+ errors.push(`Dependency ${record.decisionId} verification commands must be sorted.`);
393
+ }
394
+ if (!isSorted(record.handoff.officialEvidence, (left, right) => compareText(left.id, right.id))) {
395
+ errors.push(`Dependency ${record.decisionId} official evidence must be sorted.`);
396
+ }
397
+ const selectedEvidence = new Set(record.selectedEvidenceRefs);
398
+ if (record.handoff.officialEvidence.some((item) => !selectedEvidence.has(item.id))) {
399
+ errors.push(`Dependency ${record.decisionId} handoff contains unselected official evidence.`);
400
+ }
401
+ if (!migrationDecision && record.handoff.verification.status !== 'NOT_APPLICABLE') {
402
+ errors.push(`Dependency ${record.decisionId} has migration verification under a non-actionable decision.`);
403
+ }
404
+ if (record.handoff.verification.status === 'AVAILABLE'
405
+ && record.handoff.verification.commands.length === 0) {
406
+ errors.push(`Dependency ${record.decisionId} available verification has no commands.`);
407
+ }
408
+ if (record.handoff.verification.status === 'VERIFICATION_COMMAND_UNAVAILABLE'
409
+ && (record.handoff.verification.commands.length > 0
410
+ || record.handoff.verification.limitation?.code !== 'VERIFICATION_COMMAND_UNAVAILABLE')) {
411
+ errors.push(`Dependency ${record.decisionId} unavailable verification is inconsistent.`);
412
+ }
413
+ for (const command of record.handoff.verification.commands) {
414
+ if (command.id !== verificationCommandId(command)) {
415
+ errors.push(`Verification command ${command.id} does not have a stable id.`);
416
+ }
417
+ }
418
+ if (record.handoff.recovery.status !== 'RECOVERY_PLAN_NOT_PROVIDED'
419
+ || record.handoff.recovery.evidenceRefs.length > 0) {
420
+ errors.push(`Dependency ${record.decisionId} has an unsupported recovery plan.`);
421
+ }
422
+ }
423
+
424
+ function validateFinding(record, finding, globalItemIds, errors) {
425
+ const expected = expectedFindingState(finding.eligibility.reasonCode);
426
+ if (finding.status !== expected.status || !sameJson(finding.eligibility, expected.eligibility)) {
427
+ errors.push(`Finding ${record.analysisResultId}/${finding.id} status and eligibility are inconsistent.`);
428
+ }
429
+ if (record.analysisStatus === 'analyzed' && finding.status === 'NOT_ANALYZED') {
430
+ errors.push(`Finding ${record.analysisResultId}/${finding.id} cannot be NOT_ANALYZED under an analyzed result.`);
431
+ }
432
+ if (!isSorted(finding.evidenceRefs, compareText)) {
433
+ errors.push(`Finding ${record.analysisResultId}/${finding.id} evidenceRefs must be sorted.`);
434
+ }
435
+ if (!isSorted(finding.positiveImpactLocations, compareLocations)) {
436
+ errors.push(`Finding ${record.analysisResultId}/${finding.id} positive impact locations must be sorted.`);
437
+ }
438
+ if (!isSorted(finding.items, compareItems)) {
439
+ errors.push(`Finding ${record.analysisResultId}/${finding.id} items must be sorted by id.`);
440
+ }
441
+
442
+ const selectedEvidence = new Set(record.selectedEvidenceRefs);
443
+ for (const ref of finding.evidenceRefs) {
444
+ if (!selectedEvidence.has(ref)) {
445
+ errors.push(`Finding ${record.analysisResultId}/${finding.id} references unknown selected evidence ${ref}.`);
446
+ }
447
+ }
448
+ for (const duplicate of duplicateValues(finding.positiveImpactLocations.map(locationKey))) {
449
+ errors.push(`Finding ${record.analysisResultId}/${finding.id} has duplicate positive impact location ${duplicate.replaceAll('\0', ' / ')}.`);
450
+ }
451
+ const allowedEvidence = new Set(finding.evidenceRefs);
452
+ const allowedLocations = new Set(finding.positiveImpactLocations.map(locationKey));
453
+
454
+ for (const item of finding.items) {
455
+ if (globalItemIds.has(item.id)) errors.push(`Duplicate checklist item id ${item.id}.`);
456
+ globalItemIds.add(item.id);
457
+ if (item.findingId !== finding.id) {
458
+ errors.push(`Checklist item ${item.id} references unknown finding ${item.findingId}.`);
459
+ }
460
+ const expectedId = migrationChecklistItemId(record.analysisResultId, finding.id, item);
461
+ if (item.id !== expectedId) errors.push(`Checklist item ${item.id} does not have a stable id.`);
462
+ if (!isSorted(item.evidenceRefs, compareText)) errors.push(`Checklist item ${item.id} evidenceRefs must be sorted.`);
463
+ if (!isSorted(item.candidateLocations, compareLocations)) {
464
+ errors.push(`Checklist item ${item.id} candidate locations must be sorted.`);
465
+ }
466
+ for (const ref of item.evidenceRefs) {
467
+ if (!selectedEvidence.has(ref) || !allowedEvidence.has(ref)) {
468
+ errors.push(`Checklist item ${item.id} references unknown evidence ${ref}.`);
469
+ }
470
+ }
471
+ for (const location of item.candidateLocations) {
472
+ if (!allowedLocations.has(locationKey(location))) {
473
+ errors.push(`Checklist item ${item.id} references unknown positive impact evidence/location ${location.impactEvidenceId}.`);
474
+ }
475
+ }
476
+ for (const violation of validateMigrationChecklistInstructionContent(item.instruction)) {
477
+ errors.push(`Checklist item ${item.id} violates ${violation.code}: ${violation.message}`);
478
+ }
479
+
480
+ const actionable = isActionableMigrationChecklistItem(item.kind);
481
+ if (actionable && item.evidenceRefs.length === 0) {
482
+ errors.push(`Actionable checklist item ${item.id} requires at least one evidence reference.`);
483
+ }
484
+ if (item.kind === 'REVIEW_CANDIDATE_USAGE'
485
+ && (item.basis !== 'DETERMINISTIC' || item.candidateLocations.length === 0)) {
486
+ errors.push(`Candidate usage item ${item.id} must be deterministic and contain a positive candidate location.`);
487
+ }
488
+ if (item.kind !== 'REVIEW_CANDIDATE_USAGE' && item.candidateLocations.length > 0) {
489
+ errors.push(`Checklist item ${item.id} cannot own candidate locations for kind ${item.kind}.`);
490
+ }
491
+ if (item.kind === 'MANUAL_REVIEW_REQUIRED'
492
+ && (item.basis !== 'DETERMINISTIC' || item.candidateLocations.length > 0)) {
493
+ errors.push(`Manual-review fallback ${item.id} must be deterministic and location-free.`);
494
+ }
495
+ if (item.basis === 'AI_AUTHORED') {
496
+ if (finding.eligibility.reasonCode !== 'ELIGIBLE') {
497
+ errors.push(`AI-authored checklist item ${item.id} is not allowed for an ineligible finding.`);
498
+ }
499
+ if (item.kind !== 'REVIEW_MIGRATION_INSTRUCTION') {
500
+ errors.push(`AI-authored checklist item ${item.id} must be REVIEW_MIGRATION_INSTRUCTION.`);
501
+ }
502
+ if (item.evidenceRefs.length === 0) {
503
+ errors.push(`AI-authored checklist item ${item.id} requires selected evidence.`);
504
+ }
505
+ if (item.candidateLocations.length > 0) {
506
+ errors.push(`AI-authored checklist item ${item.id} cannot own repository locations.`);
507
+ }
508
+ if (item.requiresHumanReview !== true) {
509
+ errors.push(`AI-authored checklist item ${item.id} must require human review.`);
510
+ }
511
+ }
512
+ }
513
+
514
+ const actionableItems = finding.items.filter((item) => isActionableMigrationChecklistItem(item.kind));
515
+ const fallbackItems = finding.items.filter((item) => item.kind === 'MANUAL_REVIEW_REQUIRED');
516
+ if (finding.eligibility.reasonCode === 'ELIGIBLE') {
517
+ if (finding.evidenceRefs.length === 0 || actionableItems.length === 0) {
518
+ errors.push(`Eligible finding ${record.analysisResultId}/${finding.id} requires evidence-grounded actionable items.`);
519
+ }
520
+ if (fallbackItems.length > 0) {
521
+ errors.push(`Eligible finding ${record.analysisResultId}/${finding.id} cannot contain manual-review fallbacks.`);
522
+ }
523
+ } else if (finding.eligibility.reasonCode === 'NOT_ANALYZED') {
524
+ if (finding.items.length > 0) {
525
+ errors.push(`Not-analyzed finding ${record.analysisResultId}/${finding.id} cannot contain checklist items.`);
526
+ }
527
+ } else {
528
+ if (actionableItems.length > 0 || fallbackItems.length === 0
529
+ || finding.items.some((item) => item.basis === 'AI_AUTHORED')) {
530
+ errors.push(`Ineligible finding ${record.analysisResultId}/${finding.id} may contain deterministic manual-review fallbacks only.`);
531
+ }
532
+ }
533
+ }
534
+
535
+ export function validateMigrationChecklistInvariants(checklist) {
536
+ const errors = [];
537
+ const dependencies = checklist.dependencies ?? [];
538
+ if (!isSorted(dependencies, compareDependencies)) errors.push('dependencies must be deterministically sorted.');
539
+ if (!isSorted(checklist.limitations ?? [], compareLimitations)) errors.push('limitations must be sorted.');
540
+ if (!sameJson(checklist.repository, checklist.input?.projectManifest?.repository)) {
541
+ errors.push('repository identity must match Project Manifest lineage.');
542
+ }
543
+
544
+ for (const duplicate of duplicateValues(dependencies
545
+ .map((record) => record.analysisResultId)
546
+ .filter((value) => value !== null))) {
547
+ errors.push(`Duplicate dependency checklist analysis result id ${duplicate}.`);
548
+ }
549
+ for (const duplicate of duplicateValues(dependencies.map((record) => record.decisionId))) {
550
+ errors.push(`Duplicate dependency handoff decision id ${duplicate}.`);
551
+ }
552
+
553
+ const globalItemIds = new Set();
554
+ for (const record of dependencies) {
555
+ if (!isSorted(record.selectedEvidenceRefs, compareText)) {
556
+ errors.push(`Dependency ${record.analysisResultId} selectedEvidenceRefs must be sorted.`);
557
+ }
558
+ if (!isSorted(record.findings, compareFindings)) {
559
+ errors.push(`Dependency ${record.analysisResultId} findings must be sorted.`);
560
+ }
561
+ if (!isSorted(record.limitations, compareLimitations)) {
562
+ errors.push(`Dependency ${record.analysisResultId} limitations must be sorted.`);
563
+ }
564
+ const expected = expectedDependencyState(record.analysisStatus, record.findings);
565
+ if (record.status !== expected.status || !sameJson(record.eligibility, expected.eligibility)) {
566
+ errors.push(`Dependency ${record.analysisResultId} status and eligibility are inconsistent.`);
567
+ }
568
+ if (record.analysisStatus !== 'analyzed' && record.findings.length > 0) {
569
+ errors.push(`Not-analyzed dependency ${record.analysisResultId} cannot contain generated findings or actions.`);
570
+ }
571
+ for (const duplicate of duplicateValues(record.findings.map((finding) => finding.id))) {
572
+ errors.push(`Dependency ${record.analysisResultId} has duplicate finding id ${duplicate}.`);
573
+ }
574
+ validateVersionSemantics(record, errors);
575
+ validateHandoff(record, errors);
576
+ for (const finding of record.findings) validateFinding(record, finding, globalItemIds, errors);
577
+ }
578
+
579
+ const expectedStatus = expectedOverallStatus(dependencies);
580
+ if (checklist.status !== expectedStatus) {
581
+ errors.push(`status is ${checklist.status}; expected ${expectedStatus}.`);
582
+ }
583
+ const expectedSummary = checklistSummary(dependencies, checklist.limitations ?? []);
584
+ if (!sameJson(checklist.summary, expectedSummary)) errors.push('summary is inconsistent.');
585
+ return errors.sort(compareText);
586
+ }
587
+
588
+ export function validateMigrationChecklist(checklist) {
589
+ if (checklist?.schemaVersion !== MIGRATION_CHECKLIST_SCHEMA_VERSION) {
590
+ throw new Error(
591
+ `Migration Checklist validation error: unsupported schema version; expected ${MIGRATION_CHECKLIST_SCHEMA_VERSION}.`
592
+ );
593
+ }
594
+ if (!validateSchema(checklist)) {
595
+ throw new Error(
596
+ `Migration Checklist validation error: schema validation failed: ${ajv.errorsText(validateSchema.errors, { separator: '; ' })}`
597
+ );
598
+ }
599
+ const errors = validateMigrationChecklistInvariants(checklist);
600
+ if (errors.length > 0) {
601
+ throw new Error(`Migration Checklist validation error: runtime invariants failed: ${errors.join(' ')}`);
602
+ }
603
+ return checklist;
604
+ }
605
+
606
+ export function buildMigrationChecklist({
607
+ input,
608
+ repository,
609
+ dependencies = [],
610
+ limitations = [],
611
+ generatedAt
612
+ }) {
613
+ if (generatedAt === undefined) {
614
+ throw new Error('Migration Checklist input error: generatedAt is required.');
615
+ }
616
+ const records = structuredClone(dependencies).map(normalizeDependency).sort(compareDependencies);
617
+ const sortedLimitations = structuredClone(limitations).sort(compareLimitations);
618
+ const checklist = {
619
+ schemaVersion: MIGRATION_CHECKLIST_SCHEMA_VERSION,
620
+ generatedAt: generatedAt instanceof Date ? generatedAt.toISOString() : generatedAt,
621
+ generator: { name: ARTIFACT_GENERATOR_NAME, version: VERSION },
622
+ input: structuredClone(input),
623
+ repository: structuredClone(repository ?? input?.projectManifest?.repository),
624
+ status: expectedOverallStatus(records),
625
+ summary: checklistSummary(records, sortedLimitations),
626
+ dependencies: records,
627
+ limitations: sortedLimitations
628
+ };
629
+ return validateMigrationChecklist(checklist);
630
+ }
631
+
632
+ export function serializeMigrationChecklist(checklist) {
633
+ validateMigrationChecklist(checklist);
634
+ return `${JSON.stringify(checklist, null, 2)}\n`;
635
+ }