# Feature: Project constitution and the spec-consistency gate

<!-- toc -->
- [What the constitution holds](#what-the-constitution-holds)
- [Where it lives](#where-it-lives)
- [How the analysis run derives it](#how-the-analysis-run-derives-it)
- [What the gate checks](#what-the-gate-checks)
- [Where it runs](#where-it-runs)
- [The ledger, and why it is not mandatory at commit](#the-ledger-and-why-it-is-not-mandatory-at-commit)
- [Limits](#limits)
- [Reference](#reference)
<!-- /toc -->

**Pattern**: a project has a few rules that hold for every feature built in it:
what may never reach a log, which accessibility floor every screen meets, which
layer may call which, which licenses may ship. The analysis run writes them down
once per project as a constitution, each rule tied to the file or document that
states it. Before a spec becomes tickets or code, `spec-consistency-gate.mjs`
checks by id that every requirement has a task and a test, that every task
traces to a requirement, and that nothing waives a binding rule.

## What the constitution holds

Four categories, one id prefix each:

| Category | Id | Typical source |
|---|---|---|
| security | `CON-SEC-NN` | `CLAUDE.md` / `AGENTS.md` rules, `SECURITY.md`, a standards document |
| accessibility | `CON-A11Y-NN` | the repo's rules files, lint rules, a standards document |
| architecture | `CON-ARCH-NN` | rules files, lint configs, the stack the adapter table resolved |
| licensing | `CON-LIC-NN` | `LICENSE`, a dependency policy |

Every rule carries exactly one source, with the labels of
`research/engine.md`:

| Label | `ref` | Status it may have |
|---|---|---|
| `repo` | `path` or `path:line` inside the repository | binding or proposed |
| `evidence` | the URL or id of a fetched document: a Standards source, a Confluence page, a tracker item | binding or proposed |
| `inference` | the one-line rationale | proposed only |

A rule the run reasoned its way to is a proposal. Only a rule that a file, a
document or a person states can bind; `constitution.mjs check` and the schema
(`schemas/constitution.schema.json`) both reject an inference-sourced rule
marked `binding`.

## Where it lives

In the project's knowledge store, beside `code-graph.json`:

```
~/.claude/knowledge/<project>/constitution.md     for people
~/.claude/knowledge/<project>/constitution.json   for the gate
```

`<project>` is the main checkout's directory name, resolved through git's common
directory, so every worktree of the repository reads the same file. A run with
no repository keys it on the analysis name instead. Both resolve through one
command:

```bash
node "$HOME/.claude/scripts/constitution.mjs" path --repo "<repo>"
node "$HOME/.claude/scripts/constitution.mjs" path --feature "<analysis name>"
```

The file is per project, not per feature: a later analysis run reads it, keeps
its ids, and adds to it.

## How the analysis run derives it

Runs after convention extraction (Phase 1c), or after the fetch when no
repository was selected.

1. **Read what exists.** An existing `constitution.json` is the starting
   point. Ids never change meaning; a rule is never renumbered.
2. **List the repository evidence.** Deterministic, by name at fixed places:

   ```bash
   node "$HOME/.claude/scripts/constitution.mjs" evidence --repo "<repo>" --json
   ```

   It returns the license file with its detected license, the agent
   instruction files (`CLAUDE.md`, `AGENTS.md`, `.claude/rules/*.md`,
   `.github/copilot-instructions.md`, `SECURITY.md`), lint and format configs,
   and the stack manifest `_stack-adapter.mjs` resolved.
3. **Write a rule only where a source states one.** Read each listed file and
   the run's Standards evidence (`evidence.standards[]`). A rule a file states
   is `repo`, cited at the line. A rule a standards document states is
   `evidence`, cited by its id. The license file supports "ships under MIT";
   it does not support "every dependency must be MIT-compatible", which is an
   inference and stays proposed unless a policy says it.
4. **Mark the rest proposed.** A rule the run thinks the project should have,
   and no source states, is `inference` and `proposed`. It is listed in the
   report so a person can adopt it by adding a source.
5. **Check it.** Both files are written from the same rows; the markdown is one
   table per category with Id, Rule, Source and Status columns. Then:

   ```bash
   node "$HOME/.claude/scripts/constitution.mjs" check "<constitution.json>" --repo "<repo>"
   ```

   Exit 0 valid, 1 invalid, 3 unreadable. With `--repo`, every `repo` ref must
   name a file and a line that exist. A failing check is fixed before the
   analysis reports, never shipped.

**No platform yet (Locked 34).** There is no repository, so no rule can carry
the `repo` label and `greenfield: true` is set; `check` rejects a repo-labelled
rule in a greenfield file. Rules come from the Standards evidence (binding when
the document states them) or are proposed. When the project gets its
repository, the first analysis run on it derives the repository's own
constitution and may carry a greenfield rule over, citing the greenfield file
as `evidence`.

**Citing a rule.** A requirement row or a plan task that relies on a rule cites
its id: `per CON-SEC-01`. An exception is written with a fixed keyword, and
that is what the gate reads as a contradiction: before the id, in any tense or
as a gerund (`waives`, `waived for`, `waiving`, `a waiver of`, `overrides`,
`violates`, `bypasses`, `ignores`, `suspends`, `disregards`, `exempt from`,
`exception to`, `deviates from`), or after it in the passive (`CON-SEC-01 is
waived`, `was bypassed`, `does not apply`). Ids match in any case. The keywords
are English in every document language, as the EARS keywords are.

## What the gate checks

`spec-consistency-gate.mjs`, deterministic, no model call:

| Check | A gap is |
|---|---|
| requirement -> task | a `BR-<slug>-NN` (global) or `FG-NN` (corporate) id no task cites |
| requirement -> test | an id no Test Plan row cites (global Section 15, corporate Section 19; a row counts under a sub-table label that names the id), and no test task cites |
| task -> requirement | a task that cites no defined requirement, or cites an id no document defines |
| constitution | a requirement row or task that waives a `binding` rule, or cites a `CON-` id the constitution does not define |

A task cites ids through `requirements[]` (`planning-output.schema.json`), its
title, its acceptance criteria, or its todo text. A test task is `type: test`,
or one whose files the stack adapter counts as test files (`testFilePatterns`).
Waiving a `proposed` rule is reported and passes. With no constitution file,
constitution references are counted as unchecked, never failed: a project
without one is a normal state.

A document with no development layer (a repo-less run) carries no Test Plan.
In analysis-jira, where the stories are the tasks, the test check is then
reported as not run. In a pipeline run the plan's test tasks are checked
whatever the document carries.

Exit codes, with the quality gates active: 0 pass, 1 gap, 2 not-applicable (no
requirement id in any document), 3 usage or unreadable input (a missing
document, an unparseable plan, requirements with no plan, an invalid
constitution).

## Where it runs

| Where | Gates active (`gatesActive`) | Attended |
|---|---|---|
| `/multi-agent:analysis-jira`, inside `analysis-story-tree.mjs` after planning | a gap or unreadable input is exit 4, the existing "not issue-ready" refusal: no issue is created | the preview gains a `consistency:` line marked advisory, one line per finding below it, and the JSON a `consistency` field with `advisory: true`; exit code, approval options and writes unchanged |
| Pipeline Phase 1 Step 12, after the validator gate and before Phase 2 | exit 1: revise the plan once, as Step 12 does, and re-run; still 1, or 3: park with `gate-ledger.mjs park --outcome verification-failed --gate spec-consistency`, and Phase 2 does not start. Exit 2: continue | the report prints on stdout marked advisory and feeds the existing Consistency gaps banner; exit 0 (a usage error exits 3), no state written, no stderr, plan approval unchanged |

```bash
node "$HOME/.claude/scripts/spec-consistency-gate.mjs" --state "$STATE_FILE" \
  --plan "$WORKTREE/.pipeline/plan.json"
```

`--analysis` defaults to `state.analysis.docPath[]`, `--plan` to
`<worktree>/.pipeline/plan.json` and then `state.plan.todos`, and
`--constitution` to the knowledge store.

## The ledger, and why it is not mandatory at commit

With the gates active every verdict is appended to `state.gates[]` as
`spec-consistency` through `gate-ledger.mjs`: pass, fail (gap or unreadable) or
not-applicable.

It is not in `MANDATORY_AT_COMMIT`. A gate joins that list only when every
autopilot run has something for it to check, and many do not: a bug fix from a ticket may carry no
requirement id at all. Making it mandatory would block those commits for the
absence of an entry from a phase the run never entered. Its decision point is
the start of development, and a failing verdict parks the run there.

## Limits

- The mapping is by id. A task that cites the right id and implements something
  else passes; review is where that is caught.
- A contradiction is only what a row writes with a waiver keyword next to the
  id. A requirement that breaks a rule without saying so is not seen.
- In the pipeline the gate is invoked by the agent at Step 12. It is not a
  commit-time requirement, so an unattended agent that skips it leaves no entry
  and nothing blocks the commit on that account.

## Reference

Scripts: `constitution.mjs`, `spec-consistency-gate.mjs`, the consistency step of
`analysis-story-tree.mjs`, `validate-planning.mjs` (`requirements[]`). Schemas:
`constitution.schema.json`, `planning-output.schema.json`. Tests:
`test/constitution.test.mjs`, `test/spec-consistency-gate.test.mjs`, fixtures in
`test/fixtures/spec-consistency/`; smoke `smoke-gates-interactive-noop.sh`. The plan critic judges
its objections by the same rule ids: `features/plan-critic.md`.
