| name | mutils:faithful-migration |
| description | This skill should be used when the user asks to 'port a feature from repo A to repo B', 'migrate a repository feature faithfully', 'rewrite faithfully', 'fork-and-customize without dropping flows', 'ãªããžããªéç§»æ€', 'å¥ãªããžããªã®æ©èœãç§»æ€', 'å¿ å®ã«ç§»æ€', 'A ã®æ©èœã B ã«ãã®ãŸãŸæã£ãŠããŠ', or wants to port / rewrite / re-implement an existing repository's feature set into another codebase without silently shrinking scope. It forces 1:1 enumeration, source/target architecture diagrams reviewed by the consecutive-review-loop skill, a shrink-vocabulary ban, one-outcome-per-PR, and a pre-merge local verification with screenshots. |
| allowed-tools | Read, Write, Edit, Glob, Grep, Bash(ls *), Bash(find *), Bash(rg *), Bash(git status), Bash(git diff *), Bash(git log *), Bash(gh issue *), Bash(gh pr *), Agent, TodoWrite, AskUserQuestion |
faithful-migration
ãªããžã㪠A ã®æ©èœããªããžã㪠B ã«ãæ¬åœã«ãã®ãŸãŸãç§»æ€ã»ãªã©ã€ãã»ãã©ãŒã¯åŸã«ã¹ã¿ãã€ãºããããã®ãªãŒã±ã¹ãã¬ãŒã¿ã¹ãã«ãAI ãçž®å°åçç£ïŒå®äºå€å®ã®ããæ¿ã / subset å¯Ÿå¿ / æ€èšŒå
éãïŒã§éããªãããã«ãåæã»æ§æå³ã»èªåœå¶çŽã»PR æ§é ã»æ€èšŒã¹ããããæ§é çã«åŒ·å¶ããã
$ARGUMENTS
ãã€èµ·åããã
- IF: ãŠãŒã¶ãŒããªããžã㪠A ã®æ©èœããªããžã㪠B ã«ãç§»æ€ / ãªã©ã€ã / æã£ãŠãã / ãã©ãŒã¯åŸã«ã¹ã¿ãã€ãºãããäœæ¥ãäŸé Œãã; THEN MUST: æ¬ã¹ãã«ãèµ·åãã
- IF: æ¢åå®è£
ã®ãå¿ å®ãªåçŸãã1:1 åå®è£
ãããŠãŒã¶ãŒæäœã®ç¶²çŸ
çãªããªãã£ããèŠä»¶ã§ãã; THEN MUST: æ¬ã¹ãã«ãèµ·åãã
- IF: ã¹ãã«æ¬äœã® Phase ãå®äºããåã«ãŠãŒã¶ãŒãå®äºå®£èšãæ±ããŠãã; THEN MUST: æ®ãã§ãŒãºãšæ®äœæ¥ãæç€ºããŠãã倿ãä»°ã
çž®å°åçç£ã®çŠæ¢
ãã®ã¹ãã«ãé²ã倱æã¢ãŒãïŒèŠ³æž¬å®çžŸããïŒ:
- å®äºå€å®ãæ©èœã«ããŽãªç¶²çŸ
ïŒéªšæ Œ / domain / api / webïŒã«ããæ¿ãããåå¥ UI ãããŒã® 1:1 æ§ã¯å®äºæ¡ä»¶ããå€ãã
- åå¥ UI ãããŒã®çªåããmock é€å»ããå®ãããŒæ¥ç¶ãã®æèšã§æžãŸã
- èšèšå€æŽãèŠããéšåã
(ç·šéã¢ãŒã)ã®ã¿å¯Ÿå¿ ã®ãã㪠subset 宣èšã§éãã
- çªåã¹ã³ãŒããèªåã§æ£æçã«çžããå¿ å®ãªç§»æ€ã確èªããããšå®£èšãã
- åäœæ€èšŒããmerge åŸã«ããŒã«ã«ã§ç¢ºèªããšå
éããã
ãããå
šãŠã Phase 5 ã®çŠæ¢èªåœ + Phase 7 ã® merge åæ€èšŒ + Phase 2 ã®å
šåæã§æ§é çã«å¡ãã
å®äºæ¡ä»¶ïŒçæåã«åºå®ããïŒ
- MUST: çæåã«ãŠãŒã¶ãŒãæå
ã§éã å
·äœç㪠1 åšãã㌠ã outcome ãšããŠå®£èšãããäŸ: ããŠãŒã¶ãŒãæ°èŠäœæ wizard ãéãã3 ã¹ãããå
¥åããŠä¿åããäžèЧã§è¡ã確èªã§ããã
- MUST NOT: ãå Story å
šå®è£
ããmock é€å»å®äºããäž»èŠç»é¢çªåæžã¿ãã®ãããªäžé KPI ã§å®äºå®£èšãã
- MUST: outcome 㯠1 PR ã«ã€ã 1 〠ã«çµãïŒPhase 6 ãåç
§ïŒ
- MUST: outcome å®£èšæã
.agents/workspaces/[workspace-id]/000000-outcome.md ã«ä¿åãã
äŸåã¹ãã«
æ¬ã¹ãã«ã¯ä»¥äžã®ã¹ãã«ã åŒã³åºãåŽ ãšããŠåäœããã
workspace-id â äœæ¥ãã£ã¬ã¯ããªèŠçŽã«åºã¥ã workspace çæ
diagram-render:draw-diagram â ç§»æ€å
/ ç§»æ€å
ã·ã¹ãã æ§æå³ã® Mermaid äœæ
diagram-render:render â æ§æå³ Markdown ã HTML ãžå€æ
mutils:consecutive-review-loop â æ§æå³ã®ç²ŸåºŠã N åé£ç¶ã¬ãã¥ãŒã§æ
ä¿ãã
ææç©ãã£ã¬ã¯ããª
workspace-id ã¹ãã«ã§çæããã .agents/workspaces/[workspace-id]/ é
äžã«ä»¥äžãçµ±äžä¿åããã
000000-outcome.md â çæåã«å®£èšãã 1 åšãã㌠outcome
000001-enumeration.md â ç§»æ€å
ã®å
šç»é¢ + å
š API endpoint åæè¡šãç§»æ€å
å¯Ÿå¿ path ãš 1:1 diffïŒ3 å衚ïŒ
000002-source-diagram.md** â ç§»æ€å
ã® **å®ç§ãª ã·ã¹ãã æ§æå³ïŒMermaidïŒ
000002-source-diagram.html â å HTMLïŒdiagram-render:render ã§çæïŒ
000003-target-diagram.md â 000002 ãç§»æ€å
ãªããžããªã®èŠçŽã«åºã¥ããŠåæ§æããçïŒMermaidïŒ
000003-target-diagram.html â å HTML
000004-pr-bodies/ â outcome åäœã® PR æ¬æãã©ããïŒ1 PR = 1 outcomeïŒ
000005-screenshots/[flow-name]/ â merge åå¿
é ã® 1 åšãããŒã¹ã¯ãªãŒã³ã·ã§ãã
Phase 1: Workspace Bootstrap
- MUST:
workspace-id ã¹ãã«ãåŒã³åºããfeature-name ã faithful-migration-[short-feature-tag] 圢åŒã§çæãã
- MUST: çæããã workspace ãã£ã¬ã¯ããªé
äžã«äžèšææç©ãä¿åãã
- MUST NOT: ad-hoc ãªäœæ¥ãã£ã¬ã¯ããªïŒ
/tmp / ãªããžããªçŽäž / å¥ PR ã® workspaceïŒã«ææç©ã眮ã
ws_id = invoke("workspace-id", feature_name = "faithful-migration-" + short_tag)
ensure_dir(".agents/workspaces/" + ws_id)
Phase 2: å
šåæïŒEnumerationïŒ
ç§»æ€å
ã® å
šç»é¢ + å
š API endpoint ãåæããç§»æ€å
ã®å¯Ÿå¿ path ãš 1:1 ã§å·®åãåºãã
- MUST:
000001-enumeration.md ã«äžèš 3 åè¡šãæžã
- MUST: ãã¹ãŠã®è¡ã«
parity status ãåãããTBD è¡ã 1 è¡ãæ®ããŠã¯ãªããªã
- MUST NOT: ãäž»èŠç»é¢ã®ã¿çªåãããµã³ããªã³ã°ããŠçªåãã®ãããªæ£æççž®å°ã蚱容ãã
- IF: ç§»æ€å
ã«å¯Ÿå¿ path ããªã; THEN MUST:
parity status: missing ãšæžããè£å®èšç»ã Phase 6 ã® PR æ¬æã«çŽä»ãã
- IF: æåå·®ããã; THEN MUST:
parity status: behavior-diff ãšæžããå
·äœçãªå·®åã 1 è¡ã§æ·»ãã
åæè¡šãã³ãã¬
# 000001 Enumeration
source repo: <org/repo@sha>
target repo: <org/repo@sha>
enumerated by: <session id / agent>
enumerated at: <yyyy-mm-dd HH:MM>
| source path | target path | parity status |
| ----------------------- | ----------------------- | -------------------------------------------------------- |
| `/users/new` (UI) | `/admin/users/new` | 1:1 |
| `POST /api/users` (API) | `POST /admin/api/users` | behavior-diff: ç»åã¢ããããŒããå¥ API ã«åå²ãããŠãã |
| `/users/:id/edit` (UI) | (none) | missing |
åæã«ãŒã«ïŒç䌌ã³ãŒãïŒ
rows = []
for path in walk_source_routes(source_repo):
target = lookup_target_path(path)
rows.append({source: path, target: target, status: classify(path, target)})
for endpoint in walk_source_api(source_repo):
target = lookup_target_endpoint(endpoint)
rows.append({source: endpoint, target: target, status: classify(endpoint, target)})
assert every row has status in {"1:1", "missing", "behavior-diff"}
assert no row has status == "TBD"
write rows to 000001-enumeration.md
Phase 3: ç§»æ€å
ã®å®ç§ãªã·ã¹ãã æ§æå³
ç§»æ€å
ãªããžããªã®æ§é ã Mermaid ã§å®å
šã«è¡šçŸããconsecutive-review-loop ã§ç²ŸåºŠãæ
ä¿ããã
- MUST:
diagram-render:draw-diagram ã§ 000002-source-diagram.md ãäœæãã
- MUST:
diagram-render:render ã§ 000002-source-diagram.html ãçæãã
- MUST:
mutils:consecutive-review-loop ã N = 3 ã§åŒã³åºããç¬ç«ãª subagent 3 åé£ç¶ PASS ãŸã§å埩ãã
- MUST: ã¬ãã¥ãŒ subagent ã¯
sonnet æå®ãflat promptïŒéå»ãã£ãŒãããã¯éäŒéïŒã§èµ·åãã
- MUST: 倱ææã® streak ãªã»ãã㯠consecutive-review-loop ã®ä»æ§ïŒFULL RESET = streak ã 0 ã«æ»ãïŒã«åŸã
- MUST: ä¿®æ£ã¯æ¬ãšãŒãžã§ã³ãïŒã¡ã€ã³ãšãŒãžã§ã³ãïŒã
Edit / Write ã§çŽæ¥è¡ããä¿®æ£ããµããšãŒãžã§ã³ãã«å§è²ããŠã¯ãªããªã
- MUST: ä¿®æ£ã®ãã³ã«
000002-source-diagram.html ãåã¬ã³ããªã³ã°ãã
ã¬ãã¥ãŒ pass æ¡ä»¶
ã¬ãã¥ãŒ subagent ã¯ä»¥äžãå€å®ãã:
Pass iff every flow / API listed in 000001-enumeration.md is represented in 000002-source-diagram.md as a node, and every interaction between them is represented as an edge with the correct direction.
ç䌌ã³ãŒã
write 000002-source-diagram.md via draw-diagram
render 000002-source-diagram.html
flatPrompt := buildFlatPrompt(
target = "000002-source-diagram.md",
focus = "å
šç»é¢/å
š API ã enumeration ã®éãã«ããŒãåãããé¢ä¿ããšããžã§è¡šçŸãããŠããã"
)
streak = 0
attempts = 0
while streak < 3:
attempts += 1
result = spawnSubagent(prompt = flatPrompt, model = "sonnet")
if result == PASS:
streak += 1
else:
streak = 0 # full reset
for finding in result.findings:
main_agent_edit(000002-source-diagram.md, finding)
re_render(000002-source-diagram.html)
Phase 4: ç§»æ€å
èŠçŽã§ã®åæ§æå³
000002 ãããŒã¹ã«ãç§»æ€å
ãªããžããªã®åœåã»ã¬ã€ã€ã»FSD ãªã©ã®èŠçŽã«åŸã£ãŠåæ§æããçãä¿åããã
- MUST: ç§»æ€å
ãªããžããªã®
AGENTS.md / README.md / ã¬ã€ã¢ãŠãèŠçŽ / åœåèŠçŽ / ã¬ã€ã€èŠçŽãèªã
- MUST:
000002-source-diagram.md ãã³ããŒã㊠000003-target-diagram.md ãäœã
- MUST: ããŒãå / ãµãã°ã©ã / ã¬ã€ã€æ§é ãç§»æ€å
èŠçŽã«æãã
- MUST NOT: ããŒãã silent ã«çç¥ ãããçç¥ããå Žåã¯
000001-enumeration.md ã® parity status: missing ãšäžèŽããŠããããš
- MUST:
000003-target-diagram.html ãåã¬ã³ããªã³ã°ãã
- MUST:
consecutive-review-loop ã N = 3 ã§å床åŒã³åºãïŒå¥ã® flat promptïŒ
ã¬ãã¥ãŒ pass æ¡ä»¶ïŒtarget çšïŒ
Pass iff (a) every node from 000002-source-diagram.md is represented in 000003-target-diagram.md (no silent omission; explicit missing annotations are allowed and must match 000001-enumeration.md), AND (b) layout / naming / layer follows the target repo conventions documented in its AGENTS.md / layout rules.
ç䌌ã³ãŒã
target_conventions := read_target_conventions(target_repo)
copy 000002-source-diagram.md -> 000003-target-diagram.md
rename_nodes_per(target_conventions)
re_render 000003-target-diagram.html
flatPrompt := buildFlatPrompt(
target = "000003-target-diagram.md",
focus = "(a) 000002 ã®ããŒãç¶²çŸ
+ (b) ç§»æ€å
èŠçŽãžã®é©å"
)
streak = 0
while streak < 3:
result = spawnSubagent(prompt = flatPrompt, model = "sonnet")
if result == PASS:
streak += 1
else:
streak = 0
for finding in result.findings:
main_agent_edit(000003-target-diagram.md, finding)
re_render(000003-target-diagram.html)
Phase 5: çž®å°èªåœã®çŠæ¢
çž®å°åçç£ãç³å¡ããèªåœã®äœ¿çšãæ€åºããŠãããã¯ããã
çŠæ¢èªãªã¹ã
- éšå察å¿ã®ç³å¡ â
(ç·šéã¢ãŒã)ã®ã¿å¯Ÿå¿ãsubsetãéšå察å¿ãas-is
- å®äºã«èŠããéªšæ Œ â
éªšæ Œãwalking skeletonãmock é€å»ãæ©èœããªãã£
- æ€èšŒå
éã â
åŸç¶ PR ã§å¯Ÿå¿ã次㮠PR ã§ãã¬ãã¥ãŒåŸã«ããŒã«ã«ã§ç¢ºèªãmerge åŸã«ããŒã«ã«ç¢ºèª
æ€åºæã®æåïŒç䌌ã³ãŒãïŒ
forbidden = ["(ç·šéã¢ãŒã)ã®ã¿å¯Ÿå¿", "subset", "éšå察å¿", "as-is",
"éªšæ Œ", "walking skeleton", "mock é€å»", "æ©èœããªãã£",
"åŸç¶ PR ã§å¯Ÿå¿", "次㮠PR ã§", "ã¬ãã¥ãŒåŸã«ããŒã«ã«ã§ç¢ºèª",
"merge åŸã«ããŒã«ã«ç¢ºèª"]
function lintTextForShrinkVocab(text, contextHeading):
for term in forbidden:
if term in text:
if contextHeading == "æªå¯Ÿå¿ã®æ®äœæ¥":
continue # æç€ºæ®äœæ¥ãšããŠå®£èšãããŠãããªã蚱容
else:
BLOCK and require: 該åœè¡ããæªå¯Ÿå¿ã®æ®äœæ¥ãããã£ã³ã°é
äžã«ç§»ããåé€ãããéžã°ãã
on commit_message_drafted(msg):
lintTextForShrinkVocab(msg, "commit body")
on pr_body_drafted(body):
lintTextForShrinkVocab(body, "pr body")
- MUST: ã³ãããã¡ãã»ãŒãžãã©ãã / PR æ¬æãã©ããã®ãã¹ãŠã«å¯Ÿãäžèš lint ãå®è¡ãã
- MUST NOT:
æªå¯Ÿå¿ã®æ®äœæ¥ èŠåºã以å€ã§çŠæ¢èªã䜿ã
- IF: çŠæ¢èªã
æªå¯Ÿå¿ã®æ®äœæ¥ èŠåºãé
äžã§äœ¿ãããŠãã; THEN MAY: ééãããïŒæç€ºæ®äœæ¥ãšããŠæå¹ïŒ
Phase 6: 1 PR = 1 outcome
- MUST: 1 PR 㯠1 ã€ã®å
·äœç㪠1 åšãã㌠outcome ã ãã宣èšãã
- MUST: PR æ¬æã®
## Outcome æ¬ã«ããŠãŒã¶ãŒãæå
ã§ [X] ã 1 åšéããïŒscreenshot æ·»ä»æžã¿ïŒããæžã
- MUST NOT: EPIC å
šäœã 1 PR ã«ãŸãšãã
- MUST NOT: 1 PR ã§è€æ° outcome ã宣èšããïŒè€æ°ãããªã PR ãåå²ããïŒ
- IF: ã¹ã³ãŒãå
ã«è€æ° outcome ãå¿
èŠ; THEN MUST: outcome ããšã« PR ãåããIssue åŽã§ sub-issue / blocked-by ãåŒãïŒ
gh-projects ã® epic-story-task ãã¿ãŒã³åç
§ïŒ
PR æ¬æãã³ãã¬
000004-pr-bodies/[outcome-slug].md ã«ä¿åããPR äœææã«ã³ããããã
## Outcome
ãŠãŒã¶ãŒãæå
ã§ <å
·äœç㪠1 åšãããŒ> ãã¹ã¯ãªãŒã³ã·ã§ããä»ãã§éããã
## çªåã¹ã³ãŒã
`000001-enumeration.md` ã®è©²åœè¡ïŒsource path / target path / parity statusïŒããã®ãŸãŸåŒçšããã
| source path | target path | parity status |
| ----------- | ----------- | ------------- |
| ... | ... | ... |
## æ€èšŒã¹ã¯ãªãŒã³ã·ã§ãã
- `000005-screenshots/<flow-name>/01-start.png`
- `000005-screenshots/<flow-name>/02-input.png`
- `000005-screenshots/<flow-name>/03-confirm.png`
- `000005-screenshots/<flow-name>/04-saved.png`
## æªå¯Ÿå¿ã®æ®äœæ¥
ïŒsubset 察å¿ãèšèšå·®ç°ãããå Žåã®ã¿åæããã空æ¬ã§ãããïŒ
- ...
## Relates
Relates to #<issue>
Phase 7: merge åããŒã«ã«æ€èšŒ
- MUST: PR ã open ãã åã« ããŒã«ã«ã§ç§»æ€å
ã¢ããªãèµ·åããPhase 6 ã§å®£èšãã 1 åšãããŒãå®éã«éã
- MUST: åã¹ãããã®ã¹ã¯ãªãŒã³ã·ã§ããã
000005-screenshots/[flow-name]/ ã«ä¿åãã
- MUST: ã¹ã¯ãªãŒã³ã·ã§ãããžã®çžå¯Ÿãã¹ã PR æ¬æã®
## æ€èšŒã¹ã¯ãªãŒã³ã·ã§ãã æ¬ã«åæãã
- MUST NOT: ãmerge åŸã«ããŒã«ã«ç¢ºèªããæ¥åãããŒæå確èªã¯æ¬ PR ã§ã¯æªå®æœãã®ãããªå
éã宣èšã PR æ¬æã«å«ãã
- IF: ããŒã«ã«ã§è©°ãç®æãèŠã€ãã£ã; THEN MUST: PR ã open ããã«ä¿®æ£ããŠããåæ€èšŒãã
before_open_pr(pr):
assert local_app_running(target_repo)
capture_screenshots(flow = pr.outcome, dir = "000005-screenshots/" + slug(pr.outcome))
assert all_steps_passed_locally(flow)
if not all_steps_passed_locally:
BLOCK pr_open and fix
inject_screenshot_paths_into(pr.body)
lintTextForShrinkVocab(pr.body, "pr body")
open_pr(pr)
ã«ãŒã«ãµããª
- IF: çæåã« outcome ã宣èšãããŠããªã; THEN MUST: Phase 0 ã«æ»ã outcome ã
000000-outcome.md ã«ä¿åããŠããçæãã
- IF:
000001-enumeration.md ã« TBD è¡ã 1 è¡ã§ãæ®ã£ãŠãã; THEN MUST: Phase 2 ãå®äºãããŠããæ¬¡ã«é²ã
- IF:
000002 / 000003 ã®æ§æå³ã«å¯Ÿãã consecutive-review-loop ã 3 é£ç¶ PASS ã«éããŠããªã; THEN MUST: PR ã open ããªã
- IF: PR æ¬æ / ã³ãããã¡ãã»ãŒãžã«çŠæ¢èªã
æªå¯Ÿå¿ã®æ®äœæ¥ 以å€ã§åºçŸããŠãã; THEN MUST: ãããã¯ããŠæžãæãã
- IF:
000005-screenshots/[flow-name]/ ã空 OR PR æ¬æã«è²ŒãããŠããªã; THEN MUST: PR ã open ããªã
- MUST NOT: 1 PR ã«è€æ° outcome ãèŒãã
- MUST NOT: æ€èšŒã merge åŸã«åã
- MUST NOT: æ§æå³ã¬ãã¥ãŒãã¡ã€ã³ãšãŒãžã§ã³ãèªèº«ãè¡ãïŒconsecutive-review-loop ã®ç¬ç«æ§ã厩ããïŒ
- MUST NOT: ããŒãã silent ã«çç¥ããïŒ
missing 泚èšãå¿
èŠïŒ
ã¢ã³ããã¿ãŒã³
- ãéªšæ Œãå
šã«ããŽãªç¶²çŸ
ããã®ã§å®äºã â å®äºã¯ outcomeïŒ1 åšãããŒïŒã§å®çŸ©ãããã«ããŽãªç¶²çŸ
ã¯äžé KPI ã«éããªã
- ãäž»èŠç»é¢ 3 æãçªåããã®ã§å¿ å®ãªç§»æ€ã確èªã â çªåã¹ã³ãŒããæ£æçã«çžã£ãŠãããPhase 2 ã§å
šåæãå
šè¡ã« status ãä»ãã
- ã(ç·šéã¢ãŒã) ã®ã¿å¯Ÿå¿ããšæžããŠå®äºãšãã â subset 宣èšã§éããŠãããPhase 5 ã§çŠæ¢èªãšããŠæ€åºããã
- ãæ¥åãããŒæå確èªã¯æ¬ PR ã§ã¯æªå®æœãmerge åŸã«ç¢ºèªã â æ€èšŒå
éã㯠Phase 5 / Phase 7 ã§ãããã¯ããã
- EPIC ã 1 PR ã«ãŸãšãã â è©°ã¿ UI ã diff éã«åããããPhase 6 ã§ 1 PR = 1 outcome
- æ§æå³ã¬ãã¥ãŒãã¡ã€ã³ãšãŒãžã§ã³ãèªèº«ãçºã㊠OK ãšå€æ â ç¬ç«æ§ã厩ãããconsecutive-review-loop ã§ flat prompt subagent ã䜿ã
- target diagram ã§ããŒãããç§»æ€å
ã«ã¯ç¡ãããããš silent ã«åé€ â
parity status: missing ã 000001 ãšäžèŽãããåœ¢ã§æ®ããsilent çç¥ã¯çŠæ¢