migrate-workflow
SkillDev toolsMigrate a branch's docs/adr/<dir>/_workflow/** artifacts by replaying workflow-format commits from the per-artifact stamp base through HEAD. Resumable across /clear. TRIGGER: branch has stale _workflow/ after a workflow-format change on develop. SKIP: branches with no _workflow/.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the migrate-workflow skill
What this skill tells your AI
The instructions your AI receives, as published by jetbrains/youtrackdb in .claude/skills/migrate-workflow/SKILL.md and read by ahel’s review.
Reading workflow files (TOC protocol)
When you Read any file under .claude/workflow/ or .claude/skills/, follow the protocol in conventions.md §1.8:
- Read the TOC region: from
<!--Document index start-->to<!--Document index end-->(read to the closing delimiter, not a fixed line count). If the file has no TOC region (a file whose only##heading is this bootstrap block carries none, per§1.8(d)), read the file in full. - Match TOC rows where Roles contains any of your roles (or your role is
any, or the row's Roles isany) AND Phases contains any of your phases (or your phase isany, or the row's Phases isany). - Use
Read(offset, limit)to read only matched sections; if no row matches your role/phase, the file holds nothing for you — do not read further.
Your role: migrator. Your phase: any (migration sits outside the phase taxonomy).
Inline refs you find inside workflow files carry the same name:roles:phases suffix; apply file-level filtering before opening: a ref matches when any of your roles is in its roles and any of your phases is in its phases, your own any on either axis matches every ref on that axis, and a ref whose own roles or phases is any matches you. Backtick-wrapped refs carry no suffix; open or skip them at your discretion.
| Section | Roles | Phases | Summary |
|---|---|---|---|
| §Inputs | migrator | any | The plan directory and worktree the migration operates on, resolved before any replay step runs. |
| §Step 0 — Create progress tracker | migrator | any | Create the TaskCreate progress tracker so a /clear mid-migration can resume from the last completed commit. |
| §Step 1 — Preflight | migrator | any | Resolve the plan directory and worktree, confirm the branch has migratable artifacts, and abort cleanly when none exist. |
| §Step 2.0 — Bootstrap unstamped artifacts | migrator | any | Stamp any artifact still missing a line-1 workflow-sha before the replay range is computed. |
| §Step 2 — Compute commit range | migrator | any | Derive the replay range from per-artifact stamps and HEAD, bounding retries on an unresolvable stamp. |
| §Step 3 — Load or initialize progress file | migrator | any | Load an existing .migration-progress file to resume, or initialize a fresh one when the migration starts clean. |
| §Step 4 — Per-commit migration loop | migrator | any | Replay each format-relevant commit: context check, read, classify, apply edits, advance stamps, record progress. |
| §4.1 Context check (mandatory before starting the commit) | migrator | any | Check context budget before each commit and checkpoint to a fresh session when it would not fit the whole commit. |
| §4.2 Read the commit | migrator | any | Read the commit's diff and message to determine what workflow-format change it introduced. |
| §4.3 Classify the commit | migrator | any | Classify the commit as format-relevant, no-op, or manual-review-needed to route how its edits are replayed. |
| §4.4 Apply the migration | migrator | any | Apply the commit's format edits to the matching artifacts in place, or route to manual review on a stamp-format halt. |
| §4.5 Advance stamps in lockstep | migrator | any | After the edits land, rewrite every artifact's line-1 stamp to this commit SHA; the order matters for crash resume. |
| §4.6 Update the progress file | migrator | any | Record the replayed commit and its classification in .migration-progress as the crash-resume marker. |
| §4.7 Mark the per-commit task completed | migrator | any | Mark the commit's TaskCreate entry complete so the progress tracker reflects the replayed commit. |
| §4.8 Final stamp-to-HEAD batch | migrator | any | After the loop, batch-rewrite every artifact's line-1 stamp to HEAD; already-at-HEAD artifacts are benign no-ops. |
| §Step 5 — Final summary | migrator | any | Report the replayed and manual-review commits and leave the dirty worktree for the user to review and commit. |
| §Step 6 — Self-improvement reflection | migrator | any | Run the shared reflection pass to capture migration-process friction as YouTrack proposals before ending the session. |
| §Notes | migrator | any | Standing caveats on in-place editing, the dirty-worktree contract, and the no-automatic-commits rule. |
Auto-detection runs in /create-plan Step 1.5 and /execute-tracks startup via workflow-drift-check.md:orchestrator,planner:2,3A.
The skill runs inside the branch's own worktree. The branch is a self-contained capsule: workflow-format commits enter its view only when the user explicitly rebases or merges develop, so the migration's commit range is derived from per-artifact stamps and HEAD (see conventions.md §1.6), never from a develop-relative fork point. The skill applies each format-relevant commit's edits to the corresponding artifact files under the active plan's _workflow/.
The skill edits files in place and leaves the worktree dirty for the user to review and commit. No automatic commits.
Inputs
$ARGUMENTS — optional. When supplied, it must equal the current
branch name (git branch --show-current); the migration always runs
in the current worktree on the current branch. Step 1 enforces the
equality and rejects mismatches.
Step 0 — Create progress tracker
Before any other tool call, create one task per step below using TaskCreate. Mark each in_progress when starting, completed when done. This list is the checklist; do not skip entries.
- Preflight: verify clean tree under the active plan's
_workflow/and resolve the active plan dir - Bootstrap unstamped artifacts: prompt for a base SHA covering any unstamped
_workflow/**artifact - Compute commit range + format-relevant commit list
- Load or initialize progress file
- Per-commit migration loop (one task per commit will be added at the start of Step 4, after Step 3 trims the resume queue)
- Final stamp-to-HEAD batch
- Final summary
- Self-improvement reflection: invoke
.claude/workflow/self-improvement-reflection.mdwithsession-type=migrate-workflow
Step 1 — Preflight
Run these checks in order. Halt on any failure.
Argument check. When $ARGUMENTS is non-empty, it must equal the
current branch name. The single equality covers every legacy reject
case — refs/heads/..., origin/..., a 7- to 40-character hex SHA,
or a non-existent branch name all fail this check, since none of them
can equal git branch --show-current's plain-branch-name output:
if [ -n "$ARGUMENTS" ] \
&& [ "$ARGUMENTS" != "$(git branch --show-current)" ]; then
echo "ERROR: \$ARGUMENTS ($ARGUMENTS) does not match the current branch ($(git branch --show-current))"
exit 1
fi
Active-plan-dir resolution. Enumerate docs/adr/*/_workflow/
directories in the current worktree:
ls -d docs/adr/*/_workflow/ 2>/dev/null
Apply this ladder and capture the result as $PLAN_DIR (the parent
directory, without the trailing /_workflow/):
- Zero matches — halt with "no
_workflow/directory on the current branch; nothing to migrate". - Exactly one match — use it. The parent directory name need not match the current branch; branch names and ADR directory names are not guaranteed to match.
- More than one match — list them and ask the user which one to migrate. One plan at a time.
Subsequent steps reference $PLAN_DIR (e.g.,
$PLAN_DIR/_workflow/implementation-plan.md) instead of re-running
the ladder.
Narrow-scope dirty check. Refuse to start when any tracked file
under the active plan's §1.6(h) artifact paths has uncommitted
changes (working tree or index), or when any untracked file lives
there. The scope covers the implementation plan, the design files,
and every track file under $PLAN_DIR/_workflow/plan/. The staged
subtree at $PLAN_DIR/_workflow/staged-workflow/ is deliberately
outside the check: on workflow-modifying branches, in-flight
workflow-document changes accumulate under that subtree during
execution (Phase 4 then promotes them to the live paths in one
commit), and a whole-subtree check would refuse every such session
with staged content present — the dogfood path this skill accepts.
DIRTY=$(git status --porcelain -- \
"$PLAN_DIR/_workflow/implementation-plan.md" \
"$PLAN_DIR/_workflow/design.md" \
"$PLAN_DIR/_workflow/design-mechanics.md" \
"$PLAN_DIR/_workflow/plan/" \
| grep -v '^?? \.migration-progress$')
if [ -n "$DIRTY" ]; then
echo "ERROR: uncommitted or untracked changes under the active plan's _workflow/ artifacts:"
printf '%s\n' "$DIRTY"
echo "Commit, stash, or remove these files before re-running /migrate-workflow."
exit 1
fi
The .migration-progress sentinel (Step 3) is the only carve-out;
it is allowed to be untracked or modified mid-session. Any tracked
file under the narrow scope showing a non-? status (modified,
added, deleted, renamed, etc.) or any untracked file showing ??
halts the session with the offending paths printed; the user must
commit, stash, or remove them before re-invoking.
The check is deliberately narrow. The dropped develop-side
whole-tree check incidentally guarded the live skill file
(.claude/skills/migrate-workflow/SKILL.md); the staging convention
for workflow-modifying branches (in-flight rewrites land under
staged-workflow/ rather than the live paths) now keeps the live
skill at develop's state throughout execution, so the migration
always reads the develop-state skill regardless of staged rewrites —
the side-effect protection is no longer needed.
Step 2.0 — Bootstrap unstamped artifacts
Run the startup script in migrate-range mode and read the
unstamped_files array it reports. The script owns the
conventions.md §1.6(h) artifact walk and the stamp classification;
the skill reads the result instead of re-deriving the walk in prose.
The one migrate-range invocation here also produces the
stamped_artifacts / base_sha / log_range / merge_base_failed
fields Step 2 consumes, so the same JSON file feeds both steps.
.claude/scripts/workflow-startup-precheck.sh --mode migrate-range \
> /tmp/claude-migrate-range-$PPID.json
If the script exits non-zero (it emits no JSON on a usage or flag
error, exit 2), surface the stderr diagnostic and halt the migration;
do not Read the /tmp file, which on a failed invocation holds stale
or empty content. The script's stdout is the JSON; redirect it to a
/tmp file rather than capturing it into the conversation. The migrate-range.log_range
array is intentionally uncapped, because the migration must replay
every workflow-touching commit, unlike the drift gate's head -10
display cap. A long branch range could otherwise dump an unbounded
commit list into context on every invocation. Read the file with the Read
tool's offset / limit so the uncapped log_range never lands in
context whole; read unstamped_files here, and read the range fields
in Step 2 in bounded slices.
The migrate-range JSON shape (cite the live shipped
.claude/scripts/workflow-startup-precheck.sh emit_json
migrate-range branch, which is authoritative over any frozen design
text):
{
"stamped_artifacts": [{"file": "<path>", "sha": "<40-hex>"}, ...],
"unstamped_files": ["<path>", ...],
"base_sha": "<40-hex>" | null,
"log_range": [{"sha": "<40-hex>", "subject": "<commit subject>"}, ...],
"merge_base_failed": [{"base": "<40-hex>", "sha": "<40-hex>", "files": ["<path>", ...]}, ...]
}
unstamped_files is the set of _workflow/** artifacts the script
found with no parseable line-1 stamp — exactly the classification this
step needs. $PLAN_DIR is documentary here (resolved in Step 1); the
script resolves the active plan dir from the branch the same way.
When unstamped_files is empty. No prompt fires. Continue to
Step 2 (which reads the same JSON file for the range fields).
When unstamped_files is non-empty. Print the list of unstamped
artifacts and ask the user once for a base SHA covering the set. The
prompt must include the rationale below so the user understands why
the migration cannot guess:
The artifacts listed above carry no
<!-- workflow-sha: ... -->stamp on line 1, so the migration has no anchor for "what workflow version was this artifact last synced to." An auto-computed default (the branch's fork-point withdevelop,git merge-base origin/develop HEAD, HEAD itself, or any other variant) would shift forward whenever the branch is rebased onto a newerdevelopand silently mark unstamped artifacts as already-current, skipping the migration. Provide the SHA of the workflow-format commit these artifacts were last synced to; a short prefix or the full 40-char form are both accepted.
Validate the response with the two-subcommand check from
conventions.md §1.6(d). The ^{commit} peel rejects tag and ref
names; only commit SHAs pass. The reachability check enforces the
range's upper-bound rule from §1.6(c) (HEAD is the comparison anchor,
so the bootstrap SHA must be reachable from HEAD).
The validation block below runs inside a retry loop bounded at three
attempts. On either subcommand failure, print the cause, increment
the attempt counter, and re-prompt the user with the same artifact
list. On the third rejection halt the session with ERROR: three rejected attempts; bootstrap aborted and exit with no edits applied;
do not fall through to Step 2:
if ! CANON_SHA="$(git rev-parse --verify "$SHA^{commit}" 2>&1)"; then
echo "ERROR: $SHA is not a valid commit SHA: $CANON_SHA"
# increment attempt counter, re-enter the prompt; see retry policy below
elif ! git merge-base --is-ancestor "$CANON_SHA" HEAD 2>/dev/null; then
echo "ERROR: $SHA ($CANON_SHA) is not reachable from HEAD."
# increment attempt counter, re-enter the prompt; see retry policy below
else
USER_BOOTSTRAP_SHA="$CANON_SHA"
fi
Store the canonical 40-char rev-parse stdout ($CANON_SHA), not
the user's raw input, as $USER_BOOTSTRAP_SHA. The canonicalization
happens here, in the agent-side §1.6(d) git rev-parse --verify /
git merge-base --is-ancestor check above; the script does no
validation of its own --bootstrap-sha value (the arg parser folds it
into git merge-base raw). The canonical form is mandatory on two
counts: Step 4's per-commit lockstep advance writes the value into
artifact stamps, and the stamp regex [0-9a-f]{40} in §1.6(a1)
rejects shorter values on subsequent parse, so a short-prefix stamp
would fail every drift-check re-read.
Retry policy (bounded, session-bound counter). On validation
failure (either subcommand returns non-zero), print the failure cause
and re-prompt the user with the same artifact list. Cap the retry
count at three attempts per conventions.md §1.6(d); after the third
rejection print a one-line diagnostic naming the current HEAD SHA
(echo "Reachability is checked against git rev-parse HEAD = $(git rev-parse HEAD); if the SHA you supplied is on a different branch, check out the migration branch first."), then halt the
session with ERROR: three rejected attempts; bootstrap aborted and
exit with no edits applied. The user /clears the session to abandon
the migration.
The counter is session-bound: it lives in the conversation, not on
disk. The orchestrator holds the counter across both Step 2.0's
initial prompt and Step 2's recovery re-prompt; the bash blocks above
run once per attempt, and the orchestrator decides whether to
re-invoke them. A /clear between attempts resets it. Step 3's
.migration-progress file does not exist yet at Step 2.0 time, so no
persistent counter is available; the bound is by design soft against
/clear-based abandonment and re-entry. This matches §1.6(d)'s
explicit "/clears the session to abandon the migration" exit shape.
The prompt records the user's best guess; the per-commit replay
loop's halt-on-ambiguity in Step 4 is a partial safety net, not a
guarantee. A too-old SHA silently bloats the replay range; a too-new
SHA silently skips needed migrations. Both failure modes are
documented in conventions.md §1.6(d) so debug sessions have a
starting point.
Step 2 re-invokes the script with --bootstrap-sha $USER_BOOTSTRAP_SHA
(when set) so the script folds the bootstrap SHA into the range
alongside the stamped artifacts. When Step 2.0 did not fire (because
unstamped_files was empty), $USER_BOOTSTRAP_SHA stays unset and
Step 2 reads the range fields straight from the JSON file this step
already produced, with no re-invocation.
Step 2 — Compute commit range
The range comes from the script's migrate-range output, not from a
re-derived walk in this skill. The script owns both the
conventions.md §1.6(h) artifact walk and the git merge-base fold
that collapses the stamp set to base_sha (the oldest stamp reachable
from HEAD per conventions.md §1.6(c)), then emits the path-scoped
git log base_sha..HEAD range as log_range. The skill reads those
fields and drives the conversational recovery the script cannot — the
script never prompts.
Step 2.0 already produced the migrate-range JSON at
/tmp/claude-migrate-range-$PPID.json. Read the range fields from the
same file with the Read tool's offset / limit so the
uncapped log_range array never lands in context whole (WB1): read
base_sha and merge_base_failed first (both bounded), and read
log_range in bounded slices when building the per-commit queue. The
fields this step consumes:
base_sha— the foldedBASE_SHA(full%HSHA), or JSONnullwhen the fold produced no clean base (no stamps and no--bootstrap-sha, or one or more merge-base failures).log_range—[{sha, subject}, ...]forbase_sha..HEADover the workflow pathspecs, oldest first; the empty array when there is no clean base or the range is empty. Uncapped by design.merge_base_failed—[{base, sha, files}, ...], one entry per failing merge-base pair, withfilesalready naming the artifact paths that emitted the failing SHAs (the script resolves them via itsSTAMPED_PAIRStable). The empty array on a clean fold.stamped_artifacts/unstamped_files— the classification Step 2.0 consumed; available here too if a recovery re-prompt needs the current stamped set.
Halt — no stampable artifacts. When both stamped_artifacts and
unstamped_files are empty, the active plan has no stampable artifacts
on disk (a freshly-created _workflow/ dir holding only a transient
handoff-*.md, for example). Per conventions.md §1.6(h)'s
both-arrays-empty rule, halt the migration with no artifacts to migrate and exit without computing a range.
Merge-base failure recovery. When merge_base_failed is non-empty,
the script's fold hit a merge-base failure: a stamp pointing at a
git-gc-pruned commit, or two stamps with no reachable common ancestor
in the local repo. The script collects every failing pair into
merge_base_failed (the continue fold mode) rather than stopping at
the first, so one recovery re-prompt covers the full failing set; the
break-shape the drift gate uses would re-prompt once per failing pair
serially. Each entry's files array already names the artifact paths
that emitted the failing SHAs, so the skill does not re-resolve SHAs to
paths — it reads them straight from merge_base_failed[].files.
Drive the recovery agent-side (the script cannot prompt):
-
Re-prompt the combined set. Route the union of the originally-unstamped files (
unstamped_files) and everymerge_base_failed[].filesentry back through Step 2.0's bootstrap prompt perconventions.md§1.6(c). The user supplies one new SHA covering the combined set; the validated value replaces the prior$USER_BOOTSTRAP_SHA(the variable stays singular, matchingconventions.md§1.6(d)'s one-SHA-per-prompt shape). The re-prompt's user-facing text uses the combined file list. -
Drop the failed SHAs. Pass one
--exclude-sha <sha>permerge_base_failed[].shaon the re-invoke alongside--bootstrap-sha "$USER_BOOTSTRAP_SHA". The repeatable--exclude-shaflag drops those stamps from the script's fold input, so the restarted fold does not re-rungit merge-baseover the same failing pair and fail again at the same point; the fresh bootstrap SHA anchors the excluded artifacts' range (matching the "treat as unstamped" framing inconventions.md§1.6(c)). Without the exclusion the restarted fold would re-walk the same failing stamp and exhaust the 3-attempt cap on input the user cannot fix. -
Enforce the session-wide 3-attempt cap. The 3-attempt counter is shared across Step 2.0's initial prompt and this recovery prompt — session-wide, not per-prompt — so a user cannot chain failures across both prompts to escape the bound. A user who already burned 2 attempts in Step 2.0's initial run has 1 attempt left here. On three rejections (across any combination of initial + recovery attempts) the migration halts with
ERROR: three rejected attempts; bootstrap abortedand exits with no edits applied. -
Re-invoke and restart the fold. On a validated SHA, re-invoke the script with the fresh bootstrap SHA and one
--exclude-shapermerge_base_failed[].sha, then re-read the JSON file, restarting the fold from the top:.claude/scripts/workflow-startup-precheck.sh --mode migrate-range \ --bootstrap-sha "$USER_BOOTSTRAP_SHA" \ --exclude-sha "$FAILED_SHA_1" --exclude-sha "$FAILED_SHA_2" \ > /tmp/claude-migrate-range-$PPID.jsonSupply one
--exclude-shaflag permerge_base_failed[].sha. If the script exits non-zero (it emits no JSON on a usage or flag error, exit 2), surface the stderr diagnostic and halt the migration; do notReadthe/tmpfile, which on a failed invocation holds stale or empty content. Otherwise re-readmerge_base_failedfrom the refreshed file. If it is still non-empty (a different pair failed, or the bootstrap SHA itself does not reach the stamps), restart the recovery loop — still under the shared 3-attempt cap. When it is empty, continue to the range computation below.
Range computation and the empty-log halt. Once the script reports
a non-null base_sha with an empty merge_base_failed, the range is
log_range. If log_range is empty, halt with:
No workflow-touching commits between stamp base
<short-base_sha>and HEAD<short-HEAD>. Nothing to migrate.
The empty-log halt fires on a fully-stamped branch whose stamps already point at HEAD (or at every workflow-touching commit between them and HEAD): the fold collapses to a stamp that is itself the newest workflow-format commit reachable from HEAD, so the range is empty and the migration has nothing to replay.
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 433
- Forks
- 16
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
migrate-workflow- Source
- github.com/jetbrains/youtrackdb