# Archive Prd

> Archives completed stories from prd.json to reduce token usage.

- Skill: `djnsty23/archive-prd` (Agent Skill)
- Install (CLI): `npx skillmds@latest add djnsty23/archive-prd`
- Raw SKILL.md: https://api.skillmd.com/api/skills/djnsty23/archive-prd/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Product & Planning
- Author: djnsty23 (https://skillmd.com/u/djnsty23)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/djnsty23/archive-prd

---


# PRD Archival System

Archive completed stories to keep prd.json fast and small. Keep only last 3 sprints active.

## "archive" Command

```
1. Read prd.json
2. Separate stories with isArchivable() from scripts/prd-states.js:
   - ARCHIVE: isArchivable(story) === true  (passes===true, i.e. isDone)
   - KEEP:    everything else — null, false, "deferred", "needs-setup",
              a MISSING passes key, any unrecognised value, and type="qa"
              (even passed QA stories stay, for re-testing)
3. PROVE THE SPLIT BEFORE WRITING: archive-count + keep-count must equal the
   before-count, and no story id may appear in both sets or in neither.
   If the invariant fails, STOP — write nothing, report the ids that fell
   through. This runs BEFORE step 4, not after: a loss detected after the
   write is a loss.
4. Create archive file: prd-archives/prd-archive-YYYY-MM.json (a TRACKED path)
5. Update main prd.json with summary
6. Report: "Archived X stories, Y remain active" with both counts and the total
```

**Why the two-bucket version was destructive:** the old split (ACTIVE:
false|null|qa / COMPLETED: true) matched neither bucket for `"deferred"`,
`"needs-setup"`, and keyless stories — they were written to neither file and
silently deleted. `isArchivable()` exists because of exactly that incident (its
own comment block records it); route through it rather than re-deriving buckets.

## New prd.json Schema (After Archive)

```json
{
  "project": "Project Name",
  "version": "1.3.2",
  "lastUpdated": "2026-01-22",
  "roadmapPhase": "Current Phase",

  "archived": {
    "totalCompleted": 41,
    "lastArchived": "2026-01-22",
    "files": ["prd-archives/prd-archive-2026-01.json"],
    "summary": {
      "S01-S10": "Core foundation - registry, funnels, OAuth, caching",
      "S11-S20": "Navigation, QA, dashboard, exports, favorites",
      "S21-S30": "Time granularity, GA4 schema, accessibility, mobile",
      "S31-S41": "Token refresh, metrics, documentation, scope rules"
    }
  },

  "stories": [
    // Only active/pending stories here
  ]
}
```

## Archive File Schema

```json
{
  "archivedAt": "2026-01-22T10:00:00Z",
  "project": "Project Name",
  "version": "1.3.2",
  "stories": [
    // Full story objects for reference
  ]
}
```

## When to Archive

| Condition | Action |
|-----------|--------|
| 4+ total sprints | Auto-suggest archive |
| prd.json > 500 lines | Suggest archive |
| prd.json > 50KB | Force archive |
| User says "archive" | Manual archive |
| All stories complete | Archive and start fresh |

Keep only the last 3 sprints in prd.json. Archive everything older.

## Archive Process

```
0. PROVE THE DESTINATION IS DURABLE — BEFORE ANY WRITE
   node ${CLAUDE_PLUGIN_ROOT}/scripts/check-archive-path.js prd-archives/prd-archive-$(date +%Y-%m).json
   Exit 1 means git would ignore the path: STOP and write nothing. Do not
   "fix" it with a .gitignore negation — a `!` rule cannot re-include a file
   whose parent directory is excluded, so it looks like an exception and grants
   nothing. Write somewhere outside the ignored tree instead.

1. BACKUP
   mkdir -p prd-archives
   cp prd.json prd-archives/prd-backup-$(date +%Y%m%d).json

2. EXTRACT ARCHIVABLE
   Filter with isArchivable() from scripts/prd-states.js (and keep type="qa"
   regardless). KEEP everything it rejects: null, false, "deferred",
   "needs-setup", a missing passes key, any unrecognised value.

3. PROVE THE SPLIT — BEFORE ANY WRITE
   archiveCount + keepCount === beforeCount, zero id overlap, zero ids in
   neither set. On failure: stop, write nothing, name the ids that fell
   through. (After the write this check can only report a loss, not prevent
   one.)

4. CREATE ARCHIVE
   Write to: prd-archives/prd-archive-YYYY-MM.json  (step 0 proved git keeps it)
   If prd.json already has an "archived" section, this is a RE-archive:
   append to files[] and ADD to totalCompleted — never overwrite it.

5. GENERATE SUMMARY
   Group stories by ID range (10 per group)
   Write 1-line summary per group

6. UPDATE MAIN PRD
   Remove archived stories
   Add/extend "archived" section with summary
   Keep all QA stories (even passed ones for re-testing)

7. VALIDATE
   Ensure main prd.json < 1500 lines
   Re-assert the step-3 invariant against the files as written
   Re-run step 0 against the archive AS WRITTEN, and confirm `git status`
   actually shows it. Counting stories proves completeness, not durability.
```

Note the shape this must survive: real projects store `stories` as an OBJECT
keyed by id (never `.filter()` it — `Object.values()` first), and `archived`
is a top-level key the archive itself adds, so a re-archive must not treat an
already-archived file as un-archived.

## Accessing Archived Stories

If you need details on an archived story:

```
User: "What was S15 about?"
Claude:
1. Check archived.summary for S15 range
2. Read prd-archives/prd-archive-2026-01.json if needed
3. Report story details
```

## Quick Reference

| Say | Action |
|-----|--------|
| `archive` | Archive completed stories |
| `archive status` | Show archive stats |
| `archive S15` | Show archived story S15 |
| `unarchive S15` | Restore story to active |

---

## Token Optimization

| State | Estimated Tokens |
|-------|-----------------|
| Full prd.json (70+ stories, 10 sprints) | ~25,000+ |
| After archive (current sprint + 2) | ~3,000-5,000 |
| With summary | +500 |
| **Total Savings** | **~80%** |

**Real example:** a long-running project can grow to 800+ rows / 10 sprints / ~20K tokens. After archiving to keep only the current sprint + 2 prior, prd.json drops to ~150 rows / ~3K tokens.

## Proving the run

Two properties, and they fail independently. Assert both.

**Observable 1 — COMPLETENESS: no story is lost.** Stories in the archive plus
stories left in prd.json equals the count before archiving.

```bash
node -e "const a=require('./prd.json');…"   # or just read both files and add up
```

Check the total before and after and state both numbers. A story dropped during
the move looks exactly like a story that was never there.

**Observable 2 — DURABILITY: the archive is a file git will keep.**

```bash
node ${CLAUDE_PLUGIN_ROOT}/scripts/check-archive-path.js <the archive you wrote>
git status --short <the archive you wrote>     # it must appear
```

⚠️ **Counting is not durability, and this is how the archive was lost.**
`[measured 2026-08-29]` a project archived 159 completed stories, and its count
check passed *correctly* — both files existed at that moment. The archive had
been written to `.claude/archives/`, a gitignored path, so `git add -A` skipped
it in silence and the commit carried only the deletion from prd.json. The archive
and the backup taken beside it lived on one machine's disk and nowhere else.
The count check could never have caught it: it measures completeness while the
failure mode is durability. A check that reports green about a property it does
not examine is this project's signature failure — see `skills/rule-gate-integrity`.

**If an archive was already lost this way, it is probably recoverable.**
Archiving REMOVES stories from a tracked `prd.json`, so the commit *before* the
archive commit holds the complete pre-archive state — every story with its full
`verified` record:

```bash
git log --oneline -S'"archived"' -- prd.json    # find the archive commit
git show <archive-commit>^:prd.json             # the real backup
```

That recovered the full 164-story file in the incident above. Do **not** send
anyone to the archive path for recovery: under the conditions that lose the
archive, that file is exactly what does not exist.

