migrate-workflow

SkillDev tools

Migrate 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.

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:

  1. 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.
  2. Match TOC rows where Roles contains any of your roles (or your role is any, or the row's Roles is any) AND Phases contains any of your phases (or your phase is any, or the row's Phases is any).
  3. 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.

SectionRolesPhasesSummary
§InputsmigratoranyThe plan directory and worktree the migration operates on, resolved before any replay step runs.
§Step 0 — Create progress trackermigratoranyCreate the TaskCreate progress tracker so a /clear mid-migration can resume from the last completed commit.
§Step 1 — PreflightmigratoranyResolve the plan directory and worktree, confirm the branch has migratable artifacts, and abort cleanly when none exist.
§Step 2.0 — Bootstrap unstamped artifactsmigratoranyStamp any artifact still missing a line-1 workflow-sha before the replay range is computed.
§Step 2 — Compute commit rangemigratoranyDerive the replay range from per-artifact stamps and HEAD, bounding retries on an unresolvable stamp.
§Step 3 — Load or initialize progress filemigratoranyLoad an existing .migration-progress file to resume, or initialize a fresh one when the migration starts clean.
§Step 4 — Per-commit migration loopmigratoranyReplay each format-relevant commit: context check, read, classify, apply edits, advance stamps, record progress.
§4.1 Context check (mandatory before starting the commit)migratoranyCheck context budget before each commit and checkpoint to a fresh session when it would not fit the whole commit.
§4.2 Read the commitmigratoranyRead the commit's diff and message to determine what workflow-format change it introduced.
§4.3 Classify the commitmigratoranyClassify the commit as format-relevant, no-op, or manual-review-needed to route how its edits are replayed.
§4.4 Apply the migrationmigratoranyApply 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 lockstepmigratoranyAfter 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 filemigratoranyRecord the replayed commit and its classification in .migration-progress as the crash-resume marker.
§4.7 Mark the per-commit task completedmigratoranyMark the commit's TaskCreate entry complete so the progress tracker reflects the replayed commit.
§4.8 Final stamp-to-HEAD batchmigratoranyAfter the loop, batch-rewrite every artifact's line-1 stamp to HEAD; already-at-HEAD artifacts are benign no-ops.
§Step 5 — Final summarymigratoranyReport the replayed and manual-review commits and leave the dirty worktree for the user to review and commit.
§Step 6 — Self-improvement reflectionmigratoranyRun the shared reflection pass to capture migration-process friction as YouTrack proposals before ending the session.
§NotesmigratoranyStanding 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.

  1. Preflight: verify clean tree under the active plan's _workflow/ and resolve the active plan dir
  2. Bootstrap unstamped artifacts: prompt for a base SHA covering any unstamped _workflow/** artifact
  3. Compute commit range + format-relevant commit list
  4. Load or initialize progress file
  5. Per-commit migration loop (one task per commit will be added at the start of Step 4, after Step 3 trims the resume queue)
  6. Final stamp-to-HEAD batch
  7. Final summary
  8. Self-improvement reflection: invoke .claude/workflow/self-improvement-reflection.md with session-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/):

  1. Zero matches — halt with "no _workflow/ directory on the current branch; nothing to migrate".
  2. 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.
  3. 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 with develop, git merge-base origin/develop HEAD, HEAD itself, or any other variant) would shift forward whenever the branch is rebased onto a newer develop and 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 folded BASE_SHA (full %H SHA), or JSON null when the fold produced no clean base (no stamps and no --bootstrap-sha, or one or more merge-base failures).
  • log_range[{sha, subject}, ...] for base_sha..HEAD over 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, with files already naming the artifact paths that emitted the failing SHAs (the script resolves them via its STAMPED_PAIRS table). 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):

  1. Re-prompt the combined set. Route the union of the originally-unstamped files (unstamped_files) and every merge_base_failed[].files entry back through Step 2.0's bootstrap prompt per conventions.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, matching conventions.md §1.6(d)'s one-SHA-per-prompt shape). The re-prompt's user-facing text uses the combined file list.

  2. Drop the failed SHAs. Pass one --exclude-sha <sha> per merge_base_failed[].sha on the re-invoke alongside --bootstrap-sha "$USER_BOOTSTRAP_SHA". The repeatable --exclude-sha flag drops those stamps from the script's fold input, so the restarted fold does not re-run git merge-base over 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 in conventions.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.

  3. 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 aborted and exits with no edits applied.

  4. Re-invoke and restart the fold. On a validated SHA, re-invoke the script with the fresh bootstrap SHA and one --exclude-sha per merge_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.json
    

    Supply one --exclude-sha flag per merge_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 not Read the /tmp file, which on a failed invocation holds stale or empty content. Otherwise re-read merge_base_failed from 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