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.
Files changed (93) hide show
  1. package/README.md +27 -72
  2. package/package.json +3 -3
  3. package/src/commands/adapter.js +27 -3
  4. package/src/commands/analyze.js +16 -212
  5. package/src/commands/change.js +144 -267
  6. package/src/commands/ci.js +182 -0
  7. package/src/commands/clarify.js +65 -21
  8. package/src/commands/close.js +349 -45
  9. package/src/commands/completion.js +8 -2
  10. package/src/commands/context.js +284 -111
  11. package/src/commands/contract.js +95 -75
  12. package/src/commands/coverage.js +58 -273
  13. package/src/commands/decision.js +66 -7
  14. package/src/commands/doctor.js +293 -130
  15. package/src/commands/handoff.js +10 -57
  16. package/src/commands/hooks.js +17 -3
  17. package/src/commands/index-rebuild.js +55 -44
  18. package/src/commands/init.js +88 -16
  19. package/src/commands/intake.js +85 -99
  20. package/src/commands/intent.js +14 -1
  21. package/src/commands/metrics.js +65 -88
  22. package/src/commands/next.js +105 -147
  23. package/src/commands/prime.js +23 -91
  24. package/src/commands/report.js +40 -109
  25. package/src/commands/review.js +68 -36
  26. package/src/commands/search.js +18 -4
  27. package/src/commands/show.js +24 -9
  28. package/src/commands/skill.js +23 -39
  29. package/src/commands/spec.js +38 -11
  30. package/src/commands/status.js +69 -171
  31. package/src/commands/templates.js +35 -148
  32. package/src/commands/trace.js +36 -71
  33. package/src/commands/triage.js +4 -82
  34. package/src/commands/upgrade.js +31 -6
  35. package/src/commands/validate.js +44 -877
  36. package/src/commands/verify.js +60 -138
  37. package/src/commands/why.js +2 -2
  38. package/src/commands/work.js +243 -267
  39. package/src/index.js +139 -12
  40. package/src/lib/actions.js +490 -0
  41. package/src/lib/adapters.js +0 -11
  42. package/src/lib/adr-guard.js +19 -0
  43. package/src/lib/agent-changelog.js +133 -0
  44. package/src/lib/analysis.js +264 -0
  45. package/src/lib/args.js +14 -0
  46. package/src/lib/change-ops.js +133 -0
  47. package/src/lib/commands.js +135 -10
  48. package/src/lib/config.js +206 -0
  49. package/src/lib/constitution-model.js +102 -0
  50. package/src/lib/coverage-model.js +431 -0
  51. package/src/lib/criteria.js +23 -0
  52. package/src/lib/doc-model.js +370 -3
  53. package/src/lib/docs-impact.js +270 -27
  54. package/src/lib/ears.js +24 -1
  55. package/src/lib/flag-catalog.js +0 -16
  56. package/src/lib/fs-ops.js +58 -1
  57. package/src/lib/gates.js +231 -29
  58. package/src/lib/git.js +132 -0
  59. package/src/lib/index-json.js +42 -24
  60. package/src/lib/intake-model.js +155 -0
  61. package/src/lib/json-out.js +100 -2
  62. package/src/lib/ledger.js +192 -0
  63. package/src/lib/lexicon.js +228 -0
  64. package/src/lib/metrics-model.js +189 -0
  65. package/src/lib/names.js +42 -0
  66. package/src/lib/playbook.js +97 -0
  67. package/src/lib/project.js +105 -0
  68. package/src/lib/reproducibility.js +137 -0
  69. package/src/lib/runtime.js +191 -5
  70. package/src/lib/scan.js +328 -25
  71. package/src/lib/signoff.js +147 -0
  72. package/src/lib/snapshot.js +231 -0
  73. package/src/lib/spec-ops.js +72 -8
  74. package/src/lib/templates-model.js +289 -0
  75. package/src/lib/trace-model.js +95 -0
  76. package/src/lib/triage-model.js +121 -0
  77. package/src/lib/usage.js +6 -4
  78. package/src/lib/validation-model.js +1381 -0
  79. package/src/lib/version.js +22 -0
  80. package/src/lib/views.js +413 -0
  81. package/src/lib/work-model.js +102 -0
  82. package/templates/AGENTS.md.template +21 -28
  83. package/templates/README.md +35 -0
  84. package/templates/adapters/claude/.claude/commands/doctrina-work.md.template +6 -3
  85. package/templates/adapters/cursor/.cursor/commands/doctrina-work.md.template +4 -3
  86. package/templates/change/proposal.md.template +16 -0
  87. package/templates/change/tasks.md.template +3 -8
  88. package/templates/contract.md.template +2 -2
  89. package/templates/hooks/pre-commit.sample +16 -2
  90. package/templates/playbooks/bootstrap.md.template +47 -0
  91. package/templates/playbooks/chore.md.template +24 -0
  92. package/templates/playbooks/work.md.template +83 -0
  93. package/src/commands/constitution.js +0 -145
@@ -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, then `doctrina analyze <id>` → `doctrina change apply <id>`.
13
- 3. Close it: `doctrina close <id>` (verify → coverage → archive → validate) and
14
- resolve every `error:` before reporting the work done.
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 analyze <id>` → `doctrina change apply <id>`.
9
- 3. `doctrina close <id>` (verify → coverage → archive → validate); resolve
10
- every `error:` before reporting the work done.
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, including the
6
- three closing steps at the bottom.
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 analyze` refuses a
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 `doctrina validate`.
16
+ # line below with a bare `$DOCTRINA validate`.
17
17
 
18
- doctrina validate --fix || exit 1
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
- `;