forge-migrate
Overview
Backfill forge:meta headers onto pre-existing .forge/ artifacts so they enter the dependency tracking system. Reads filenames against the canonical artifact table in references/forge-dependency-graph.md to infer generated_by and depends_on. Sets generated_at to the file's mtime (UTC), and computes content_hash from the current contents. Read-then-write only — never regenerates an artifact, just annotates what's already there.
When to Use
- Upgrading from a pre-3.2.0 forge-skills release where
.forge/artifacts had no headers forge-syncreports every artifact as "untracked" / "no header"- A teammate without the plugin produced
.forge/files on a shared branch and you want them tracked - Need to bring legacy artifacts under the dependency graph without re-running expensive skills (interviews, audits)
When NOT to Use
- The artifact is genuinely stale and should be regenerated — use the source skill instead (e.g.,
/specto refreshprd.mdrather than back-stamping it) .forge/doesn't exist — there's nothing to migrate- All artifacts already have valid
forge:metaheaders —forge-syncwill tell you this; migration is a no-op - The file isn't on the canonical artifact list — don't invent dependencies for ad-hoc files
Common Rationalizations
| Thought | Reality |
|---|---|
| "I'll just re-run every skill to get fresh headers" | Forces re-interview and re-audit — burns hours when a 30-second backfill is correct |
| "The files work without headers" | They work for reading. They break for forge-sync, /forge-migrate itself, and downstream staleness detection. |
| "I'll write the headers by hand" | Hand-written headers drift from the schema. The skill enforces UTC, hash format, dependency inference. |
| "Use today's timestamp — it's close enough" | Today's timestamp tells forge-sync the artifact is newer than its upstream. False UP_TO_DATE. Use the file's mtime. |
| "All artifacts depend on prd.md, just list that" | Inferring depends_on from the canonical graph keeps cascade rules correct. Don't fabricate dependencies. |
Red Flags
- A migrated artifact has
generated_atset tonowinstead of file mtime generated_atis local time instead of UTC withZsuffixcontent_hashis missing or not 8 hex chars- A file not on the canonical artifact list received a fabricated
generated_by - The skill regenerated content instead of just adding a header
Core Process
Step 1: Scan .forge/ for unheadered files
For every file under .forge/ (recursively):
- Read the first ~30 lines.
- If a
<!-- forge:meta -->block (or# forge:metafor YAML) is present and well-formed → skip, already tracked. - If absent or malformed → candidate for migration.
If .forge/ doesn't exist, exit: "Nothing to migrate."
Step 2: Match each candidate against the canonical graph
Read references/forge-dependency-graph.md. Look up each candidate's filename in the "Artifact-producing skills" table to recover:
generated_by(the skill that owns this filename)depends_on(the canonical upstream set)
For unmatched files (not on the canonical list), do NOT fabricate a header. Report them under "unmatched — manual review" and move on. Common cases: ad-hoc notes, half-finished drafts, project-specific extensions.
For globbed outputs (contracts/*.md, adr/*.md, redacted/*), every file in the directory inherits the parent skill's depends_on.
Step 3: Compute metadata per file
For each matched candidate:
generated_at: read the file's mtime viastat; convert to ISO 8601 UTC withZsuffix (e.g.,2026-05-12T14:30:00Z). If mtime is unavailable, fall back to "now" UTC and flag for user awareness.content_hash: sha256 over the entire current file body; take the first 8 hex chars.depends_on: from Step 2.generated_by: from Step 2.
Step 4: Confirm with the user before writing
Present the proposed migration plan:
3 files will receive backfilled headers:
.forge/prd.md
generated_by: spec-driven-development
generated_at: 2026-04-22T09:15:00Z (file mtime)
depends_on: [.forge/idea-brief.md]
content_hash: 3f9a2b1c
.forge/architecture.md
...
.forge/contracts/auth-service.md
...
1 file will NOT be migrated (unmatched):
.forge/scratch-notes.md — not in canonical artifact table
Proceed? Y/n
Do not write until confirmed. Migration is structurally non-destructive, but the user's confirmation is the chain-of-custody event.
Step 5: Prepend headers
For each confirmed file, prepend the forge:meta block at the top. For Markdown, use the HTML comment form. For YAML, use the # forge:meta indented form. Do NOT modify any other content.
After writing, verify by re-reading the first lines and confirming the block parses.
Step 6: Report
Print a summary:
Migrated 3 files. Unmatched: 1 (.forge/scratch-notes.md — review and either add to canonical graph or delete).
Run /sync next to see the chain status with these new headers.
Recommend /sync as the natural follow-up — the user just installed headers; verifying the dependency chain is the immediate next step.
Verification
- Every candidate file's
generated_atis set to its mtime in UTC withZsuffix (not "now") - Every
content_hashis 8 hex chars, computed over the entire current file body -
generated_byfor each migrated file matches an entry in the canonical artifact table -
depends_onfor each migrated file matches the canonical graph (not fabricated) - Files not on the canonical list are reported under "unmatched", not migrated with invented headers
- Glob outputs (
contracts/*.md,adr/*.md) each got their own header - User confirmed the migration plan before any file was written
- No artifact's body content was modified — only the header was prepended
-
/syncrecommended as next step