<div align="center">
```
╔═══════════════════════════════════════════════════════════════╗
║ ║
║ ██╗ █████╗ ███╗ ██╗ ██████╗ ║
║ ██║ ██╔══██╗████╗ ██║██╔════╝ ║
║ ██║ ███████║██╔██╗ ██║██║ ███╗ ║
║ ██║ ██╔══██║██║╚██╗██║██║ ██║ ║
║ ███████╗██║ ██║██║ ╚████║╚██████╔╝ ║
║ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ flynn ║
║ M I G R A T I O N ║
║ ║
╚═══════════════════════════════════════════════════════════════╝
```
# lang-migration
**A Formal Methodology for AI-Driven, Evidence-Obligated Program Translation**
[](LICENSE)
[](#language-pairs)
[](#pipeline)
[](#the-evidence-obligation-protocol)
[](#runtime-environments)
[](https://clawhub.ai/suifei/lang-migration)
[](#whats-new)
**English | [中文](README.zh-CN.md)**
*Migrate any open-source codebase across programming languages — with structural fidelity,
persistent state, and verifiable proof that the AI actually did the work.*
### ✨ **What's New**
**v1.4 — AST Bridge (tree-sitter Machine Truth) + Invisible Runtime Dependency Detection**
A shipped toolchain (`scripts/ast_bridge.py`) extracts function boundaries, branch counts, call
sites, and literals directly from source ASTs — structural claims are now verified against machine
truth, not AI assertion. Stage 2 generates draft target skeletons with `TODO(migrate)` markers.
Plus: a 3-layer defense (static pattern scan → dynamic runtime trace → P5 closure check) catches
files loaded via directory traversal (`glob`, `os.listdir`) that static import analysis cannot see.
[Learn more →](CHANGELOG.md#v14--2026-06-10)
**v1.3 — Bug Triage Protocol + Real-World Validation**
Every P5 test failure now goes through a mandatory 3-step triage before any fix is attempted.
Only 1 of 5 verdicts leads to modifying the translated function. Four new root cause categories.
Validated against a real Python→Go migration (GenericAgent → go-GenericAgent). [Learn more →](#bug-triage-protocol-classify-before-fix)
</div>
---
ClawHub: [https://clawhub.ai/suifei/lang-migration](https://clawhub.ai/suifei/lang-migration)
Skillhub:[https://skillhub.cn/skills/lang-migration](https://skillhub.cn/skills/lang-migration)
## The Problem Nobody Talks About
When developers ask an LLM to "migrate my Python project to Go," one of four things happens:
1. **The LLM produces plausible-looking code that silently drops behavior.** Type semantics, precision contracts, ordering invariants — gone. No one notices until production.
2. **The LLM reports completion without doing the work.** It marks tasks done, generates placeholder content, and moves on. This is not a bug — it is a rational response to vague completion criteria.
3. **The context window collapses.** A 50,000-line codebase cannot fit in any single prompt. The LLM loses coherence halfway through, and the migration becomes an archeological exercise in figuring out what was and wasn't translated.
4. **The LLM fixes the wrong thing.** When a test fails, the AI modifies the translated function — even when that function correctly mirrors the source. The real problem was in the test fixture, the caller, or an implicit model-capability assumption that was never written down. The fix introduces behavioral divergence that didn't exist before.
**lang-migration** is a structured response to all four failure modes — not through better prompting, but through formal methodology.
---
## What This Is
`lang-migration` is an **AI skill** — a portable, agent-agnostic specification that tells any capable LLM *exactly* how to migrate a codebase, in what order, with what evidence, and how to prove it actually happened.
It is not a tool you run. It is a protocol you install into an AI coding agent.
Think of it as a **research methodology** that the AI follows, the way a graduate student follows a lab protocol — except the lab protocol has been designed specifically to detect and prevent the ways graduate students (and LLMs) tend to cut corners.
---
## Core Insight: The Evidence Obligation
The central innovation of this methodology is what we call **Evidence Obligation** — the principle that *no unit of work is complete until the AI produces an artifact that can only have been generated by actually doing the work.*
This is different from asking the AI to "be thorough." Thoroughness is a moral appeal. Evidence Obligation is a structural constraint.
### How It Works in Practice
Consider Phase 3 (IPO Analysis), where the AI must document every function before translating it. Under naive prompting, an LLM will generate plausible-sounding documentation for hundreds of functions in seconds — none of it verified against the actual source.
Under Evidence Obligation, the AI must produce a `READ_EVIDENCE` block before filling any entry:
```
READ_EVIDENCE for agent_loop.py::run_step:
file_read: "agent_loop.py:42-89"
first_statement: "outcome = self._dispatch_tool(tool_call)"
last_statement: "return StepOutcome(data=result, next_prompt=follow_up)"
literal_count: 3
call_count: 7
branch_count: 4
```
These values — the exact text of the first and last executable statement, the precise count of numeric literals and branch points — cannot be fabricated without reading the file. A hallucinated block will produce wrong line numbers, wrong counts, wrong statement text. This becomes detectable during verification.
The AI must also produce a `BEHAVIOR_PROOF` for each function:
```
BEHAVIOR_PROOF for calculate_entropy:
happy_path: "Given data=[0.3,0.3,0.4], base=2 → returns 1.5710 (bits)"
edge_case_1: "Given data=[0.0, 1.0], filters 0.0 (epsilon=1e-10), returns 0.0"
would_fail_if: "sum(data) >> 1.0 — inferred invariant violated, results meaningless"
```
Generic answers ("returns the expected result given valid input") are prohibited. Specific values require genuine understanding.
---
## The Six-Phase Pipeline
```
┌─────────────────────────────────────────────────────────────────┐
│ │
│ P0 Bootstrap Detect language pair, initialize workspace │
│ │ │
│ P1 Asset Scan Every file classified — nothing skipped │
│ │ │
│ P2 Ecosystem Map Every import/type mapped with evidence │
│ │ │
│ P3 IPO Analysis Every function: Inputs, Process, Outputs │
│ │ READ_EVIDENCE + BEHAVIOR_PROOF enforced │
│ │ │
│ P4 Translation Translate from IPO spec, not source code │
│ │ target_lines mandatory per function │
│ │ │
│ P5 Verification Real tests, real data — no mocks, ever │
│ │ │
│ P6 Gap Report Invoke anytime: "what's still missing?" │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### Why Translate from IPO, Not Source?
Phase 4 has an unusual constraint: the AI translates **from the IPO registry**, not from the source code directly. The source may be consulted for clarification, but the IPO entry is the contract.
This separation is deliberate. When an AI reads source code and immediately writes target code, it produces a *stylistic approximation* — capturing surface syntax while silently dropping semantic contracts, magic number purposes, and inferred invariants. By forcing a two-step process (understand → document → translate), the methodology creates a checkpoint where lost information becomes visible before translation, not after.
---
## Persistent State as a First-Class Concern
Every migration lives in five YAML files in `migration_workspace/`:
```
migration_workspace/
├── migration-state.yaml ← Session entry point. Read this first, always.
├── asset-inventory.yaml ← Every source file, its migration strategy, its status
├── ecosystem-map.yaml ← Every library/type/idiom mapped to target equivalent
├── ipo-registry.yaml ← Every function: inputs, process steps, outputs
└── retrospective-checklist.yaml ← Lessons learned from every P4/P5 fix
```
These files are the migration's "memory." Any AI agent — Claude Code, Cursor, GitHub Copilot, OpenCode — can pick up a migration mid-session by reading `migration-state.yaml`. The state is not in the AI's context window; it's on disk.
This makes migrations **resumable, auditable, and transferable.** A migration started in one tool can be continued in another. A migration interrupted by a context collapse can be resumed exactly where it stopped.
### The State Machine
```yaml
# migration-state.yaml (excerpt)
phases:
P0_bootstrap: DONE
P1_asset_scan: DONE
P2_ecosystem_map: IN_PROGRESS
P3_ipo_analysis: TODO
P4_translation: TODO
P5_verification: TODO
current_task:
phase: P2_ecosystem_map
item_id: "numpy.random.default_rng"
status: BLOCKED
block_reason: "RNG algorithm differs (Mersenne Twister vs PCG64); bit-identical output impossible"
human_input_required: |
Statistical equivalence sufficient, or is bit-identical seed behavior required?
```
When `status: BLOCKED`, the AI stops immediately, presents the structured block to the operator, and waits. It does not proceed, does not label-and-continue, does not silently substitute a guess.
---
## The Ecosystem Map: Type-System Contracts Across Languages
Before any translation begins, every library symbol, type, and stdlib function used in the source must be mapped to its target equivalent, classified by equivalence type, and — critically — annotated with evidence that the mapping was actually researched.
```yaml
- id: "numpy.random.default_rng"
source_semantics: "PCG64 PRNG, seedable, reproducible across platforms"
target_equivalent: "math/rand/v2.New(rand.NewPCG(seed, 0))"
equivalence_type: structural
precision_delta: none
gap_notes: "Go 1.22+ rand/v2 uses PCG; pre-1.22 uses different algorithm"
confirmation_evidence:
source_behavior: "NumPy default_rng uses PCG64 with 128-bit state since NumPy 1.17"
target_behavior: "math/rand/v2 PCG source: identical algorithm, same output for same seed"
verified_from: "numpy.org/doc/stable/reference/random/generator.html + pkg.go.dev/math/rand/v2"
```
The `confirmation_evidence` block is mandatory for every `CONFIRMED` mapping. An empty evidence field is treated as `NEEDS_REVIEW` regardless of the `status` field. This prevents the common failure mode where an AI confirms hundreds of mappings without looking any of them up.
---
## Language Pairs
The methodology ships with 14 pre-built language pair modules covering common migration paths. Each module contains:
- Pre-mapped standard library and ecosystem symbols
- Known semantic gaps and their compensation strategies
- Language-specific anti-patterns to avoid during translation
- Testing toolchain mapping
- CI/CD adaptation templates
| Source | Target | Difficulty | Key Challenges |
|--------|--------|-----------|----------------|
| Python | **Rust** | ★★★★ | Ownership, GC→borrow checker, trait system |
| Python | **Go** | ★★★ | Error handling idiom, goroutines, interface duck-typing |
| Python | **C** | ★★★★ | Manual memory, pointer arithmetic, no stdlib collections |
| Python | **C++** | ★★★ | RAII, template metaprogramming, priority_queue direction |
| Python | **Zig** | ★★★★ | Explicit allocators, comptime generics, error unions |
| Python | **TypeScript** | ★★ | `undefined` vs `null`, structural typing, utility types |
| Python | **Bun (JS)** | ★★ | number precision gap, no seedable RNG |
| Rust | **Python** | ★★ | Ownership → comments, Result → exceptions |
| Go | **Python** | ★★ | Multi-return → exceptions, defer → context manager |
| C | **Python** | ★★ | Pointer pairs → single object, string encoding |
| C++ | **Python** | ★★ | RAII → `__del__`/contextmanager, template → TypeVar |
| Zig | **Python** | ★★ | Allocators disappear, comptime → TypeVar |
| Bun | **Python** | ★★ | Number disambiguation, `undefined` mapping |
| TypeScript | **Python** | ★ | Near 1:1 type mapping, utility types → dataclass |
New language pairs can be added using `TEMPLATE.md` — the methodology is language-agnostic by design.
---
## P6: The Gap Report — Continuous Completeness Audit
Most migration tools answer the question "what have we done?" The Gap Report answers the harder question: **"what is genuinely missing, and why?"**
It is the only phase that can be invoked at any time — before P5, during P3, or after the AI has declared the migration complete. In practice, it is most useful precisely when the AI *has* declared completion, because that is when hidden gaps are most dangerous.
Invoke with: *"还差什么" / "gap report" / "show migration status"*
### The Five Dimensions
The report does not compute a single completion percentage. It runs five independent audits, each of which can reveal a different category of failure:
**Dimension 1 — File Coverage**: Cross-references every entry in `asset-inventory.yaml` against the actual files on disk in the target directory. A `translate`-strategy file with `status: DONE` in the registry but absent from disk is a critical gap — the AI updated the YAML but never wrote the code. This dimension catches that.
**Dimension 2 — Function Coverage**: Scans `ipo-registry.yaml` for functions marked `translation_status: DONE` but with an empty `target_lines` field. This is the canonical signature of the batch-fabrication problem: the AI marked functions done without producing or locating the actual implementation. The report calls these "DONE but no evidence" and flags them as requiring re-verification.
**Dimension 3 — Directory Structure Gap**: Walks both the source and target directory trees independently, then identifies source directories that have no correspondent in the target. This catches structural omissions that file-level checks miss — an entire module tree that was skipped, a subdirectory of test fixtures with no target equivalent, an internal package that was documented in P1 but never addressed in P4.
**Dimension 4 — Non-Code Asset Coverage**: Checks whether `direct_use` files (test fixtures, binary data, static assets) were actually copied to the target. Also checks whether every file marked `p3_required: true` in the asset inventory was actually cited in an IPO registry entry. A design document that was supposed to inform IPO analysis but was never referenced is not a minor oversight — it means the functions it described may have been analyzed without their full context.
**Dimension 5 — Skip Classification**: Distinguishes three categories of missing files with fundamentally different implications. *Intentional documented skips* (in `decisions_log`) require no action. *Platform-specific skips* (Streamlit, PyQt5, tkinter, ultralytics) are auto-detected by keyword and require no action. *Accidental gaps* — files with `status: DONE` in the inventory but no target file and no documented reason — are the critical finding. They represent work the AI claimed to have done but demonstrably has not.
### The "TRUE 1:1 COMPLETION" Metric
The gap report does not produce a single optimistic percentage. It surfaces a breakdown that makes incompleteness **specific and actionable**. The following is actual output from the [GenericAgent → go-GenericAgent migration](https://github.com/suifei/lang-migration-samples/blob/main/migration_workspace/gap-report.yaml):
```
═══════════════════════════════════════════════════════════════
MIGRATION COMPLETENESS AUDIT — genericagent → go-GenericAgent
python → go | 150 source files
═══════════════════════════════════════════════════════════════
FILE COVERAGE (52 translate-strategy files)
confirmed at expected path: 24 (46.2%)
⚠ path drift (present, wrong location): 14 ← need path correction
✗ truly missing: 14 ← untranslated
FUNCTION COVERAGE
translation_status DONE: 142 / 142 (100%)
verification checks passed: 898 / 898 (0 failures)
test packages passing: 37 / 37
NON-CODE ASSETS
binary assets absent: 33 (demo images, GIFs, PDFs)
← no compilation impact; needed for desktop UI
SKIP CLASSIFICATION
Class A — intentional documented: 8 (PySide6, Streamlit, no Go equivalent)
Class C — accidental omissions: 16 (CLI entry points, shell scripts)
⚠ Class C items need resolution before production readiness
───────────────────────────────────────────────────────────────
OVERALL: Core logic fully translated; 14 path corrections + 16 omissions pending
───────────────────────────────────────────────────────────────
```
The gap report intentionally refuses to compute a single completion percentage, because a headline number hides the structure of what's missing. 14 path drifts (files exist but at wrong paths) require different action than 14 truly missing files — and both require different action than 33 binary assets that don't affect compilation.
This conservatism is the point. A metric that trusts the AI's self-assessment is not a completion metric; it is a record of what the AI claimed. Only a metric grounded in physical artifacts — files that exist on disk, line ranges that are populated — can answer the question the human actually cares about.
---
## Why Not Just Use a Transpiler?
Existing automated transpilers (like `py2many`, `Transcrypt`, or language-specific tools) operate on syntax — they map AST nodes to AST nodes. They are fast and often correct for simple cases.
They fail systematically when:
- **Semantic contracts cross library boundaries.** `numpy.float64` is not just "a float" — it carries broadcasting semantics, NaN propagation rules, and precision guarantees that no syntactic transform can discover.
- **Magic numbers carry domain knowledge.** The literal `1e-10` in an entropy function is not a random constant — it is an epsilon floor preventing `log(0)`, derived from the author's understanding of numerical stability. No transpiler documents this. Our methodology requires it.
- **Inferred invariants shape correctness.** Code often works because callers follow unwritten rules. A function that divides by `len(data)` without a guard works only because every caller has been passing non-empty lists for years. Transpilers cannot discover this. Our IPO analysis requires it to be documented before translation.
- **Architectural intent is lost.** When a Python class uses a `collections.OrderedDict`, the author chose ordered iteration. A transpiler maps it to `map[K]V` and loses the ordering contract silently. Our ecosystem map requires this gap to be documented and compensated.
---
## Bug Triage Protocol: Classify Before Fix
The most expensive mistake in a migration is **fixing code that was correct**.
When a test fails in the migrated project, the naive response is to modify the translated function. But the translated function may be exactly right — faithfully reproducing the source's behavior. The problem may lie elsewhere: in the test assertion, in the caller, in how the source design assumed a specific model capability that the target environment doesn't have.
**lang-migration** enforces a mandatory 3-step triage before any fix is applied:
```
T1: Map to IPO registry — does the target code match the documented spec?
YES → function is correct; continue to T2
NO → confirmed translation error → Fix + Retrospective
T2: Read the actual source lines — does the source exhibit the same behavior?
YES → source-faithful: annotate, don't fix; update test expectation
NO → IPO analysis recorded wrong lines → fix P3, then re-translate
T3: Investigate the consumer / caller path
CONSUMER_ERROR → fix caller or test only
IMPLICIT_CAPABILITY_GAP → fix integration layer only
ECOSYSTEM_DIFFERENCE → verify compensation strategy
CONFIRMED_TRANSLATION_ERROR → Fix + Retrospective
```
Only one of five verdicts results in modifying the translated function. The other four resolve in the integration layer, the caller, or the test — without touching the function that was, in fact, correctly translated.
### Real-World Validation: The Conductor Case
In a Python→Go migration of **GenericAgent → go-GenericAgent**, the conductor agent failed to read incoming user messages. The first "fix" embedded message content directly into the system prompt — changing the source design.
The correct triage:
- **T1**: Go `conductorPrompt` matched `conductor.py:277-279` exactly. ✅ Function is correct.
- **T2**: Source reads: `unread = sum(1 for m in chat_messages if not m.get("read"))` — count only, no message body. Target behavior identical. **Source-faithful — do not change the function.**
- **T3**: The Python design assumed the consumer (Claude) would infer "call `GET /chat` when `unread > 0`." The Go deployment ran Qwen3.5, which needs an explicit `code_run` example to trigger the same action. **Verdict: `implicit_capability_assumption`** — fix the system prompt, not the translated function.
The first "fix" was reverted. The system prompt was strengthened with an explicit `GET /chat?last=20` example and a trigger rule. The `conductorPrompt` logic — count only — was preserved exactly as the Python source designed it.
This class of bug is now its own root cause category: **`implicit_capability_assumption`** — source design relied on consumer inference ability that the target consumer lacks. Documented in the IPO registry as an `inferred_invariant`; resolved in the integration layer.
### The 12 Root Cause Categories
Every bug found during migration is classified into one of 12 categories. The first nine produce code fixes. The last three resolve **without touching the translated function**:
| Category | Resolution |
|---|---|
| `ecosystem_gap_unapplied` | Code fix + scope scan |
| `semantic_contract_lost` | Code fix + scope scan |
| `invariant_not_transferred` | Code fix + scope scan |
| `magic_number_decontextualized` | Code fix + scope scan |
| `control_flow_collapsed` | Code fix + scope scan |
| `error_class_narrowed` | Code fix + scope scan |
| `side_effect_dropped` | Code fix + scope scan |
| `ipo_source_lines_wrong` | Re-run P3, then re-translate |
| `test_fixture_mismatch` | Fix fixture only |
| **`consumer_error`** | **Fix caller/test only — function is correct** |
| **`source_faithful_behavior`** | **Annotate + fix test — function is correct** |
| **`implicit_capability_assumption`** | **Fix integration layer — function is correct** |
This classification prevents the most common AI migration failure mode: confidently modifying correct code because a test failed.
---
## The No-Mock Principle
Every test in the migrated project uses real implementations against real data.
This is not idealism — it is a correctness requirement. A migration that passes a mock-based test suite has proven nothing about behavioral equivalence. It has proven only that the mock was correctly wired.
When a test cannot run without a real dependency (a database, a hardware device, a third-party API), the methodology does not mock it. It blocks, documents the missing dependency, and waits for the operator to provide the real environment. The test remains in the suite, marked as requiring a real dependency, and passes when that dependency becomes available.
This is uncomfortable. It is also the only way to know whether the migration is correct.
---
## Real-World Case Study: GenericAgent Python → Go
> **Sample repository**: [suifei/lang-migration-samples](https://github.com/suifei/lang-migration-samples)
> Full YAML state files, IPO registry, and the complete Go output are public.
The methodology has been applied in full to a production AI agent framework:
**[GenericAgent](https://github.com/suifei/lang-migration-samples/tree/main/genericagent)** (Python 3.11)
→ **[go-GenericAgent](https://github.com/suifei/lang-migration-samples/tree/main/go-GenericAgent)** (Go 1.25)
GenericAgent is a minimal self-evolving autonomous agent framework with 12 chat platform
integrations (Telegram, Discord, Lark, WeChat Work, …) and 10 memory subsystems.
### Migration Outcome
| Metric | Value | Source |
|--------|-------|--------|
| Source files scanned | 150 | [asset-inventory.yaml](https://github.com/suifei/lang-migration-samples/blob/main/migration_workspace/asset-inventory.yaml) |
| Strategy breakdown | 52 translate · 15 adapt · 57 direct-use · 26 reference-only | |
| Ecosystem symbols mapped | 58 (38 structural · 10 behavioral · 7 partial · 3 none) | [ecosystem-map.yaml](https://github.com/suifei/lang-migration-samples/blob/main/migration_workspace/ecosystem-map.yaml) |
| Functions in IPO registry | 142 / 142 documented and translated | [ipo-registry.yaml](https://github.com/suifei/lang-migration-samples/blob/main/migration_workspace/ipo-registry.yaml) |
| Go test packages passing | 37 / 37 | [migration-state.yaml](https://github.com/suifei/lang-migration-samples/blob/main/migration_workspace/migration-state.yaml) |
| Test coverage | 60.1% | |
| Verification checks run | 898 | |
| Verification failures | **0** | |
| Build / vet | ✅ Clean | |
| Phases complete | P0 → P5 all DONE | |
| Active AI sessions | ~7 | |
All phases were completed as verified by [migration-state.yaml](https://github.com/suifei/lang-migration-samples/blob/main/migration_workspace/migration-state.yaml):
```yaml
phases:
P0_bootstrap: DONE
P1_asset_scan: DONE
P2_ecosystem_map: DONE
P3_ipo_analysis: DONE
P4_translation: DONE
P5_verification: DONE
```
### What the Ecosystem Map Captured
A sample from [ecosystem-map.yaml](https://github.com/suifei/lang-migration-samples/blob/main/migration_workspace/ecosystem-map.yaml) — including the non-trivial gaps:
| Python | Go | Equivalence | Decision |
|---|---|---|---|
| `asyncio` | goroutines + channels | structural | `async def` → goroutine; `await` → channel receive |
| `collections.defaultdict` | `map[K]V` | behavioral | No auto-init; explicit `if _, ok := m[k]` required |
| `importlib` (dynamic import) | compile-time registry | partial | No Go equivalent; plugin registration via `init()` |
| `streamlit` | HTTP + SSE | none | Reactive UI has no Go equivalent; replaced with conductor web UI |
| `ultralytics` (YOLO) | ONNX Runtime + CGO | none | Export model to ONNX; bind via `onnxruntime_go`; CGO dependency |
### What the IPO Registry Documented
Magic numbers from [ipo-registry.yaml](https://github.com/suifei/lang-migration-samples/blob/main/migration_workspace/ipo-registry.yaml) — each with source line, type, and inferred purpose:
```yaml
# agent_loop.py — turn management thresholds
magic_numbers:
- value: 40 type: iteration_limit purpose: "default max turns, interactive sessions"
- value: 70 type: iteration_limit purpose: "max turns, task one-shot mode"
- value: 100 type: iteration_limit purpose: "plan-mode hard cap"
- value: 0.6 type: threshold purpose: "target context fill ratio after trimming"
- value: 10 type: iteration_limit purpose: "reset tool schema every N turns"
# Inferred invariants (undocumented contracts exposed by P3):
inferred_invariants:
- "history managed inside LLM client backend — not in messages list [inferred from: no append in body]"
- "bad_json is a reserved built-in tool, never in JSON schema [inferred from: special-cased in dispatcher]"
- "unknown tool resets last_tools to force re-sending schema next turn [inferred from: line 312]"
```
### Bugs Discovered During Verification
These were found by real tests on real data — impossible to detect through code review alone:
1. **Goroutine deadlock** — `streamCh` was never closed after streaming LLM response completed.
`for-select` in the consumer goroutine blocked forever.
Fix: `defer close(streamCh)` added before `defer close(loopDone)` in `agent.Run()`.
Root cause: `side_effect_dropped` — Python's generator exhaustion is implicit; Go channels require explicit close.
2. **`init()` panic** — Package-level `init()` called `regexp.MustCompile` with a pattern using
lookahead syntax (`(?=…)`), which Go's `regexp` package (RE2 semantics) does not support.
Root cause: `ecosystem_gap_unapplied` — the `re` → `regexp` ecosystem map entry notes RE2 limitations, but the specific lookahead was not caught in P3.
### Gap Report Findings (Honest View)
The [gap-report.yaml](https://github.com/suifei/lang-migration-samples/blob/main/migration_workspace/gap-report.yaml) P6 audit found:
- **14 path drifts** — files present in Go output but at different paths than planned
- **14 truly missing** — files not yet translated (mostly CLI entry points and shell scripts)
- **33 binary assets absent** — demo images, GIFs, PDFs (no compilation impact; needed for desktop UI)
- **8 intentional skips** — GUI frontends (`qtapp.py` PySide6, `stapp.py` Streamlit) with no Go equivalent
The methodology's gap report makes this residual incompleteness **visible and enumerated** — not hidden under a claimed "100% done."
### Honest Assessment
The migration required approximately **7 active AI sessions** of sustained human attention: approving gap decisions, resolving path-drift discrepancies, and validating bug fixes. It is not fully autonomous — human judgment is required at ecosystem decision points and when the methodology issues a `BLOCKED` status.
The methodology's value is not speed — it is **structural fidelity and verified completeness**. Every function translated has an IPO entry. Every magic number has a source line. Every ecosystem gap has a documented compensation. Every test failure went through triage before any fix.
---
## Runtime Environments
The methodology is agent-agnostic. It has been designed to run in:
| Environment | Mode | Notes |
|-------------|------|-------|
| **Claude Code** | `full_mode` | Bash + file system; runs scan scripts directly |
| **OpenCode** | `full_mode` | Same as Claude Code |
| **Cursor** | `editor_mode` | File system only; scripts run manually |
| **GitHub Copilot** | `editor_mode` | Same as Cursor |
In `full_mode`, the AI runs `scan_assets.py` and `gap_report.py` directly.
In `editor_mode`, the AI generates the file lists manually and instructs the operator to run scripts.
---
## Installation
```bash
# Clone into your project's .agents/skills directory
mkdir -p .agents/skills
git clone https://github.com/suifei/lang-migration-skill.git .agents/skills/lang-migration-skill
```
Then tell your AI agent:
```
/lang-migration source: my-python-project/ target: my-go-project/ pair: python-go
```
The agent reads `SKILL.md`, initializes `migration_workspace/`, and begins Phase 1.
---
## Repository Structure
```
lang-migration/
├── SKILL.md ← Agent entry point and orchestration protocol
├── CHANGELOG.md ← Version history
├── templates/
│ ├── migration-state.yaml ← Session state machine (includes phase_gates block)
│ ├── asset-inventory.yaml ← File classification registry
│ ├── ecosystem-map.yaml ← Library/type mapping registry
│ ├── ipo-registry.yaml ← Function IPO specification registry
│ └── retrospective-checklist.yaml ← Fix root-cause log (5th workspace file)
├── references/
│ ├── schemas.md ← Full field definitions for all five YAML files
│ ├── phase-0-bootstrap.md ← P0 workspace initialization
│ ├── phase-1-asset-scan.md
│ ├── phase-2-ecosystem-map.md
│ ├── phase-3-ipo-analysis.md ← Evidence Obligation protocol lives here
│ ├── phase-4-translation.md
│ ├── phase-5-verification.md
│ ├── phase-6-gap-report.md
│ ├── phase-gate-review.md ← PGR autonomous closure protocol (v1.2)
│ ├── tdd-retrospective.md ← Fix retrospective protocol (v1.1)
│ ├── ast-bridge.md ← tree-sitter AST integration protocol (v1.4)
│ └── lang-pairs/
│ ├── TEMPLATE.md ← Extend to new language pairs
│ ├── python-rust.md
│ ├── python-go.md
│ ├── python-c.md
│ ├── python-cpp.md
│ ├── python-zig.md
│ ├── python-bun.md
│ ├── python-typescript.md
│ ├── rust-python.md
│ ├── go-python.md
│ ├── c-python.md
│ ├── cpp-python.md
│ ├── zig-python.md
│ ├── bun-python.md
│ └── typescript-python.md
└── scripts/
├── scan_assets.py ← Phase 1 file scanner (+ --dirload-report, v1.4)
├── trace_runtime.py ← Phase 1 dynamic file-access tracer (v1.4)
├── ast_bridge.py ← tree-sitter AST toolchain: doctor/index/verify/skeleton/todos (v1.4)
└── gap_report.py ← Phase 6 completeness auditor
```
---
## Contributing
The most valuable contributions are **new language pair modules** and **real-world migration reports.**
**New language pair**: Copy `references/lang-pairs/TEMPLATE.md`, fill in all sections, submit a PR. Focus on gaps that are non-obvious — things a senior developer in the source language would assume but a developer in the target language would not know to look for.
**Migration reports**: If you use this methodology on a real project, we want to know: what did the gap report catch? Which ecosystem mappings were wrong in the pre-built modules? What blocking decisions came up?
The methodology improves through accumulated case knowledge. Every real migration that reports back makes the next one more reliable.
---
## Open Questions (Research Directions)
This methodology raises questions we haven't fully answered:
**On semantic equivalence**: The `BEHAVIOR_PROOF` requirement asks the AI to specify concrete input-output pairs for every function. This is a weaker form of a *behavioral contract*. Could these contracts be automatically checked using property-based testing frameworks (Hypothesis, proptest) to formally verify equivalence across language boundaries?
**On the evidence trail**: The `READ_EVIDENCE` and `BEHAVIOR_PROOF` blocks are currently ephemeral — they appear in the AI's response and are not stored. Could they be captured and used to build a persistent *proof-of-understanding* archive that survives across sessions?
**On non-determinism**: When source functions use RNG with reproducible seeds, the migration must document whether bit-identical output is required or statistical equivalence suffices. This is currently a human decision. Could it be automated by analyzing downstream consumers of the non-deterministic output?
**On multi-agent parallelism**: The current methodology is sequential — one AI agent, one function at a time. For large codebases (100k+ LOC), could multiple agents work in parallel on independent subtrees of the dependency graph, with a coordinator merging their IPO registries?
---
## License
MIT. Translate freely.
---
<div align="center">
*The gap between "it looks like it works" and "it works" is where most migrations live.*
*This methodology is an attempt to make that gap visible, measurable, and closeable.*
**Star this if you've ever received a "migration complete" report that wasn't.**
</div>