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/README.md +20 -2
- package/package.json +1 -1
- package/src/commands/change.js +91 -18
- package/src/commands/contract.js +254 -0
- package/src/commands/coverage.js +179 -0
- package/src/commands/decision.js +83 -1
- package/src/commands/index-rebuild.js +9 -0
- package/src/commands/init.js +7 -1
- package/src/commands/intake.js +32 -8
- package/src/commands/search.js +42 -7
- package/src/commands/spec.js +16 -5
- package/src/commands/templates.js +1 -1
- package/src/commands/trace.js +157 -0
- package/src/commands/validate.js +149 -2
- package/src/commands/verify.js +153 -0
- package/src/commands/work.js +36 -13
- package/src/index.js +14 -16
- package/src/lib/clarity.js +49 -0
- package/src/lib/index-json.js +17 -2
- package/src/lib/scan.js +37 -3
- package/src/lib/version.js +21 -0
- package/templates/change/proposal.md.template +10 -1
- package/templates/contract.md.template +58 -0
- package/templates/decision.md.template +18 -0
- package/templates/doctrina/index.json.template +1 -1
- package/templates/doctrina/product.md.template +18 -1
- package/templates/spec.md.template +45 -3
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
|
-
|
|
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
|
-
<!--
|
|
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
|
|
|
@@ -28,4 +28,21 @@ Out of scope (deferred or rejected):
|
|
|
28
28
|
|
|
29
29
|
## Success criteria
|
|
30
30
|
|
|
31
|
-
<!--
|
|
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:**
|
|
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
|
-
<!--
|
|
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
|
-
|
|
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
|
|