doctrina-cli 0.10.0 → 0.11.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 +1 -1
- package/package.json +1 -1
- package/src/commands/completion.js +116 -0
- package/src/commands/constitution.js +6 -4
- package/src/commands/context.js +108 -24
- package/src/commands/coverage.js +41 -16
- package/src/commands/doctor.js +135 -0
- package/src/commands/handoff.js +94 -0
- package/src/commands/init.js +12 -1
- package/src/commands/next.js +32 -16
- package/src/commands/prime.js +118 -0
- package/src/commands/report.js +142 -0
- package/src/commands/show.js +173 -0
- package/src/commands/status.js +70 -40
- package/src/commands/trace.js +15 -2
- package/src/commands/validate.js +33 -1
- package/src/commands/why.js +72 -9
- package/src/index.js +10 -41
- package/src/lib/commands.js +72 -3
- package/templates/README.md +7 -1
- package/templates/adapters/aider/CONVENTIONS.md.template +4 -3
package/src/commands/status.js
CHANGED
|
@@ -7,6 +7,7 @@ import { deriveIndex, indexesMatch, specHeader, listHeader } from "../lib/scan.j
|
|
|
7
7
|
import { cliVersion } from "../lib/version.js";
|
|
8
8
|
import { summarize as coverageSummary } from "./coverage.js";
|
|
9
9
|
import { summarize as traceSummary } from "./trace.js";
|
|
10
|
+
import { flagBool } from "../lib/args.js";
|
|
10
11
|
import { c } from "../lib/colors.js";
|
|
11
12
|
|
|
12
13
|
// One-glance project health (review 2026-06-27 passive-user feature #1): a
|
|
@@ -19,53 +20,47 @@ import { c } from "../lib/colors.js";
|
|
|
19
20
|
|
|
20
21
|
const CONFIG_REL = ".doctrina/verify.json";
|
|
21
22
|
|
|
22
|
-
export async function run(_positional,
|
|
23
|
+
export async function run(_positional, flags) {
|
|
23
24
|
const projectRoot = process.cwd();
|
|
24
25
|
if (!exists(path.join(projectRoot, ".doctrina"))) {
|
|
25
26
|
throw new Error("not a Doctrina project (no .doctrina/ in cwd). Run `doctrina init` first.");
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
const s = collectStatus(projectRoot);
|
|
30
|
+
|
|
31
|
+
if (flagBool(flags, "json", false)) {
|
|
32
|
+
console.log(JSON.stringify(s, null, 2));
|
|
33
|
+
return 0;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
const stamp = index?.framework_version ?? "—";
|
|
37
|
-
const cli = cliVersion();
|
|
38
|
-
console.log(c.bold("Doctrina status") + c.gray(` — ${project} (framework ${stamp} / CLI ${cli})`));
|
|
36
|
+
console.log(c.bold("Doctrina status") + c.gray(` — ${s.project} (framework ${s.stamp ?? "—"} / CLI ${s.cli})`));
|
|
39
37
|
console.log("");
|
|
40
38
|
|
|
41
39
|
// --- Gates ---
|
|
42
40
|
console.log(c.bold(" Gates"));
|
|
43
|
-
// Index drift (the silent rot validate now errors on).
|
|
44
41
|
let driftLine;
|
|
45
|
-
if (
|
|
42
|
+
if (s.indexState === "missing") {
|
|
46
43
|
driftLine = c.red("missing/unreadable") + c.gray(" — run `doctrina index rebuild`");
|
|
47
|
-
} else if (
|
|
44
|
+
} else if (s.indexState === "in-sync") {
|
|
48
45
|
driftLine = c.green("in sync");
|
|
49
46
|
} else {
|
|
50
47
|
driftLine = c.yellow("drifted") + c.gray(" — run `doctrina validate --fix`");
|
|
51
48
|
}
|
|
52
49
|
console.log(` ${"index".padEnd(11)} ${driftLine}`);
|
|
53
50
|
|
|
54
|
-
|
|
55
|
-
const stampLine = stamp === cli
|
|
51
|
+
const stampLine = s.stamp === s.cli
|
|
56
52
|
? c.green("current")
|
|
57
|
-
: c.yellow(`${stamp} (CLI ${cli})`) + c.gray(" — `doctrina index rebuild`");
|
|
53
|
+
: c.yellow(`${s.stamp ?? "—"} (CLI ${s.cli})`) + c.gray(" — `doctrina index rebuild`");
|
|
58
54
|
console.log(` ${"stamp".padEnd(11)} ${stampLine}`);
|
|
59
55
|
|
|
60
|
-
|
|
61
|
-
const cov = coverageSummary(projectRoot);
|
|
56
|
+
const cov = s.coverage;
|
|
62
57
|
const covExtra = cov.totalDangling + cov.totalConditional > 0
|
|
63
58
|
? c.yellow(` ${cov.totalDangling} dangling, ${cov.totalConditional} conditional`)
|
|
64
59
|
: "";
|
|
65
60
|
const covColor = cov.pct === 100 ? c.green : cov.pct >= 50 ? c.yellow : c.red;
|
|
66
61
|
console.log(` ${"coverage".padEnd(11)} ${covColor(`${cov.pct}%`)} ${c.gray(`(${cov.totalCovered}/${cov.totalCriteria} criteria)`)}${covExtra}`);
|
|
67
62
|
|
|
68
|
-
const tr =
|
|
63
|
+
const tr = s.trace;
|
|
69
64
|
const trExtra = tr.untraceable + tr.dropped + tr.dangling > 0
|
|
70
65
|
? c.yellow(` ${tr.dropped} dropped, ${tr.untraceable} untraceable`)
|
|
71
66
|
: "";
|
|
@@ -73,40 +68,72 @@ export async function run(_positional, _flags) {
|
|
|
73
68
|
const trText = tr.anchors === 0 ? "no anchors" : `${tr.realized}/${tr.anchors} anchors`;
|
|
74
69
|
console.log(` ${"trace".padEnd(11)} ${trColor(trText)}${trExtra}`);
|
|
75
70
|
|
|
76
|
-
// Verify config presence (running it is the slow, authoritative gate).
|
|
77
|
-
const verifyCfg = path.join(projectRoot, CONFIG_REL);
|
|
78
71
|
let verifyLine = c.gray("not configured") + c.gray(" — `doctrina verify --init`");
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
} catch {
|
|
85
|
-
verifyLine = c.red("invalid JSON");
|
|
86
|
-
}
|
|
72
|
+
if (s.verify.invalid) {
|
|
73
|
+
verifyLine = c.red("invalid JSON");
|
|
74
|
+
} else if (s.verify.configured) {
|
|
75
|
+
const n = s.verify.checks;
|
|
76
|
+
verifyLine = c.cyan(`${n} check${n === 1 ? "" : "s"}`) + c.gray(" — run `doctrina verify`");
|
|
87
77
|
}
|
|
88
78
|
console.log(` ${"verify".padEnd(11)} ${verifyLine}`);
|
|
89
79
|
|
|
90
80
|
// --- Work ---
|
|
91
81
|
console.log("");
|
|
92
82
|
console.log(c.bold(" Work"));
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
console.log(` ${"
|
|
96
|
-
console.log(` ${"changes".padEnd(11)} ${specs.openChanges} open`);
|
|
97
|
-
const adr = countDecisions(projectRoot);
|
|
83
|
+
const implBreak = Object.entries(s.specs.impl).map(([k, v]) => `${v} ${k}`).join(", ");
|
|
84
|
+
console.log(` ${"specs".padEnd(11)} ${s.specs.total}${implBreak ? c.gray(` (${implBreak})`) : ""}`);
|
|
85
|
+
console.log(` ${"changes".padEnd(11)} ${s.specs.openChanges} open`);
|
|
98
86
|
const adrNotes = [];
|
|
99
|
-
if (
|
|
100
|
-
if (
|
|
101
|
-
console.log(` ${"decisions".padEnd(11)} ${
|
|
102
|
-
|
|
103
|
-
console.log(` ${"skills".padEnd(11)} ${skills}`);
|
|
87
|
+
if (s.decisions.proposed > 0) adrNotes.push(c.yellow(`${s.decisions.proposed} proposed`));
|
|
88
|
+
if (s.decisions.bare > 0) adrNotes.push(c.yellow(`${s.decisions.bare} unproven`));
|
|
89
|
+
console.log(` ${"decisions".padEnd(11)} ${s.decisions.total}${adrNotes.length ? c.gray(" (") + adrNotes.join(c.gray(", ")) + c.gray(")") : ""}`);
|
|
90
|
+
console.log(` ${"skills".padEnd(11)} ${s.skills}`);
|
|
104
91
|
|
|
105
92
|
console.log("");
|
|
106
93
|
console.log(c.gray(" Full gates: `doctrina validate` · `doctrina verify`. Next step: `doctrina next`."));
|
|
107
94
|
return 0;
|
|
108
95
|
}
|
|
109
96
|
|
|
97
|
+
// Pure, render-free status snapshot — the data behind the dashboard, shared
|
|
98
|
+
// with `prime`, `handoff`, `doctor`, and the `--json` output so every consumer
|
|
99
|
+
// reads the same numbers.
|
|
100
|
+
export function collectStatus(projectRoot) {
|
|
101
|
+
let index = null;
|
|
102
|
+
try {
|
|
103
|
+
index = idx.load(projectRoot);
|
|
104
|
+
} catch {
|
|
105
|
+
index = null;
|
|
106
|
+
}
|
|
107
|
+
let indexState = "missing";
|
|
108
|
+
if (index) {
|
|
109
|
+
indexState = indexesMatch(deriveIndex(projectRoot, index), index) ? "in-sync" : "drifted";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const verifyCfg = path.join(projectRoot, CONFIG_REL);
|
|
113
|
+
let verify = { configured: false, checks: 0, invalid: false };
|
|
114
|
+
if (isFile(verifyCfg)) {
|
|
115
|
+
try {
|
|
116
|
+
const cfg = JSON.parse(read(verifyCfg));
|
|
117
|
+
verify = { configured: true, checks: Array.isArray(cfg?.checks) ? cfg.checks.length : 0, invalid: false };
|
|
118
|
+
} catch {
|
|
119
|
+
verify = { configured: true, checks: 0, invalid: true };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
project: index?.project ?? path.basename(projectRoot),
|
|
125
|
+
stamp: index?.framework_version ?? null,
|
|
126
|
+
cli: cliVersion(),
|
|
127
|
+
indexState,
|
|
128
|
+
coverage: coverageSummary(projectRoot),
|
|
129
|
+
trace: traceSummary(projectRoot),
|
|
130
|
+
verify,
|
|
131
|
+
specs: countSpecs(projectRoot),
|
|
132
|
+
decisions: countDecisions(projectRoot),
|
|
133
|
+
skills: countSkills(projectRoot),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
110
137
|
function countSpecs(projectRoot) {
|
|
111
138
|
const specsDir = path.join(projectRoot, ".doctrina", "specs");
|
|
112
139
|
const impl = {};
|
|
@@ -165,13 +192,16 @@ function countSkills(projectRoot) {
|
|
|
165
192
|
}
|
|
166
193
|
|
|
167
194
|
export const help = `
|
|
168
|
-
Usage: doctrina status
|
|
195
|
+
Usage: doctrina status [--json]
|
|
169
196
|
|
|
170
197
|
Print a one-glance health dashboard for the .doctrina/ project: the gate
|
|
171
198
|
signals (index drift, framework stamp, coverage %, trace anchors, whether
|
|
172
199
|
verify is configured) and the artifact counts (specs by implementation
|
|
173
200
|
state, open changes, decisions, skills). Read-only; always exits 0.
|
|
174
201
|
|
|
202
|
+
Flags:
|
|
203
|
+
--json Emit the snapshot as JSON (stable shape for agents and CI).
|
|
204
|
+
|
|
175
205
|
It is a fast summary, not the authoritative gate: run \`doctrina validate\`
|
|
176
206
|
and \`doctrina verify\` for the full structural and build checks, and
|
|
177
207
|
\`doctrina next\` for the recommended next action.
|
package/src/commands/trace.js
CHANGED
|
@@ -32,6 +32,7 @@ export async function run(_positional, flags) {
|
|
|
32
32
|
throw new Error("not a Doctrina project (no .doctrina/ in cwd). Run `doctrina init` first.");
|
|
33
33
|
}
|
|
34
34
|
const strict = flagBool(flags, "strict", false);
|
|
35
|
+
const json = flagBool(flags, "json", false);
|
|
35
36
|
|
|
36
37
|
const anchors = collectAnchors(projectRoot); // [{ id }], in document order
|
|
37
38
|
const specs = collectSpecs(projectRoot); // [{ cap, status, realizes: [id] }]
|
|
@@ -41,6 +42,10 @@ export async function run(_positional, flags) {
|
|
|
41
42
|
|
|
42
43
|
// The feature is unused: do not nag a project that never opted in.
|
|
43
44
|
if (anchors.length === 0 && !anyRealizes) {
|
|
45
|
+
if (json) {
|
|
46
|
+
console.log(JSON.stringify({ anchors: [], dangling: [], untraceable: [], summary: summarize(projectRoot) }, null, 2));
|
|
47
|
+
return 0;
|
|
48
|
+
}
|
|
44
49
|
console.log(
|
|
45
50
|
c.gray(
|
|
46
51
|
"no intent-provenance markers found — tag product.md bullets with `[SC1]` " +
|
|
@@ -69,6 +74,13 @@ export async function run(_positional, flags) {
|
|
|
69
74
|
.filter((s) => s.realizes === null && s.status === "active")
|
|
70
75
|
.map((s) => s.cap);
|
|
71
76
|
|
|
77
|
+
if (json) {
|
|
78
|
+
const rows = anchors.map((a) => ({ id: a.id, realizedBy: (realizedBy.get(a.id) ?? []).sort() }));
|
|
79
|
+
const clean = rows.every((r) => r.realizedBy.length > 0) && dangling.length === 0 && untraceable.length === 0;
|
|
80
|
+
console.log(JSON.stringify({ anchors: rows, dangling, untraceable, summary: summarize(projectRoot) }, null, 2));
|
|
81
|
+
return clean ? 0 : strict ? 1 : 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
72
84
|
console.log(c.bold("Trace") + c.gray(" — product intent → capability provenance:"));
|
|
73
85
|
console.log("");
|
|
74
86
|
|
|
@@ -183,6 +195,7 @@ trace reports three provenance breaks:
|
|
|
183
195
|
untraceable spec an active spec with no Realizes: header
|
|
184
196
|
|
|
185
197
|
Read-only. Exits 0 as a report; with --strict, exits 1 when any break
|
|
186
|
-
exists (CI gate).
|
|
187
|
-
that
|
|
198
|
+
exists (CI gate). --json emits anchors/dangling/untraceable + summary
|
|
199
|
+
as JSON. It checks that the link exists and is complete — not that a
|
|
200
|
+
criterion is faithful to the intent (that stays a human/LLM call).
|
|
188
201
|
`;
|
package/src/commands/validate.js
CHANGED
|
@@ -499,7 +499,35 @@ export async function run(_positional, flags) {
|
|
|
499
499
|
}
|
|
500
500
|
}
|
|
501
501
|
|
|
502
|
+
// 16. Bilingual docs parity. Projects that keep an EN-source docs tree
|
|
503
|
+
// with a PT mirror (docs/en + docs/pt) get filename-parity checking:
|
|
504
|
+
// a file added in one language and missing in the other is invisible
|
|
505
|
+
// to every other gate, and the mirror silently falls behind. Only the
|
|
506
|
+
// deterministic half (file sets) is checked — content staleness stays
|
|
507
|
+
// a human judgement. Projects without both trees never see this.
|
|
508
|
+
const docsEn = path.join(projectRoot, "docs", "en");
|
|
509
|
+
const docsPt = path.join(projectRoot, "docs", "pt");
|
|
510
|
+
if (isDir(docsEn) && isDir(docsPt)) {
|
|
511
|
+
const mdSet = (dir) => new Set(readdirSync(dir).filter((f) => f.endsWith(".md")));
|
|
512
|
+
const enSet = mdSet(docsEn);
|
|
513
|
+
const ptSet = mdSet(docsPt);
|
|
514
|
+
for (const f of [...enSet].sort()) {
|
|
515
|
+
if (!ptSet.has(f)) {
|
|
516
|
+
warnings.push(`docs/en/${f} has no docs/pt/${f} counterpart (EN is the source; the PT mirror fell behind)`);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
for (const f of [...ptSet].sort()) {
|
|
520
|
+
if (!enSet.has(f)) {
|
|
521
|
+
warnings.push(`docs/pt/${f} has no docs/en/${f} counterpart (PT translates an EN source, never leads it)`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
502
526
|
// Output
|
|
527
|
+
if (flagBool(flags, "json", false)) {
|
|
528
|
+
console.log(JSON.stringify({ ok: errors.length === 0, errors, warnings }, null, 2));
|
|
529
|
+
return errors.length === 0 ? 0 : 1;
|
|
530
|
+
}
|
|
503
531
|
for (const w of warnings) console.log(c.yellow("warn: ") + w);
|
|
504
532
|
for (const e of errors) console.log(c.red("error: ") + e);
|
|
505
533
|
|
|
@@ -613,9 +641,13 @@ function isLikelyPath(s) {
|
|
|
613
641
|
}
|
|
614
642
|
|
|
615
643
|
export const help = `
|
|
616
|
-
Usage: doctrina validate
|
|
644
|
+
Usage: doctrina validate [--fix] [--json]
|
|
617
645
|
|
|
618
646
|
Run schema, artifact-existence, and structural checks against the
|
|
619
647
|
.doctrina/ tree in the current working directory. Exits 0 if no errors
|
|
620
648
|
(warnings allowed), 1 otherwise.
|
|
649
|
+
|
|
650
|
+
Flags:
|
|
651
|
+
--fix Rebuild index.json from the tree before checking (heals drift).
|
|
652
|
+
--json Emit { ok, errors, warnings } as JSON (stable shape for agents/CI).
|
|
621
653
|
`;
|
package/src/commands/why.js
CHANGED
|
@@ -26,10 +26,17 @@ export async function run(positional, _flags) {
|
|
|
26
26
|
const known = isDir(specsDir) ? readdirSync(specsDir).filter((e) => isFile(path.join(specsDir, e, "spec.md"))) : [];
|
|
27
27
|
|
|
28
28
|
if (!cap) {
|
|
29
|
-
console.error(c.red("error:") + " why requires a <capability>");
|
|
29
|
+
console.error(c.red("error:") + " why requires a <capability> (or an intent anchor like SC1)");
|
|
30
30
|
if (known.length) console.error(c.gray("known: ") + known.sort().join(", "));
|
|
31
31
|
return 2;
|
|
32
32
|
}
|
|
33
|
+
|
|
34
|
+
// Reverse mode: `doctrina why SC1` walks the chain the other way — from a
|
|
35
|
+
// product intent anchor to the capabilities that realize it and the work
|
|
36
|
+
// that delivered them. Forward mode asks "why does this capability exist?";
|
|
37
|
+
// reverse mode asks "who delivers this promise?".
|
|
38
|
+
if (/^[A-Z]+\d+$/.test(cap)) return whyAnchor(projectRoot, cap, specsDir, known);
|
|
39
|
+
|
|
33
40
|
const specPath = path.join(specsDir, cap, "spec.md");
|
|
34
41
|
if (!isFile(specPath)) {
|
|
35
42
|
console.error(c.red("error:") + ` no spec for capability "${cap}"`);
|
|
@@ -130,6 +137,58 @@ export async function run(positional, _flags) {
|
|
|
130
137
|
return 0;
|
|
131
138
|
}
|
|
132
139
|
|
|
140
|
+
// Reverse provenance: one intent anchor → the capabilities that realize it,
|
|
141
|
+
// each with its proof state, plus the archived changes that built them.
|
|
142
|
+
function whyAnchor(projectRoot, id, specsDir, known) {
|
|
143
|
+
const anchors = productAnchors(projectRoot);
|
|
144
|
+
if (!anchors.has(id)) {
|
|
145
|
+
console.error(c.red("error:") + ` no anchor [${id}] in .doctrina/product.md`);
|
|
146
|
+
if (anchors.size) console.error(c.gray("known: ") + [...anchors.keys()].join(", "));
|
|
147
|
+
return 1;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
console.log(c.bold(`Why ${id}`) + c.gray(" — reverse provenance (intent → capabilities → proof → history)"));
|
|
151
|
+
console.log("");
|
|
152
|
+
console.log(c.bold(" Intent"));
|
|
153
|
+
console.log(` ${c.cyan(id)} ${anchors.get(id)}`);
|
|
154
|
+
|
|
155
|
+
const realizing = [];
|
|
156
|
+
for (const capName of known.sort()) {
|
|
157
|
+
const text = read(path.join(specsDir, capName, "spec.md"));
|
|
158
|
+
const realizesRaw = specHeader(text, "Realizes");
|
|
159
|
+
const ids = realizesRaw ? (realizesRaw.match(/[A-Z]+\d+/g) ?? []) : [];
|
|
160
|
+
if (ids.includes(id)) realizing.push({ cap: capName, text });
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
console.log("");
|
|
164
|
+
console.log(c.bold(" Realized by"));
|
|
165
|
+
if (realizing.length === 0) {
|
|
166
|
+
console.log(` ${c.red("nothing")} ${c.gray("— dropped intent: no spec declares Realizes: " + id + " (doctrina trace)")}`);
|
|
167
|
+
return 0;
|
|
168
|
+
}
|
|
169
|
+
for (const s of realizing) {
|
|
170
|
+
const status = specHeader(s.text, "Status") ?? "—";
|
|
171
|
+
const impl = specHeader(s.text, "Implementation") ?? "—";
|
|
172
|
+
const criteria = parseAcceptanceCriteria(s.text);
|
|
173
|
+
const proven = criteria.filter((cr) => cr.proofPaths.length > 0).length;
|
|
174
|
+
console.log(
|
|
175
|
+
` ${c.cyan(s.cap.padEnd(14))} ${c.gray(`status: ${status} · implementation: ${impl} · proof: ${proven}/${criteria.length} criteria cite evidence`)}`,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
console.log("");
|
|
180
|
+
console.log(c.bold(" History"));
|
|
181
|
+
let any = false;
|
|
182
|
+
for (const s of realizing) {
|
|
183
|
+
for (const ch of archivedChangesFor(projectRoot, s.cap)) {
|
|
184
|
+
console.log(` ${c.cyan(ch.applied ?? "—")} ${ch.title} ${c.gray(`(${s.cap}, ${ch.op})`)}`);
|
|
185
|
+
any = true;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (!any) console.log(` ${c.gray("no archived change recorded against the realizing capabilities")}`);
|
|
189
|
+
return 0;
|
|
190
|
+
}
|
|
191
|
+
|
|
133
192
|
// Archived changes whose recorded specs_affected include this capability,
|
|
134
193
|
// oldest first. Read from the index ledger (the same data `index rebuild`
|
|
135
194
|
// derives from each change's spec deltas); absent or unreadable index → none.
|
|
@@ -202,14 +261,18 @@ function decisionsMentioning(projectRoot, cap) {
|
|
|
202
261
|
}
|
|
203
262
|
|
|
204
263
|
export const help = `
|
|
205
|
-
Usage: doctrina why <capability>
|
|
264
|
+
Usage: doctrina why <capability | anchor>
|
|
265
|
+
|
|
266
|
+
Explain provenance in either direction, read-only:
|
|
206
267
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
268
|
+
doctrina why billing forward — the product intent the capability
|
|
269
|
+
Realizes:, its purpose/status, the acceptance
|
|
270
|
+
criteria that prove it, the accepted ADRs that
|
|
271
|
+
name it, and the archived changes that built it.
|
|
272
|
+
doctrina why SC1 reverse — the intent anchor's product.md text,
|
|
273
|
+
the capabilities that realize it (with proof
|
|
274
|
+
state), and the archived changes behind them.
|
|
212
275
|
|
|
213
|
-
Builds on the intent provenance (ADR 0006/0011) — answer "why was X built
|
|
214
|
-
|
|
276
|
+
Builds on the intent provenance (ADR 0006/0011) — answer "why was X built"
|
|
277
|
+
or "who delivers this promise?" without grepping the tree by hand.
|
|
215
278
|
`;
|
package/src/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { parseArgs } from "./lib/args.js";
|
|
|
4
4
|
import { c } from "./lib/colors.js";
|
|
5
5
|
import { suggest } from "./lib/suggest.js";
|
|
6
6
|
import { cliVersion } from "./lib/version.js";
|
|
7
|
+
import { surfaceHelp } from "./lib/commands.js";
|
|
7
8
|
|
|
8
9
|
import * as init from "./commands/init.js";
|
|
9
10
|
import * as spec from "./commands/spec.js";
|
|
@@ -32,58 +33,26 @@ import * as review from "./commands/review.js";
|
|
|
32
33
|
import * as watch from "./commands/watch.js";
|
|
33
34
|
import * as why from "./commands/why.js";
|
|
34
35
|
import * as constitution from "./commands/constitution.js";
|
|
36
|
+
import * as prime from "./commands/prime.js";
|
|
37
|
+
import * as handoff from "./commands/handoff.js";
|
|
38
|
+
import * as show from "./commands/show.js";
|
|
39
|
+
import * as doctor from "./commands/doctor.js";
|
|
40
|
+
import * as report from "./commands/report.js";
|
|
41
|
+
import * as completion from "./commands/completion.js";
|
|
35
42
|
|
|
36
43
|
const COMMANDS = {
|
|
37
44
|
init, spec, change, decision, validate, hooks, analyze, clarify,
|
|
38
45
|
templates, skill, index: indexCmd, next, metrics, context, search,
|
|
39
46
|
intake, work, coverage, verify, contract, trace,
|
|
40
47
|
status, close, review, watch, why, constitution,
|
|
48
|
+
prime, handoff, show, doctor, report, completion,
|
|
41
49
|
};
|
|
42
50
|
|
|
43
51
|
const TOP_HELP = `
|
|
44
52
|
Usage: doctrina <command> [options]
|
|
45
53
|
|
|
46
54
|
Commands:
|
|
47
|
-
|
|
48
|
-
intake Store the full project description; print the bootstrap playbook
|
|
49
|
-
work Brief prompt -> scaffolded change + guided work playbook
|
|
50
|
-
spec new Create a new capability spec (--bug for bug-shape)
|
|
51
|
-
spec list List specs with version, status, and size
|
|
52
|
-
change new Open a change proposal
|
|
53
|
-
change apply Apply spec deltas (ADDED/REMOVED auto, MODIFIED manual)
|
|
54
|
-
change archive Archive an applied change
|
|
55
|
-
change diff Preview spec deltas (line diff for MODIFIED)
|
|
56
|
-
contract new Own the integration surface (ports, env, interfaces)
|
|
57
|
-
contract check Verify port collisions, env drift, referenced specs
|
|
58
|
-
decision new Create the next sequentially numbered ADR
|
|
59
|
-
decision accept Flip a proposed ADR to accepted
|
|
60
|
-
decision land Record that an accepted ADR is now implemented (non-mutating)
|
|
61
|
-
decision supersede Create a new ADR that supersedes an existing one
|
|
62
|
-
decision list List ADRs with status, date, and title
|
|
63
|
-
skill new Scaffold an on-demand procedural memory skill
|
|
64
|
-
skill list List skills with their descriptions
|
|
65
|
-
skill sync Mirror skill frontmatter descriptions into index.json
|
|
66
|
-
skill suggest Surface fix-shaped lessons worth a skill (--write scaffolds)
|
|
67
|
-
analyze Inspect a change folder before applying
|
|
68
|
-
clarify Smell-test a Markdown file for ambiguity (--all for the tree)
|
|
69
|
-
context Print the context pack for a task in read order
|
|
70
|
-
search Search the artifact tree, grouped by category
|
|
71
|
-
validate Run schema and structural checks
|
|
72
|
-
coverage Report acceptance criteria with linked evidence (--strict gates)
|
|
73
|
-
trace Report intent provenance: product intent → specs (--strict gates)
|
|
74
|
-
review Conformance review of your changes vs specs/ADRs/contracts
|
|
75
|
-
verify Run project-declared typecheck/test/build checks (the real gate)
|
|
76
|
-
close Run the whole close sequence for a change in one pass
|
|
77
|
-
status One-glance project health dashboard
|
|
78
|
-
why Explain a capability's provenance (intent → proof → ADRs)
|
|
79
|
-
constitution Print the standing rules: accepted ADRs + product non-goals
|
|
80
|
-
templates list List the templates shipped by the installed CLI
|
|
81
|
-
templates check Compare the project against the recommended template shape
|
|
82
|
-
hooks install Install the pre-commit hook
|
|
83
|
-
index rebuild Regenerate index.json from the artifacts on disk
|
|
84
|
-
next Print the recommended next workflow actions
|
|
85
|
-
watch Re-run validate --fix + next on every change (--once for one pass)
|
|
86
|
-
metrics Local git-derived adoption metrics (no network)
|
|
55
|
+
${surfaceHelp()}
|
|
87
56
|
|
|
88
57
|
Global flags:
|
|
89
58
|
--help, -h Show this message (or per-command help if after a command)
|
|
@@ -92,7 +61,7 @@ Global flags:
|
|
|
92
61
|
|
|
93
62
|
async function main(argv) {
|
|
94
63
|
const { positional, flags } = parseArgs(argv, {
|
|
95
|
-
boolean: ["help", "h", "version", "v", "force", "non-interactive", "check", "save", "bug", "write", "all", "concat", "archive", "strict", "list", "init", "fix", "once", "clean"],
|
|
64
|
+
boolean: ["help", "h", "version", "v", "force", "non-interactive", "check", "save", "bug", "write", "all", "concat", "archive", "strict", "list", "init", "fix", "once", "clean", "json"],
|
|
96
65
|
});
|
|
97
66
|
|
|
98
67
|
if (flags.get("version") || flags.get("v")) {
|
package/src/lib/commands.js
CHANGED
|
@@ -15,13 +15,82 @@ export const COMMAND_NAMES = [
|
|
|
15
15
|
// authoring
|
|
16
16
|
"spec", "change", "contract", "decision", "skill",
|
|
17
17
|
// read / orient
|
|
18
|
-
"context", "search", "status", "next", "why", "constitution",
|
|
18
|
+
"prime", "context", "show", "search", "status", "next", "why", "handoff", "constitution",
|
|
19
19
|
// gates
|
|
20
|
-
"analyze", "clarify", "validate", "coverage", "trace", "review", "verify", "close",
|
|
20
|
+
"analyze", "clarify", "validate", "coverage", "trace", "review", "verify", "close", "doctor",
|
|
21
21
|
// maintenance
|
|
22
|
-
"templates", "hooks", "index", "watch", "metrics",
|
|
22
|
+
"templates", "hooks", "index", "watch", "metrics", "report", "completion",
|
|
23
23
|
];
|
|
24
24
|
|
|
25
|
+
// The full operation surface — every `doctrina <command> [<subcommand>]` a
|
|
26
|
+
// user can invoke, each with the one-line summary `--help` prints. This is
|
|
27
|
+
// the single source the top-level help is GENERATED from, so an operation
|
|
28
|
+
// that exists in a dispatch switch but not here is now a loud test failure
|
|
29
|
+
// instead of a hidden feature (`spec set` / `change abandon` shipped and
|
|
30
|
+
// stayed invisible to `--help`, AGENTS.md, and the CLI reference — the exact
|
|
31
|
+
// drift the AGENTS.md gate could not see because its catalog stops at the
|
|
32
|
+
// top-level names). Tests assert: every op resolves to a real handler, the
|
|
33
|
+
// top-level words equal COMMAND_NAMES, and the cli-reference docs cover
|
|
34
|
+
// every op.
|
|
35
|
+
export const OPERATIONS = [
|
|
36
|
+
["init", "Scaffold AGENTS.md and .doctrina/ in the current directory"],
|
|
37
|
+
["intake", "Store the full project description; print the bootstrap playbook"],
|
|
38
|
+
["work", "Brief prompt -> scaffolded change + guided work playbook"],
|
|
39
|
+
["spec new", "Create a new capability spec (--bug for bug-shape)"],
|
|
40
|
+
["spec list", "List specs with version, status, and size"],
|
|
41
|
+
["spec set", "Edit spec headers / a criterion mark and resync the index"],
|
|
42
|
+
["change new", "Open a change proposal"],
|
|
43
|
+
["change apply", "Apply spec deltas (ADDED/REMOVED auto, MODIFIED manual)"],
|
|
44
|
+
["change archive", "Archive an applied change"],
|
|
45
|
+
["change diff", "Preview spec deltas (line diff for MODIFIED)"],
|
|
46
|
+
["change abandon", "Discard an open change cleanly (recorded in the ledger)"],
|
|
47
|
+
["contract new", "Own the integration surface (ports, env, interfaces)"],
|
|
48
|
+
["contract list", "List contracts with status and last-updated date"],
|
|
49
|
+
["contract check", "Verify port collisions, env drift, referenced specs"],
|
|
50
|
+
["decision new", "Create the next sequentially numbered ADR"],
|
|
51
|
+
["decision accept", "Flip a proposed ADR to accepted"],
|
|
52
|
+
["decision land", "Record that an accepted ADR is now implemented (non-mutating)"],
|
|
53
|
+
["decision supersede", "Create a new ADR that supersedes an existing one"],
|
|
54
|
+
["decision list", "List ADRs with status, date, and title"],
|
|
55
|
+
["skill new", "Scaffold an on-demand procedural memory skill"],
|
|
56
|
+
["skill list", "List skills with their descriptions"],
|
|
57
|
+
["skill sync", "Mirror skill frontmatter descriptions into index.json"],
|
|
58
|
+
["skill suggest", "Surface fix-shaped lessons worth a skill (--write scaffolds)"],
|
|
59
|
+
["analyze", "Inspect a change folder before applying"],
|
|
60
|
+
["clarify", "Smell-test a Markdown file for ambiguity (--all for the tree)"],
|
|
61
|
+
["prime", "Session primer: gates, rules, open work, next steps in one read"],
|
|
62
|
+
["context", "Print the context pack for a task in read order"],
|
|
63
|
+
["show", "Point-read a requirement/criterion/ADR (cli-R12, cli-C3, 0007)"],
|
|
64
|
+
["search", "Search the artifact tree, grouped by category"],
|
|
65
|
+
["validate", "Run schema and structural checks"],
|
|
66
|
+
["coverage", "Report acceptance criteria with linked evidence (--strict gates)"],
|
|
67
|
+
["trace", "Report intent provenance: product intent → specs (--strict gates)"],
|
|
68
|
+
["review", "Conformance review of your changes vs specs/ADRs/contracts"],
|
|
69
|
+
["verify", "Run project-declared typecheck/test/build checks (the real gate)"],
|
|
70
|
+
["close", "Run the whole close sequence for a change in one pass"],
|
|
71
|
+
["doctor", "Aggregate diagnostic with per-finding remediation (a driver)"],
|
|
72
|
+
["status", "One-glance project health dashboard"],
|
|
73
|
+
["why", "Explain provenance: a capability's chain, or an anchor's (SC1)"],
|
|
74
|
+
["handoff", "Markdown handoff note: open work, task state, resume command"],
|
|
75
|
+
["constitution", "Print the standing rules: accepted ADRs + product non-goals"],
|
|
76
|
+
["templates list", "List the templates shipped by the installed CLI"],
|
|
77
|
+
["templates check", "Compare the project against the recommended template shape"],
|
|
78
|
+
["templates update", "Additive fixer for check findings (preview; --write applies)"],
|
|
79
|
+
["hooks install", "Install the pre-commit hook"],
|
|
80
|
+
["index rebuild", "Regenerate index.json from the artifacts on disk"],
|
|
81
|
+
["next", "Print the recommended next workflow actions"],
|
|
82
|
+
["watch", "Re-run validate --fix + next on every change (--once for one pass)"],
|
|
83
|
+
["metrics", "Local git-derived adoption metrics (no network)"],
|
|
84
|
+
["report", "Markdown digest for a period: changes, gates, git summary"],
|
|
85
|
+
["completion", "Print bash/zsh/pwsh completions (generated from the catalog)"],
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
// The Commands block of `doctrina --help`, generated from OPERATIONS so the
|
|
89
|
+
// printed surface can never omit an operation the catalog knows about.
|
|
90
|
+
export function surfaceHelp() {
|
|
91
|
+
return OPERATIONS.map(([op, summary]) => ` ${op.padEnd(21)}${summary}`).join("\n");
|
|
92
|
+
}
|
|
93
|
+
|
|
25
94
|
// Extract the set of top-level `doctrina <command>` references that appear in
|
|
26
95
|
// CODE context (fenced blocks or inline backtick spans) of a Markdown file —
|
|
27
96
|
// AGENTS.md, typically. Restricting to code context avoids matching prose like
|
package/templates/README.md
CHANGED
|
@@ -21,6 +21,9 @@ replace. No conditionals, loops, or includes.
|
|
|
21
21
|
| `{{DECISION_NUMBER}}` | Zero-padded ADR number (e.g. `0007`) | next available |
|
|
22
22
|
| `{{DECISION_TITLE}}` | ADR title | required, no default |
|
|
23
23
|
| `{{DECISION_SLUG}}` | kebab-case slug derived from `{{DECISION_TITLE}}` | derived |
|
|
24
|
+
| `{{SKILL_NAME}}` | Slug for a new on-demand skill | required, no default |
|
|
25
|
+
| `{{CONTRACT_NAME}}` | Slug for a new integration contract | required, no default |
|
|
26
|
+
| `{{FRAMEWORK_VERSION}}` | Version of the CLI that scaffolded/stamped the tree | running CLI version |
|
|
24
27
|
| `{{AGENTS_MD_PATH}}` | Path to root `AGENTS.md` from the adapter location | per adapter |
|
|
25
28
|
|
|
26
29
|
CLI authors and template authors share this vocabulary. Adding a token
|
|
@@ -49,13 +52,16 @@ change/
|
|
|
49
52
|
design.md.template
|
|
50
53
|
spec-delta.md.template
|
|
51
54
|
decision.md.template one ADR (Nygard/MADR)
|
|
55
|
+
contract.md.template one integration contract (ports/env/interfaces)
|
|
52
56
|
hooks/
|
|
53
57
|
pre-commit.sample installed by `doctrina hooks install`
|
|
54
58
|
watch.sample on-save validation wrapper (manual wiring)
|
|
55
59
|
adapters/
|
|
56
60
|
claude/CLAUDE.md.template
|
|
61
|
+
claude/.claude/commands/doctrina-*.md.template native slash commands (core loop)
|
|
57
62
|
codex/README.md
|
|
58
63
|
cursor/.cursor/rules/00-doctrina.mdc.template
|
|
64
|
+
cursor/.cursor/commands/doctrina-*.md.template native slash commands (core loop)
|
|
59
65
|
copilot/.github/copilot-instructions.md.template
|
|
60
66
|
gemini/GEMINI.md.template
|
|
61
67
|
aider/CONVENTIONS.md.template
|
|
@@ -76,7 +82,7 @@ adapters/
|
|
|
76
82
|
| `cursor` | `.cursor/rules/00-doctrina.mdc` with `alwaysApply: true` | Cursor's `.mdc` rules live in a dedicated directory; the file is a thin pointer. |
|
|
77
83
|
| `copilot` | `.github/copilot-instructions.md` | GitHub Copilot's repository-level custom instructions path. |
|
|
78
84
|
| `gemini` | `GEMINI.md` at project root | Gemini CLI's native context file. |
|
|
79
|
-
| `aider` | `CONVENTIONS.md` at project root |
|
|
85
|
+
| `aider` | `CONVENTIONS.md` at project root | Read-only context once wired in (`aider --read CONVENTIONS.md`, or `read:` in `.aider.conf.yml`). |
|
|
80
86
|
| `windsurf` | `.windsurfrules` at project root | Windsurf's rules file convention. |
|
|
81
87
|
| `continue` | `.continue/rules/00-doctrina.md` | Continue.dev's rules directory. |
|
|
82
88
|
| `amp` / `devin` / `factory` / `jules` | nothing | AGENTS.md-native (same posture as `codex`); the directory marks the support contract and gives future per-agent affordances a home. |
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# CONVENTIONS.md — {{PROJECT_NAME}}
|
|
2
2
|
|
|
3
|
-
Aider reads this file as read-only context
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Aider reads this file as read-only context once wired in: launch
|
|
4
|
+
with `aider --read CONVENTIONS.md`, or add `read: CONVENTIONS.md`
|
|
5
|
+
to `.aider.conf.yml` so it loads on every run. This project routes
|
|
6
|
+
all operational rules through the open AGENTS.md standard.
|
|
6
7
|
|
|
7
8
|
See `{{AGENTS_MD_PATH}}` for the canonical operational source of
|
|
8
9
|
truth. The Doctrina framework artifacts (specs, decisions,
|