doctrina-cli 0.2.2 → 0.4.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/src/lib/scan.js CHANGED
@@ -55,6 +55,7 @@ export function deriveIndex(projectRoot, current) {
55
55
  changes: [],
56
56
  changes_archive: [],
57
57
  skills: [],
58
+ contracts: [],
58
59
  },
59
60
  };
60
61
 
@@ -65,13 +66,28 @@ export function deriveIndex(projectRoot, current) {
65
66
  if (!isFile(p)) continue;
66
67
  const text = read(p);
67
68
  const prev = (cur.specs ?? []).find((s) => s.id === cap);
68
- out.artifacts.specs.push({
69
+ const entry = {
69
70
  id: cap,
70
71
  path: `.doctrina/specs/${cap}/spec.md`,
71
72
  status: specHeader(text, "Status") ?? prev?.status ?? "active",
72
73
  version: specHeader(text, "Version") ?? prev?.version ?? "0.1.0",
73
74
  last_updated: specHeader(text, "Last updated") ?? prev?.last_updated ?? date,
74
- });
75
+ };
76
+ // Implementation is the capability axis (planned -> partial ->
77
+ // implemented -> verified), independent of the document Status. Only
78
+ // record it when the spec declares it or the index already tracked it,
79
+ // so specs that never opted in (e.g. bug specs) stay off the axis.
80
+ // The stored value keeps any explanatory note after the state word.
81
+ const implRaw = specHeader(text, "Implementation");
82
+ const impl = implRaw ? implRaw.trim() : prev?.implementation;
83
+ if (impl) entry.implementation = impl;
84
+ // Realizes: the product intent anchors this capability delivers (ADR
85
+ // 0006). Stored as an id list so the provenance link is queryable, not
86
+ // just prose. Header wins over the previous index, like Implementation.
87
+ const realizesRaw = specHeader(text, "Realizes");
88
+ const realizes = realizesRaw ? (realizesRaw.match(/[A-Z]+\d+/g) ?? []) : prev?.realizes;
89
+ if (realizes && realizes.length) entry.realizes = realizes;
90
+ out.artifacts.specs.push(entry);
75
91
  }
76
92
 
77
93
  // Decisions — NNNN-slug.md files; Status/Date headers win.
@@ -94,6 +110,8 @@ export function deriveIndex(projectRoot, current) {
94
110
  if (supersedes && supersedes !== "—") entry.supersedes = supersedes;
95
111
  const supersededBy = listHeader(text, "Superseded by");
96
112
  if (supersededBy && supersededBy !== "—") entry.superseded_by = supersededBy;
113
+ const landed = listHeader(text, "Landed");
114
+ if (landed && landed !== "—") entry.landed = landed;
97
115
  out.artifacts.decisions.push(entry);
98
116
  }
99
117
 
@@ -143,6 +161,22 @@ export function deriveIndex(projectRoot, current) {
143
161
  });
144
162
  }
145
163
 
164
+ // Contracts — the integration/runtime surface (ports, env, interfaces)
165
+ // that no single capability owns. Headers inside each contract win.
166
+ const contractsDir = path.join(dot, "contracts");
167
+ for (const f of walk(contractsDir)) {
168
+ if (!f.endsWith(".md")) continue;
169
+ const id = path.basename(f, ".md");
170
+ const text = read(f);
171
+ const prev = (cur.contracts ?? []).find((s) => s.id === id);
172
+ out.artifacts.contracts.push({
173
+ id,
174
+ path: `.doctrina/contracts/${id}.md`,
175
+ status: specHeader(text, "Status") ?? prev?.status ?? "active",
176
+ last_updated: specHeader(text, "Last updated") ?? prev?.last_updated ?? date,
177
+ });
178
+ }
179
+
146
180
  // Skills — frontmatter description wins.
147
181
  const skillsDir = path.join(dot, "skills");
