/modernize-uplift migrates one representative project end-to-end and writes its lessons to analysis/<system>/PLAYBOOK.md before touching the rest. The remaining projects then fan out through a new uplift-migrate workflow, one uplift-migrator agent per project, in dependency-aware escalating batches behind a per-batch circuit breaker. A recorded per-test baseline (analysis/<system>/BASELINE.md) gates the migration, and the delta catalog reports a test framework whose runner does not support the target as its own highest-blast-radius dependency. The three execution commands (uplift, transform, reimagine) read MODERNIZATION_BRIEF.md and treat their phase's scope and entry and exit criteria as gates, so editing the brief steers execution. For a same-stack uplift the brief requires the delta catalog and applies the same ordering overrides the execution command does. /modernize-preflight opens with a short interview (scope, local build and test, bespoke build infrastructure, prior attempts, what is off limits) without blocking on the answers, reads the CI/build definition for how the system builds, escalates the smoke test to a whole-project restore and build, and adds a scope-boundary check that enumerates inbound and outbound dependencies when the system directory is a slice of a larger repository. Workflow scripts accept args delivered as either a JSON string or an object.
12 KiB
description, argument-hint
| description | argument-hint |
|---|---|
| Environment readiness check — analysis tools, build toolchain, source completeness, telemetry access | <system-dir> [target-stack] |
Check whether this environment is ready to analyze — and eventually
transform — legacy/$1, and tell the user exactly what to fix before the
other commands run into it. Modernization sessions fail late and
confusingly when this isn't done: assessment metrics silently degrade
without analysis tools, characterization tests can't run without a build
toolchain, and dependency maps come out wrong when half the source isn't
in the tree.
Run every check even when an early one fails — the point is one complete readiness report, not the first error.
Check 0 — Ask the human (these answers are not in the source)
Before any automated check, ask the person running this command the five questions below. The most expensive modernization mistakes are things a person who knows the system answers in seconds and that cost real money to discover wrong from the source alone. Ask only these — add none — and accept "don't know" for any of them.
Ask, then do not block on the answers. None of Checks 1–6 needs one
(Check 6 verifies the scope boundary from the source independently — the
human's answer says whether a crossing matters, not whether it exists), so
proceed to the checks immediately after asking and write the report with
whatever answers exist by then. Any question still unanswered goes in the
report verbatim, marked as an open item the human must fill in — it is
not dropped. This way an interactive user answers while the checks run, a
headless or scripted run still produces a complete PREFLIGHT.md, and the
one thing that never happens is a readiness report silently missing the
questions.
- Scope — Is
legacy/$1the complete system, or one slice of a larger codebase? If a slice: what outside it depends on code inside it, and is breaking those consumers acceptable? (Check 6 verifies this from the source independently; the human's answer says whether it matters.) - Build & test locally — Can this environment restore, build, and run the tests? Roughly how long does the full CI pipeline take? (A pipeline measured in hours changes the whole validation strategy: you cannot afford to first learn you were wrong from CI.)
- Bespoke build infrastructure — Is there organization-specific build or dependency-resolution machinery (an internal package feed, a custom binary store, a code generator, a wrapper around the standard build tool) that someone new to this codebase would not guess? Where is it documented?
- Prior attempts — Has anyone tried to modernize any of this before? What went wrong?
- Off limits — Is anything under
legacy/$1not allowed to change in this pass (a component another team owns, a frozen branch, generated code)?
Record every answer verbatim in the report — downstream commands, and
/modernize-brief most of all, read them from there. Do not paraphrase
away a caveat the human gave you.
Check 1 — Detect the stack
Fingerprint legacy/$1 from file extensions and manifests: languages,
build system, deployment/config descriptors. This drives which checks
below apply. Report what was detected and the rough file split.
Check 2 — Analysis tooling
For each, check availability (command -v) and report version, what it's
used for, and what degrades without it:
| Tool | Used by | Without it |
|---|---|---|
scc (or cloc) |
assess | LOC/complexity fall back to find+wc; the COCOMO complexity index gets coarser |
lizard |
assess --portfolio | complexity estimated from decision-keyword counts |
glow |
all | markdown artifacts render as plain text |
delta |
transform | side-by-side diffs fall back to diff -y |
Include the platform's install one-liner for anything missing
(brew install scc, apt install cloc, pip install lizard, …).
Check 3 — Build toolchain (prove it on THIS codebase, not just presence)
3a — The build definition is the ground truth. Find it and read it
before guessing. Something already builds this system; go find out how.
Look for the CI/pipeline definition (azure-pipelines.yml, Jenkinsfile,
.github/workflows/, .gitlab-ci.yml, bitbucket-pipelines.yml, build
JCL procs, a Makefile) and any organization-level build configuration
above or beside the source (Directory.Build.props/.targets and
nuget.config in .NET; a parent POM, a settings.xml mirror, or a
.mvn/ directory in Java; a private-registry .npmrc/pip.conf; a root
build/, eng/, tools/, or scripts/ directory). These files are the
single most honest document about how the system actually builds: the
exact toolchain version it pins, where dependency binaries really come
from, and which steps a naive build invocation skips. Every mid-migration
"wait, how do dependencies resolve here?" surprise is already written down
in one of them. Report what you found (or that none exists), quote the
pinned toolchain version and the dependency source, and flag anything
bespoke — a homegrown binary-resolution scheme is exactly the thing a
transformation must not have to discover halfway through.
3b — Smoke test, escalating. Identify the compiler/interpreter for the
detected legacy stack — e.g. GnuCOBOL (cobc) for COBOL, a JDK +
Maven/Gradle for Java, cc/make for C, dotnet for .NET — then prove
it works on this codebase, at the strongest level available:
- Level 1 (any stack) — syntax-compile one representative source file
(
cobc -fsyntax-only,javac,gcc -fsyntax-only, …). This catches missing copybooks/includes, dialect flags, fixed-vs-free format. - Level 2 (any stack with a build system) — restore + build ONE whole project/module the way 3a says the CI does. A single file syntax-compiling proves almost nothing about a real build system: a restore that hits a private feed, a code-generation step, a shared props file, a pinned SDK are all invisible to a one-file compile — and are exactly where large codebases hide their surprises. Pick one small real unit and take it all the way through.
A failed smoke test at either level is the most valuable output of this
whole command — report the actual error and diagnose it: missing
copybook/include path, missing dialect flag (-std=ibm etc.), fixed vs
free format, a dependency the standard feed cannot resolve. These are the
errors that otherwise surface mid-transformation with far less context.
Level 2 being impossible (no build system in the tree, a mainframe stack
with no local runtime) is normal for some legacy code: report it as a
fact, not a failure — equivalence then degrades to recorded traces, which
the other commands already handle.
If the user passed a [target-stack], do the same for it: runtime,
package manager, test framework (mvn -v, npm -v, pytest --version, …).
Check 4 — Source completeness
The dependency map is only as good as what's in the tree. Check for the detected stack's equivalents of:
- Referenced-but-missing includes — copybooks (
COPY Xwith noX.cpy), headers, imports that resolve nowhere. Count and list the top missing names. - Deployment/config descriptors — JCL for batch COBOL, CICS CSD
definitions,
web.xml/route configs, cron/scheduler definitions. Without these, entry-point detection and the code↔storage join in/modernize-mapare guesswork. - Data definitions — DDL, schemas, copybook record layouts, ORM mappings.
- Binary-only artifacts — load modules, jars, DLLs with no matching source. These become unmappable black boxes; flag them now.
Check 5 — Optional context
- Production telemetry — is an observability/APM MCP server connected,
or are batch job logs / runtime exports available? (Enables the runtime
overlay in
/modernize-assessStep 4 and timing annotations in/modernize-map.) - Version control history — is
legacy/$1under git with meaningful history? (Change-frequency data sharpens risk ranking.)
Check 6 — Scope boundary (is $1 the whole world, or a slice of one?)
Every downstream command assumes legacy/$1 is the system. When it is
actually one directory inside a larger source repository — a module in
a monorepo, one solution folder inside a much bigger solution, a subsystem
sharing copybooks or includes with siblings — that assumption is the most
dangerous thing in the whole run, and nothing else checks it.
Detect it: after resolving the legacy/$1 symlink (the recommended setup
symlinks real code in), is there a repository / solution / workspace /
reactor root above it? Do manifests or includes inside $1 reference
paths outside it? If either is true, report both directions of the
boundary crossing:
- Outbound — things inside
$1that depend on source outside it (project/module references, shared includes, a parent build file). The/modernize-maptopology and any delta catalog only see what is under$1, so every outbound reference is a dependency they will silently miss. List them. - Inbound — things outside
$1that depend on things inside it. This is the blast radius: an in-place migration (/modernize-uplift) of a node with external consumers breaks every one of them. Grep the sibling manifests for references into$1, enumerate the inbound-referenced nodes, and say plainly that each needs an explicit decision before any in-place change — keep it buildable for both old and new consumers during the transition, expand the scope to include the consumers, or accept and schedule the break. Never let this be discovered by a broken build in a directory nobody was looking at.
If $1 really is a standalone repository, one line saying so is the whole
check — it is cheap when it does not apply.
Report
Write analysis/$1/PREFLIGHT.md. It leads with the Check 0 answers,
verbatim, and the Check 6 scope-boundary finding — those two are read by
every downstream command (/modernize-brief above all) and are worth
nothing paraphrased. Then a status table — one row per check, status
✅ / ⚠️ / ❌, what was found, and the fix for anything not green — followed
by a Ready / Ready-with-gaps / Not ready verdict per command:
assess+map+extract-rules— need Checks 1–2 green-ish and Check 4's missing-include count lowbrief— needs only the three discovery artifacts (plusDELTA_CATALOG.mdwhen the plan is a same-stack uplift); no toolingtransform+reimagine— additionally need Check 3 green for the target stack. A red legacy toolchain downgrades these to Ready-with-gaps, not Not-ready: equivalence testing falls back to recorded traces / golden-master fixtures instead of dual execution (common and expected for CICS/IMS code that has no local runtime)harden— needs Check 2 plus any stack-specific SAST tooling founduplift(same-stack version bump) — needs Check 3 green for the target version. Two uplift-specific signals to report when a[target-stack]that looks like a version bump was passed: (a) is the source runtime also available here? Both present = a true dual-run is possible; target-only = equivalence degrades to characterization tests against recorded outputs (say which). (b) Is the stack's migration tool installed (dotnet tool listforupgrade-assistant,apiport, OpenRewrite,pyupgrade,ng)? Missing is Ready-with-gaps, not Not-ready — the delta catalog is then fully Claude-derived and loses the tool's coverage; note that. (c) Did Check 6 find inbound external consumers of$1? That is Ready-with-gaps, not Not-ready — preflight runs before any plan exists, so there is nowhere yet to record a decision — but it is the gap that matters most: name the inbound-referenced shared nodes and say that/modernize-briefmust give each one an explicit transition decision as its own line item (Check 6 lists the options), and that/modernize-upliftStep 1 will not migrate a shared node in place without one. Never let this be discovered from a sibling's broken build.
Print the table in the session too, and end with the single most important fix if anything is red.