@bruc3van/dsh-doctor 0.5.5 → 0.5.7
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.
- package/README.en.md +77 -181
- package/README.md +77 -181
- package/migrations/dsh-v0.1.1-rc.2__dsh-v0.1.2-alpha.2/packages.json +1 -3
- package/migrations/dsh-v0.1.1-rc.2__dsh-v0.1.2-alpha.2/symbols.json +8 -0
- package/migrations/dsh-v0.1.1-rc.2__dsh-v0.1.2-alpha.3/behavior.md +14 -0
- package/migrations/dsh-v0.1.1-rc.2__dsh-v0.1.2-alpha.3/config-rules.json +36 -0
- package/migrations/dsh-v0.1.1-rc.2__dsh-v0.1.2-alpha.3/manifest.json +19 -0
- package/migrations/dsh-v0.1.1-rc.2__dsh-v0.1.2-alpha.3/packages.json +74 -0
- package/migrations/dsh-v0.1.1-rc.2__dsh-v0.1.2-alpha.3/services.json +28 -0
- package/migrations/dsh-v0.1.1-rc.2__dsh-v0.1.2-alpha.3/symbols.json +134 -0
- package/package.json +1 -1
- package/skills/dsh-plugin-upgrade/SKILL.md +54 -23
- package/skills/dsh-plugin-upgrade/evals/evals.json +55 -0
- package/skills/dsh-plugin-upgrade/evals/files/import-move-plugin/README.md +5 -0
- package/skills/dsh-plugin-upgrade/evals/files/import-move-plugin/package.json +31 -0
- package/skills/dsh-plugin-upgrade/evals/files/import-move-plugin/src/index.ts +20 -0
- package/skills/dsh-plugin-upgrade/evals/files/import-move-plugin/src/stores.ts +15 -0
- package/skills/dsh-plugin-upgrade/evals/files/settings-plugin/README.md +7 -0
- package/skills/dsh-plugin-upgrade/evals/files/settings-plugin/package.json +20 -0
- package/skills/dsh-plugin-upgrade/evals/files/settings-plugin/src/index.ts +2 -0
- package/skills/dsh-plugin-upgrade/evals/files/settings-plugin/src/namespace-read.ts +6 -0
- package/skills/dsh-plugin-upgrade/evals/files/settings-plugin/src/settings.ts +21 -0
- package/skills/dsh-plugin-upgrade/evals/trigger-evals.json +22 -0
- package/skills/dsh-plugin-upgrade/references/cli-bootstrap.md +6 -4
- package/skills/dsh-plugin-upgrade/references/migration-map.md +5 -1
- package/skills/dsh-plugin-upgrade/references/source-investigation.md +2 -0
- package/skills/dsh-plugin-upgrade/references/verification.md +6 -0
- package/src/cli.mjs +7 -3
- package/src/migrate-verify.mjs +3 -2
- package/src/migrate.mjs +53 -22
|
@@ -4,12 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
Static analysis covers source/type imports, dependency ranges, client graph declarations, and generated artifacts. A pass means no known blocking finding; it does not execute project code.
|
|
6
6
|
|
|
7
|
+
The current known static target is the catalog target declared in SKILL.md. When the actual target is later than that catalog ref on the same 0.1.2 release line, first inspect the additional interval, then use `--target-version <version>` at every phase. The report keeps `migration.to` as the catalog ref and records `migration.actualTarget` separately, so a successful dependency or runtime check cannot be mistaken for catalog coverage of the additional API delta.
|
|
8
|
+
|
|
7
9
|
## Build
|
|
8
10
|
|
|
9
11
|
Build and runtime verification require `--yes --install`. The CLI selects the package manager from lockfiles, synchronizes dependencies with dependency lifecycle scripts disabled, records the lockfile hash before and after, and verifies installed DSH/Cordis versions against `dependencies`, `devDependencies`, `peerDependencies`, and `optionalDependencies`. Required peers must resolve and satisfy every declared range. Missing optional dependencies or peers are recorded as `optional-missing`; when installed, they must satisfy their ranges. It then runs declared scripts in this order: `typecheck`, `build`, `test`, `pack:check`, stopping at the first failure. Artifact verification requires a successful `build` or `pack:check`; `typecheck`/`test` alone do not pass this gate.
|
|
10
12
|
|
|
11
13
|
If dependency installation or resolution verification fails, stop before running project scripts. Review and retain package manifest and lockfile changes with the source migration.
|
|
12
14
|
|
|
15
|
+
If pnpm's `minimumReleaseAge` blocks a newly published target prerelease, do not disable or weaken the repository-wide supply-chain policy. Confirm the selected target and registry provenance, then add only the exact `package@version` rows required under `minimumReleaseAgeExclude`, including transitive DSH packages reported by pnpm. Review this workspace-policy change alongside the lockfile and report it explicitly.
|
|
16
|
+
|
|
13
17
|
## Runtime
|
|
14
18
|
|
|
15
19
|
Runtime verification:
|
|
@@ -24,6 +28,8 @@ The gate also checks the DSH CLI's exact target version, the created profile man
|
|
|
24
28
|
|
|
25
29
|
Use a built target Harness CLI through `--harness-root`, or pass `--dsh-command` explicitly. The temporary home is deleted only after a successful run unless `--keep-temp` is set.
|
|
26
30
|
|
|
31
|
+
The runtime executable must report the exact actual target selected by `--target-version` (or the catalog target when the option is omitted). A later prerelease is not accepted implicitly because that would disconnect the runtime evidence from the reviewed dependency and source target.
|
|
32
|
+
|
|
27
33
|
## Behavior
|
|
28
34
|
|
|
29
35
|
Choose focused checks from the plugin contract: render the real UI, invoke each command, exercise settings, verify service availability, repeat activation/deactivation, check listener cleanup, and cover failure recovery. Record exact commands, screenshots, logs, or tests. This gate is deliberately not inferred by dsh-doctor.
|
package/src/cli.mjs
CHANGED
|
@@ -43,7 +43,8 @@ Options:
|
|
|
43
43
|
--fix, --repair legacy confirmed repairs (never removes a plugin)
|
|
44
44
|
--yes confirm an explicit write or command
|
|
45
45
|
--from <ref> source DSH ref (default: dsh-v0.1.1-rc.2)
|
|
46
|
-
--to <ref> target DSH ref (default: dsh-v0.1.2-alpha.
|
|
46
|
+
--to <ref> target DSH ref (default: dsh-v0.1.2-alpha.3)
|
|
47
|
+
--target-version <ver> actual DSH 0.1.2 version for dependency/runtime checks
|
|
47
48
|
--safe restrict migrate apply to catalog-confirmed exact rewrites
|
|
48
49
|
--plan-file <path> persist or consume the reviewed migrate apply plan
|
|
49
50
|
--level <level> static, build, or runtime verification
|
|
@@ -85,7 +86,8 @@ const HELP_ZH = `用法:
|
|
|
85
86
|
--fix, --repair 旧式确认修复(绝不移除插件)
|
|
86
87
|
--yes 确认一个明确的写入或命令动作
|
|
87
88
|
--from <ref> 源 DSH ref(默认:dsh-v0.1.1-rc.2)
|
|
88
|
-
--to <ref> 目标 DSH ref(默认:dsh-v0.1.2-alpha.
|
|
89
|
+
--to <ref> 目标 DSH ref(默认:dsh-v0.1.2-alpha.3)
|
|
90
|
+
--target-version <版本> 依赖与 runtime 验证使用的实际 DSH 0.1.2 版本
|
|
89
91
|
--safe migrate apply 仅执行 catalog 确认的精确改写
|
|
90
92
|
--plan-file <路径> 保存或读取已审阅的 migrate apply 计划
|
|
91
93
|
--level <级别> static、build 或 runtime
|
|
@@ -146,6 +148,8 @@ function parse(args) {
|
|
|
146
148
|
else if (arg.startsWith('--from=')) options.from = optionValue(arg, '--from')
|
|
147
149
|
else if (arg === '--to') options.to = valueAfter(args, index++, arg)
|
|
148
150
|
else if (arg.startsWith('--to=')) options.to = optionValue(arg, '--to')
|
|
151
|
+
else if (arg === '--target-version') options.targetVersion = valueAfter(args, index++, arg)
|
|
152
|
+
else if (arg.startsWith('--target-version=')) options.targetVersion = optionValue(arg, '--target-version')
|
|
149
153
|
else if (arg === '--safe') options.safe = true
|
|
150
154
|
else if (arg === '--plan-file') options.planFile = valueAfter(args, index++, arg)
|
|
151
155
|
else if (arg.startsWith('--plan-file=')) options.planFile = optionValue(arg, '--plan-file')
|
|
@@ -300,7 +304,7 @@ async function main() {
|
|
|
300
304
|
if (!['analyze', 'apply', 'verify'].includes(options.migrateAction)) throw new Error('migrate needs analyze, apply, or verify')
|
|
301
305
|
if (options.migrateAction === 'apply' && options.safe !== true) throw new Error('migrate apply requires --safe')
|
|
302
306
|
if (options.migrateAction === 'apply' && options.pluginRoot === undefined) throw new Error('migrate apply requires an explicit plugin root')
|
|
303
|
-
const migrationOptions = { from: options.from, to: options.to, harnessRoot: options.harnessRoot, dshCommand: options.dshCommand, level: options.level, yes: options.yes, safe: options.safe, keepTemp: options.keepTemp, planFile: options.planFile, install: options.install }
|
|
307
|
+
const migrationOptions = { from: options.from, to: options.to, targetVersion: options.targetVersion, harnessRoot: options.harnessRoot, dshCommand: options.dshCommand, level: options.level, yes: options.yes, safe: options.safe, keepTemp: options.keepTemp, planFile: options.planFile, install: options.install }
|
|
304
308
|
if (options.migrateAction === 'verify') {
|
|
305
309
|
const { formatVerification, verifyMigration } = await import('./migrate-verify.mjs')
|
|
306
310
|
const result = verifyMigration(options.pluginRoot, migrationOptions)
|
package/src/migrate-verify.mjs
CHANGED
|
@@ -214,7 +214,8 @@ export function verifyMigration(pluginRoot = process.cwd(), options = {}) {
|
|
|
214
214
|
const [versionCommand, versionArgs] = dshInvocation(dsh, ['--version'])
|
|
215
215
|
const versionRun = commandResult(versionCommand, versionArgs, { cwd: root, env })
|
|
216
216
|
const actualDshVersion = versionRun.stdout.trim()
|
|
217
|
-
const
|
|
217
|
+
const expectedDshVersion = analysis.migration.actualTarget.version
|
|
218
|
+
const versionPassed = versionRun.passed && actualDshVersion === expectedDshVersion
|
|
218
219
|
const pack = commandResult(manager.command, manager.pack(packDir), { cwd: root })
|
|
219
220
|
const tarball = tarballFromPack(pack, packDir)
|
|
220
221
|
const runtimeCommands = []
|
|
@@ -236,7 +237,7 @@ export function verifyMigration(pluginRoot = process.cwd(), options = {}) {
|
|
|
236
237
|
result.stages.push({
|
|
237
238
|
name: 'runtime',
|
|
238
239
|
passed: runtimePassed,
|
|
239
|
-
dshVersion: { expected: analysis.migration.to.version, actual: actualDshVersion, passed: versionPassed, command: commandEvidence(versionRun) },
|
|
240
|
+
dshVersion: { expected: expectedDshVersion, catalog: analysis.migration.to.version, actual: actualDshVersion, passed: versionPassed, command: commandEvidence(versionRun) },
|
|
240
241
|
pack: commandEvidence(pack),
|
|
241
242
|
tarball,
|
|
242
243
|
commands: runtimeCommands.map(commandEvidence),
|
package/src/migrate.mjs
CHANGED
|
@@ -80,12 +80,17 @@ function moduleLiteral(node) {
|
|
|
80
80
|
|
|
81
81
|
function namedImports(node) {
|
|
82
82
|
const bindings = node.importClause?.namedBindings
|
|
83
|
-
if (
|
|
84
|
-
return bindings.elements.map(item => ({
|
|
83
|
+
if (bindings && ts.isNamedImports(bindings)) return bindings.elements.map(item => ({
|
|
85
84
|
imported: item.propertyName?.text ?? item.name.text,
|
|
86
85
|
local: item.name.text,
|
|
87
86
|
typeOnly: node.importClause.isTypeOnly || item.isTypeOnly,
|
|
88
87
|
}))
|
|
88
|
+
if (ts.isExportDeclaration(node) && node.exportClause && ts.isNamedExports(node.exportClause)) return node.exportClause.elements.map(item => ({
|
|
89
|
+
imported: item.propertyName?.text ?? item.name.text,
|
|
90
|
+
local: item.name.text,
|
|
91
|
+
typeOnly: node.isTypeOnly || item.isTypeOnly,
|
|
92
|
+
}))
|
|
93
|
+
return []
|
|
89
94
|
}
|
|
90
95
|
|
|
91
96
|
function quoteModule(text) {
|
|
@@ -117,10 +122,11 @@ function analyzeFile(file, pluginRoot, catalog, origin) {
|
|
|
117
122
|
handledLiterals.add(literal)
|
|
118
123
|
const moduleName = literal.text
|
|
119
124
|
const rootName = packageRoot(moduleName)
|
|
120
|
-
|
|
125
|
+
const moduleRemoved = removed.has(rootName)
|
|
126
|
+
const symbolRules = catalog.symbols.modules[moduleName]
|
|
127
|
+
if (moduleRemoved || symbolRules !== undefined) {
|
|
121
128
|
const where = location(source, literal, pluginRoot)
|
|
122
|
-
const
|
|
123
|
-
const specs = ts.isImportDeclaration(node) ? namedImports(node) : []
|
|
129
|
+
const specs = namedImports(node)
|
|
124
130
|
const exact = []
|
|
125
131
|
const remaining = []
|
|
126
132
|
for (const spec of specs) {
|
|
@@ -128,7 +134,7 @@ function analyzeFile(file, pluginRoot, catalog, origin) {
|
|
|
128
134
|
if (rule?.confidence === 'exact') {
|
|
129
135
|
exact.push({ ...spec, imported: rule.toSymbol, toModule: rule.toModule, fromSymbol: spec.imported, reason: rule.reason })
|
|
130
136
|
findings.push({ code: 'MIG_MOVED_SYMBOL', severity: 'error', message: `${spec.imported} moved to ${rule.toModule}`, location: where, evidence: { module: moduleName, symbol: spec.imported, targetModule: rule.toModule, replacement: rule.toSymbol }, autoFix: 'safe' })
|
|
131
|
-
} else {
|
|
137
|
+
} else if (rule?.confidence === 'semantic' || moduleRemoved) {
|
|
132
138
|
remaining.push(spec)
|
|
133
139
|
const semantic = rule?.confidence === 'semantic'
|
|
134
140
|
findings.push({ code: semantic ? 'MIG_SEMANTIC_API_CHANGE' : 'MIG_REMOVED_PACKAGE_REFERENCE', severity: 'error', message: semantic ? `${spec.imported} requires a semantic migration: ${rule.reason}` : `${moduleName} was removed without a safe automatic replacement`, location: where, evidence: { module: moduleName, ...(spec.imported ? { symbol: spec.imported } : {}) }, autoFix: 'none' })
|
|
@@ -140,7 +146,7 @@ function analyzeFile(file, pluginRoot, catalog, origin) {
|
|
|
140
146
|
...(rule?.toSymbol ? { targetSymbol: rule.toSymbol } : {}),
|
|
141
147
|
reason: rule?.reason ?? 'No exact replacement is known.',
|
|
142
148
|
})
|
|
143
|
-
}
|
|
149
|
+
} else remaining.push(spec)
|
|
144
150
|
}
|
|
145
151
|
const declarationText = ts.isImportDeclaration(node) ? text.slice(node.getStart(source), node.getEnd()) : ''
|
|
146
152
|
const simpleNamedImport = exact.length > 0 && ts.isImportDeclaration(node) && node.importClause?.namedBindings && ts.isNamedImports(node.importClause.namedBindings) && node.importClause.name === undefined && node.attributes === undefined && node.assertClause === undefined && !/\/(?:\/|\*)/.test(declarationText)
|
|
@@ -160,7 +166,7 @@ function analyzeFile(file, pluginRoot, catalog, origin) {
|
|
|
160
166
|
const generated = [...groups].map(([target, items]) => importText(target, items, node.importClause.isTypeOnly))
|
|
161
167
|
if (remaining.length > 0) generated.unshift(importText(moduleName, remaining, node.importClause.isTypeOnly))
|
|
162
168
|
replacements.push({ start: node.getStart(source), end: node.getEnd(), next: generated.join('\n'), exact })
|
|
163
|
-
} else if (specs.length === 0 || exact.length > 0) {
|
|
169
|
+
} else if (moduleRemoved && (specs.length === 0 || exact.length > 0)) {
|
|
164
170
|
for (const finding of findings) if (finding.code === 'MIG_MOVED_SYMBOL' && finding.location.line === where.line && exact.some(item => item.fromSymbol === finding.evidence.symbol)) finding.autoFix = 'none'
|
|
165
171
|
findings.push({ code: 'MIG_REMOVED_PACKAGE_REFERENCE', severity: 'error', message: `${moduleName} was removed and this reference cannot be rewritten safely`, location: where, evidence: { module: moduleName, kind: ts.SyntaxKind[node.kind] }, autoFix: 'none' })
|
|
166
172
|
unresolved.push({ file: where.file, package: rootName, module: moduleName, reason: 'Default, namespace, side-effect, export, require, dynamic import, or module augmentation reference.' })
|
|
@@ -189,12 +195,28 @@ function readManifest(pluginRoot) {
|
|
|
189
195
|
return { file, value, text }
|
|
190
196
|
}
|
|
191
197
|
|
|
192
|
-
function
|
|
198
|
+
function actualTargetVersion(catalog, requested) {
|
|
199
|
+
if (requested === undefined) return catalog.manifest.to.version
|
|
200
|
+
const version = semver.valid(requested)
|
|
201
|
+
if (version === null) throw new Error(`--target-version must be an exact semantic version, received ${requested}`)
|
|
202
|
+
const catalogVersion = semver.parse(catalog.manifest.to.version)
|
|
203
|
+
const target = semver.parse(version)
|
|
204
|
+
if (catalogVersion === null || target === null || target.major !== catalogVersion.major || target.minor !== catalogVersion.minor || target.patch !== catalogVersion.patch) {
|
|
205
|
+
throw new Error(`--target-version ${requested} is outside the catalog's ${catalogVersion?.major}.${catalogVersion?.minor}.${catalogVersion?.patch} release line`)
|
|
206
|
+
}
|
|
207
|
+
if (semver.lt(target, catalogVersion)) {
|
|
208
|
+
throw new Error(`--target-version ${requested} predates the catalog target ${catalog.manifest.to.version}`)
|
|
209
|
+
}
|
|
210
|
+
return version
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function targetVersion(name, catalog, actualTarget) {
|
|
214
|
+
if (name.startsWith('@deepseek-ai/dsh-')) return actualTarget
|
|
193
215
|
if (catalog.packages.targetVersions?.[name] !== undefined) return catalog.packages.targetVersions[name]
|
|
194
|
-
return
|
|
216
|
+
return undefined
|
|
195
217
|
}
|
|
196
218
|
|
|
197
|
-
function manifestFindings(manifest, catalog) {
|
|
219
|
+
function manifestFindings(manifest, catalog, actualTarget) {
|
|
198
220
|
const findings = []
|
|
199
221
|
for (const field of DEPENDENCY_FIELDS) {
|
|
200
222
|
const dependencyMap = manifest.value[field]
|
|
@@ -207,7 +229,7 @@ function manifestFindings(manifest, catalog) {
|
|
|
207
229
|
findings.push({ code: 'MIG_REMOVED_PACKAGE_REFERENCE', severity: 'error', message: `${field}.${name} targets a removed package`, location: { file: 'package.json' }, evidence: { field, package: name, range }, autoFix: 'conditional' })
|
|
208
230
|
continue
|
|
209
231
|
}
|
|
210
|
-
const target = targetVersion(name, catalog)
|
|
232
|
+
const target = targetVersion(name, catalog, actualTarget)
|
|
211
233
|
if (target === undefined) continue
|
|
212
234
|
const validRange = typeof range === 'string' ? semver.validRange(range) : null
|
|
213
235
|
if (validRange === null) findings.push({ code: 'MIG_INVALID_DEPENDENCY_RANGE', severity: 'error', message: `${field}.${name} has a non-registry range that cannot prove target compatibility`, location: { file: 'package.json' }, evidence: { field, package: name, range, target }, autoFix: field === 'devDependencies' ? 'safe' : 'none' })
|
|
@@ -307,7 +329,7 @@ function patchTargetFindings(pluginRoot, manifest, harness) {
|
|
|
307
329
|
return findings
|
|
308
330
|
}
|
|
309
331
|
|
|
310
|
-
function planManifest(manifest, sourceResults, artifactResults, opaqueReferences, catalog) {
|
|
332
|
+
function planManifest(manifest, sourceResults, artifactResults, opaqueReferences, catalog, actualTarget) {
|
|
311
333
|
const next = structuredClone(manifest.value)
|
|
312
334
|
const exactTargets = sourceResults.flatMap(result => result.exactTargets)
|
|
313
335
|
const sourceReferences = new Set(sourceResults.flatMap(result => result.remainingPackages))
|
|
@@ -323,14 +345,14 @@ function planManifest(manifest, sourceResults, artifactResults, opaqueReferences
|
|
|
323
345
|
}
|
|
324
346
|
if (field === 'devDependencies') {
|
|
325
347
|
for (const [name, range] of Object.entries(deps)) {
|
|
326
|
-
const target = targetVersion(name, catalog)
|
|
348
|
+
const target = targetVersion(name, catalog, actualTarget)
|
|
327
349
|
if (target !== undefined && !catalog.packages.removed.includes(name) && (semver.validRange(range) === null || !semver.satisfies(target, range))) deps[name] = target
|
|
328
350
|
}
|
|
329
351
|
}
|
|
330
352
|
}
|
|
331
353
|
for (const move of new Map(exactTargets.map(item => [`${item.relationship}\0${item.toPackage}`, item])).values()) {
|
|
332
354
|
const fields = catalog.packages.dependencyPolicies?.[move.toPackage]?.[move.relationship]
|
|
333
|
-
const version = targetVersion(move.toPackage, catalog)
|
|
355
|
+
const version = targetVersion(move.toPackage, catalog, actualTarget)
|
|
334
356
|
if (!Array.isArray(fields) || version === undefined) continue
|
|
335
357
|
for (const field of fields) {
|
|
336
358
|
if (!DEPENDENCY_FIELDS.includes(field)) throw new Error(`migration catalog has an invalid dependency field ${field}`)
|
|
@@ -369,7 +391,8 @@ function analysisInputs(root) {
|
|
|
369
391
|
export function analyzeMigration(pluginRoot = process.cwd(), options = {}) {
|
|
370
392
|
const root = resolve(pluginRoot)
|
|
371
393
|
if (!statSync(root).isDirectory()) throw new Error(`plugin root is not a directory: ${root}`)
|
|
372
|
-
const catalog = loadMigration(options.from ?? 'dsh-v0.1.1-rc.2', options.to ?? 'dsh-v0.1.2-alpha.
|
|
394
|
+
const catalog = loadMigration(options.from ?? 'dsh-v0.1.1-rc.2', options.to ?? 'dsh-v0.1.2-alpha.3')
|
|
395
|
+
const actualTarget = actualTargetVersion(catalog, options.targetVersion)
|
|
373
396
|
const harness = verifyHarnessCheckout(catalog, options.harnessRoot)
|
|
374
397
|
const manifest = readManifest(root)
|
|
375
398
|
const sourceFiles = walk(root, SOURCE_DIRS_TO_SKIP).filter(file => !isTopLevelArtifact(root, file))
|
|
@@ -378,10 +401,10 @@ export function analyzeMigration(pluginRoot = process.cwd(), options = {}) {
|
|
|
378
401
|
const opaqueReferences = opaqueReferenceFindings(root, sourceFiles, manifest, catalog)
|
|
379
402
|
const sourceRemoved = sources.some(item => item.findings.some(finding => ['MIG_REMOVED_PACKAGE_REFERENCE', 'MIG_SEMANTIC_API_CHANGE', 'MIG_MOVED_SYMBOL'].includes(finding.code)))
|
|
380
403
|
const artifactRemoved = artifacts.some(item => item.findings.some(finding => ['MIG_REMOVED_PACKAGE_REFERENCE', 'MIG_SEMANTIC_API_CHANGE', 'MIG_MOVED_SYMBOL'].includes(finding.code)))
|
|
381
|
-
const findings = [...manifestFindings(manifest, catalog), ...patchTargetFindings(root, manifest, harness), ...sources.flatMap(item => item.findings), ...opaqueReferences.findings, ...artifacts.flatMap(item => item.findings)]
|
|
404
|
+
const findings = [...manifestFindings(manifest, catalog, actualTarget), ...patchTargetFindings(root, manifest, harness), ...sources.flatMap(item => item.findings), ...opaqueReferences.findings, ...artifacts.flatMap(item => item.findings)]
|
|
382
405
|
if (!sourceRemoved && artifactRemoved) findings.push({ code: 'MIG_SOURCE_ARTIFACT_DRIFT', severity: 'error', message: 'built artifacts still reference removed APIs although source files do not', location: { file: '.' }, evidence: { artifactDirectories: ARTIFACT_DIRS }, autoFix: 'none' })
|
|
383
406
|
findings.push({ code: 'MIG_RUNTIME_VERIFICATION_REQUIRED', severity: 'info', message: 'static analysis cannot prove activation and lifecycle behavior; run migrate verify --level runtime', location: { file: '.' }, evidence: {}, autoFix: 'none' })
|
|
384
|
-
const manifestPlan = planManifest(manifest, sources, artifacts, opaqueReferences.packages, catalog)
|
|
407
|
+
const manifestPlan = planManifest(manifest, sources, artifacts, opaqueReferences.packages, catalog, actualTarget)
|
|
385
408
|
const changed = [...sources.filter(item => item.changed), ...(manifestPlan.changed ? [manifestPlan] : [])]
|
|
386
409
|
const safeEdits = changed.map(item => ({
|
|
387
410
|
file: relativePath(root, item.file),
|
|
@@ -393,14 +416,21 @@ export function analyzeMigration(pluginRoot = process.cwd(), options = {}) {
|
|
|
393
416
|
return {
|
|
394
417
|
schemaVersion: 1,
|
|
395
418
|
command: 'migrate analyze',
|
|
396
|
-
migration: {
|
|
419
|
+
migration: {
|
|
420
|
+
id: catalog.manifest.id,
|
|
421
|
+
from: catalog.manifest.from,
|
|
422
|
+
to: catalog.manifest.to,
|
|
423
|
+
actualTarget: { version: actualTarget, catalogVersion: catalog.manifest.to.version, catalogExact: actualTarget === catalog.manifest.to.version },
|
|
424
|
+
references: catalog.manifest.references ?? {},
|
|
425
|
+
harness,
|
|
426
|
+
},
|
|
397
427
|
plugin: { root, name: manifest.value.name ?? basename(root), version: manifest.value.version ?? 'unknown', manifestFile: manifest.file, packageManager: existsSync(join(root, 'pnpm-lock.yaml')) ? 'pnpm' : existsSync(join(root, 'yarn.lock')) ? 'yarn' : 'npm' },
|
|
398
428
|
summary: summarize(findings, safeEdits, unresolved),
|
|
399
429
|
findings,
|
|
400
430
|
safeEdits,
|
|
401
431
|
semanticTasks: unresolved,
|
|
402
432
|
sourceInvestigation: {
|
|
403
|
-
required: harness.exact !== true || unresolved.length > 0,
|
|
433
|
+
required: harness.exact !== true || unresolved.length > 0 || actualTarget !== catalog.manifest.to.version,
|
|
404
434
|
targetRef: catalog.manifest.to.ref,
|
|
405
435
|
catalogReferences: catalog.manifest.references ?? {},
|
|
406
436
|
semanticTargets: [...new Set(unresolved.map(item => item.targetModule).filter(Boolean))],
|
|
@@ -429,6 +459,7 @@ export function createMigrationPlan(report) {
|
|
|
429
459
|
id: report.migration.id,
|
|
430
460
|
from: report.migration.from.ref,
|
|
431
461
|
to: report.migration.to.ref,
|
|
462
|
+
targetVersion: report.migration.actualTarget.version,
|
|
432
463
|
harness: {
|
|
433
464
|
status: report.migration.harness.status,
|
|
434
465
|
fromCommit: report.migration.harness.fromCommit,
|
|
@@ -453,7 +484,7 @@ function readMigrationPlan(file) {
|
|
|
453
484
|
function assertMigrationPlan(report, plan) {
|
|
454
485
|
const current = createMigrationPlan(report)
|
|
455
486
|
if (plan.plugin.root !== current.plugin.root || plan.plugin.manifestFile !== current.plugin.manifestFile) throw new Error('migration plan targets a different plugin')
|
|
456
|
-
if (plan.migration.id !== current.migration.id || plan.migration.from !== current.migration.from || plan.migration.to !== current.migration.to) throw new Error('migration plan targets a different catalog')
|
|
487
|
+
if (plan.migration.id !== current.migration.id || plan.migration.from !== current.migration.from || plan.migration.to !== current.migration.to || plan.migration.targetVersion !== current.migration.targetVersion) throw new Error('migration plan targets a different catalog or actual target version')
|
|
457
488
|
if (plan.reportHash !== current.reportHash || JSON.stringify(plan.inputs) !== JSON.stringify(current.inputs) || JSON.stringify(plan.edits) !== JSON.stringify(current.edits)) throw new Error('plugin analysis changed after the preview; create and review a new migration plan')
|
|
458
489
|
return current
|
|
459
490
|
}
|
|
@@ -478,7 +509,7 @@ export function applyMigration(report, options = {}) {
|
|
|
478
509
|
if (currentExists !== item.snapshot.exists || sha256(current) !== item.snapshot.hash) throw new Error(`${item.snapshot.file} changed after the preview; diagnose again before applying`)
|
|
479
510
|
}
|
|
480
511
|
const writes = report._plan.map(item => ({ ...atomicWrite(item.snapshot, item.nextText), beforeHash: item.snapshot.hash, afterHash: sha256(item.nextText) }))
|
|
481
|
-
const verification = analyzeMigration(report.plugin.root, { from: report.migration.from.ref, to: report.migration.to.ref, harnessRoot: report.migration.harness.root })
|
|
512
|
+
const verification = analyzeMigration(report.plugin.root, { from: report.migration.from.ref, to: report.migration.to.ref, targetVersion: report.migration.actualTarget.version, harnessRoot: report.migration.harness.root })
|
|
482
513
|
return { mode: 'applied', plan: { file: planFile, id: verifiedPlan.planId }, writes, report: publicMigrationReport(verification) }
|
|
483
514
|
}
|
|
484
515
|
|