148
182
  for (const f of walk(skillsDir)) {
@@ -183,7 +217,7 @@ export function indexesMatch(a, b) {
183
217
  const clone = { ...x };
184
218
  delete clone.last_updated;
185
219
  const arts = { ...(clone.artifacts ?? {}) };
186
- for (const cat of ["specs", "decisions", "changes", "changes_archive", "skills"]) {
220
+ for (const cat of ["specs", "decisions", "changes", "changes_archive", "skills", "contracts"]) {
187
221
  arts[cat] = arts[cat] ?? [];
188
222
  }
189
223
  clone.artifacts = arts;
@@ -0,0 +1,21 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { readFileSync } from "node:fs";
4
+
5
+ // Single source for "which CLI version is running." Read from the package
6
+ // manifest so it tracks the published version with no extra wiring. Used to
7
+ // stamp index.json (so an artifact tree records the framework that manages
8
+ // it — `doctrina validate` warns and `index rebuild` migrates when the stamp
9
+ // falls behind the running CLI) and to print `doctrina --version`.
10
+
11
+ const here = path.dirname(fileURLToPath(import.meta.url));
12
+
13
+ export function cliVersion() {
14
+ // src/lib/version.js -> packages/doctrina-cli/package.json
15
+ const pkgPath = path.resolve(here, "..", "..", "package.json");
16
+ try {
17
+ return JSON.parse(readFileSync(pkgPath, "utf8")).version ?? "0.0.0";
18
+ } catch {
19
+ return "0.0.0";
20
+ }
21
+ }
@@ -19,7 +19,16 @@
19
19
 
20
20
  ## Verification
21
21
 
22
- <!-- How you will know the change is correctly applied. -->
22
+ <!--
23
+ How you will know the change is correctly applied. Use checkboxes: every
24
+ box here is a claim that must be PROVEN before the change is done.
25
+ `doctrina change archive` refuses to archive while any box below is
26
+ unchecked (pass --force to archive anyway and record the gap). Distinguish
27
+ "task marked done" from "verification passed" — link the evidence.
28
+ -->
29
+
30
+ - [ ] Automated checks pass (`doctrina verify`, or the project's typecheck/test/build).
31
+ - [ ] The affected spec's acceptance criteria are met and cite their evidence (`doctrina coverage`).
23
32
 
24
33
  ## Open questions
25
34
 
@@ -0,0 +1,58 @@
1
+ # Contract — {{CONTRACT_NAME}}
2
+
3
+ **Contract:** {{CONTRACT_NAME}}
4
+ **Status:** active
5
+ **Last updated:** {{DATE}}
6
+
7
+ ## Purpose
8
+
9
+ <!--
10
+ The integration/runtime surface this contract OWNS. No single capability
11
+ spec owns the seams between services and front-ends — this artifact does.
12
+ If a login response shape, a WebSocket path, or a Kafka topic falls in the
13
+ gap between two specs, it belongs here.
14
+ -->
15
+
16
+ ## Ports
17
+
18
+ <!--
19
+ The single source of truth for who listens where. `doctrina contract check`
20
+ fails when two rows claim the same port (the "front points at the wrong
21
+ port" class of bug). Front-ends and clients read their targets from here.
22
+ -->
23
+
24
+ | Service | Port | Protocol |
25
+ |---------|------|----------|
26
+ | gateway | 8080 | http |
27
+
28
+ ## Environment
29
+
30
+ <!--
31
+ Variables the running system requires, named IDENTICALLY across code,
32
+ `.env.example`, and infra (compose/helm). `doctrina contract check` warns
33
+ when a variable here is missing from `.env.example` — the DATABASE_URL /
34
+ KAFKA_BROKER drift class.
35
+ -->
36
+
37
+ | Variable | Required | Example |
38
+ |--------------|----------|-------------------------------------------|
39
+ | DATABASE_URL | yes | postgres://user:pass@localhost:5432/app |
40
+
41
+ ## Interfaces
42
+
43
+ <!--
44
+ API / WebSocket / event-stream contracts: endpoints, message shapes, event
45
+ schemas, and the exact response shape each consumer can rely on (e.g. login
46
+ returns `{ token, user }`, not a bare `TokenPair`).
47
+ -->
48
+
49
+ -
50
+
51
+ ## References
52
+
53
+ <!--
54
+ The capability specs that must conform to this contract. List each as
55
+ `specs/<capability>`; `doctrina contract check` verifies each spec exists.
56
+ -->
57
+
58
+ - `specs/<capability>`
@@ -5,6 +5,24 @@
5
5
  - **Deciders:**
6
6
  - **Supersedes:** —
7
7
  - **Superseded by:** —
8
+ - **Evidence:** —
9
+ - **Landed:** —
10
+
11
+ <!--
12
+ Evidence anchors the decision to reality. Once accepted, cite the file(s)
13
+ that prove this decision is actually implemented, in backticks, e.g.
14
+ "- **Evidence:** `proto/order.proto`, `services/gateway/grpc.ts`".
15
+ `doctrina validate` warns when cited evidence is missing on disk (the
16
+ decision drifted from the code) or when an accepted ADR cites none. For a
17
+ process decision with no code artifact, write "n/a — <why>".
18
+
19
+ Landed is the non-mutating "this decision is now implemented and verified
20
+ here" stamp. Many ADRs are accepted at design time with "Evidence: n/a — no
21
+ implementation yet"; once the capability ships, run
22
+ "doctrina decision land NNNN `path/to/proof.ts` ..." to record the date and
23
+ proof without editing the immutable body. A filled Landed satisfies the
24
+ accepted-ADR evidence check.
25
+ -->
8
26
 
9
27
  ## Context
10
28
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema_version": "0.1.0",
3
3
  "project": "{{PROJECT_NAME}}",
4
- "framework_version": "0.0.0",
4
+ "framework_version": "{{FRAMEWORK_VERSION}}",
5
5
  "last_updated": "{{DATE}}",
6
6
  "artifacts": {
7
7
  "product": {
@@ -28,4 +28,21 @@ Out of scope (deferred or rejected):
28
28
 
29
29
  ## Success criteria
30
30
 
31
- <!-- How you will know this project is working. Prefer measurable signals. -->
31
+ <!--
32
+ How you will know this project is working. Prefer measurable signals.
33
+ Tag a bullet with a short stable id — "- [SC1] ..." — to make it an intent
34
+ anchor that capability specs cite via "**Realizes:** SC1" (`doctrina trace`;
35
+ ADR 0006). Untagged bullets are fine; anchors are opt-in.
36
+ -->
37
+
38
+ ## Delivery order (walking skeleton)
39
+
40
+ <!--
41
+ Depth before breadth. Name the ONE end-to-end path that must work and be
42
+ verified before any capability fans out — the walking skeleton (e.g.
43
+ auth -> ingest -> ws -> ui). A spec'd capability with no working slice
44
+ through it is breadth bought on credit: build and `doctrina verify` the
45
+ skeleton first, then spec the next capability.
46
+ -->
47
+
48
+ 1.
@@ -1,10 +1,28 @@
1
1
  # Spec — {{CAPABILITY}}
2
2
 
3
3
  **Capability:** {{CAPABILITY}}
4
- **Status:** active
4
+ **Status:** draft
5
+ **Implementation:** planned
5
6
  **Last updated:** {{DATE}}
6
7
  **Version:** 0.1.0
7
8
 
9
+ <!--
10
+ Two independent axes — keep them honest:
11
+ Status — the document: draft -> active -> deprecated
12
+ Implementation — the capability: planned -> partial -> implemented -> verified
13
+ Flip Status to "active" only once this spec reflects intended truth.
14
+ Advance Implementation as code lands; "verified" means a test proves it
15
+ (cite the test per acceptance criterion — see `doctrina coverage`).
16
+ An "active" spec left at "planned" is an inventory claim with nothing
17
+ behind it, and `doctrina validate` warns. To record a deliberate gap and
18
+ silence the warning, add a note after the value, e.g.
19
+ "**Implementation:** planned — backend deferred to Q3, see ADR 0007".
20
+
21
+ Optional: trace this capability to product intent by adding a header
22
+ "**Realizes:** SC1, SC3" that names the product.md anchors it delivers
23
+ (`doctrina trace`; ADR 0006).
24
+ -->
25
+
8
26
  ## Purpose
9
27
 
10
28
  <!-- One paragraph: what this capability does and why it exists. -->
@@ -43,9 +61,33 @@ Easy Approach to Requirements Syntax. Use one of the five forms per item:
43
61
 
44
62
  ## Acceptance criteria
45
63
 
46
- <!-- Concrete, observable signals that the capability is correctly implemented. -->
64
+ <!--
65
+ Concrete, observable signals that the capability is correctly implemented.
66
+ Each criterion is also a claim that must be proven: mark it [verified] or
67
+ [unverified] and cite the artifact/test in backticks. `doctrina coverage`
68
+ reports which criteria have no linked evidence — an [unverified] criterion
69
+ with no test is explicit debt, not a fact.
70
+ -->
71
+
72
+ 1. [unverified] <observable signal> — verified by `path/to/test`.
47
73
 
48
- 1.
74
+ ## Maturity
75
+
76
+ <!--
77
+ The line between an MVP and a product wishlist. List under MVP only what
78
+ this capability COMMITS to now. Anything aspirational goes under Future and
79
+ is explicitly NOT accepted scope — do not write Future items in EARS as if
80
+ they were already built (no WebGPU/multi-cloud/"50k entities at 30fps"
81
+ smuggled in as committed requirements).
82
+ -->
83
+
84
+ **MVP (committed):**
85
+
86
+ -
87
+
88
+ **Future (aspirational, not committed):**
89
+
90
+ -
49
91
 
50
92
  ## Out of scope for this spec
51
93