status, sync, verify, seal, and validate accept --format=json for
consumers that run SpecMan as a subprocess. --format=human is the default;
any other value is a usage error and the command exits 1 without doing work.
Every JSON-mode run that reaches a result writes exactly one document to stdout:
{
"schemaVersion": 1,
"command": "sync",
"result": {}
}schemaVersionis one integer for the whole tool. Adding a field, or a new member of an existing set of values, does not bump it; renaming a field, removing one, or changing what a value means does.- Nothing else goes to stdout in JSON mode. When a run fails before a result
exists (no
specs/, missing FEAT-ID, dirty tree), the error goes to stderr, stdout stays empty, and the exit code is the one documented for that failure. - Exit codes are identical in human and JSON mode. JSON changes the output channel, not the semantics.
messagefields are human-oriented and may be reworded at any time. Branch on discriminators (outcome,reason,status,type), never on prose.
Per-command payloads are described under each command below.
Initialize SpecMan in the current directory.
specman init
Creates specs/, .specman/, .specman/implemented/, .specman/plans/. Purely additive — never deletes or overwrites. Idempotent — safe to run multiple times.
| Condition | Behavior |
|---|---|
| Clean directory | Creates all 4 directories, exits 0 |
| Already initialized | Prints "Already initialized", exits 0 |
| Partial state | Creates only missing directories, exits 0 |
| Path conflict (file where dir expected) | Error, exits 1, creates nothing |
No .git |
Warning (sync/status need git), still creates, exits 0 |
Create a new spec scaffold.
specman new "<title>" [--group <name>] [--id FEAT-NNNN]
| Flag | Description |
|---|---|
--group <name> |
Place file in specs/<name>/, creating the dir if needed |
--id FEAT-NNNN |
Force a specific ID instead of auto-assigning |
ID assignment: Scans specs/ recursively for the highest existing FEAT-NNNN and assigns max + 1. IDs are never reused.
Slug: Derived from title — lowercase, non-alphanumeric → hyphens, no leading/trailing hyphens.
Output: Prints the created file path to stdout (single line, nothing else).
Scaffold: Required frontmatter + empty ## Intent and ## Acceptance criteria. Not yet valid per specman validate — the author fills in content.
| Exit code | Meaning |
|---|---|
| 0 | File created |
| 1 | Missing title, ID collision, or other error |
Check specs for errors and warnings.
specman validate [--format=json] [--strict]
| Flag | Description |
|---|---|
--format=json |
Machine-readable output: result is { summary, findings } |
--strict |
Warnings cause non-zero exit (normally only errors do) |
Checks performed:
| Code | Severity | What |
|---|---|---|
| E000 | error | Spec file fails to parse (malformed YAML, no frontmatter) |
| E001 | error | Duplicate id across files |
| E002 | error | Missing required frontmatter field (id, title, status, depends_on) |
| E003 | error | Wrong type for frontmatter field |
| E005 | error | Required section (Intent, Acceptance criteria) empty or missing |
| E006 | error | depends_on references nonexistent spec |
| E007 | error | Dependency cycle (length ≥ 2; self-refs ignored) |
| E008 | error | Duplicate AC ID within a single spec |
| E009 | error | Orphan snapshot (no matching spec) |
| E010 | error | Snapshot ID mismatch (filename vs content) |
| E011 | error | Orphan plan (no matching spec) |
| E012 | error | Invalid status value |
| W001 | warning | Filename doesn't match <FEAT-ID>-<slug>.md convention |
| Exit code | Meaning |
|---|---|
| 0 | No errors (warnings allowed unless --strict) |
| 1 | Validation errors found |
| 2 | No specs/ directory found |
Show drift status of all specs.
specman status [--verbose | -v] [--diff] [--format=json]
| Flag | Description |
|---|---|
--verbose, -v |
Show all specs including in-sync ones |
--diff |
Include unified diff for drifted specs |
--format=json |
Machine-readable output (see below) |
Output format:
FEAT-0001 drifted (changed since last sync)
FEAT-0002 new (no snapshot yet)
3 specs in-sync
Drift detection uses canonical-form comparison: the spec is parsed and re-serialized before comparing against the snapshot. This means formatting-only differences (frontmatter key order, blank lines) don't count as drift.
JSON payload: result.entries lists every spec (--verbose and --diff
shape the human view only) with id, status, specPath, snapshotPath, and
hint when there is one. new and drifted entries also carry driftSet: the
acceptance criteria that changed, each with id, type (added, modified,
removed), and text. When the drift set cannot be computed (unparseable
snapshot), the entry carries driftSet: null and driftError instead; the exit
code stays 0.
| Exit code | Meaning |
|---|---|
| 0 | Always (status is informational) |
Sync drifted specs — generate plans, run verification, seal snapshots.
specman sync [<FEAT-ID>] [--dry-run] [--regenerate] [--format=json]
| Flag | Description |
|---|---|
<FEAT-ID> |
Sync a single spec. Without it, syncs all drifted/new specs |
--dry-run |
Show what would be synced without generating plans |
--regenerate |
Discard a plan with uncommitted changes and write a fresh scaffold |
--format=json |
Machine-readable output (see below) |
Single-spec sync: Generates a plan scaffold targeting only changed ACs, writes it to .specman/plans/<FEAT-ID>.md.
Multi-spec sync: Processes specs in dependency order. If a spec fails, it and all transitive dependents are skipped; independent specs continue.
Clean working tree required at start, except uncommitted plan files in the sync scope.
One sync, two invocations. Execution happens outside SpecMan, so a sync normally takes two runs: one writes the plan, and a later one closes it out. The second run attempts to seal when the plan declares at least one verification command and at least one commit exists since the plan's base commit (recorded in the plan header). Then, in order: the scope check, the trailer check, the plan's verification commands, and finally the snapshot commit.
| Situation | What sync does |
|---|---|
| Plan declares no commands | Leaves the plan alone; it is still an unfilled scaffold |
| No commits since the base commit | Reports the plan as awaiting implementation; nothing is sealed |
A commit lacks a Spec: <FEAT-ID>/AC-N trailer |
trailer-check-failed, naming the commits; nothing is sealed |
| A verification command fails, or dirties the tree | verification-failed, with the command detail; nothing is sealed |
| The plan's ACs no longer match the spec's drift set | Scope error; nothing runs |
| The plan has no base commit (written by an older SpecMan) | Refuses, directing to --regenerate |
| Everything passes | sealed: the snapshot commit is the last commit of the invocation |
On any of those failures the plan stays on disk untouched and the spec stays drifted, so a later invocation retries from the same plan. --regenerate takes precedence over closing out.
Resume, scaffold, or regenerate. If the plan file has uncommitted changes (including an untracked file), sync resumes from it as-is and never overwrites it. --regenerate is the only way to discard that content. If the plan is absent or byte-identical to HEAD, a fresh scaffold is written; the committed plan it replaced is recoverable with git restore. Sync is non-interactive: it never asks which path to take, and it always says which one it took.
| Path | planSource |
What happened |
|---|---|---|
| resume | resumed |
An uncommitted plan was kept verbatim |
| scaffold | scaffolded |
No plan existed, or the existing one matched HEAD |
| regenerate | regenerated |
--regenerate discarded an uncommitted plan |
JSON payload: three shapes under one "command": "sync", told apart by their
fields.
| Invocation | result |
|---|---|
| single spec | featId, outcome, message, plus planPath / planSource / verificationResult / trailerResult when they apply |
| all specs | results (one per spec, same shape as above) and skipped (featId, reason) |
--dry-run |
dryRun: true and entries (featId, status, summary) |
outcome is one of in-sync, no-ac-drift, plan-written, sealed,
verification-failed, trailer-check-failed, error.
| Exit code | Meaning |
|---|---|
| 0 | Sync completed: plan written, spec sealed, or nothing to sync |
| 1 | Error (dirty tree, parse failure, scope mismatch), failed verification, or failed trailer check |
Seal a snapshot for editorial or initial changes.
specman seal <FEAT-ID> [--initial] [--verify] [--format=json]
| Flag | Description |
|---|---|
--initial |
Create first snapshot for a new spec (no prior sync needed) |
--verify |
Run the plan's verification, then seal if it passes (allows AC-level drift) |
--format=json |
Machine-readable output (see below) |
Without flags: Updates the snapshot for a drifted spec. Requires no AC-level drift (only prose/metadata changes). Refuses if ACs were added, removed, or changed.
With --initial: Creates the first snapshot for a new spec. Use when implementation was done outside SpecMan.
With --verify: Seals a spec whose acceptance criteria did change, on the strength of its plan's verification. Runs the commands in the plan's ## Verification section exactly as specman verify does, and writes the snapshot only if all of them pass on a clean tree.
The AC-drift refusal asks for evidence rather than permission: the snapshot is SpecMan's only assertion that a spec is implemented, so it has to mean "verification passed" and not "someone said so". --verify produces that evidence at seal time, from commands the plan declares. There is deliberately no flag that simply skips the gate.
Consequences worth knowing:
- A plan with at least one runnable command is required. SpecMan never infers verification commands (FEAT-0009), so with no plan the honest path is to run
specman sync <FEAT-ID>and declare what would convince a reader the work is done. - The clean-tree requirement is not relaxed, before or after the commands run. Only this spec's own plan file may be uncommitted.
- Works on a
newspec too, where it does what--initialdoes but with evidence attached.
Clean working tree required.
JSON payload: outcome (sealed or error) and message. A refusal also
carries reason:
reason |
Meaning |
|---|---|
ac-drift |
Acceptance criteria changed; the spec needs specman sync. Carries driftSet |
wrong-status |
The spec's status does not match the operation (e.g. --initial on a spec that already has a snapshot) |
dirty-tree |
Uncommitted changes present. Carries dirtyPaths |
no-spec |
No spec exists for that id |
drift-error |
The drift set could not be computed (e.g. unparseable snapshot) |
commit-failed |
The snapshot commit itself failed |
verification-failed |
--verify only: a command exited non-zero. Carries verificationResult |
no-plan |
--verify only: no plan file to read verification commands from |
empty-verification |
--verify only: the plan declares no runnable commands |
plan-parse-error |
--verify only: the plan file could not be parsed |
| Exit code | Meaning |
|---|---|
| 0 | Snapshot sealed |
| 1 | Error (AC drift, wrong status, dirty tree) |
Run a plan's verification commands standalone.
specman verify <FEAT-ID> [--plan <path>] [--format=json]
| Flag | Description |
|---|---|
--plan <path> |
Use a plan file at an arbitrary path |
--format=json |
Machine-readable output (see below) |
Reads verification commands from the plan's ## Verification section. Runs each sequentially via sh -c. Stops on first failure. Checks for dirty working tree after each command.
Does not write snapshots or create commits — purely diagnostic.
JSON payload: outcome (passed, command-failed, dirty-tree, no-plan,
empty-verification, parse-error), message, and planPath. When commands
ran, verificationResult carries passed and one entry per command with
command, exitCode, stdout, and stderr (every command that ran, not just
the failing one), plus dirtyPaths on a dirty-tree failure.
| Exit code | Meaning |
|---|---|
| 0 | All commands passed, tree clean |
| 1 | Command failed, dirty tree, or no plan found |
Remove a spec and all tracked artifacts.
specman delete <FEAT-ID> [--dry-run]
| Flag | Description |
|---|---|
--dry-run |
Show what would be removed without deleting |
Removes:
- Spec file (
specs/**/<FEAT-ID>-*.md) - Snapshot (
.specman/implemented/<FEAT-ID>.md) - Plan (
.specman/plans/<FEAT-ID>.md) - Assets directory (
specs/assets/<FEAT-ID>/)
Warns about dependent specs but proceeds. Non-interactive — no confirmation prompt (git is the undo mechanism).
| Exit code | Meaning |
|---|---|
| 0 | Deleted (or dry-run completed) |
| 1 | Spec not found |