spec-drift-finder
Spec drift finder — specs vs code, a person decides
Splits every spec (OpenSpec, ADRs, SPEC docs, API contracts) into atomic requirements, maps each to code, gets a calibrated verdict with evidence, asks a person change-code-or-change-spec per item, makes the minimal fix, re-proves each requirement conforms, and opens one PR behind approval.
| Source | templates/spec-drift-finder/workflow.yaml |
| Category | development |
| Trigger | manual (wfx run) · schedule 0 7 * * 1 |
| Steps | 15 |
| Human approval before | publish |
| Providers named | gemini |
Use it
Section titled “Use it”wfx templates spec-drift-finderwfx new spec-drift-finder --as my-spec-drift-finderwfx dryrun my-spec-drift-finderWhat it does
Section titled “What it does”Finds every spec in the repository, splits it into atomic requirements with file:line, maps each requirement to the code that implements it (one mapper per spec file, coverage proven by code), classifies each with a calibrated verdict over evidence read from the files, recommends change_code / change_spec / clarify with the smallest edit, and asks a person to decide every item that touches a spec. Makes the minimal code fix (with a test) or spec edit (an OpenSpec change delta where that is the convention), proves the suite stays green and every changed requirement now conforms, has a different model review it, and opens a PR with the requirement → verdict → decision → change table.
What you fill in
Section titled “What you fill in”- extra_specs: documents this repo treats as specs that the default globs miss
- test_cmd: the suite command, if auto-detection (Taskfile test / go / cargo / npm / pytest) picks the wrong one
- TYPESAFE_API_KEY (or OPENROUTER_API_KEY) in the env store for the calibrated verdicts
Inputs
Section titled “Inputs”| Input | Type | Default | Required | Meaning |
|---|---|---|---|---|
repo_path |
string | |||
repo_url |
string | |||
base_branch |
string | |||
extra_specs |
string | |||
exclude |
string | "openspec/changes/archive/**,**/node_modules/**,**/vendor/**,.wfx-*/**,**/README.md,**/INDEX.md,**/template*.md" |
||
max_requirements |
integer | 0 |
||
test_cmd |
string | |||
decisions |
string | |||
pr_prefix |
string | "[spec-drift]" |
||
dry_run |
boolean | true |
||
isolate |
boolean | true |
| # | Step | Kind | Skills | Gates |
|---|---|---|---|---|
| 1 | collectCollect specs and split into requirements |
shell (run:) |
exitCode = 70 → failexitCode = 78 → skip to report |
|
| 2 | mapMap every requirement to code (one mapper per spec file) |
agent team (1) | repo-navigator |
|
| 3 | coverageProve every requirement was mapped (code) |
shell (run:) |
exitCode = 70 → failexitCode = 78 → skip to judge |
|
| 4 | map_gapsMap the requirements nobody mapped |
agent | repo-navigator |
|
| 5 | judgeJudge every requirement (JEV) and plan |
shell (run:) |
exitCode = 70 → failexitCode = 78 → skip to report |
|
| 6 | recommendRecommend change_code / change_spec / clarify with the smallest edit |
agent | repo-navigator doc-syncer |
|
| 7 | decideA person decides every item (change_code / change_spec / skip) |
shell (run:) |
exitCode = 70 → failexitCode = 20 → needs_inputexitCode = 78 → skip to report |
|
| 8 | actMake each decided change (fix-author for code, doc-syncer for specs) |
agent | fix-author doc-syncer repo-navigator |
|
| 9 | verifyProve the branch (lanes, suite, re-judge to conforms) |
shell (run:) |
exitCode = 70 → failexitCode = 20 → needs_input |
|
| 10 | reviewAdversarial review on a different model (change-reviewer) |
agent on gemini |
change-reviewer |
approved = false → needs_input |
| 11 | write_prWrite the PR body (code) |
shell (run:) |
exitCode = 70 → fail |
|
| 12 | claimsCheck every change claim against its commit (JEV) |
shell (run:) |
exitCode = 20 → needs_inputexitCode = 70 → fail |
|
| 13 | dry_run_stopStop here on a dry run |
shell (run:) |
exitCode = 78 → skip to report |
|
| 14 | publishPush and open the PR |
shell (run:) |
human approval firstexitCode = 70 → fail |
|
| 15 | reportReport |
shell (run:) |
Human gates
Section titled “Human gates”publishwaits forwfx approve <run-id>(orwfx reject <run-id> -m "why") before it runs.decidepauses for input whenexitCodeis20: “{{ .Output.stdout }}”. Resume withwfx answer <run-id> -m "...".verifypauses for input whenexitCodeis20: “The branch did not prove out — {{ .Output.stdout }} (answer accept-verify to continue anyway; the PR will say so)”. Resume withwfx answer <run-id> -m "...".reviewpauses for input whenapprovedisfalse: “The reviewer blocked the branch: {{ .Output.verdict }}”. Resume withwfx answer <run-id> -m "...".claimspauses for input whenexitCodeis20: “PR claims not supported by their commits — {{ .Output.stdout }} (answer accept-claims to publish anyway; the body will say so)”. Resume withwfx answer <run-id> -m "...".
Real result
Section titled “Real result”- hand-drawn-diagrams: the run published hand-drawn-diagrams#4, titled “[spec-drift] 1 code fix(es), 2 spec update(s) from 94 checked requirements”, and it was merged (commit
62d8df7: “Proven live: hand-drawn-diagrams PR #4”). - citenexus: a run over 698 requirements failed at publish with GitHub’s “Body is too long”. The PR body is now trimmed to fit GitHub’s 65,536-character limit: conforming rows are dropped first, and the full table is kept in
pr-body-full.md(commit4a9520f).