Epic Migrator
Thin wrapper around ${CLAUDE_PLUGIN_ROOT}/scripts/migrate-to-epic.sh. The script implements transactional migration; this skill exists to be callable-via-Skill and to spell out the invocation contract + session-context handoff.
Contract
Input (CLI args to the script):
<project_path>— absolute project path<task_name>— the task folder to operate on. The script picks the operation by where the name resolves and its currentkind:- flat task at project level → flat → epic promotion
- subtask nested inside an epic → subtask → sub_epic promotion
- already an epic/sub_epic + child names passed → epic expansion (adds children to the existing epic, preserving existing children + artifacts)
- already an epic/sub_epic + no child names → no-op (exit 1 with a "pass children to expand" hint)
[--dry-run]— prints plan, changes nothing[<child1> <child2> ...]— zero or more child names
Output:
- Stdout: step-by-step log ending with a structure summary
- Stderr on live-run success: three
KEY=VALUElines —SESSION_CONTEXT_CASE,EPIC_FOR_CTX,NEW_TASK_PATH
Exit codes:
0success (live or dry-run)1abort (preflight/validation/mid-migration error — nothing committed) OR no-op (epic with no children to add)2usage error (missing required arg)
Invocation
"${CLAUDE_PLUGIN_ROOT}/scripts/migrate-to-epic.sh" "$PROJECT_PATH" "$TASK_NAME" --dry-run child_a child_b
"${CLAUDE_PLUGIN_ROOT}/scripts/migrate-to-epic.sh" "$PROJECT_PATH" "$TASK_NAME" child_a child_b
"${CLAUDE_PLUGIN_ROOT}/scripts/migrate-to-epic.sh" "$PROJECT_PATH" "$TASK_NAME"
Session-context dispatch (apply after live run)
Parse the stderr KEY=VALUE lines (SESSION_CONTEXT_CASE, EPIC_FOR_CTX, NEW_TASK_PATH), then write session context by running scripts/session-context-write.sh directly (Bash, zero model context — v4.16.0):
"${CLAUDE_PLUGIN_ROOT}/scripts/session-context-write.sh" \
"$PROJECT_NAME" "$PROJECT_PATH" "$TASK" "$TASK_PATH" "$EPIC_ARG"
Choose the $EPIC_ARG (5th positional) and $TASK_PATH by the case the script emitted:
| Case | When | $EPIC_ARG (5th arg) |
$TASK_PATH (4th arg) |
|---|---|---|---|
| A | Migrated task != active task | literal {CURRENT_EPIC_OR_NULL} (preserve) |
unchanged |
| B | Migrated task == active task | null (clear) |
unchanged |
| C | A move_existing child == active task |
$EPIC_FOR_CTX (= $TASK_NAME, set epic) |
$NEW_TASK_PATH |
See also
${CLAUDE_PLUGIN_ROOT}/scripts/migrate-to-epic.sh— the script (header comments document invariants, preflight rejections, and architectural rationale)${CLAUDE_PLUGIN_ROOT}/scripts/fm-helpers.sh— shared helperstask-frontmatter-readerskill — wraps fm-read.sh/ai-dev-assistant:migrate-to-epiccommand — user orchestrator
Do NOT
- Do not duplicate the migration logic in another skill. Call this skill (which calls the script).
- Do not modify the script's exit-code contract without updating consumers.
- Do not use
git mv— task folders are NOT in git (framework memory).