doctrina-cli 0.15.1 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -72
- package/package.json +3 -3
- package/src/commands/adapter.js +27 -3
- package/src/commands/analyze.js +16 -212
- package/src/commands/change.js +144 -267
- package/src/commands/ci.js +182 -0
- package/src/commands/clarify.js +65 -21
- package/src/commands/close.js +349 -45
- package/src/commands/completion.js +8 -2
- package/src/commands/context.js +284 -111
- package/src/commands/contract.js +95 -75
- package/src/commands/coverage.js +58 -273
- package/src/commands/decision.js +66 -7
- package/src/commands/doctor.js +293 -130
- package/src/commands/handoff.js +10 -57
- package/src/commands/hooks.js +17 -3
- package/src/commands/index-rebuild.js +55 -44
- package/src/commands/init.js +88 -16
- package/src/commands/intake.js +85 -99
- package/src/commands/intent.js +14 -1
- package/src/commands/metrics.js +65 -88
- package/src/commands/next.js +105 -147
- package/src/commands/prime.js +23 -91
- package/src/commands/report.js +40 -109
- package/src/commands/review.js +68 -36
- package/src/commands/search.js +18 -4
- package/src/commands/show.js +24 -9
- package/src/commands/skill.js +23 -39
- package/src/commands/spec.js +38 -11
- package/src/commands/status.js +69 -171
- package/src/commands/templates.js +35 -148
- package/src/commands/trace.js +36 -71
- package/src/commands/triage.js +4 -82
- package/src/commands/upgrade.js +31 -6
- package/src/commands/validate.js +44 -877
- package/src/commands/verify.js +60 -138
- package/src/commands/why.js +2 -2
- package/src/commands/work.js +243 -267
- package/src/index.js +139 -12
- package/src/lib/actions.js +490 -0
- package/src/lib/adapters.js +0 -11
- package/src/lib/adr-guard.js +19 -0
- package/src/lib/agent-changelog.js +133 -0
- package/src/lib/analysis.js +264 -0
- package/src/lib/args.js +14 -0
- package/src/lib/change-ops.js +133 -0
- package/src/lib/commands.js +135 -10
- package/src/lib/config.js +206 -0
- package/src/lib/constitution-model.js +102 -0
- package/src/lib/coverage-model.js +431 -0
- package/src/lib/criteria.js +23 -0
- package/src/lib/doc-model.js +370 -3
- package/src/lib/docs-impact.js +270 -27
- package/src/lib/ears.js +24 -1
- package/src/lib/flag-catalog.js +0 -16
- package/src/lib/fs-ops.js +58 -1
- package/src/lib/gates.js +231 -29
- package/src/lib/git.js +132 -0
- package/src/lib/index-json.js +42 -24
- package/src/lib/intake-model.js +155 -0
- package/src/lib/json-out.js +100 -2
- package/src/lib/ledger.js +192 -0
- package/src/lib/lexicon.js +228 -0
- package/src/lib/metrics-model.js +189 -0
- package/src/lib/names.js +42 -0
- package/src/lib/playbook.js +97 -0
- package/src/lib/project.js +105 -0
- package/src/lib/reproducibility.js +137 -0
- package/src/lib/runtime.js +191 -5
- package/src/lib/scan.js +328 -25
- package/src/lib/signoff.js +147 -0
- package/src/lib/snapshot.js +231 -0
- package/src/lib/spec-ops.js +72 -8
- package/src/lib/templates-model.js +289 -0
- package/src/lib/trace-model.js +95 -0
- package/src/lib/triage-model.js +121 -0
- package/src/lib/usage.js +6 -4
- package/src/lib/validation-model.js +1381 -0
- package/src/lib/version.js +22 -0
- package/src/lib/views.js +413 -0
- package/src/lib/work-model.js +102 -0
- package/templates/AGENTS.md.template +21 -28
- package/templates/README.md +35 -0
- package/templates/adapters/claude/.claude/commands/doctrina-work.md.template +6 -3
- package/templates/adapters/cursor/.cursor/commands/doctrina-work.md.template +4 -3
- package/templates/change/proposal.md.template +16 -0
- package/templates/change/tasks.md.template +3 -8
- package/templates/contract.md.template +2 -2
- package/templates/hooks/pre-commit.sample +16 -2
- package/templates/playbooks/bootstrap.md.template +47 -0
- package/templates/playbooks/chore.md.template +24 -0
- package/templates/playbooks/work.md.template +83 -0
- package/src/commands/constitution.js +0 -145
package/templates/README.md
CHANGED
|
@@ -29,10 +29,45 @@ replace. No conditionals, loops, or includes.
|
|
|
29
29
|
CLI authors and template authors share this vocabulary. Adding a token
|
|
30
30
|
requires extending the templates spec at `.doctrina/specs/templates/spec.md`.
|
|
31
31
|
|
|
32
|
+
### Playbook tokens
|
|
33
|
+
|
|
34
|
+
The playbooks under `playbooks/` carry the variable parts of the procedure
|
|
35
|
+
the agent executes. Each is PRE-RENDERED by the CLI into a plain string, so
|
|
36
|
+
the template stays a document rather than a language with conditionals and
|
|
37
|
+
loops in it.
|
|
38
|
+
|
|
39
|
+
| Token | Meaning | Default if not supplied |
|
|
40
|
+
|-------|---------|-------------------------|
|
|
41
|
+
| `{{TITLE}}` | "Work playbook" or "Backfill playbook" | required in `work` |
|
|
42
|
+
| `{{PROMPT}}` | The prompt the change was opened from, verbatim | required |
|
|
43
|
+
| `{{FROM_DIFF_NOTE}}` | The code-first note, on `--from-diff` | empty |
|
|
44
|
+
| `{{THIN_WARNING}}` | The thin-prompt clarification block | empty |
|
|
45
|
+
| `{{CAPABILITY_BLOCK}}` | The pinned capability, the ranked hints, or "no match" | required in `work` |
|
|
46
|
+
| `{{DIFF_MATCHES}}` | Capabilities the working tree touched | empty |
|
|
47
|
+
| `{{STEP3_INTRO}}` | The delta step's opening, pinned or not | required in `work` |
|
|
48
|
+
| `{{FROM_DIFF_DELTA_NOTE}}` | The backfill caveat on writing the delta | empty |
|
|
49
|
+
| `{{MISSING_SPECS_WARNING}}` | Warning that `.doctrina/specs/` is absent | empty |
|
|
50
|
+
|
|
51
|
+
A line holding NOTHING but one token whose value is empty is removed rather
|
|
52
|
+
than left blank, which is what lets the optional blocks above be plain tokens.
|
|
53
|
+
|
|
54
|
+
Colour is marked inline and expanded before substitution, so a token's value
|
|
55
|
+
can never inject it:
|
|
56
|
+
|
|
57
|
+
| Markup | Renders as |
|
|
58
|
+
|--------|-----------|
|
|
59
|
+
| `[[c]]...[[/c]]` | cyan — a command to run |
|
|
60
|
+
| `[[g]]...[[/g]]` | gray — commentary |
|
|
61
|
+
| `[[b]]...[[/b]]` | bold — a heading |
|
|
62
|
+
| `[[y]]...[[/y]]` | yellow — a warning |
|
|
63
|
+
|
|
32
64
|
## Inventory (v0)
|
|
33
65
|
|
|
34
66
|
```
|
|
35
67
|
README.md this file
|
|
68
|
+
playbooks/work.md.template the work / backfill playbook
|
|
69
|
+
playbooks/chore.md.template the spec-less chore playbook
|
|
70
|
+
playbooks/bootstrap.md.template the intake -> specs playbook
|
|
36
71
|
AGENTS.md.template root rules for the target repo
|
|
37
72
|
doctrina/ the .doctrina/ skeleton
|
|
38
73
|
product.md.template
|
|
@@ -9,9 +9,12 @@ Drive the Doctrina spec-driven workflow for **{{PROJECT_NAME}}**.
|
|
|
9
9
|
(No prompt, but you already edited code? Use `doctrina work --from-diff`.)
|
|
10
10
|
2. Execute the printed playbook in order: read the pack with
|
|
11
11
|
`doctrina context <capability> --concat`, write the spec delta, fill
|
|
12
|
-
`tasks.md`, implement
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
`tasks.md`, implement. `doctrina change check <id>` previews everything
|
|
13
|
+
the close would refuse.
|
|
14
|
+
3. Close it: `doctrina close <id>` — one attested pass over every gate. The
|
|
15
|
+
sequence is declared in one place and printed by `doctrina close --help`;
|
|
16
|
+
read it there rather than from a copy. Resolve every `error:` before
|
|
17
|
+
reporting the work done.
|
|
15
18
|
|
|
16
19
|
Reach for the CLI instead of hand-authoring artifacts. Ask the human only on
|
|
17
20
|
genuine ambiguity.
|
|
@@ -5,8 +5,9 @@ Turn the request into a Doctrina change and drive it through the gates.
|
|
|
5
5
|
1. Run `doctrina work "<the request>"` (or `doctrina work --from-diff` if you
|
|
6
6
|
already edited code) and follow the printed playbook.
|
|
7
7
|
2. `doctrina context <capability> --concat` → write the spec delta → fill
|
|
8
|
-
`tasks.md` → implement → `doctrina
|
|
9
|
-
3. `doctrina close <id>`
|
|
10
|
-
|
|
8
|
+
`tasks.md` → implement → `doctrina change check <id>` (the close's preview).
|
|
9
|
+
3. `doctrina close <id>` — one attested pass over every gate; the sequence is
|
|
10
|
+
declared in one place and printed by `doctrina close --help`, so read it
|
|
11
|
+
there and not from a copy. Resolve every `error:` before reporting done.
|
|
11
12
|
|
|
12
13
|
Prefer the CLI over hand-authoring artifacts. Ask only on genuine ambiguity.
|
|
@@ -3,8 +3,24 @@
|
|
|
3
3
|
- **Status:** proposed
|
|
4
4
|
- **Date:** {{DATE}}
|
|
5
5
|
- **Owner:**
|
|
6
|
+
- **Lane:**
|
|
6
7
|
- **Affects specs:**
|
|
7
8
|
|
|
9
|
+
<!--
|
|
10
|
+
Optional, and usually absent. The closing docs gate reads COMMAND and FLAG
|
|
11
|
+
names out of the prose below and asks for documentation when it finds any.
|
|
12
|
+
It cannot tell a change from a mention: explaining an effect, or writing a
|
|
13
|
+
Scope boundaries line about what this deliberately does NOT touch, names
|
|
14
|
+
things just as loudly as changing them would.
|
|
15
|
+
|
|
16
|
+
When that happens, say so on the record instead of forcing the close:
|
|
17
|
+
|
|
18
|
+
- **Documented surface:** n/a — names two commands to explain an effect; alters neither
|
|
19
|
+
|
|
20
|
+
`none` reads the same as `n/a`, and a BARE one silences nothing — the
|
|
21
|
+
reason is the declaration.
|
|
22
|
+
-->
|
|
23
|
+
|
|
8
24
|
## Why
|
|
9
25
|
|
|
10
26
|
<!-- The user-visible or technical reason this change exists. -->
|
|
@@ -2,16 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
<!--
|
|
4
4
|
Each task is a single checkable item. Keep tasks small (under a few hours
|
|
5
|
-
of work). The change is done when every box is checked
|
|
6
|
-
|
|
5
|
+
of work). The change is done when every box is checked and
|
|
6
|
+
`doctrina close {{CHANGE_ID}}` succeeds — the close applies the deltas,
|
|
7
|
+
archives the change and updates the index, so those are not boxes here.
|
|
7
8
|
-->
|
|
8
9
|
|
|
9
10
|
- [ ]
|
|
10
11
|
- [ ]
|
|
11
12
|
- [ ]
|
|
12
|
-
|
|
13
|
-
## Closing steps
|
|
14
|
-
|
|
15
|
-
- [ ] Apply the change: merge each delta into the corresponding spec.
|
|
16
|
-
- [ ] Archive the change folder to `.doctrina/changes/archive/{{DATE}}-{{CHANGE_ID}}/`.
|
|
17
|
-
- [ ] Update `.doctrina/index.json` with new or modified artifacts.
|
|
@@ -107,8 +107,8 @@ Example (Behave): features/**/*.feature with pattern @([a-z0-9_-]+)
|
|
|
107
107
|
<!--
|
|
108
108
|
Ceilings that are a CONTRACT, not a preference: token limits, payload
|
|
109
109
|
caps, timeouts. Direction distinguishes what goes IN from what comes
|
|
110
|
-
OUT, because they fail differently — and `doctrina
|
|
111
|
-
change that "fixes" an overflow by raising an OUTPUT ceiling, which
|
|
110
|
+
OUT, because they fail differently — and `doctrina change check` and the
|
|
111
|
+
close refuse a change that "fixes" an overflow by raising an OUTPUT ceiling, which
|
|
112
112
|
buys headroom by truncating what mattered rather than sending less.
|
|
113
113
|
-->
|
|
114
114
|
|
|
@@ -13,12 +13,26 @@
|
|
|
13
13
|
# EARS, ledger/index disagreement, and so on.
|
|
14
14
|
#
|
|
15
15
|
# To gate without auto-repairing (CI-style, fail on any drift), replace the
|
|
16
|
-
# line below with a bare
|
|
16
|
+
# line below with a bare `$DOCTRINA validate`.
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
# The CLI that installed this hook, pinned by path (the hook is local to
|
|
19
|
+
# the clone). Set DOCTRINA in the environment to run another one.
|
|
20
|
+
DOCTRINA="${DOCTRINA:-{{DOCTRINA_BIN}}}"
|
|
21
|
+
|
|
22
|
+
$DOCTRINA validate --fix || exit 1
|
|
19
23
|
|
|
20
24
|
# Re-stage the index if --fix rewrote it, so the repaired index lands in this
|
|
21
25
|
# commit rather than as a leftover unstaged change. Harmless when unchanged.
|
|
22
26
|
if [ -f .doctrina/index.json ]; then
|
|
23
27
|
git add .doctrina/index.json
|
|
24
28
|
fi
|
|
29
|
+
|
|
30
|
+
# ...and then check that the index this commit carries describes THIS COMMIT.
|
|
31
|
+
#
|
|
32
|
+
# Everything above reads the working tree. index.json is derived from the
|
|
33
|
+
# whole tree, so it cannot describe a subset of one: stage part of
|
|
34
|
+
# .doctrina/ and the index that just got staged names artifacts the commit
|
|
35
|
+
# does not contain. On your disk they are all still there, so `validate`,
|
|
36
|
+
# `index rebuild --check` and the close all pass — and the checkout in CI
|
|
37
|
+
# fails with "index.json references missing artifact".
|
|
38
|
+
$DOCTRINA index rebuild --check --staged || exit 1
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[[b]]Bootstrap playbook[[/b]][[g]] — agent-executed (ADR 0005); the CLI does no interpretation.[[/g]]
|
|
2
|
+
|
|
3
|
+
Execute in order, in a single linear pass:
|
|
4
|
+
|
|
5
|
+
1. Read [[c]].doctrina/intake.md[[/c]] (raw intent) and [[c]].doctrina/product.md[[/c]].
|
|
6
|
+
|
|
7
|
+
2. Fill every product.md section from the intake: Vision, Problem,
|
|
8
|
+
Target users, Scope (in/out), Non-goals, Success criteria, and the
|
|
9
|
+
Delivery order — name the ONE end-to-end walking skeleton to build
|
|
10
|
+
and verify before fanning out (depth before breadth).
|
|
11
|
+
One fact, one home — product.md holds vision, never requirements.
|
|
12
|
+
|
|
13
|
+
3. Derive the capability list: kebab-case, one per area of behaviour
|
|
14
|
+
(e.g. auth, billing, reports). For each capability:
|
|
15
|
+
[[c]]doctrina spec new <capability>[[/c]]
|
|
16
|
+
then replace the template placeholders in
|
|
17
|
+
.doctrina/specs/<capability>/spec.md with EARS requirements derived
|
|
18
|
+
from the intake (Ubiquitous / Event-driven / State-driven /
|
|
19
|
+
Unwanted-behavior / Optional). Keep the two axes honest — leave
|
|
20
|
+
Implementation: planned for what is not built yet; keep aspiration
|
|
21
|
+
under ## Maturity → Future, not in EARS; write concrete Acceptance
|
|
22
|
+
criteria, each [unverified] until a cited test proves it.
|
|
23
|
+
|
|
24
|
+
4. If the project spans services/front-ends, own the seams between
|
|
25
|
+
them (port map, env, API/WS/event shapes) as a contract:
|
|
26
|
+
[[c]]doctrina contract new <name>[[/c]] → fill it → [[c]]doctrina contract check[[/c]]
|
|
27
|
+
|
|
28
|
+
5. Record any architectural decision the intake forces (cite Evidence):
|
|
29
|
+
[[c]]doctrina decision new "<title>"[[/c]] → edit → [[c]]doctrina decision accept <num>[[/c]]
|
|
30
|
+
|
|
31
|
+
6. Quality gates — fix everything they report before moving on:
|
|
32
|
+
[[c]]doctrina clarify --all[[/c]]
|
|
33
|
+
[[c]]doctrina validate[[/c]]
|
|
34
|
+
[[c]]doctrina coverage[[/c]] (and [[c]]doctrina verify --init[[/c]] to declare the build gate)
|
|
35
|
+
If something in the intake is genuinely ambiguous, ask the user
|
|
36
|
+
before assuming.
|
|
37
|
+
|
|
38
|
+
7. Mark the intake consumed — through the CLI, not by hand:
|
|
39
|
+
[[c]]doctrina intake --converted[[/c]]
|
|
40
|
+
[[g]] `next` branches on that header and `validate` refuses any other[[/g]]
|
|
41
|
+
[[g]] value, because every word but the two declared ones used to read[[/g]]
|
|
42
|
+
[[g]] as pending in silence. It refuses while `validate` reports an[[/g]]
|
|
43
|
+
[[g]] error — the step-6 gates are what "done" means.[[/g]]
|
|
44
|
+
|
|
45
|
+
8. [[c]]doctrina next[[/c]] — follow the recommendation. Implementation then
|
|
46
|
+
flows through [[c]]doctrina work "<brief prompt>"[[/c]], one change at a time.
|
|
47
|
+
{{MISSING_SPECS_WARNING}}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[[b]]Chore playbook — change {{CHANGE_ID}}[[/b]][[g]] — agent-executed (ADR 0005); no spec deltas.[[/g]]
|
|
2
|
+
|
|
3
|
+
Prompt: "{{PROMPT}}"
|
|
4
|
+
|
|
5
|
+
Execute in order, in a single linear pass:
|
|
6
|
+
|
|
7
|
+
1. Replace the placeholder tasks in .doctrina/changes/{{CHANGE_ID}}/tasks.md
|
|
8
|
+
with small, checkable steps; implement them, checking each box.
|
|
9
|
+
[[g]] (Do it before implementing — the close refuses leftover scaffold[[/g]]
|
|
10
|
+
[[g]] placeholders, and `change tick` will not tick an empty box.)[[/g]]
|
|
11
|
+
2. Prove it (the close refuses unchecked boxes):
|
|
12
|
+
[[c]]doctrina verify[[/c]] — the project's typecheck/test/build gate
|
|
13
|
+
[[c]]doctrina verify --clean[[/c]] — clean-checkout reproducibility lint
|
|
14
|
+
Then check the proposal's ## Verification boxes and every task.
|
|
15
|
+
3. This is a chore: no spec changes. If you find yourself needing a
|
|
16
|
+
spec delta, it is not a chore — reopen with [[c]]doctrina work "<prompt>"[[/c]].
|
|
17
|
+
4. Close in one attested pass — the same close as any change; a chore
|
|
18
|
+
simply has no delta to apply:
|
|
19
|
+
[[c]]doctrina close {{CHANGE_ID}}[[/c]]
|
|
20
|
+
[[g]] It stops at the first failure with the exact rerun command, and the[[/g]]
|
|
21
|
+
[[g]] sequence is printed by the close itself. A chore that alters a[[/g]]
|
|
22
|
+
[[g]] documented surface still owes the docs and a changelog entry — or[[/g]]
|
|
23
|
+
[[g]] `- **Documented surface:** n/a — <why>` in the proposal.[[/g]]
|
|
24
|
+
[[g]] If this fix taught a lesson worth not relearning, capture it: [[/g]][[c]]doctrina skill new <slug>[[/c]][[g]].[[/g]]
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
[[b]]{{TITLE}} — change {{CHANGE_ID}}[[/b]][[g]] — agent-executed (ADR 0005).[[/g]]
|
|
2
|
+
|
|
3
|
+
Prompt: "{{PROMPT}}"
|
|
4
|
+
{{FROM_DIFF_NOTE}}
|
|
5
|
+
{{THIN_WARNING}}
|
|
6
|
+
{{CAPABILITY_BLOCK}}
|
|
7
|
+
{{DIFF_MATCHES}}
|
|
8
|
+
|
|
9
|
+
Execute in order, in a single linear pass:
|
|
10
|
+
|
|
11
|
+
1. Read the context pack and confirm (or correct) the capability:
|
|
12
|
+
[[c]]doctrina context {{CAPABILITY}} --concat[[/c]]
|
|
13
|
+
|
|
14
|
+
2. If the capability has no spec yet:
|
|
15
|
+
[[c]]doctrina spec new <capability>[[/c]]
|
|
16
|
+
[[g]] Trace it to product intent: tag the product.md success-criteria bullet[[/g]]
|
|
17
|
+
[[g]] it serves with an anchor ("- [SC1] ...") and set the spec's[[/g]]
|
|
18
|
+
[[g]] "**Realizes:** SC1" header (or "n/a — <why>"). `doctrina validate` warns[[/g]]
|
|
19
|
+
[[g]] on an active spec with no Realizes; `doctrina trace` reports the link.[[/g]]
|
|
20
|
+
|
|
21
|
+
{{STEP3_INTRO}}
|
|
22
|
+
[[g]] # Spec Delta — capability: <capability>[[/g]]
|
|
23
|
+
[[g]] **Operation:** ADDED | MODIFIED | REMOVED[[/g]]
|
|
24
|
+
[[g]] **Target spec on apply:** `.doctrina/specs/<capability>/spec.md`[[/g]]
|
|
25
|
+
[[g]] ---[[/g]]
|
|
26
|
+
[[g]] <EARS body. Keep the two axes honest (Status vs Implementation),[[/g]]
|
|
27
|
+
[[g]] keep aspiration under ## Maturity → Future, and cite the proof per[[/g]]
|
|
28
|
+
[[g]] criterion: "1. [unverified] <signal> — verified by `path/to/test`">[[/g]]
|
|
29
|
+
[[g]] New capability? `spec new` then an ADDED delta with the FULL body — apply[[/g]]
|
|
30
|
+
[[g]] replaces the untouched scaffold. Bookkeeping edits? MODIFIED with an ops[[/g]]
|
|
31
|
+
[[g]] block, applied mechanically (all ops or none):[[/g]]
|
|
32
|
+
[[g]] ```ops[[/g]]
|
|
33
|
+
[[g]] set-header Implementation: verified — `src/x.js`[[/g]]
|
|
34
|
+
[[g]] bump-version minor[[/g]]
|
|
35
|
+
[[g]] set-criterion 1: verified[[/g]]
|
|
36
|
+
[[g]] append-criterion [unverified] new signal — verified by `test/y.test.js`[[/g]]
|
|
37
|
+
[[g]] append-requirement event: When <trigger>, the system shall <action>.[[/g]]
|
|
38
|
+
[[g]] replace-requirement ubiquitous 2: The system shall <action>.[[/g]]
|
|
39
|
+
[[g]] ```[[/g]]
|
|
40
|
+
[[g]] append-* ops number/position at APPLY time, so concurrent changes[[/g]]
|
|
41
|
+
[[g]] appending to the same spec never collide. Only free-prose rewrites[[/g]]
|
|
42
|
+
[[g]] (Purpose, Maturity, ...) stay a by-hand merge.[[/g]]
|
|
43
|
+
{{FROM_DIFF_DELTA_NOTE}}
|
|
44
|
+
|
|
45
|
+
4. Replace the placeholder tasks in .doctrina/changes/{{CHANGE_ID}}/tasks.md
|
|
46
|
+
with small, checkable implementation tasks (a few hours each, max),
|
|
47
|
+
and record the change's What/Scope in its proposal.md. Do this BEFORE
|
|
48
|
+
implementing — `change check` and close refuse a change whose scaffold
|
|
49
|
+
placeholders were never replaced, and `change tick` will not tick them.
|
|
50
|
+
|
|
51
|
+
5. Implement task by task, checking each box as it lands. Advance the
|
|
52
|
+
spec's Implementation: planned → partial → implemented as code lands.
|
|
53
|
+
If the prompt is genuinely ambiguous, ask the user before assuming.
|
|
54
|
+
|
|
55
|
+
6. ADR checkpoint — does this change decide something structural
|
|
56
|
+
(an architecture, a boundary, a trade-off a future session must
|
|
57
|
+
not relitigate)? If yes: [[c]]doctrina decision new "<title>"[[/c]] now,
|
|
58
|
+
before closing — the definition of done requires it recorded.
|
|
59
|
+
[[g]] (close re-checks this: it warns when the change touches capabilities[[/g]]
|
|
60
|
+
[[g]] cited by an accepted ADR — amend via decision supersede, not silence.)[[/g]]
|
|
61
|
+
Touching an integration surface (ports, env vars, public endpoints)?
|
|
62
|
+
Own it in a contract: [[c]]doctrina contract new <id>[[/c]] · [[c]]doctrina contract check[[/c]].
|
|
63
|
+
|
|
64
|
+
7. Close in one attested pass (preferred — runs every gate and stops
|
|
65
|
+
at the first failure with the exact rerun command):
|
|
66
|
+
[[c]]doctrina close {{CHANGE_ID}}[[/c]]
|
|
67
|
+
[[g]] It self-reviews on the way through: the close runs `doctrina review`[[/g]]
|
|
68
|
+
[[g]] before the apply, where its findings can still change what gets[[/g]]
|
|
69
|
+
[[g]] written. Advisory — it reports and the close continues.[[/g]]
|
|
70
|
+
[[g]] The sequence is declared in one place and printed by the close itself,[[/g]]
|
|
71
|
+
[[g]] so read it there rather than from a copy that goes stale.[[/g]]
|
|
72
|
+
Before it: check the proposal's ## Verification boxes and every task,
|
|
73
|
+
and bump Implementation to verified.
|
|
74
|
+
Two gates ask for WRITING, and both refuse late if you leave them:
|
|
75
|
+
a change that alters a documented surface needs the documentation to
|
|
76
|
+
move with it, AND an entry in the project's changelog — one says how it
|
|
77
|
+
works, the other says that it changed. If the command and flag names in
|
|
78
|
+
your prose are only MENTIONED, declare it instead of forcing:
|
|
79
|
+
[[c]]- **Documented surface:** n/a — <why>[[/c]] in the proposal.
|
|
80
|
+
|
|
81
|
+
8. [[c]]doctrina next[[/c]] for the follow-up.
|
|
82
|
+
[[g]] If this change taught a reusable lesson (a fix you'd hate to relearn,[[/g]]
|
|
83
|
+
[[g]] a recurring convention), capture it: [[/g]][[c]]doctrina skill new <slug>[[/c]][[g]].[[/g]]
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import process from "node:process";
|
|
4
|
-
import { readdirSync } from "node:fs";
|
|
5
|
-
import { exists, isDir, isFile, read } from "../lib/fs-ops.js";
|
|
6
|
-
import { listHeader } from "../lib/scan.js";
|
|
7
|
-
import { c } from "../lib/colors.js";
|
|
8
|
-
import { notADoctrinaProject } from "../lib/exit-codes.js";
|
|
9
|
-
|
|
10
|
-
// The project's standing rules in one read — Spec Kit parity for its
|
|
11
|
-
// `constitution.md`, but ASSEMBLED, not a new home for facts. Doctrina's
|
|
12
|
-
// constitution is its accepted ADRs (the immutable decisions that govern how
|
|
13
|
-
// the codebase evolves) plus the product's declared non-goals. Both already
|
|
14
|
-
// own those facts elsewhere; this is a read-only digest that gathers them so a
|
|
15
|
-
// human or agent (especially one migrating from Spec Kit, which expects a
|
|
16
|
-
// single constitution) can see "the rules" without reading the whole tree.
|
|
17
|
-
//
|
|
18
|
-
// To change a principle, supersede its ADR; to change a non-goal, edit
|
|
19
|
-
// product.md. The command never writes — it has nothing of its own to own.
|
|
20
|
-
|
|
21
|
-
// Flags this command accepts. Declared HERE, with the command, so
|
|
22
|
-
// adding a command never requires editing the entrypoint — the gap that
|
|
23
|
-
// let six flags ship undeclared and silently swallow a positional (C3).
|
|
24
|
-
export const flags = { boolean: ["json"], string: [] };
|
|
25
|
-
|
|
26
|
-
export async function run(_positional, _flags) {
|
|
27
|
-
const projectRoot = process.cwd();
|
|
28
|
-
if (!exists(path.join(projectRoot, ".doctrina"))) {
|
|
29
|
-
throw notADoctrinaProject();
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const project = projectName(projectRoot);
|
|
33
|
-
console.log(
|
|
34
|
-
c.bold("Doctrina constitution") +
|
|
35
|
-
c.gray(` — ${project} (standing rules: accepted decisions + non-goals)`),
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
// Principles — the accepted ADRs, by number. Every accepted decision is a
|
|
39
|
-
// binding rule; superseded/withdrawn/proposed ADRs are not yet (or no longer)
|
|
40
|
-
// in force and stay out.
|
|
41
|
-
console.log("");
|
|
42
|
-
console.log(c.bold(" Principles") + c.gray(" (immutable — supersede an ADR to change one)"));
|
|
43
|
-
const adrs = acceptedDecisions(projectRoot);
|
|
44
|
-
if (adrs.length === 0) {
|
|
45
|
-
console.log(` ${c.gray("no accepted ADRs yet — record decisions with `doctrina decision new`")}`);
|
|
46
|
-
} else {
|
|
47
|
-
for (const a of adrs) console.log(` ${c.cyan("ADR " + a.id)} ${a.title}`);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Non-goals — the explicit "what this project will not be", from product.md.
|
|
51
|
-
console.log("");
|
|
52
|
-
console.log(c.bold(" Non-goals") + c.gray(" (.doctrina/product.md)"));
|
|
53
|
-
const nonGoals = productSection(projectRoot, "Non-goals");
|
|
54
|
-
if (nonGoals.length === 0) {
|
|
55
|
-
console.log(` ${c.gray("none declared — add a `## Non-goals` section to product.md")}`);
|
|
56
|
-
} else {
|
|
57
|
-
for (const g of nonGoals) console.log(` ${c.gray("•")} ${g}`);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
console.log("");
|
|
61
|
-
console.log(
|
|
62
|
-
c.gray(
|
|
63
|
-
` ${adrs.length} accepted decision${adrs.length === 1 ? "" : "s"} · ` +
|
|
64
|
-
`${nonGoals.length} non-goal${nonGoals.length === 1 ? "" : "s"} · read-only`,
|
|
65
|
-
),
|
|
66
|
-
);
|
|
67
|
-
return 0;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function projectName(projectRoot) {
|
|
71
|
-
try {
|
|
72
|
-
return JSON.parse(read(path.join(projectRoot, ".doctrina", "index.json"))).project
|
|
73
|
-
?? path.basename(projectRoot);
|
|
74
|
-
} catch {
|
|
75
|
-
return path.basename(projectRoot);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Accepted ADRs (NNNN-slug.md with Status: accepted), oldest first, with the
|
|
80
|
-
// title read from the `# ADR NNNN — <title>` heading. Exported for `prime`,
|
|
81
|
-
// which digests the same standing rules into the session primer.
|
|
82
|
-
export function acceptedDecisions(projectRoot) {
|
|
83
|
-
const dir = path.join(projectRoot, ".doctrina", "decisions");
|
|
84
|
-
const out = [];
|
|
85
|
-
if (!isDir(dir)) return out;
|
|
86
|
-
for (const f of readdirSync(dir).sort()) {
|
|
87
|
-
const m = f.match(/^(\d{4})-.*\.md$/);
|
|
88
|
-
if (!m) continue;
|
|
89
|
-
const text = read(path.join(dir, f));
|
|
90
|
-
if ((listHeader(text, "Status") ?? "").toLowerCase() !== "accepted") continue;
|
|
91
|
-
const titleMatch = text.match(/^#\s+ADR\s+\d{4}\s*[—-]\s*(.+)$/m);
|
|
92
|
-
out.push({ id: m[1], title: titleMatch ? titleMatch[1].trim() : f });
|
|
93
|
-
}
|
|
94
|
-
return out;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Bullets under a named `## <section>` of product.md, each accumulated across
|
|
98
|
-
// its continuation lines so a wrapped bullet reads as one rule. Exported for
|
|
99
|
-
// `prime`.
|
|
100
|
-
export function productSection(projectRoot, name) {
|
|
101
|
-
const p = path.join(projectRoot, ".doctrina", "product.md");
|
|
102
|
-
if (!isFile(p)) return [];
|
|
103
|
-
const lines = read(p).split(/\r?\n/);
|
|
104
|
-
let inSection = false;
|
|
105
|
-
const out = [];
|
|
106
|
-
let cur = null;
|
|
107
|
-
const flush = () => {
|
|
108
|
-
if (cur) out.push(cur.replace(/\s+/g, " ").trim());
|
|
109
|
-
cur = null;
|
|
110
|
-
};
|
|
111
|
-
for (const line of lines) {
|
|
112
|
-
if (/^##\s+/.test(line)) {
|
|
113
|
-
if (inSection) {
|
|
114
|
-
flush();
|
|
115
|
-
break;
|
|
116
|
-
}
|
|
117
|
-
if (new RegExp(`^##\\s+${name}\\b`, "i").test(line)) inSection = true;
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
if (!inSection) continue;
|
|
121
|
-
const m = line.match(/^\s*[-*]\s+(.+)$/);
|
|
122
|
-
if (m) {
|
|
123
|
-
flush();
|
|
124
|
-
cur = m[1];
|
|
125
|
-
} else if (cur && line.trim() !== "") {
|
|
126
|
-
cur += " " + line.trim();
|
|
127
|
-
} else if (line.trim() === "") {
|
|
128
|
-
flush();
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
flush();
|
|
132
|
-
return out;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export const help = `
|
|
136
|
-
Usage: doctrina constitution
|
|
137
|
-
|
|
138
|
-
Print the project's standing rules in one read: the accepted ADRs (the
|
|
139
|
-
immutable decisions that govern how the codebase evolves) and the product's
|
|
140
|
-
declared non-goals. Read-only — it assembles facts the ADRs and product.md
|
|
141
|
-
already own; it never writes.
|
|
142
|
-
|
|
143
|
-
This is the Spec Kit \`constitution.md\` analogue: a single place to see the
|
|
144
|
-
non-negotiables. To change one, supersede the ADR or edit product.md.
|
|
145
|
-
`;
|