osdlc

entities-migrate

Apply mechanical, auto-fixable schema drift to entity instances under docs/planning/<plural>/. Spins up a worktree, runs the audit, applies every fix the audit flagged auto_fixable — stamping the missing schema_version into frontmatter AND running the deterministic body transforms (the v_n->v_n+1 chain that reshapes body sections, e.g. a task's bulleted Files-to-touch/Today blocks into typed v3 tables) — and opens a PR. Body transforms are deterministic TS that reshape what's unambiguous and flag the ambiguous tail (definition_gap + status downshift, mid-flight preserved); no LLM in the migrate path. Skips closed entities by default; pass --include-closed to forward that flag to the audit and treat closed entities like active ones.

osdlc 3 Actualitzat fa 5 d
GitHub

Instal·la

npx skillscat add osdlc/osdlc/claude-plugin-packs-sdlc-skills-entities-migrate

Instal·la des del registre de SkillsCat.

SKILL.md

/sdlc:entities-migrate

Usage:

  • /sdlc:entities-migrate — apply every auto_fixable=true drift entry across all entity types.
    Closed entities are skipped (the audit filters them out before migrate ever sees them).
  • /sdlc:entities-migrate --include-closed — forward --include-closed to the audit so closed
    entities are drift-checked and migrated like active ones.
  • /sdlc:entities-migrate --type task — restrict to one entity type. Repeatable.
  • /sdlc:entities-migrate --dry-run — report what would change without writing.

Project context (don't re-derive every run):

  • sdlc entities audit is the source of truth for what counts as drift and which drift entries are
    mechanically fixable (auto_fixable: true). Dispatch only on what the audit tagged; do not invent
    new fix logic.
  • Closed entities are skipped at the audit layer, not at migrate. The audit early-returns for
    any entity whose state indicates closed (closed/<reason> or legacy bare-closed values like
    closed, done, superseded), so migrate never sees those entries. Pass --include-closed to
    forward that flag through to audit so closed entities are drift-checked and fixed alongside active
    ones — reach for it only when every record must track the current schema, since closed = frozen:
    a closed entity was authored against an earlier schema and a mechanical reinterpretation under
    newer semantics can corrupt a finished artefact.
  • The 0 sentinel (schema_version: 0) means "intentionally pre-SDLC-managed"; audit treats it as
    not-drift. It's a manual stamp users apply to specific active records they want excluded from
    versioning.
  • Two auto_fixable=true paths exist today:
    • Missing schema_version — stamp the schema's current version into the file's frontmatter,
      preserving canonical key order.
    • Older-than-current schema_version with a transform on disk — import the matching
      entities/<type>/migrations/v<file>-to-v<current>.ts module, apply its migrate callable,
      write the migrated content back. Transforms come in two signature shapes; the runner introspects
      each and routes accordingly (see "Transform signature dispatch" below). The transform is
      responsible for any field-shape changes (status remaps, dropped legacy keys, completion-note
      synthesis), any body rewrites, and for stamping the new schema_version on its result.
  • One PR per invocation. One commit covering all entities (not split by type).

Transform signature dispatch

A transform module at entities/<type>/migrations/v<src>-to-v<dst>.ts
may declare its migrate callable in one of two shapes:

  • Frontmatter-only (historical): migrate(fm: dict) -> new_fm: dict.
    The runner passes the parsed frontmatter and writes the returned dict
    back. Body content is preserved byte-for-byte. Use this when the
    migration only touches frontmatter fields (status remaps, field
    renames, dropped keys). Example: entities/task/migrations/v1-to-v2.ts.
  • Body-aware: migrate(fm: dict, body: str) -> (new_fm: dict, new_body: str).
    The runner passes both frontmatter and the body string, and writes
    both back. Use this when the migration must rewrite prose / tables /
    lists inside the body. Example:
    entities/task/migrations/v2-to-v3.ts (converts bulleted
    Files-to-touch and Today blocks into typed markdown tables).

The entities migrate op inspects the callable's signature and dispatches:
≥2 positional parameters → body-aware; otherwise frontmatter-only.
There is no per-module flag; the contract is the signature.

Steps

1. Pre-flight

Confirm the main repo's working tree is clean. From the project root:

git status --porcelain

If anything is staged or modified, stop and report to the user. Do NOT
sweep their work into this skill's commits. (Untracked files unrelated
to entity instances are fine — the migrate script itself uses
git status --porcelain and will refuse to act on a dirty tree without
--allow-dirty.)

2. Dry-run the audit first

Before creating a worktree, surface what's about to change:

${CLAUDE_PLUGIN_ROOT}/cli/sdlc entities audit --json

Parse the JSON. Count entries with at least one
drift[*].auto_fixable=true. By default the audit's JSON output
already excludes closed entities from drift detection; if the user
passed --include-closed, forward it to the audit invocation
(sdlc entities audit --json --include-closed) so closed entries
appear in the report.

  • If zero auto-fixable items qualify: report nothing to migrate.
    to the user and exit. Do not create a worktree or branch.
  • Otherwise proceed to Step 3.

3. Create the worktree

git worktree add .sdlc/worktrees/entities-migrate -b chore/entities-migrate main

If a worktree at that path or a branch by that name already exists, stop
and ask the user — there may be an unfinished prior run. Do not delete
existing state without confirmation.

From here on, all subsequent work happens in that worktree path. Use
absolute paths in tool calls; do not cd the parent session into the
worktree.

4. Run the migration

In the worktree:

${CLAUDE_PLUGIN_ROOT}/cli/sdlc entities migrate \
  --project-root .sdlc/worktrees/entities-migrate

Forward any flags the user passed (--type, --include-closed,
--dry-run). The op:

  • Refuses if the worktree is dirty (it won't be — you just created it).
  • Pulls the audit report (forwarding --include-closed if set).
  • For each auto_fixable=true drift entry, applies the corresponding
    fix — either stamping schema_version with the schema's current
    version or running the matching v-to-v transform.
  • Prints a summary: what was fixed, what's pending manual review, and
    any failures.

Capture the summary — Step 6 reuses it for the PR body.

Interpret the exit code:

  • 0 — clean run. Either fixes applied successfully, or there was
    nothing mechanical to do.
  • 1 — the run failed against the state it found: at least one fix
    attempt failed, a directory is missing, or the audit JSON is malformed.
    Stop here. Surface the failure output to the user; do not commit a
    half-applied state.
  • 18 — the working tree is dirty. Stop, report, ask the user to
    commit/stash before retrying. Do not auto-stash.

If --dry-run was passed, the op wrote nothing. Surface its output
to the user and exit — no commit, no PR.

5. Commit the changes

In the worktree:

git status --porcelain

Expect only files under docs/planning/<plural>/ to be modified. If
anything else changed, stop — something is off.

Stage every modified instance file (use the script's summary as the
authoritative list):

git add docs/planning/...
git commit -m "chore(entities): apply auto-fixable schema drift"

One commit. Do not split by entity type. The commit message body should
list the files modified and the fix applied to each:

chore(entities): apply auto-fixable schema drift

Stamped current schema_version into the frontmatter of:
- docs/planning/tasks/foo.md (task v1)
- docs/planning/milestones/M0042.md (milestone v1)

Files left for manual review (auto_fixable=false drift):
- docs/planning/tasks/bar.md — schema_version 0 < current 1 (needs a real migration)

6. Open the PR

git push -u origin chore/entities-migrate
${CLAUDE_PLUGIN_ROOT}/cli/sdlc pr create --base main --head chore/entities-migrate \
  --title "chore(entities): apply auto-fixable schema drift" \
  --body-file <body-file>

PR body shape:

## Summary

Applied mechanical schema-drift fixes flagged `auto_fixable=true` by
/sdlc:entities-audit. That means both stamping the current
`schema_version` into frontmatter where it was missing AND running the
deterministic body transforms (the `v_n`→`v_n+1` chain) on older-version
records — which reshape unambiguous body sections (e.g. a task's bulleted
Files-to-touch/Today blocks into typed v3 tables) and flag the genuinely
ambiguous tail (`definition_gap:` + status downshift, mid-flight
preserved) rather than guessing. No LLM is involved.

## What changed

<list each file fixed and the fix description, mirroring the commit
message body>

## Manual review needed

<list each `auto_fixable=false` drift entry the audit reported — older
schema_version, frontmatter validation errors, missing template H2s.
One bullet per file; group drift entries beneath.>

## How this was generated

Invoked `/sdlc:entities-migrate`. The skill ran
`sdlc entities audit --json`, then `sdlc entities migrate` against this
worktree.

Return the PR URL to the user.

7. After the PR merges — close the loop

Same template as /sdlc:task-work Steps 11a–11c:

  1. On main, pull the merged commit:

    git -C <main-repo> fetch origin
    git -C <main-repo> pull --rebase --autostash
  2. Tear down the worktree:

    git -C <main-repo> worktree remove --force .sdlc/worktrees/entities-migrate
    git -C <main-repo> worktree prune
  3. Delete the merged branch:

    git -C <main-repo> branch -d chore/entities-migrate

    (Use -D only if you're certain the PR was squash-merged — -d is
    the safe form.)

Confirm the final state to the user: PR URL, branch gone, worktree
gone.

Failure modes

  • Dirty working tree — sdlc entities migrate exits 18. Stop, report,
    ask the user to commit/stash before retrying. Do not auto-stash.
  • Audit script error — surface stderr and exit. Don't try to fix
    what the audit can't describe.
  • Unknown auto_fixable kind — the op will surface a failure
    ("no dispatch for auto_fixable kind ''"). This means the audit
    added a new auto-fixable category that this skill doesn't yet handle;
    open a follow-up task to add the dispatch arm rather than guessing.
  • Closed entities appearing in the work list — only happens when
    --include-closed was passed all the way through. In that mode they're
    treated like active entities and migrated to current. Without the flag,
    audit filters them out before migrate sees them.

Notes

  • Idempotent. Running the skill twice in a row produces no second
    PR (the second run finds no auto-fixable drift and reports nothing to migrate.).
  • Frontmatter AND body. This skill applies both fixes the op already
    performs: the mechanical frontmatter migration (stamping the current
    schema_version) AND the deterministic body transforms — the
    v_n→v_n+1 chain reshapes unambiguous body sections (e.g. a task's
    bulleted Files-to-touch/Today blocks into typed v3 tables). The body
    transforms are deterministic TS script files (migrate(fm, body) -> [fm, body]), not LLM authoring: they reshape what's unambiguous and
    FLAG the genuinely ambiguous tail (definition_gap: + status
    downshift, mid-flight preserved — see the next note) rather than
    guessing. The remaining open-ended body work — template H2 gaps, prose
    the transform deliberately flagged as ambiguous — is left for
    /sdlc:task-define (interactive/LLM judgment), NOT done here. No LLM
    runs in the migrate path.
  • Mid-flight status is preserved. A body-aware transform may flag an ambiguous tail with
    definition_gap: and downshift the task to planning/needs-definition so it surfaces in the next
    /sdlc:task-review — EXCEPT for tasks already in-progress or in-progress/blocked, whose
    status is preserved (downshifting a mid-flight task corrupts task-work state; mirrors the
    carve-out in sdlc task ensure-ready-mutate). Implemented
    today by entities/task/migrations/v2-to-v3.ts.
  • Transform classes (pick one when adding a transform). (1) Auto-fixable: deterministic for
    every input. (2) Mechanical-with-flagging: reshape the easy rows, set definition_gap: +
    downshift the ambiguous tail (the mid-flight carve-out above applies). (3) Non-mechanical: needs
    per-instance judgment — does NOT belong here; route to /sdlc:task-define. Never call an LLM
    inside migrate.ts; reaching for one means you have left class 2. When a new transform sits on
    the (2)/(3) line, prefer (2) — flagging the ambiguous tail for human triage scales, LLMing the
    migration sweep does not.
  • Expanding mechanical-fix scope. To make a new drift category fixable: flip auto_fixable=true
    in the audit, add the matching dispatch arm in migrate, and add a fixture under tests/fixtures/.
  • Eval suite. tests/entities_migrate.test.ts (in this skill's source directory; dev-only, not
    shipped in the built plugin) validates the migrate op against fixtures (missing/older/clean
    schema_version, closed-with-drift in both default-skip and --include-closed modes, dirty tree).
    End-to-end body-transform behavior (the v2→v3 bulleted-Files-to-touch → typed-table reshape,
    plus the ambiguous-row definition_gap: flag and mid-flight-preserving downshift) is covered
    per-transform by the unit suite of
    ${CLAUDE_PLUGIN_ROOT}/lib/model/entities/task/migrations/v2-to-v3.ts. Run both before changing
    the migrate op or a transform.
  • Committing model-generated messages. See
    ${CLAUDE_PLUGIN_ROOT}/conventions/commit-messages.md — mktemp + quoted-heredoc +
    git commit -F is the canonical pattern. Avoids zsh-glob hazards on conventional-commit parens
    and tempfile collisions under parallel sessions.
  • Branch naming. This skill's worktree branch uses chore/entities-migrate-<timestamp> (NOT
    task/<task-basename> — this is bulk skill-runtime work, not a single-task implementation). See
    ${CLAUDE_PLUGIN_ROOT}/conventions/branch-naming.md.
  • Schema bump checklist. See ${CLAUDE_PLUGIN_ROOT}/conventions/schema-bump-checklist.md — the
    five canonical questions every schema-bump task spec must answer (missing field, current field,
    unknown legacy value, error path, post-migrate stamp behavior) before this skill is expected to
    migrate cleanly.