@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,702 @@
1
+ import { AI_RUNTIME_CONTRACT_VERSION, validateAiRuntime } from '../ai-runtime.js';
2
+ import { isAiRuntimeError } from '../ai-runtime-error.js';
3
+ import { canonicalJson } from '../canonical-json.js';
4
+ import { compareText } from '../portable.js';
5
+ import {
6
+ isMigrationChecklistCandidateError,
7
+ isMigrationChecklistTrustError,
8
+ MIGRATION_CHECKLIST_CANDIDATE_SCHEMA,
9
+ trustValidateMigrationChecklistCandidate,
10
+ validateMigrationChecklistCandidate
11
+ } from './ai-candidate.js';
12
+ import {
13
+ isMigrationExtractiveCandidateError,
14
+ MIGRATION_EXTRACTIVE_CANDIDATE_SCHEMA,
15
+ trustValidateMigrationExtractiveCandidate,
16
+ validateMigrationExtractiveCandidate
17
+ } from './extractive-candidate.js';
18
+ import {
19
+ buildMigrationExtractivePrompt,
20
+ MIGRATION_EXTRACTIVE_PLANNING_TASK,
21
+ MIGRATION_EXTRACTIVE_PROMPT_VERSION,
22
+ MIGRATION_EXTRACTIVE_SCHEMA_NAME
23
+ } from './extractive-prompt.js';
24
+ import {
25
+ MIGRATION_LOCATION_REASON_CODES,
26
+ MIGRATION_TASK_CONTEXT_VERSION
27
+ } from './context-runtime.js';
28
+ import { migrationChecklistItemId } from './migration-checklist.js';
29
+ import {
30
+ buildMigrationChecklistPrompt,
31
+ MIGRATION_PLANNING_PROMPT_VERSION,
32
+ MIGRATION_PLANNING_SCHEMA_NAME,
33
+ MIGRATION_PLANNING_TASK
34
+ } from './prompt.js';
35
+
36
+ export const MIGRATION_GENERATION_RESULT_VERSION = '1';
37
+ export const MIGRATION_EXTRACTIVE_GENERATION_RESULT_VERSION = '2';
38
+ export const MIGRATION_GENERATION_WARNING_CODES = Object.freeze([
39
+ 'MODEL_ABSTAINED',
40
+ 'OUTPUT_JSON_INVALID',
41
+ 'OUTPUT_SCHEMA_INVALID',
42
+ 'OUTPUT_SEMANTICS_INVALID',
43
+ 'TRUST_VALIDATION_REJECTED',
44
+ 'AI_RUNTIME_FAILED'
45
+ ]);
46
+
47
+ const DIGEST_EXPRESSION = /^sha256:[a-f0-9]{64}$/;
48
+ const ACTION_EVIDENCE_KINDS = new Set([
49
+ 'migrationGuide', 'breakingChanges', 'deprecations', 'releaseNotes', 'changelog'
50
+ ]);
51
+
52
+ const compareLimitations = (left, right) => (
53
+ compareText(left.code, right.code) || compareText(left.message, right.message)
54
+ );
55
+ const compareLocations = (left, right) => (
56
+ compareText(left.impactEvidenceId, right.impactEvidenceId)
57
+ || compareText(left.symbol, right.symbol)
58
+ || compareText(left.file, right.file)
59
+ );
60
+ const compareRecords = (left, right) => (
61
+ compareText(left.dependency.projectId, right.dependency.projectId)
62
+ || compareText(left.dependency.manifest, right.dependency.manifest)
63
+ || compareText(left.dependency.dependencyType, right.dependency.dependencyType)
64
+ || compareText(left.dependency.packageId, right.dependency.packageId)
65
+ || compareText(left.analysisResultId ?? '', right.analysisResultId ?? '')
66
+ || compareText(left.decisionId ?? '', right.decisionId ?? '')
67
+ );
68
+ const compareWarnings = (left, right) => (
69
+ compareText(left.contextId, right.contextId)
70
+ || compareText(left.code, right.code)
71
+ || compareText(left.detailCode, right.detailCode)
72
+ );
73
+
74
+ function deepFreeze(value) {
75
+ if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
76
+ for (const child of Object.values(value)) deepFreeze(child);
77
+ return Object.freeze(value);
78
+ }
79
+
80
+ function notifyContext(listener, event) {
81
+ if (typeof listener !== 'function') return;
82
+ try {
83
+ listener(deepFreeze(structuredClone(event)));
84
+ } catch {
85
+ // Progress instrumentation must never alter checklist generation semantics.
86
+ }
87
+ }
88
+
89
+ function throwIfCancelled(signal) {
90
+ if (!signal?.aborted) return;
91
+ const error = new Error('Migration Checklist generation was cancelled.', { cause: signal.reason });
92
+ error.code = 'ANALYSIS_CANCELLED';
93
+ throw error;
94
+ }
95
+
96
+ function sortedUniqueText(values = []) {
97
+ return [...new Set(values)].sort(compareText);
98
+ }
99
+
100
+ function sortedUniqueLimitations(values = []) {
101
+ const unique = new Map();
102
+ for (const value of values) unique.set(`${value.code}\0${value.message}`, structuredClone(value));
103
+ return [...unique.values()].sort(compareLimitations);
104
+ }
105
+
106
+ function inputError(message) {
107
+ return new TypeError(`Migration Checklist Generator input error: ${message}`);
108
+ }
109
+
110
+ function assertString(value, field) {
111
+ if (typeof value !== 'string' || value.length === 0) throw inputError(`${field} must be a non-empty string.`);
112
+ }
113
+
114
+ function validateEligibleContext(context) {
115
+ if (!context || typeof context !== 'object' || Array.isArray(context)) {
116
+ throw inputError('eligible context must be an object.');
117
+ }
118
+ if (context.contextVersion !== MIGRATION_TASK_CONTEXT_VERSION) {
119
+ throw inputError(`contextVersion must be ${MIGRATION_TASK_CONTEXT_VERSION}.`);
120
+ }
121
+ if (!DIGEST_EXPRESSION.test(context.contextId)) throw inputError('contextId must be a SHA-256 digest.');
122
+ if (!context.dependency || !context.versions || !context.finding) {
123
+ throw inputError(`context ${context.contextId} is missing dependency, versions, or finding identity.`);
124
+ }
125
+ assertString(context.analysisResultId, 'analysisResultId');
126
+ if (!DIGEST_EXPRESSION.test(context.analysisResultId)) {
127
+ throw inputError('analysisResultId must be a SHA-256 digest.');
128
+ }
129
+ for (const field of [
130
+ 'projectId', 'packageId', 'declaredName', 'normalizedName', 'ecosystem', 'registry',
131
+ 'dependencyType', 'manifest'
132
+ ]) {
133
+ assertString(context.dependency[field], `dependency.${field}`);
134
+ }
135
+ if (context.versions.targetVersion === null) throw inputError('eligible targetVersion cannot be null.');
136
+ assertString(context.versions.targetVersion, 'versions.targetVersion');
137
+ assertString(context.finding.id, 'finding.id');
138
+ assertString(context.finding.summary, 'finding.summary');
139
+ if (context.finding.kind !== 'breakingChange') throw inputError('finding.kind must be breakingChange.');
140
+ if (!Array.isArray(context.finding.appliesToVersions)) {
141
+ throw inputError('finding.appliesToVersions must be an array.');
142
+ }
143
+ if (context.eligibility?.status !== 'ELIGIBLE' || context.eligibility?.reasonCode !== 'ELIGIBLE') {
144
+ throw inputError(`context ${context.contextId} is not eligible.`);
145
+ }
146
+ if (context.requiresHumanReview !== true) throw inputError('requiresHumanReview must be true.');
147
+ if (context.decisionId !== undefined) {
148
+ if (!DIGEST_EXPRESSION.test(context.decisionId)
149
+ || !['PLAN_UPGRADE', 'UPGRADE_NOW'].includes(context.decision?.status)
150
+ || context.decision?.recommendationDriver === null
151
+ || context.humanReviewRequired !== true) {
152
+ throw inputError(`context ${context.contextId} has an invalid actionable Upgrade Decision projection.`);
153
+ }
154
+ }
155
+ if (!Array.isArray(context.evidence) || !Array.isArray(context.evidenceAllowlist)
156
+ || !Array.isArray(context.finding.evidenceRefs) || context.evidence.length === 0) {
157
+ throw inputError(`context ${context.contextId} must contain selected evidence and allowlists.`);
158
+ }
159
+ const evidenceIds = context.evidence.map((item) => item.id);
160
+ if (evidenceIds.some((id) => !DIGEST_EXPRESSION.test(id))
161
+ || evidenceIds.some((id, index) => evidenceIds.indexOf(id) !== index)
162
+ || context.evidence.some((item) => (
163
+ typeof item.content !== 'string' || item.content.length === 0
164
+ || typeof item.sourceId !== 'string' || item.sourceId.length === 0
165
+ || typeof item.kind !== 'string' || item.kind.length === 0
166
+ || typeof item.authority !== 'string' || item.authority.length === 0
167
+ || typeof item.trust !== 'string' || item.trust.length === 0
168
+ || !DIGEST_EXPRESSION.test(item.contentDigest)
169
+ || !Array.isArray(item.releaseVersions)
170
+ || !item.releaseVersions.includes(context.versions.targetVersion)
171
+ || !['officialProject', 'publisherProvided'].includes(item.authority)
172
+ || !['official', 'publisher'].includes(item.trust)
173
+ || !ACTION_EVIDENCE_KINDS.has(item.kind)
174
+ ))) {
175
+ throw inputError(`context ${context.contextId} contains invalid or duplicate selected evidence.`);
176
+ }
177
+ const expectedRefs = sortedUniqueText(evidenceIds);
178
+ if (JSON.stringify(context.evidenceAllowlist) !== JSON.stringify(expectedRefs)
179
+ || JSON.stringify(context.finding.evidenceRefs) !== JSON.stringify(expectedRefs)) {
180
+ throw inputError(`context ${context.contextId} evidence allowlists are inconsistent.`);
181
+ }
182
+ if (!Array.isArray(context.positiveCandidateLocations)
183
+ || !Array.isArray(context.humanReviewReasons)
184
+ || !Array.isArray(context.limitations)
185
+ || !context.locationEligibility) {
186
+ throw inputError(`context ${context.contextId} is not a normalized MP-02 context.`);
187
+ }
188
+ if (!MIGRATION_LOCATION_REASON_CODES.includes(context.locationEligibility.reasonCode)
189
+ || !['ELIGIBLE', 'REVIEW_REQUIRED'].includes(context.locationEligibility.status)) {
190
+ throw inputError(`context ${context.contextId} has invalid location eligibility.`);
191
+ }
192
+ for (const location of context.positiveCandidateLocations) {
193
+ if (!DIGEST_EXPRESSION.test(location.impactEvidenceId)) {
194
+ throw inputError(`context ${context.contextId} contains an invalid impact evidence id.`);
195
+ }
196
+ assertString(location.symbol, 'candidate location symbol');
197
+ assertString(location.file, 'candidate location file');
198
+ }
199
+ const locationKeys = context.positiveCandidateLocations.map((location) => (
200
+ `${location.impactEvidenceId}\0${location.symbol}\0${location.file}`
201
+ ));
202
+ if (new Set(locationKeys).size !== locationKeys.length) {
203
+ throw inputError(`context ${context.contextId} contains duplicate candidate locations.`);
204
+ }
205
+ if ((context.positiveCandidateLocations.length > 0)
206
+ !== (context.locationEligibility.reasonCode === 'POSITIVE_USAGE_MATCH')) {
207
+ throw inputError(`context ${context.contextId} location eligibility is inconsistent.`);
208
+ }
209
+ return context;
210
+ }
211
+
212
+ function sameIdentity(left, right) {
213
+ return canonicalJson(left.dependency) === canonicalJson(right.dependency)
214
+ && canonicalJson(left.versions) === canonicalJson(right.versions)
215
+ && left.analysisStatus === right.analysisStatus
216
+ && (left.decisionId ?? null) === (right.decisionId ?? null);
217
+ }
218
+
219
+ const HANDOFF_BASIS_FIELDS = Object.freeze([
220
+ 'decisionId',
221
+ 'decision',
222
+ 'affectedAreas',
223
+ 'coverage',
224
+ 'verification',
225
+ 'officialEvidence',
226
+ 'preconditions',
227
+ 'recovery',
228
+ 'reviewQuestions',
229
+ 'missingInformation',
230
+ 'nextStep',
231
+ 'humanReviewRequired'
232
+ ]);
233
+
234
+ function handoffBasis(context) {
235
+ return Object.fromEntries(HANDOFF_BASIS_FIELDS
236
+ .filter((field) => Object.hasOwn(context, field))
237
+ .map((field) => [field, structuredClone(context[field])]));
238
+ }
239
+
240
+ function baseRecord(context) {
241
+ return {
242
+ analysisResultId: context.analysisResultId,
243
+ ...handoffBasis(context),
244
+ dependency: structuredClone(context.dependency),
245
+ versions: structuredClone(context.versions),
246
+ analysisStatus: 'analyzed',
247
+ selectedEvidenceRefs: [...context.evidenceAllowlist],
248
+ findings: [],
249
+ limitations: structuredClone(context.limitations)
250
+ };
251
+ }
252
+
253
+ function checklistItem(analysisResultId, findingId, item) {
254
+ return {
255
+ id: migrationChecklistItemId(analysisResultId, findingId, item),
256
+ ...item
257
+ };
258
+ }
259
+
260
+ function locationItems(context) {
261
+ return [...context.positiveCandidateLocations]
262
+ .sort(compareLocations)
263
+ .map((location) => checklistItem(context.analysisResultId, context.finding.id, {
264
+ kind: 'REVIEW_CANDIDATE_USAGE',
265
+ basis: 'DETERMINISTIC',
266
+ instruction: `Review the validated usage candidate for symbol "${location.symbol}" in the referenced repository file.`,
267
+ findingId: context.finding.id,
268
+ evidenceRefs: [...context.evidenceAllowlist],
269
+ candidateLocations: [structuredClone(location)],
270
+ requiresHumanReview: true
271
+ }));
272
+ }
273
+
274
+ function actionableRecord(context, trustedCandidate) {
275
+ const items = trustedCandidate.items.map((candidateItem) => checklistItem(
276
+ context.analysisResultId,
277
+ context.finding.id,
278
+ {
279
+ kind: 'REVIEW_MIGRATION_INSTRUCTION',
280
+ basis: 'AI_AUTHORED',
281
+ instruction: candidateItem.instruction,
282
+ findingId: context.finding.id,
283
+ evidenceRefs: [...candidateItem.evidenceRefs],
284
+ candidateLocations: [],
285
+ requiresHumanReview: true
286
+ }
287
+ ));
288
+ items.push(...locationItems(context));
289
+ items.sort((left, right) => compareText(left.id, right.id));
290
+ const record = baseRecord(context);
291
+ record.findings.push({
292
+ id: context.finding.id,
293
+ kind: context.finding.kind,
294
+ summary: context.finding.summary,
295
+ eligibilityReasonCode: 'ELIGIBLE',
296
+ evidenceRefs: [...context.evidenceAllowlist],
297
+ positiveImpactLocations: structuredClone(context.positiveCandidateLocations).sort(compareLocations),
298
+ items
299
+ });
300
+ return record;
301
+ }
302
+
303
+ const FALLBACK_DETAILS = Object.freeze({
304
+ NO_GROUNDED_ACTION: Object.freeze({
305
+ instruction: 'Manual review is required because the selected evidence did not yield an explicit migration action.',
306
+ limitationMessage: 'The model abstained because selected evidence did not provide a sufficiently explicit migration action.'
307
+ }),
308
+ MANUAL_REVIEW_REQUIRED: Object.freeze({
309
+ instruction: 'Manual review is required because the generated migration draft did not pass validation.',
310
+ limitationMessage: 'The generated migration draft was not published because generation or trust validation failed.'
311
+ })
312
+ });
313
+
314
+ function fallbackRecord(context, reasonCode, limitationCode, limitationMessage) {
315
+ const details = FALLBACK_DETAILS[reasonCode];
316
+ const record = baseRecord(context);
317
+ record.limitations = sortedUniqueLimitations([
318
+ ...record.limitations,
319
+ { code: limitationCode, message: limitationMessage ?? details.limitationMessage }
320
+ ]);
321
+ const manualItem = checklistItem(context.analysisResultId, context.finding.id, {
322
+ kind: 'MANUAL_REVIEW_REQUIRED',
323
+ basis: 'DETERMINISTIC',
324
+ instruction: details.instruction,
325
+ findingId: context.finding.id,
326
+ evidenceRefs: [...context.evidenceAllowlist],
327
+ candidateLocations: [],
328
+ requiresHumanReview: true
329
+ });
330
+ record.findings.push({
331
+ id: context.finding.id,
332
+ kind: context.finding.kind,
333
+ summary: context.finding.summary,
334
+ eligibilityReasonCode: reasonCode,
335
+ evidenceRefs: [...context.evidenceAllowlist],
336
+ positiveImpactLocations: structuredClone(context.positiveCandidateLocations).sort(compareLocations),
337
+ items: [manualItem]
338
+ });
339
+ return record;
340
+ }
341
+
342
+ function warning(context, code, detailCode, message) {
343
+ return {
344
+ contextId: context.contextId,
345
+ analysisResultId: context.analysisResultId,
346
+ findingId: context.finding.id,
347
+ code,
348
+ detailCode,
349
+ message
350
+ };
351
+ }
352
+
353
+ function runtimeFailure(context, error) {
354
+ const detailCode = isAiRuntimeError(error) ? error.code : 'UNKNOWN';
355
+ return {
356
+ outcome: 'failed',
357
+ record: fallbackRecord(context, 'MANUAL_REVIEW_REQUIRED', 'AI_RUNTIME_FAILED'),
358
+ warning: warning(
359
+ context,
360
+ 'AI_RUNTIME_FAILED',
361
+ detailCode,
362
+ 'AI runtime failed for this migration context; deterministic manual review is required.'
363
+ )
364
+ };
365
+ }
366
+
367
+ function candidateFailure(context, error) {
368
+ if (isMigrationChecklistCandidateError(error) || isMigrationExtractiveCandidateError(error)) {
369
+ return {
370
+ outcome: 'rejected',
371
+ record: fallbackRecord(context, 'MANUAL_REVIEW_REQUIRED', error.code),
372
+ warning: warning(
373
+ context,
374
+ error.code,
375
+ error.code,
376
+ 'AI output did not satisfy the Migration Checklist candidate contract; deterministic manual review is required.'
377
+ )
378
+ };
379
+ }
380
+ if (isMigrationChecklistTrustError(error)) {
381
+ return {
382
+ outcome: 'rejected',
383
+ record: fallbackRecord(context, 'MANUAL_REVIEW_REQUIRED', 'TRUST_VALIDATION_REJECTED'),
384
+ warning: warning(
385
+ context,
386
+ 'TRUST_VALIDATION_REJECTED',
387
+ error.detailCode ?? error.code,
388
+ 'AI output failed evidence or capability trust validation; deterministic manual review is required.'
389
+ )
390
+ };
391
+ }
392
+ throw error;
393
+ }
394
+
395
+ async function generateMigrationChecklistForContextWithContract(context, {
396
+ aiRuntime,
397
+ runId,
398
+ promptVersion,
399
+ signal
400
+ }, contract) {
401
+ throwIfCancelled(signal);
402
+ validateEligibleContext(context);
403
+ validateAiRuntime(aiRuntime);
404
+ const prompt = contract.buildPrompt({
405
+ context,
406
+ outputSchema: contract.candidateSchema,
407
+ promptVersion
408
+ });
409
+ let runtimeResult;
410
+ try {
411
+ runtimeResult = await aiRuntime.generateStructured({
412
+ contractVersion: AI_RUNTIME_CONTRACT_VERSION,
413
+ runId,
414
+ contextId: context.contextId,
415
+ task: contract.task,
416
+ promptVersion,
417
+ systemPrompt: prompt.system,
418
+ userPrompt: prompt.user,
419
+ structuredOutput: {
420
+ mode: 'jsonSchema',
421
+ name: contract.schemaName,
422
+ schema: contract.candidateSchema
423
+ },
424
+ ...(signal ? { signal } : {})
425
+ });
426
+ } catch (error) {
427
+ return deepFreeze(runtimeFailure(context, error));
428
+ }
429
+
430
+ try {
431
+ const candidate = contract.validateCandidate(runtimeResult?.output);
432
+ if (candidate.status === 'ABSTAIN') {
433
+ return deepFreeze({
434
+ outcome: 'abstained',
435
+ record: fallbackRecord(
436
+ context,
437
+ 'NO_GROUNDED_ACTION',
438
+ 'MODEL_ABSTAINED',
439
+ `The model abstained with constrained reason ${candidate.abstentionReason}; no AI-authored action was created.`
440
+ ),
441
+ warning: warning(
442
+ context,
443
+ 'MODEL_ABSTAINED',
444
+ candidate.abstentionReason,
445
+ 'The model validly abstained; deterministic manual review is required.'
446
+ )
447
+ });
448
+ }
449
+ const trusted = contract.trustCandidate(candidate, context);
450
+ return deepFreeze({
451
+ outcome: 'generated',
452
+ record: actionableRecord(context, trusted),
453
+ warning: null
454
+ });
455
+ } catch (error) {
456
+ return deepFreeze(candidateFailure(context, error));
457
+ }
458
+ }
459
+
460
+ const FREE_FORM_CONTRACT = Object.freeze({
461
+ task: MIGRATION_PLANNING_TASK,
462
+ candidateSchema: MIGRATION_CHECKLIST_CANDIDATE_SCHEMA,
463
+ schemaName: MIGRATION_PLANNING_SCHEMA_NAME,
464
+ buildPrompt: buildMigrationChecklistPrompt,
465
+ validateCandidate: validateMigrationChecklistCandidate,
466
+ trustCandidate: trustValidateMigrationChecklistCandidate
467
+ });
468
+
469
+ const EXTRACTIVE_CONTRACT = Object.freeze({
470
+ task: MIGRATION_EXTRACTIVE_PLANNING_TASK,
471
+ candidateSchema: MIGRATION_EXTRACTIVE_CANDIDATE_SCHEMA,
472
+ schemaName: MIGRATION_EXTRACTIVE_SCHEMA_NAME,
473
+ buildPrompt: buildMigrationExtractivePrompt,
474
+ validateCandidate: validateMigrationExtractiveCandidate,
475
+ trustCandidate: trustValidateMigrationExtractiveCandidate
476
+ });
477
+
478
+ /** Historical free-form v1 generation retained for evaluation reproducibility. */
479
+ export async function generateMigrationChecklistForContext(context, {
480
+ aiRuntime,
481
+ signal,
482
+ runId = `migration:${context.contextId}`,
483
+ promptVersion = MIGRATION_PLANNING_PROMPT_VERSION
484
+ } = {}) {
485
+ return generateMigrationChecklistForContextWithContract(context, {
486
+ aiRuntime, runId, promptVersion, signal
487
+ }, FREE_FORM_CONTRACT);
488
+ }
489
+
490
+ /** Production extractive v2 generation for new experimental checklist runs. */
491
+ export async function generateMigrationExtractiveChecklistForContext(context, {
492
+ aiRuntime,
493
+ signal,
494
+ runId = `migration-extractive:${context.contextId}`,
495
+ promptVersion = MIGRATION_EXTRACTIVE_PROMPT_VERSION
496
+ } = {}) {
497
+ return generateMigrationChecklistForContextWithContract(context, {
498
+ aiRuntime, runId, promptVersion, signal
499
+ }, EXTRACTIVE_CONTRACT);
500
+ }
501
+
502
+ function validatePrepared(prepared) {
503
+ if (!prepared || typeof prepared !== 'object' || Array.isArray(prepared)) {
504
+ throw inputError('prepared contexts must be an object.');
505
+ }
506
+ if (prepared.contextVersion !== MIGRATION_TASK_CONTEXT_VERSION
507
+ || !prepared.input
508
+ || !Array.isArray(prepared.eligibleContexts)
509
+ || !Array.isArray(prepared.fallbackRecords)) {
510
+ throw inputError('prepared contexts must be normalized MP-02 output.');
511
+ }
512
+ const contextIds = new Set();
513
+ const findingKeys = new Set();
514
+ for (const context of prepared.eligibleContexts) {
515
+ validateEligibleContext(context);
516
+ if (contextIds.has(context.contextId)) throw inputError(`duplicate contextId ${context.contextId}.`);
517
+ contextIds.add(context.contextId);
518
+ const key = `${context.analysisResultId}\0${context.finding.id}`;
519
+ if (findingKeys.has(key)) throw inputError('duplicate eligible analysis result and finding identity.');
520
+ findingKeys.add(key);
521
+ }
522
+ const fallbackIds = new Set();
523
+ for (const record of prepared.fallbackRecords) {
524
+ validateFallbackRecord(record);
525
+ const fallbackId = record.analysisResultId ?? record.decisionId;
526
+ if (fallbackIds.has(fallbackId)) {
527
+ throw inputError(`duplicate fallback identity ${fallbackId}.`);
528
+ }
529
+ fallbackIds.add(fallbackId);
530
+ }
531
+ }
532
+
533
+ function validateFallbackRecord(record) {
534
+ if (!record || typeof record !== 'object' || Array.isArray(record)) {
535
+ throw inputError('fallback record must be an object.');
536
+ }
537
+ if (record.analysisResultId !== null) {
538
+ assertString(record.analysisResultId, 'fallback analysisResultId');
539
+ }
540
+ if (!record.dependency || !record.versions || typeof record.analysisStatus !== 'string') {
541
+ throw inputError(`fallback ${record.analysisResultId} is missing normalized identity.`);
542
+ }
543
+ if (record.decisionId !== undefined && !DIGEST_EXPRESSION.test(record.decisionId)) {
544
+ throw inputError(`fallback ${record.analysisResultId ?? record.decisionId} has invalid decision identity.`);
545
+ }
546
+ for (const field of [
547
+ 'projectId', 'packageId', 'declaredName', 'normalizedName', 'ecosystem', 'registry',
548
+ 'dependencyType', 'manifest'
549
+ ]) {
550
+ assertString(record.dependency[field], `fallback dependency.${field}`);
551
+ }
552
+ if (!Array.isArray(record.selectedEvidenceRefs)
553
+ || !Array.isArray(record.findings)
554
+ || !Array.isArray(record.limitations)) {
555
+ throw inputError(`fallback ${record.analysisResultId} must contain normalized arrays.`);
556
+ }
557
+ if (record.selectedEvidenceRefs.some((ref) => !DIGEST_EXPRESSION.test(ref))) {
558
+ throw inputError(`fallback ${record.analysisResultId} contains an invalid evidence ref.`);
559
+ }
560
+ const findingIds = new Set();
561
+ for (const finding of record.findings) {
562
+ assertString(finding?.id, 'fallback finding id');
563
+ if (findingIds.has(finding.id)) {
564
+ throw inputError(`fallback ${record.analysisResultId} contains duplicate finding ${finding.id}.`);
565
+ }
566
+ findingIds.add(finding.id);
567
+ if (!Array.isArray(finding.evidenceRefs)
568
+ || !Array.isArray(finding.positiveImpactLocations)
569
+ || !Array.isArray(finding.items)) {
570
+ throw inputError(`fallback ${record.analysisResultId}/${finding.id} is not normalized.`);
571
+ }
572
+ }
573
+ }
574
+
575
+ function mergeRecord(records, incoming) {
576
+ const recordKey = incoming.analysisResultId ?? incoming.decisionId;
577
+ const existing = records.get(recordKey);
578
+ if (!existing) {
579
+ records.set(recordKey, structuredClone(incoming));
580
+ return;
581
+ }
582
+ if (!sameIdentity(existing, incoming)) {
583
+ throw inputError(`conflicting identity for analysisResultId ${incoming.analysisResultId}.`);
584
+ }
585
+ const findingIds = new Set(existing.findings.map((finding) => finding.id));
586
+ for (const finding of incoming.findings) {
587
+ if (findingIds.has(finding.id)) {
588
+ throw inputError(`duplicate finding ${incoming.analysisResultId}/${finding.id} during record merge.`);
589
+ }
590
+ existing.findings.push(structuredClone(finding));
591
+ findingIds.add(finding.id);
592
+ }
593
+ existing.findings.sort((left, right) => compareText(left.id, right.id));
594
+ existing.selectedEvidenceRefs = sortedUniqueText([
595
+ ...existing.selectedEvidenceRefs,
596
+ ...incoming.selectedEvidenceRefs
597
+ ]);
598
+ existing.limitations = sortedUniqueLimitations([
599
+ ...existing.limitations,
600
+ ...incoming.limitations
601
+ ]);
602
+ }
603
+
604
+ async function generateMigrationChecklistDraftsWithContract(prepared, {
605
+ aiRuntime,
606
+ runIdPrefix = 'migration',
607
+ promptVersion,
608
+ onContextEvent,
609
+ signal
610
+ }, {
611
+ generateContext,
612
+ resultVersion
613
+ }) {
614
+ validatePrepared(prepared);
615
+ validateAiRuntime(aiRuntime);
616
+ assertString(runIdPrefix, 'runIdPrefix');
617
+ assertString(promptVersion, 'promptVersion');
618
+ const records = new Map();
619
+ for (const record of prepared.fallbackRecords) mergeRecord(records, record);
620
+ const warnings = [];
621
+ const summary = {
622
+ attempted: prepared.eligibleContexts.length,
623
+ generated: 0,
624
+ abstained: 0,
625
+ rejected: 0,
626
+ failed: 0,
627
+ preservedFallbackRecordCount: prepared.fallbackRecords.length,
628
+ recordCount: 0
629
+ };
630
+
631
+ const contexts = [...prepared.eligibleContexts].sort((left, right) => (
632
+ compareText(left.dependency.projectId, right.dependency.projectId)
633
+ || compareText(left.dependency.manifest, right.dependency.manifest)
634
+ || compareText(left.dependency.packageId, right.dependency.packageId)
635
+ || compareText(left.finding.id, right.finding.id)
636
+ || compareText(left.contextId, right.contextId)
637
+ ));
638
+ for (const context of contexts) {
639
+ throwIfCancelled(signal);
640
+ notifyContext(onContextEvent, {
641
+ phase: 'start',
642
+ contextId: context.contextId,
643
+ analysisResultId: context.analysisResultId,
644
+ findingId: context.finding.id,
645
+ packageName: context.dependency.declaredName
646
+ });
647
+ const result = await generateContext(context, {
648
+ aiRuntime,
649
+ runId: `${runIdPrefix}:${context.contextId}`,
650
+ promptVersion,
651
+ signal
652
+ });
653
+ throwIfCancelled(signal);
654
+ summary[result.outcome] += 1;
655
+ mergeRecord(records, result.record);
656
+ if (result.warning) warnings.push(structuredClone(result.warning));
657
+ notifyContext(onContextEvent, {
658
+ phase: 'complete',
659
+ contextId: context.contextId,
660
+ analysisResultId: context.analysisResultId,
661
+ findingId: context.finding.id,
662
+ packageName: context.dependency.declaredName,
663
+ outcome: result.outcome,
664
+ reasonCode: result.warning?.code ?? null,
665
+ detailCode: result.warning?.detailCode ?? null
666
+ });
667
+ }
668
+
669
+ const normalizedRecords = [...records.values()].sort(compareRecords);
670
+ summary.recordCount = normalizedRecords.length;
671
+ const output = {
672
+ resultVersion,
673
+ input: structuredClone(prepared.input),
674
+ records: normalizedRecords,
675
+ warnings: warnings.sort(compareWarnings),
676
+ summary
677
+ };
678
+ return deepFreeze(structuredClone(output));
679
+ }
680
+
681
+ /** Historical free-form v1 multi-context generation. */
682
+ export async function generateMigrationChecklistDrafts(prepared, options = {}) {
683
+ return generateMigrationChecklistDraftsWithContract(prepared, {
684
+ ...options,
685
+ promptVersion: options.promptVersion ?? MIGRATION_PLANNING_PROMPT_VERSION
686
+ }, {
687
+ generateContext: generateMigrationChecklistForContext,
688
+ resultVersion: MIGRATION_GENERATION_RESULT_VERSION
689
+ });
690
+ }
691
+
692
+ /** Production extractive v2 multi-context generation. */
693
+ export async function generateMigrationExtractiveChecklistDrafts(prepared, options = {}) {
694
+ return generateMigrationChecklistDraftsWithContract(prepared, {
695
+ ...options,
696
+ runIdPrefix: options.runIdPrefix ?? 'migration-extractive',
697
+ promptVersion: options.promptVersion ?? MIGRATION_EXTRACTIVE_PROMPT_VERSION
698
+ }, {
699
+ generateContext: generateMigrationExtractiveChecklistForContext,
700
+ resultVersion: MIGRATION_EXTRACTIVE_GENERATION_RESULT_VERSION
701
+ });
702
+ }