Legacy code doesn't have a change artifact to point at. This skill creates one:
1.5. Idempotent-rerun guard
Do not proceed silently — a second ghost change mints duplicate tasks and produces a confusing PR.
-
Load the plan from JSON
Read openspec/coverage-report.json. Extract buckets.bucket_1.
- Skip
needs_review: true entries. Tell user how many were skipped.
- Skip entries already annotated. The JSON's
annotated bucket already separates them out, but double-check during the edit pass (defensive).
Group Bucket 1 by capability → req_id → list of methods. This becomes the task layout.
-
Announce the plan and confirm
## Annotation Plan — {app}
Ghost change: retrofit-{YYYY-MM-DD}-annotate-{app}
Tasks to create: {M} (one per REQ with matches)
Files to touch: {F}
Methods to tag: {N}
Capabilities: {list}
Skipped (NEEDS-REVIEW): {K}
Use AskUserQuestion:
- Proceed — create ghost change + annotate
- Review first — print the full file list
- Cancel
-
Bootstrap labels on the target repo (once per repo)
Check whether retrofit and annotation-only labels exist:
gh label list --repo ConductionNL/{app} | grep -E "^(retrofit|annotation-only)\b" || true
If missing, create them:
gh label create retrofit --color 5319E7 --repo ConductionNL/{app} || true
gh label create annotation-only --color C5DEF5 --repo ConductionNL/{app} || true
-
Create the ghost change scaffold
Prefer the existing skill (handles schema + scaffolding):
/opsx-new retrofit-{YYYY-MM-DD}-annotate-{app}
If /opsx-new isn't available, create the directory manually:
{app}/openspec/changes/retrofit-{YYYY-MM-DD}-annotate-{app}/
proposal.md
tasks.md
Fill in:
proposal.md:
# Retrofit — annotate {app} against existing specs
Retroactive annotation of {N} methods across {F} files against {M} REQs in {K} capabilities. No code logic changes. No spec deltas (all REQs already exist in openspec/specs/).
Source: openspec/coverage-report.md generated {YYYY-MM-DD} (Bucket 1 only).
See [retrofit playbook](../../../.github/docs/claude/retrofit.md).
tasks.md — one task per REQ, numbered by encounter order. All tasks arrive [x] because the code is pre-existing:
# Tasks
- [x] task-1: meeting-management#REQ-001 — Meetings are listed chronologically (retroactive annotation)
- [x] task-2: meeting-management#REQ-003 — Meetings can be cancelled (retroactive annotation)
- [x] task-3: motion-and-voting#REQ-012 — Motions collect votes until close (retroactive annotation)
Record the task-number-for-each-REQ map — you'll need it for annotation.
-
Annotate code — one file at a time, single Edit pass per file
For each unique file in Bucket 1:
a. Read the file.
b. Determine the set of @spec tags this file needs. Collect every req_id for methods in this file + any helpers that inherited REQs from this file's methods. Map each to its task-N from step 5. The file docblock needs one @spec tag per distinct task.
c. Apply file-docblock edits:
- Locate the main file docblock (the
/** */ block immediately above class, namespace, or first declaration).
- Insert
@spec openspec/changes/retrofit-{YYYY-MM-DD}-annotate-{app}/tasks.md#task-N tags after the @link tag per the hydra-gate-spdx format. One tag per distinct task. Preserve the existing tag order.
- If the file has no main docblock, add one following the hydra-gate-spdx template (description,
@category, @package, @author, @copyright, @license, @link, @spec tag(s)).
- If docblock already has
@spec tags pointing at non-retrofit changes, leave them — append the retrofit ones after.
d. Apply method-docblock edits: For each method in this file that's in Bucket 1 or inherited-from-Bucket-1:
e. Write the file via Edit tool. Never sed/awk/python. If the linter hook reverts the Edit, rewrite the whole file via Write (project rule: high probability of breaking code when scripting edits).
One file at a time, sequential — Edit tool's file-state tracking doesn't batch safely.
-
Run the linter gates
From the workspace root:
cd {app} && composer phpcs 2>&1
For frontend-heavy apps, also:
cd {app} && npm run lint 2>&1
Also run Hydra's mechanical gates (they're what the reviewer checks against):
/hydra-gates
If any gate fails due to tag ordering/placement: do not reorder tags to satisfy the linter. The ADR-003 + hydra-gate-spdx format is fixed. Fix the PHPCS config instead. Stop, report the specific rule, wait for guidance.
-
Commit annotations + ghost change
cd {app}
git add -A
git commit -m "retrofit: annotate {N} methods across {F} files (Bucket 1)
Applied @spec tags pointing at ghost change retrofit-{YYYY-MM-DD}-annotate-{app}.
No logic changes. Source: openspec/coverage-report.md."
Capture the commit SHA.
-
Update .git-blame-ignore-revs
Append the annotation commit's SHA:
printf '# Retrofit annotation commit (opsx-annotate, {YYYY-MM-DD})\n{SHA}\n' >> .git-blame-ignore-revs
git add .git-blame-ignore-revs
git commit -m "retrofit: add annotation commit to blame-ignore-revs"
Tell the user (don't run automatically — per-developer choice):
"Each developer cloning this repo must enable it once: git config blame.ignoreRevsFile .git-blame-ignore-revs"
-
Archive the ghost change
/opsx-archive retrofit-{YYYY-MM-DD}-annotate-{app}
Moves the change dir to openspec/changes/archive/retrofit-*-annotate-.../. Since the ghost change has no spec deltas, no capability specs are modified.
-
Sanity check — verify annotations
Re-read openspec/coverage-report.json (it's stale, but still shows Bucket 1 items). Grep each file for the expected @spec ... retrofit-{YYYY-MM-DD}-annotate-{app} tag. Count annotations and compare against the Bucket 1 count. Mismatch → stop, investigate before pushing.
Do NOT call /opsx-coverage-scan again here — it's expensive and the sanity check above is sufficient.
-
Push and create PR
git push -u origin retrofit/annotate-{app}-{YYYY-MM-DD}
Invoke /create-pr (or gh pr create directly). PR title:
retrofit: annotate {app} Bucket 1 (N methods / M REQs)
PR body:
## Retrofit — Annotation Only
Applies `@spec openspec/changes/retrofit-{date}-annotate-{app}/tasks.md#task-N` tags per [ADR-003 §Spec traceability](hydra/openspec/architecture/adr-003-backend.md).
Ghost change: `openspec/changes/archive/retrofit-{date}-annotate-{app}/` (empty spec delta, {M} tasks).
### What this PR does
- Creates ghost change with {M} tasks (one per REQ with Bucket 1 matches)
- Adds {N} method-level `@spec` tags across {F} files
- Adds file-docblock `@spec` tags where missing
- Adds this commit to `.git-blame-ignore-revs`
### What this PR does NOT do
- No logic changes
- No formatting / whitespace / reordering
- No Bucket 2 (see follow-up `/opsx-reverse-spec` PRs)
- No Bucket 3/4 (separate follow-ups)
### Verification
- [ ] `composer phpcs` / `npm run lint` passes
- [ ] `/hydra-gates` passes
- [ ] Diff is annotations + ghost change only
- [ ] `.git-blame-ignore-revs` includes the annotation commit
Source: `openspec/coverage-report.md` generated {YYYY-MM-DD}
Labels: retrofit, annotation-only.
-
Summary
## Annotation Complete — {app}
Ghost change: retrofit-{YYYY-MM-DD}-annotate-{app} (archived)
Tasks created: {M}
Files touched: {F}
Methods tagged: {N}
Skipped (NEEDS-REVIEW): {K}
Branch: retrofit/annotate-{app}-{YYYY-MM-DD}
PR: {url}
Next:
1. Merge this PR before proceeding to Bucket 2
2. For each Bucket 2a cluster: `/opsx-reverse-spec {app} --extend <capability>`
3. For each Bucket 2b cluster: `/opsx-reverse-spec {app} --cluster <name>`
4. Optionally re-run `/opsx-coverage-scan {app}` after merge to refresh the report