--- name: openspec-drift description: The Level 6 spec-drift audit — cross-references OpenSpec against GSD plans, git diff, tests, and evidence to find the nine drift classes (requirement without implementation, code without requirement, closed task with no diff, behavior change with no spec delta, and so on). Consumed by openspec-verify for its roll-up and by openspec-audit in strict mode and above. allowed-tools: Read, Bash, Grep, Glob license: MIT metadata: author: openspec-gsd-integration version: "0.1.0" --- ## Purpose Every other verification level asks "did we do what the spec said?" This one asks the harder, opposite question too: "is there anything here the spec never said?" Drift is bidirectional, and the direction nobody checks — code with no requirement behind it — is where scope creep and undocumented behavior accumulate. ## Invocation conditions - Composed by `openspec-audit` in `strict`, `release`, and `forensic` modes. - Consumed by `openspec-verify` as its Level 6 — that skill reads this one's summary verdict and does **not** reimplement the checks inline. - Directly, `/openspec-drift`, when a change feels like it has grown beyond its proposal. ## Required inputs - A change-id, or "all unshipped changes" (default). - A phase range to diff against (defaults to the phase's first commit through `HEAD`). ## Files read - `openspec/changes//**` and `openspec/specs/**` (including `changes/archive/` in forensic mode) - `.planning/openspec/TRACEABILITY.md`, `EVIDENCE.json`, `id-registry.json` - `.planning/phases/-/**` — `OPENSPEC-LINK.md`, `PLAN.md`, `SUMMARY.md`, `VERIFICATION.md` - `git log --oneline ..HEAD`, `git diff --stat`, `git diff` per commit ## Files this skill may write - `.planning/openspec/DRIFT.md` (overwritten each run — a snapshot, like `AUDIT-SUMMARY.md`) - Never writes to `openspec/`. Never writes to `EVIDENCE.json` (that is `openspec-verify`/`openspec-traceability` territory). ## Algorithm — the nine drift classes Build the five-way cross-reference (OpenSpec ↔ GSD plans ↔ git diff ↔ tests ↔ evidence) from `TRACEABILITY.md`, then scan for each class: | # | Drift class | Detection | Severity | |---|---|---|---| | 1 | Requirement without implementation | Requirement ID resolves to no GSD task and no code | **blocking** if `MUST` | | 2 | Requirement without test | Requirement resolves to code but Test column is `—` | **blocking** if `MUST` | | 3 | Scenario without evidence | Scenario has no `EVIDENCE.json` entry | **blocking** | | 4 | Code without requirement | A file in the phase diff maps to no plan task carrying a requirement ID | advisory → blocking in `strict` | | 5 | Test without scenario | A new test file exercises behavior no scenario describes | advisory (often a good test — but the *spec* is then incomplete) | | 6 | Closed task with no change | `tasks.md` item checked `[x]`, but its cited files are absent from the diff | **blocking** | | 7 | Behavior change without spec delta | Diff touches a file that existing `openspec/specs/` requirements depend on, with no corresponding `MODIFIED` entry in the change's spec delta | **blocking** | | 8 | Design decision not reflected in implementation | `design.md` states an approach the code demonstrably does not take | **blocking** in `strict` | | 9 | Implementation wider than agreed scope | Diff includes capability the proposal's scope section excludes, or that `proposal.md` lists under non-goals | **blocking** | Classes 4, 5, 7, 8 and 9 require agent judgment and must cite the specific file/line and the specific spec passage they are judged against — a finding with no citation is not reportable. ## Outputs `.planning/openspec/DRIFT.md`, with frontmatter: ```yaml --- change_id: findings_total: 3 findings_blocking: 1 verdict: clean | findings | human_needed generated_at: --- ``` and one section per finding: class, severity, citation (file:line and spec passage), and the recommended route (`openspec-repair` for fixable, `/opsx:update` for spec-side, `gsd-tools state add-blocker` for external). ## Failure states - Git history unavailable or the phase's commit range cannot be resolved (squashed/rebased history) → `verdict: human_needed`; classes 1–3 are still reported from the matrix alone, and classes 4–9 are explicitly marked `NOT_CHECKED` rather than reported as clean. - `TRACEABILITY.md` missing or stale relative to the specs → run `openspec-traceability` first; do not audit against a stale matrix. ## Guardrails - Never reports `verdict: clean` for checks it could not run — `NOT_CHECKED` is a distinct, visible state, and it is never rolled up as a pass. - Never resolves drift by editing anything. This skill only observes; `openspec-repair` proposes and GSD's executor acts. - Never treats "the tests are green" as evidence against classes 4, 7, or 9 — undocumented behavior usually has passing tests; that is precisely why it goes unnoticed. - Class 5 (test without scenario) is never auto-blocking: deleting a good test to make an audit pass is explicitly forbidden by the project's guardrails, so this class always routes to "add the missing scenario," never "remove the test." ## Idempotency Re-running against unchanged specs, code, and matrix reproduces the same finding set and verdict. Findings are identified by `class + citation`, so a finding that persists across runs is recognisably the same finding, not a duplicate. ## Example ``` $ /openspec-drift add-health-check-endpoint [openspec-drift] 5-way cross-reference · 2 requirements, 3 scenarios, 7 commits ✓ class 1,2,3,6,7,8 clean ⚠ class 4 code without requirement — src/health/metrics.ts (new, 40 lines) no plan task carries a requirement ID covering metrics emission ⚠ class 5 test without scenario — tests/health.metrics.test.ts verdict: findings (0 blocking at standard, 1 blocking at strict) → recommended: /opsx:update add-health-check-endpoint (add an observability requirement) ```