diff --git a/specter/README.md b/specter/README.md index 9ecd96d..2184083 100644 --- a/specter/README.md +++ b/specter/README.md @@ -4,7 +4,7 @@ Specs without validation are just documents. They can contradict each other, reference dependencies that don't exist, have constraints that no test ever covers, and silently rot as code evolves. Specter treats specs as typed artifacts in a dependency graph, subject to the same static analysis you apply to code. -Specter is content-agnostic: a `.spec.yaml` can describe runtime behavior, data invariants, security policy, schema contracts, architecture rules, or any other component contract — the pipeline validates the shape (constraints, ACs, traceability), not the category of rule you are encoding. +Specter is content-agnostic: a `.spec.yaml` can describe runtime behavior, data invariants, security policy, schema contracts, architecture rules, or any other component contract. The pipeline validates the shape (constraints, ACs, traceability), not the category of rule you are encoding. ``` $ specter sync @@ -21,15 +21,15 @@ All checks passed. ## Human Intent, AI Execution -Specter's schema is deliberately detailed — constraints, acceptance criteria, tiers, provenance, coverage thresholds. Writing all of that by hand for every module would be impractical, and that was never the intention. +Specter's schema is deliberately detailed: constraints, acceptance criteria, tiers, provenance, coverage thresholds. Writing all of that by hand for every module would be impractical, and that was never the intention. The intended workflow is a collaboration between you and your AI coding assistant: -1. **You provide intent** — a brief description of what a module should do, its key constraints, and any non-obvious judgement calls or trade-offs -2. **The AI writes the spec** — translating your intent into a fully structured `.spec.yaml` file with constraints, ACs, and tier assignments -3. **The AI writes the tests** — derived directly from the ACs in the spec -4. **You review** — the spec and tests are the approval gate; you validate that the AI correctly captured your intent before any implementation begins -5. **The AI implements** — with the spec as the contract and the tests as the verification +1. **You provide intent.** A brief description of what a module should do, its key constraints, and any non-obvious judgment calls or trade-offs +2. **The AI writes the spec.** It translates your intent into a fully structured `.spec.yaml` file with constraints, ACs, and tier assignments +3. **The AI writes the tests.** They derive directly from the ACs in the spec +4. **You review.** The spec and tests are the approval gate. You validate that the AI correctly captured your intent before any implementation begins +5. **The AI implements.** The spec is the contract and the tests are the verification Specter enforces the discipline at every step: the spec must exist before code, tests must trace to ACs, and coverage must meet the tier threshold before `specter sync` passes. It makes the process infrastructure, not a suggestion. @@ -45,7 +45,7 @@ Search **Specter SDD** in the Extensions panel. The extension auto-downloads the ### CLI, Linux / macOS (tar.gz) -Asset names follow Go's `GOOS`/`GOARCH` conventions (lowercase `linux`/`darwin`, `amd64`/`arm64`) — not `uname`'s `Linux`/`x86_64`. This snippet translates and picks the latest version automatically: +Asset names follow Go's `GOOS`/`GOARCH` conventions (lowercase `linux`/`darwin`, `amd64`/`arm64`), not `uname`'s `Linux`/`x86_64`. This snippet translates and picks the latest version automatically: ```bash OS=$(uname -s | tr '[:upper:]' '[:lower:]') @@ -97,7 +97,7 @@ make build ### Manual download -If you prefer clicking, every asset is listed on the [Releases page](https://github.com/Hanalyx/specter/releases/latest). Naming pattern: `specter___.` — lowercase OS, `amd64`/`arm64` arch. +If you prefer clicking, every asset is listed on the [Releases page](https://github.com/Hanalyx/specter/releases/latest). Naming pattern: `specter___.`, with lowercase OS and `amd64`/`arm64` arch. --- @@ -121,7 +121,7 @@ Specter runs five stages in sequence. Each stage catches a different class of pr ### What each stage catches -**`specter parse`** — Catches malformed specs before anything else runs. Missing required fields, IDs that don't match the allowed pattern, invalid enum values, wrong types. Like a compiler catching syntax errors. +**`specter parse`** catches malformed specs before anything else runs. Missing required fields, IDs that don't match the allowed pattern, invalid enum values, wrong types. Like a compiler catching syntax errors. ```bash specter parse specs/auth.spec.yaml @@ -130,7 +130,7 @@ specter parse specs/auth.spec.yaml # ERROR: spec-auth.spec.yaml [pattern] constraint ID 'constraint-1' does not match C-NN format ``` -**`specter resolve`** — Builds the dependency graph across all specs and validates it. Catches circular dependencies and references to specs that don't exist. +**`specter resolve`** builds the dependency graph across all specs and validates it. Catches circular dependencies and references to specs that don't exist. ```bash specter resolve @@ -139,16 +139,16 @@ specter resolve # ERROR: spec-auth depends on spec-session@^1.0.0 but spec-session is not found ``` -**`specter check`** — Finds structural problems within and between specs. An orphan constraint — one that no acceptance criterion references — is a constraint that can never be tested. A tier conflict catches when a Tier 1 spec depends on a Tier 3 spec. +**`specter check`** finds structural problems within and between specs. An orphan constraint, one that no acceptance criterion references, is a constraint that can never be tested. A tier conflict catches when a spec's declared `tier:` disagrees with the tier `settings.tier_overrides` assigns it in `specter.yaml`. The mismatch is reported, and the declared tier stays in effect. ```bash specter check -# WARN: spec-auth [orphan_constraint] C-04 is not referenced by any AC -# ERROR: spec-payments [tier_conflict] Tier 1 spec depends on Tier 3 spec-util +# ERROR: spec-auth [orphan_constraint] C-04 is not referenced by any AC +# WARN: spec-payments [tier_conflict] declares tier: 2 but tier_overrides assigns tier: 1 ``` -**`specter coverage`** — Reads `@spec` and `@ac` annotations from your test files and produces a traceability matrix. Enforces tier-based coverage thresholds. +**`specter coverage`** reads `@spec` and `@ac` annotations from your test files and produces a traceability matrix. Enforces tier-based coverage thresholds. ```bash specter coverage @@ -159,7 +159,7 @@ specter coverage # spec-payments T2 5 5 100% PASS ``` -**`specter sync`** — Runs all four stages and exits 0 only when everything passes. Put this in CI. +**`specter sync`** runs all four stages and exits 0 only when everything passes. Put this in CI. --- @@ -253,9 +253,9 @@ Coverage thresholds scale with risk: | Tier | Examples | Coverage required | |---|---|---| -| **T1** — Security / Money | Auth, payments, encryption | 100% | -| **T2** — Business logic | Booking flow, pricing rules | 80% | -| **T3** — Utility | Formatters, helpers | 50% | +| **T1**: Security / Money | Auth, payments, encryption | 100% | +| **T2**: Business logic | Booking flow, pricing rules | 80% | +| **T3**: Utility | Formatters, helpers | 50% | A Tier 1 spec below threshold is a CI failure. A Tier 3 spec below threshold is a warning. @@ -267,12 +267,12 @@ Specs map to programming type concepts one-for-one: | Type system | Specter equivalent | |---|---| -| Type definition | Constraint — defines what's allowed | -| Function signature | Acceptance criterion — input → expected output | -| Import statement | `depends_on` — formal contract between specs | -| Type error | Spec conflict — caught before code runs | -| Unused variable | Orphan constraint — no AC references it | -| Missing null check | Coverage gap — an AC with no test | +| Type definition | Constraint: defines what's allowed | +| Function signature | Acceptance criterion: input to expected output | +| Import statement | `depends_on`: formal contract between specs | +| Type error | Spec conflict: caught before code runs | +| Unused variable | Orphan constraint: no AC references it | +| Missing null check | Coverage gap: an AC with no test | --- @@ -305,7 +305,7 @@ make dogfood-strict # mechanical eval gate: go test -json + jest → specter in make build-all # cross-compile for linux/darwin/windows ``` -Every package in `internal/` is a pure function — no I/O, no CLI dependencies. +Every package in `internal/` is a pure function: no I/O, no CLI dependencies. --- diff --git a/specter/docs/CLI_REFERENCE.md b/specter/docs/CLI_REFERENCE.md index 89bb1b4..8874237 100644 --- a/specter/docs/CLI_REFERENCE.md +++ b/specter/docs/CLI_REFERENCE.md @@ -1,12 +1,12 @@ # Specter CLI Reference -Specter is a spec compiler toolchain — "a type system for specs." It validates, links, and type-checks `.spec.yaml` files the way `tsc` validates `.ts` files. +Specter is a spec compiler toolchain, "a type system for specs." It validates, links, and type-checks `.spec.yaml` files the way `tsc` validates `.ts` files. --- ## Installation -Install the VS Code extension for the smoothest path — it auto-downloads the CLI and sets PATH. For CLI-only installs (tar.gz, `.deb`, `.rpm`, Windows zip, or build from source), see the [Install section in the Specter README](../README.md#install). Asset naming pattern: `specter___.` with lowercase `linux`/`darwin`/`windows` and `amd64`/`arm64`. +Install the VS Code extension for the smoothest path. It auto-downloads the CLI and sets PATH. For CLI-only installs (tar.gz, `.deb`, `.rpm`, Windows zip, or build from source), see the [Install section in the Specter README](../README.md#install). Asset naming pattern: `specter___.` with lowercase `linux`/`darwin`/`windows` and `amd64`/`arm64`. --- @@ -187,7 +187,7 @@ specter check [--json] [--tier ] [--strict] [--test] |------------|-----------------|-------------| | `orphan_constraint` | T1=error, T2=warning, T3=info | A constraint is not referenced by any acceptance criterion. Individual constraints may override severity via `constraint.enforcement`. | | `structural_conflict` | error (override via `constraint.enforcement`) | An upstream constraint requires something that a downstream AC handles as absent. | -| `tier_conflict` | warning | A higher-tier spec depends on a lower-tier spec (e.g., Tier 1 depends on Tier 3). | +| `tier_conflict` | warning (text output only; not escalated by `--strict`) | A spec's declared `tier:` disagrees with the tier `settings.tier_overrides` assigns it in `specter.yaml`. The disagreement is reported, not resolved. See the note below the table. | | `unknown_spec_ref` | error (under `--test`) | A test annotates `@spec ` but no spec with that ID was parsed. Emitted only under `--test`. | | `unknown_ac_ref` | error (under `--test`) | A test annotates `@ac AC-NN` but the spec has no AC with that ID. Emitted only under `--test`. | | `unreachable_annotation` | by `settings.strictness`: annotation→suppressed, threshold→warning, zero-tolerance→error | Source-comment `@ac` whose enclosing test produces no runner-visible `/AC-NN` token (Convention A) and no runtime print (Convention B). Such annotations would silently demote under `coverage --strict`. Per-file off-switch: `// @reachable manual` (`# @reachable manual` for Python). Added in v0.13.0. | @@ -195,17 +195,20 @@ specter check [--json] [--tier ] [--strict] [--test] When a constraint has a `type` (e.g. `security`, `performance`), it appears in parentheses after the constraint ID so diagnostics can be grouped by category. +**`tier_conflict` reports a disagreement; it does not change behavior.** The declared `tier:` in the `.spec.yaml` continues to govern orphan severity and coverage thresholds. `settings.tier_overrides` is parsed and compared against the declaration, but the override value is never applied. spec-manifest C-14 specifies parsing and warning only. Because the diagnostic originates in the manifest layer rather than the checker, `--strict` does not upgrade it and it does not appear in `--json` output, so the JSON warning count can be lower than the text one. Note also that the emitted message ends with `— using override (N)`, which overstates what happens: the override is not in effect. + **Example:** ``` $ specter check -warn [orphan_constraint] spec-auth C-04 (security): C-04 is not referenced by any AC -error [tier_conflict] spec-payments: Tier 1 spec depends on Tier 3 spec-util +warn [tier_conflict] spec "spec-payments" declares tier: 2 but specter.yaml tier_overrides assigns tier: 1 — using override (1) +warn [orphan_constraint] spec-auth C-04 (security): Constraint C-04 in "spec-auth" is not referenced by any acceptance criterion -1 error(s), 1 warning(s), 0 info +0 error(s), 2 warning(s), 0 info $ specter check --strict -# Warnings are now treated as errors — exits 1 +# Checker warnings are now treated as errors — the orphan_constraint becomes an +# error and the command exits 1. The tier_conflict warning is unaffected. ``` **Exit codes:** `0` = no errors (warnings allowed unless `--strict`). `1` = one or more errors. @@ -226,13 +229,13 @@ specter coverage [--json] [--failing] [--strict] [--scope ] [--tests ` | — | Narrow `--strict`'s demand to ACs of specs in the named `specter.yaml` domain. Specs outside the domain fall back to v0.9 boolean-passed logic. Enables staged adoption. Requires `--strict`; unknown domain fails fast. Added in v0.10. | -| `--strictness ` | manifest setting | Override `settings.strictness`. Values: `annotation`, `threshold`, `zero-tolerance`. `threshold` and `zero-tolerance` route through the same strict path as `--strict` — `.specter-results.json` is required (the error names the active mode and offers both remedies) and non-passed annotated ACs demote across all tiers. Because the manifest default is `threshold`, plain `specter coverage` behaves strictly unless `settings.strictness: annotation` is set. `annotation` keeps structural annotation counting. `--strict` enables the same strict path and is equivalent to `--strictness threshold` under the default manifest strictness; it does not override a manifest-set level (a `zero-tolerance` manifest keeps its zero-tolerance gates under `--strict`, and `--strict` with `strictness: annotation` is an error). | +| `--json` | n/a | Output the coverage report as JSON. | +| `--failing` | n/a | Show only specs below 100% coverage in the table. Summary header still reflects the full report. When all specs are at 100%, emits a single-line confirmation instead of an empty table. Added in v0.9.2. | +| `--strict` | n/a | Require `.specter-results.json` and treat any annotated AC whose status is not `passed` as uncovered, across **all tiers**. Missing file is a hard failure; empty file emits a warning and proceeds. Pairs with `specter ingest`. Added in v0.10. | +| `--scope ` | n/a | Narrow `--strict`'s demand to ACs of specs in the named `specter.yaml` domain. Specs outside the domain fall back to v0.9 boolean-passed logic. Enables staged adoption. Requires `--strict`; unknown domain fails fast. Added in v0.10. | +| `--strictness ` | manifest setting | Override `settings.strictness`. Values: `annotation`, `threshold`, `zero-tolerance`. `threshold` and `zero-tolerance` route through the same strict path as `--strict`. `.specter-results.json` is required (the error names the active mode and offers both remedies) and non-passed annotated ACs demote across all tiers. Because the manifest default is `threshold`, plain `specter coverage` behaves strictly unless `settings.strictness: annotation` is set. `annotation` keeps structural annotation counting. `--strict` enables the same strict path and is equivalent to `--strictness threshold` under the default manifest strictness; it does not override a manifest-set level (a `zero-tolerance` manifest keeps its zero-tolerance gates under `--strict`, and `--strict` with `strictness: annotation` is an error). | | `--tests ` | auto-discover | Glob pattern for test files. Default discovers `*.test.ts`, `*.test.js`, `*.test.py`, `*_test.go`, `*_test.py`. | -| `--quiet` | — | Suppress per-AC source-only hints under `--strict`. JSON output still includes `diagnostic_hints`. | +| `--quiet` | n/a | Suppress per-AC source-only hints under `--strict`. JSON output still includes `diagnostic_hints`. | **Annotation format:** @@ -241,7 +244,7 @@ Specter reads annotations from two places. 1. **Source comments** above the test function: `// @spec ` and `// @ac AC-NN`. `specter coverage --strictness annotation` counts these structurally. 2. **Test title or runtime log** carrying `/AC-NN`. `specter ingest` reads this. The strict path (`--strict`, or the default `threshold`/`zero-tolerance` strictness) requires it. -Source comments alone: `--strictness annotation` counts it; the strict path — including plain `specter coverage` under the manifest default `threshold` — demotes it. Write both forms. +Source comments alone: `--strictness annotation` counts it; the strict path, including plain `specter coverage` under the manifest default `threshold`, demotes it. Write both forms. For the full rules (regex contract, zero-padding, one-AC-per-test, per-runner examples, parameterized tests, Python limitation, migration recipe, troubleshooting), see [`TEST_ANNOTATION_REFERENCE.md`](TEST_ANNOTATION_REFERENCE.md). @@ -275,7 +278,7 @@ func TestUserRegistration(t *testing.T) { - Separator between spec id and AC id is `/` or `:`. - One test (or subtest) covers one `(spec-id, AC-NN)` pair. Do not put two ACs in one test. -**Alternate form — runtime log.** When you can't rename titles (shared naming, snapshot tests, external contracts), emit the pair from inside the test body: +**Alternate form: runtime log.** When you can't rename titles (shared naming, snapshot tests, external contracts), emit the pair from inside the test body: ```typescript test('rejects zero amount', () => { @@ -325,7 +328,7 @@ spec-payments T2 5 5 100% P - A **summary header** precedes the table: total-specs count, arithmetic-mean coverage, and per-tier breakdown (`Tier K: X/Y passing (Z%)`). Tiers with zero specs in the workspace are omitted. - Entries are **sorted worst-first**: failing (below threshold) → partial (below 100% but passing threshold) → 100% covered. Within each bucket, tier descending (T1 before T2 before T3) so higher-risk specs surface first. -- Spec IDs longer than 40 characters are **truncated** in the table with a trailing ellipsis (`…`). This keeps column alignment on workspaces with long path-derived IDs. The `--json` output is unaffected — it emits the full spec_id. +- Spec IDs longer than 40 characters are **truncated** in the table with a trailing ellipsis (`…`). This keeps column alignment on workspaces with long path-derived IDs. The `--json` output is unaffected. It emits the full spec_id. **Example (`--failing`, v0.9.2+):** @@ -432,10 +435,10 @@ When specs fail to parse, the report carries a `parse_errors` array and a groupe | `spec_file` | v0.9.0 | Path to the source `.spec.yaml` for this entry. Lets downstream consumers open the file. | **Exit codes:** -- `0` — all specs parsed AND all meet their coverage thresholds -- `1` — one or more specs failed to parse, OR one or more specs are below threshold, OR `.specter-results.json` is missing under a strict mode (`--strict`, or effective strictness `threshold`/`zero-tolerance`) -- `2` — zero-tolerance strictness: an annotated AC has a results-file status other than `passed` -- `3` — zero-tolerance strictness: an AC carries `approval_gate: true` with an unset `approval_date` +- `0`: all specs parsed AND all meet their coverage thresholds +- `1`: one or more specs failed to parse, OR one or more specs are below threshold, OR `.specter-results.json` is missing under a strict mode (`--strict`, or effective strictness `threshold`/`zero-tolerance`) +- `2`: zero-tolerance strictness: an annotated AC has a results-file status other than `passed` +- `3`: zero-tolerance strictness: an AC carries `approval_gate: true` with an unset `approval_date` **Consuming the JSON programmatically:** @@ -473,7 +476,7 @@ specter sync [--json] [--tests ] [--only ] [--strict] [--strictness | `--tests ` | Glob pattern for test files. | | `--only ` | Run only one phase: `parse`, `resolve`, `check`, or `coverage`. Prerequisites run without halting on failure. | | `--strict` | Treat warnings as errors. Alias for `--strictness zero-tolerance` when `--strictness` is not set. | -| `--strictness ` | Override `settings.strictness` for the coverage phase. Values: `annotation`, `threshold`, `zero-tolerance`. Matches `coverage --strictness` semantics exactly — sync's coverage phase delegates to the strict path so demotions match. When both `--strict` and `--strictness` are passed, `--strictness` wins. | +| `--strictness ` | Override `settings.strictness` for the coverage phase. Values: `annotation`, `threshold`, `zero-tolerance`. Matches `coverage --strictness` semantics exactly. Sync's coverage phase delegates to the strict path so demotions match. When both `--strict` and `--strictness` are passed, `--strictness` wins. | **Example:** @@ -527,7 +530,7 @@ specter reverse [path] [--adapter ] [--output ] [--group-by ` | `file` | Grouping strategy: `file` (one spec per source file) or `directory` (one spec per directory). | | `--dry-run` | false | Preview generated YAML to stdout without writing files. | | `--overwrite` | false | Overwrite existing spec files. Default skips files that already exist. | -| `--exclude ` | — | Exclude paths matching pattern. Can be repeated. | +| `--exclude ` | n/a | Exclude paths matching pattern. Can be repeated. | | `--json` | false | Output results as JSON. | **Example:** @@ -571,20 +574,20 @@ specter init --ai | `--name ` | System name for the manifest. Defaults to the current directory name. | | `--force` | Overwrite an existing `specter.yaml`. Mutually exclusive with `--refresh`. | | `--template ` | Create a draft `.spec.yaml` from a template instead of a manifest. Types: `api-endpoint`, `service`, `auth`, `data-model`. | -| `--refresh` | Update only `domains.default.specs` in an existing `specter.yaml`. Preserves every other field — `settings`, `registry`, tier overrides, custom domains. Added in v0.9.2. | +| `--refresh` | Update only `domains.default.specs` in an existing `specter.yaml`. Preserves every other field: `settings`, `registry`, tier overrides, custom domains. Added in v0.9.2. | | `--dry-run` | Used with `--refresh`: print the proposed diff to stdout without writing the file. Added in v0.9.2. | | `--install-hook` | Install a git pre-push hook that blocks implementation-only pushes with no `@spec` / `@ac` annotation delta. | | `--ai ` | Write an AI assistant instruction file. Values: `claude`, `codex`, `cursor`, `copilot`, `gemini`. | -**Behaviour (v0.9.0+):** +**Behavior (v0.9.0+):** `specter init` scans the workspace's `specs/` directory and populates the manifest's default domain based on what it finds. - **Greenfield workspace (no spec files):** emits a manifest with an empty `domains.default` entry whose description invites you to add spec IDs as you author them. - **Workspace with parseable specs:** reads each one, extracts its `spec.id`, and populates `domains.default.specs: [...]`. -- **Workspace with specs that fail to parse:** still writes the manifest (with an explanatory placeholder default domain) and prints a warning that includes a **Pattern analysis** block naming the shape of the failure — if every discovered spec hit the same error, init calls out schema version drift and points at `specter doctor` for deeper diagnosis. +- **Workspace with specs that fail to parse:** still writes the manifest (with an explanatory placeholder default domain) and prints a warning that includes a **Pattern analysis** block naming the shape of the failure. If every discovered spec hit the same error, init calls out schema version drift and points at `specter doctor` for deeper diagnosis. -**Important (v0.9.0+):** init always emits a `domains:` section, even in the greenfield case. Previous versions omitted `domains:` entirely when no spec IDs were discovered, which caused later `specter sync` runs to silently skip every spec the user added afterward — a silent-exclusion footgun now eliminated. +**Important (v0.9.0+):** init always emits a `domains:` section, even in the greenfield case. Previous versions omitted `domains:` entirely when no spec IDs were discovered, which caused later `specter sync` runs to silently skip every spec the user added afterward, a silent-exclusion footgun now eliminated. **Example (greenfield):** @@ -620,7 +623,7 @@ the parse errors are resolved. **Refresh mode (v0.9.2+):** -`specter init --refresh` is the non-destructive counterpart to `--force`. It reads the existing `specter.yaml`, rescans `settings.specs_dir` (or default `specs/`), and updates **only** `domains.default.specs` with the current on-disk spec set. Every other field is preserved — `settings`, `registry`, system metadata, and any custom domains declared under `domains.` (anything that isn't `default`). +`specter init --refresh` is the non-destructive counterpart to `--force`. It reads the existing `specter.yaml`, rescans `settings.specs_dir` (or default `specs/`), and updates **only** `domains.default.specs` with the current on-disk spec set. Every other field is preserved: `settings`, `registry`, system metadata, and any custom domains declared under `domains.` (anything that isn't `default`). Specs claimed by a custom domain (listed under a non-default `domains..specs`) stay in that domain and are **not** migrated into `default`. A spec belongs to exactly one domain. @@ -682,11 +685,11 @@ specter doctor [--fix] [--dry-run] [--yes] | Check | PASS | WARN | FAIL | |-------|------|------|------| -| `manifest` | `specter.yaml` found | No `specter.yaml` (optional) | — | -| `spec-files` | ≥1 `.spec.yaml` found | — | No spec files found | -| `parse` | All specs parse cleanly | — | Parse errors in ≥1 spec | -| `annotations` | `@spec`/`@ac` annotations found in tests | No annotations found | — | -| `coverage` | All specs meet tier thresholds | — | ≥1 spec below threshold | +| `manifest` | `specter.yaml` found | No `specter.yaml` (optional) | n/a | +| `spec-files` | ≥1 `.spec.yaml` found | n/a | No spec files found | +| `parse` | All specs parse cleanly | n/a | Parse errors in ≥1 spec | +| `annotations` | `@spec`/`@ac` annotations found in tests | No annotations found | n/a | +| `coverage` | All specs meet tier thresholds | n/a | ≥1 spec below threshold | **Example (happy path):** @@ -706,7 +709,7 @@ Result: OK — project is ready for `specter sync` **Pattern analysis on parse failure (v0.9.0+):** -When the parse check fails, `specter doctor` prints a **Pattern analysis** block that groups errors by `(type, path)`. If every discovered spec hit the same pattern, doctor names it explicitly as the signature of schema version drift — a common shape for projects whose specs predate the current schema. +When the parse check fails, `specter doctor` prints a **Pattern analysis** block that groups errors by `(type, path)`. If every discovered spec hit the same pattern, doctor names it explicitly as the signature of schema version drift, a common shape for projects whose specs predate the current schema. ``` $ specter doctor @@ -827,7 +830,7 @@ specter watch ### `specter diff` -Polymorphic diff verb — the single command for diffing any Specter artifact. Dispatches on an optional first `` argument; defaults to the `spec` kind for backward compat with v1.x. +Polymorphic diff verb, the single command for diffing any Specter artifact. Dispatches on an optional first `` argument; defaults to the `spec` kind for backward compat with v1.x. **Synopsis:** @@ -844,9 +847,9 @@ specter diff coverage # coverage kind | `spec` | Semantic diff between two spec versions (v1.x behavior; default when no kind argument is present). Classifies as `breaking`, `additive`, `patch`, or `unchanged`. | | `coverage` | Per-spec AC delta between two `coverage --json` snapshots. Useful for tracking coverage drift across CI runs. | -Future cycles add more kinds (e.g., `ingest`, `check`) under the same `specter diff ` grammar. New diffable artifacts MUST NOT introduce a per-subcommand `--diff` flag — they land as kinds here. +Future cycles add more kinds (e.g., `ingest`, `check`) under the same `specter diff ` grammar. New diffable artifacts MUST NOT introduce a per-subcommand `--diff` flag. They land as kinds here. -**spec kind — change classes:** +**spec kind, change classes:** | Class | Meaning | |-------|---------| @@ -855,7 +858,7 @@ Future cycles add more kinds (e.g., `ingest`, `check`) under the same `specter d | `patch` | Wording-only changes that don't alter meaning. PATCH version bump. | | `unchanged` | No changes detected. | -**Example — spec kind:** +**Example: spec kind** ``` $ specter diff specs/auth.spec.yaml@HEAD~3 specs/auth.spec.yaml @@ -869,7 +872,7 @@ $ specter diff specs/auth.spec.yaml specs/auth.spec.yaml spec spec-auth 1.1.0 → 1.1.0: no changes ``` -**Example — coverage kind:** +**Example: coverage kind** ``` $ specter coverage --json > baseline.json # later, after changes: @@ -882,7 +885,7 @@ $ specter diff coverage baseline.json current.json ~spec-auth coverage_pct: 80.0 → 90.0 (passes_threshold: true → true) ``` -Exit code is always 0 for both kinds — diff is a diagnostic surface, not a gate. +Exit code is always 0 for both kinds. Diff is a diagnostic surface, not a gate. --- @@ -902,10 +905,10 @@ At least one of `--junit` or `--go-test` is required. Both flags accept glob pat | Option | Default | Description | |--------|---------|-------------| -| `--junit ` | — | JUnit XML file (vitest, jest, pytest, playwright). Accepts glob patterns; may be repeated. | -| `--go-test ` | — | Newline-delimited JSON from `go test -json`. Accepts glob patterns; may be repeated. | +| `--junit ` | n/a | JUnit XML file (vitest, jest, pytest, playwright). Accepts glob patterns; may be repeated. | +| `--go-test ` | n/a | Newline-delimited JSON from `go test -json`. Accepts glob patterns; may be repeated. | | `--output ` | `.specter-results.json` | Where to write the merged results. | -| `--verbose` | — | Emit one stderr line per dropped testcase (testcases without a recognizable `(spec_id, ac_id)` annotation). Off by default; the summary line is always emitted. | +| `--verbose` | n/a | Emit one stderr line per dropped testcase (testcases without a recognizable `(spec_id, ac_id)` annotation). Off by default; the summary line is always emitted. | **Diagnostics:** every run writes to stderr a summary line: @@ -913,15 +916,15 @@ At least one of `--junit` or `--go-test` is required. Both flags accept glob pat Scanned N test cases; extracted M (spec_id, ac_id) pairs; dropped K with no runner-visible annotation. ``` -If `M` is 0 despite `N` being non-zero, your tests carry annotations only in source comments — those are invisible to `ingest` by design. See the explainer's Conventions A (test title) and B (runtime `t.Log`) for migrating. +If `M` is 0 despite `N` being non-zero, your tests carry annotations only in source comments. Those are invisible to `ingest` by design. See the explainer's Conventions A (test title) and B (runtime `t.Log`) for migrating. **Annotation extraction:** Each test needs a discoverable `(spec_id, ac_id)` pair or it's dropped silently. Sources in order of preference: -1. **Test name** — `spec-id/AC-NN` or `spec-id:AC-NN` embedded in the test case name. -2. **Classname** — same pattern, parsed from the JUnit `classname` attribute. -3. **Test body** — `// @spec ` and `// @ac ` comments surfaced via `system-out` (JUnit) or `output`-action lines (go test -json). +1. **Test name.** `spec-id/AC-NN` or `spec-id:AC-NN` embedded in the test case name. +2. **Classname.** Same pattern, parsed from the JUnit `classname` attribute. +3. **Test body.** `// @spec ` and `// @ac ` comments surfaced via `system-out` (JUnit) or `output`-action lines (go test -json). **Status mapping:**