Migration Assessment — act101 Agent Skill
Use when assessing migration readiness, planning a port to another language, understanding what makes this codebase hard to rewrite, or requesting "how ready is this for porting?". Depth 2 — investigate. Produces per-module readiness cards, recommended migration order, hard/soft blocker list, and platform dependency assessment. Optionally enriches the plan with security and history evidence (taint flow, secret surface, unsafe constructs, coverage, churn, ownership) so blockers reflect real porting risk, not structure alone. Replaces the migration-readiness-plus skill.
Migration Assessment
Depth: Level 2 (Investigate).
See ../analysis-protocol/references/protocol.md for: artifact directory structure,
the investigation loop, depth levels, summary format, token budget rules, and project
map structure. Read that document before proceeding.
Phase 1: Parallel Tool Dispatch
Dispatch all available tools in a single parallel batch.
Each subagent runs one tool, saves raw JSON to raw/<tool-name>.json, returns a
structured summary.
Must-have tools:
| Tool | Purpose | MCP call |
|---|---|---|
analyze_readiness |
Composite migration score (M1) | analyze_readiness |
If analyze_readiness is unavailable, report that and stop — no useful migration
assessment is possible without it.
Extended tools (use if available, skip and note in manifest if not):
| Tool | Purpose | MCP call |
|---|---|---|
analyze_features |
Language feature inventory (M3) | analyze_features |
analyze_platform_deps |
Platform/runtime dependencies (M5) | analyze_platform_deps |
analyze_interfaces |
Cross-module contracts (M4) | analyze_interfaces |
analyze_type_completeness |
Type boundary holes (M7) | analyze_type_completeness |
analyze_fan_balance |
Migration ordering — fan-in/fan-out (M6) | analyze_fan_balance |
analyze_depth |
Dependency chain depth (S4) | analyze_depth |
analyze_inheritance |
Tangled inheritance hierarchies (H6) | analyze_inheritance |
analyze_patterns |
Hard porting blockers | analyze_patterns (use --pattern porting_blockers if available, else --tier all) |
Phase 2: Investigation
For each file or module scored "hard" in readiness, or flagged as a hard blocker:
Platform dependency hypothesis example:
Hypothesis N: The
<module>module is migration-ready despite<N>platform deps because they're isolated behind a<WrapperClass>abstraction. Evidence:analyze_readinessscored it "needs-work";analyze_platform_depsshows N imports of<platform-api>. Confirming query:skeletonon the wrapper file +referenceson the platform import symbol. Confirms if: Only the wrapper file imports the platform module directly. Refutes if: Multiple files import the platform module directly.
Complexity blocker hypothesis example:
Hypothesis N:
<file>is a hard blocker due to dynamic dispatch, not complexity. Evidence:analyze_readinessscored it "hard"; patterns flagged reflection/eval use. Confirming query:skeletonon the file to count dynamic call sites. Confirms if: Multiple reflection/eval call sites with no static alternative. Refutes if: Single call site that can be wrapped or replaced.
Save investigation notes to investigation/hypothesis-N.md.
Report Structure
# Migration Assessment: <project name>
## Readiness Summary
Ready / Needs work / Hard counts with percentages.
Overall verdict: **Ready** / **Needs work** / **Hard** — with confidence note.
## Recommended Migration Order
Foundation modules first (high fan-in, low fan-out — ported early, depended upon).
Orchestrators last (low fan-in, high fan-out — ported after their dependencies).
Numbered list with rationale per module.
## Porting Blockers
Per-file list with context from investigation.
Distinguish clearly:
- **Hard blockers:** eval, reflection, FFI, dynamic dispatch — no mechanical equivalent
- **Soft blockers:** high complexity, tight coupling — require refactor before porting
## Platform Dependencies
By category (filesystem, network, OS, process, browser, FFI).
For each dependency:
- Which files use it
- Wrapped vs. direct (direct = higher porting cost)
- Adaptation strategy for the target language
## Type Boundary Gaps
Modules with low type completeness.
Per-gap: module path, missing type annotations, impact on mechanical translation.
## Language Feature Concerns
Features with no direct equivalent in the target language.
Per-feature: count, affected files, suggested adaptation approach.
## Inheritance Complexity
Deep hierarchies and diamond inheritance patterns that complicate porting.
Per-finding: chain depth, members, recommended simplification before porting.
## Dependency Depth
Deepest dependency chains (leaf modules to port first).
Per-chain: length, root, leaf, recommended port order.
## Per-Module Readiness Cards
For each top-level module:
- Readiness score: ready / needs-work / hard
- Blocker count (hard / soft)
- Platform dep count
- Recommended action
Project Map Updates
Appends or updates the "Migration Readiness" section. Appends to the Analysis History table.
Optional Enrichment: Security & History Evidence
The readiness scores above are structural/heuristic. When the workspace has the relevant evidence, enrich the plan so the recommended migration order reflects real porting risk — code that is hard to port AND dangerous AND historically volatile — not just structural readiness. This is optional: skip any dimension whose evidence or tier is unavailable and mark it UNASSESSED (never a clean bill).
Tier: composed tools enforce their own tiers — unsafe_surface Free,
secret_surface Engineer, taint_flow/coverage_overlay/ownership_map Architecture,
churn_hotspots Free for a single file / Architecture in workspace mode. If a tool is
tier-blocked, its dimension is UNASSESSED — say so.
Honesty caveat: taint_flow/secret_surface/unsafe_surface report
modeled_kinds — read it per call. A non-empty mask means the dimension was
modeled for that grammar (an empty finding is then genuine); an empty/absent mask
means it was not modeled — "no evidence," not "clean." coverage_overlay/
churn_hotspots/ownership_map reflect one coverage run and the git history
present; state the window and unmapped.
| Tool | Enriches the plan with |
|---|---|
taint_flow |
Source→sink data-flow paths a port must preserve exactly — a hard porting hazard. |
secret_surface |
Secret-touching code needing careful handling / rotation during the move. |
unsafe_surface |
Unsafe blocks, eval, raw SQL, FFI, reflection, unsafe deserialization — rarely port 1:1. |
coverage_overlay |
Which modules have a test safety net for the port vs. which are flying blind. |
churn_hotspots |
Volatile modules — porting a still-changing target is a moving-target risk. |
ownership_map |
Bus factor — low ownership on a hard module concentrates the knowledge to port it. |
Enrichment workflow (after the structural plan):
- Run
unsafe_surfaceandsecret_surfaceper module; runtaint_flowon entry functions. Any unsafe construct, secret surface, or live taint path raises that module's effective difficulty above its structural score. coverage_overlay(lcovreport) — a low-coverage module cannot be safely verified after porting; front-load test work or reorder.churn_hotspots(workspace) andownership_map— a high-churn or low-bus-factor module is a scheduling risk: stabilize or pair-port it.- Recompute the order: structurally-ready AND well-tested AND low-risk first; hard + dangerous + volatile last (or split first). Reclassify any soft blocker that an unsafe/taint/secret finding promotes to a hard blocker.
When enrichment runs, each readiness card adds: taint paths, secret/unsafe hits, coverage %, churn rank, and bus factor — and each blocker cites which evidence dimension fired.
Monorepos: frame readiness cards by package, not directory
When the repo is a workspace with package manifests, run the structural pass at package granularity so readiness cards map to the team's real packages (workspace members), not arbitrary directories:
analyze_clustersandanalyze_layerswithgranularity: packagecluster and layer the package dependency graph (DependsOnedges parsed from manifests), giving the true cross-package porting order — port a package only after the packages it depends on.analyze_conformancewithgranularity: packageevaluates deny rules against package names, so a "don't let package X depend on Y" boundary is checked at the level the team actually reasons about.
Which manifest kinds were modeled is reported from the graph's package nodes (empty when none) — never assume an ecosystem is covered; read what was modeled. Fall back to file/directory granularity when no workspace manifest is present.