doctrina-cli 0.10.0 → 0.12.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/analyze.js +8 -2
- package/src/commands/change.js +78 -7
- package/src/commands/clarify.js +76 -4
- package/src/commands/close.js +42 -9
- package/src/commands/completion.js +116 -0
- package/src/commands/constitution.js +6 -4
- package/src/commands/context.js +118 -25
- package/src/commands/coverage.js +185 -44
- package/src/commands/doctor.js +135 -0
- package/src/commands/handoff.js +94 -0
- package/src/commands/init.js +12 -1
- package/src/commands/intent.js +173 -0
- package/src/commands/next.js +32 -16
- package/src/commands/prime.js +118 -0
- package/src/commands/report.js +142 -0
- package/src/commands/review.js +25 -4
- package/src/commands/show.js +173 -0
- package/src/commands/skill.js +46 -7
- package/src/commands/spec.js +13 -3
- package/src/commands/status.js +70 -40
- package/src/commands/trace.js +15 -2
- package/src/commands/upgrade.js +129 -0
- package/src/commands/validate.js +150 -3
- package/src/commands/why.js +83 -10
- package/src/commands/work.js +45 -16
- package/src/index.js +20 -42
- package/src/lib/commands.js +76 -4
- package/src/lib/scan.js +16 -0
- package/templates/AGENTS.md.template +8 -4
- package/templates/README.md +7 -1
- package/templates/adapters/aider/CONVENTIONS.md.template +4 -3
- package/templates/spec.md.template +9 -1
package/src/commands/why.js
CHANGED
|
@@ -2,7 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import process from "node:process";
|
|
3
3
|
import { readdirSync } from "node:fs";
|
|
4
4
|
import { exists, isDir, isFile, read } from "../lib/fs-ops.js";
|
|
5
|
-
import { specHeader, listHeader } from "../lib/scan.js";
|
|
5
|
+
import { specHeader, listHeader, parseDependsOn } from "../lib/scan.js";
|
|
6
6
|
import { parseAcceptanceCriteria, isVerified } from "../lib/criteria.js";
|
|
7
7
|
import * as idx from "../lib/index-json.js";
|
|
8
8
|
import { c } from "../lib/colors.js";
|
|
@@ -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}"`);
|
|
@@ -68,6 +75,16 @@ export async function run(positional, _flags) {
|
|
|
68
75
|
const purpose = sectionParagraph(text, "Purpose");
|
|
69
76
|
if (purpose) console.log(" " + purpose.replace(/\s+/g, " ").trim());
|
|
70
77
|
|
|
78
|
+
// Dependency graph, both directions (the machine-readable **Depends on:**
|
|
79
|
+
// header — specs used to cite each other only in prose, invisible here).
|
|
80
|
+
const deps = parseDependsOn(text);
|
|
81
|
+
const dependents = known
|
|
82
|
+
.filter((other) => other !== cap)
|
|
83
|
+
.filter((other) => parseDependsOn(read(path.join(specsDir, other, "spec.md"))).includes(cap))
|
|
84
|
+
.sort();
|
|
85
|
+
if (deps.length > 0) console.log(` ${c.gray("depends on:")} ${deps.map((d) => c.cyan(d)).join(", ")}`);
|
|
86
|
+
if (dependents.length > 0) console.log(` ${c.gray("depended on by:")} ${dependents.map((d) => c.cyan(d)).join(", ")}`);
|
|
87
|
+
|
|
71
88
|
// 3. The proof (acceptance criteria with cited evidence). Read through the
|
|
72
89
|
// shared multi-line parser so the marks here agree with `doctrina
|
|
73
90
|
// coverage` — a criterion that cites its proof on a continuation line is
|
|
@@ -130,6 +147,58 @@ export async function run(positional, _flags) {
|
|
|
130
147
|
return 0;
|
|
131
148
|
}
|
|
132
149
|
|
|
150
|
+
// Reverse provenance: one intent anchor → the capabilities that realize it,
|
|
151
|
+
// each with its proof state, plus the archived changes that built them.
|
|
152
|
+
function whyAnchor(projectRoot, id, specsDir, known) {
|
|
153
|
+
const anchors = productAnchors(projectRoot);
|
|
154
|
+
if (!anchors.has(id)) {
|
|
155
|
+
console.error(c.red("error:") + ` no anchor [${id}] in .doctrina/product.md`);
|
|
156
|
+
if (anchors.size) console.error(c.gray("known: ") + [...anchors.keys()].join(", "));
|
|
157
|
+
return 1;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
console.log(c.bold(`Why ${id}`) + c.gray(" — reverse provenance (intent → capabilities → proof → history)"));
|
|
161
|
+
console.log("");
|
|
162
|
+
console.log(c.bold(" Intent"));
|
|
163
|
+
console.log(` ${c.cyan(id)} ${anchors.get(id)}`);
|
|
164
|
+
|
|
165
|
+
const realizing = [];
|
|
166
|
+
for (const capName of known.sort()) {
|
|
167
|
+
const text = read(path.join(specsDir, capName, "spec.md"));
|
|
168
|
+
const realizesRaw = specHeader(text, "Realizes");
|
|
169
|
+
const ids = realizesRaw ? (realizesRaw.match(/[A-Z]+\d+/g) ?? []) : [];
|
|
170
|
+
if (ids.includes(id)) realizing.push({ cap: capName, text });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.log("");
|
|
174
|
+
console.log(c.bold(" Realized by"));
|
|
175
|
+
if (realizing.length === 0) {
|
|
176
|
+
console.log(` ${c.red("nothing")} ${c.gray("— dropped intent: no spec declares Realizes: " + id + " (doctrina trace)")}`);
|
|
177
|
+
return 0;
|
|
178
|
+
}
|
|
179
|
+
for (const s of realizing) {
|
|
180
|
+
const status = specHeader(s.text, "Status") ?? "—";
|
|
181
|
+
const impl = specHeader(s.text, "Implementation") ?? "—";
|
|
182
|
+
const criteria = parseAcceptanceCriteria(s.text);
|
|
183
|
+
const proven = criteria.filter((cr) => cr.proofPaths.length > 0).length;
|
|
184
|
+
console.log(
|
|
185
|
+
` ${c.cyan(s.cap.padEnd(14))} ${c.gray(`status: ${status} · implementation: ${impl} · proof: ${proven}/${criteria.length} criteria cite evidence`)}`,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
console.log("");
|
|
190
|
+
console.log(c.bold(" History"));
|
|
191
|
+
let any = false;
|
|
192
|
+
for (const s of realizing) {
|
|
193
|
+
for (const ch of archivedChangesFor(projectRoot, s.cap)) {
|
|
194
|
+
console.log(` ${c.cyan(ch.applied ?? "—")} ${ch.title} ${c.gray(`(${s.cap}, ${ch.op})`)}`);
|
|
195
|
+
any = true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (!any) console.log(` ${c.gray("no archived change recorded against the realizing capabilities")}`);
|
|
199
|
+
return 0;
|
|
200
|
+
}
|
|
201
|
+
|
|
133
202
|
// Archived changes whose recorded specs_affected include this capability,
|
|
134
203
|
// oldest first. Read from the index ledger (the same data `index rebuild`
|
|
135
204
|
// derives from each change's spec deltas); absent or unreadable index → none.
|
|
@@ -202,14 +271,18 @@ function decisionsMentioning(projectRoot, cap) {
|
|
|
202
271
|
}
|
|
203
272
|
|
|
204
273
|
export const help = `
|
|
205
|
-
Usage: doctrina why <capability>
|
|
274
|
+
Usage: doctrina why <capability | anchor>
|
|
275
|
+
|
|
276
|
+
Explain provenance in either direction, read-only:
|
|
206
277
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
278
|
+
doctrina why billing forward — the product intent the capability
|
|
279
|
+
Realizes:, its purpose/status, the acceptance
|
|
280
|
+
criteria that prove it, the accepted ADRs that
|
|
281
|
+
name it, and the archived changes that built it.
|
|
282
|
+
doctrina why SC1 reverse — the intent anchor's product.md text,
|
|
283
|
+
the capabilities that realize it (with proof
|
|
284
|
+
state), and the archived changes behind them.
|
|
212
285
|
|
|
213
|
-
Builds on the intent provenance (ADR 0006/0011) — answer "why was X built
|
|
214
|
-
|
|
286
|
+
Builds on the intent provenance (ADR 0006/0011) — answer "why was X built"
|
|
287
|
+
or "who delivers this promise?" without grepping the tree by hand.
|
|
215
288
|
`;
|
package/src/commands/work.js
CHANGED
|
@@ -71,14 +71,18 @@ export async function run(positional, flags) {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
const effPrompt = prompt || `backfill specs from ${files.length} changed file${files.length === 1 ? "" : "s"}`;
|
|
74
|
-
|
|
74
|
+
// --title separates the short display name from the full brief: the title
|
|
75
|
+
// drives the slug and the proposal H1; the whole prompt still lands, intact,
|
|
76
|
+
// under ## Why. Without it a long prompt used to become a 900-char H1.
|
|
77
|
+
const title = flagString(flags, "title") ?? effPrompt;
|
|
78
|
+
const slug = prompt || flags.has("title") ? slugify(title) : "backfill";
|
|
75
79
|
const id = flagString(flags, "id") ?? `${nextChangeNumber(projectRoot)}-${slug}`;
|
|
76
80
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(id)) {
|
|
77
81
|
console.error(c.red("error:") + ` invalid change id "${id}" (lowercase letters, digits, hyphens)`);
|
|
78
82
|
return 2;
|
|
79
83
|
}
|
|
80
84
|
|
|
81
|
-
const code = changeNew([id,
|
|
85
|
+
const code = changeNew([id, title], flags);
|
|
82
86
|
if (code !== 0) return code;
|
|
83
87
|
|
|
84
88
|
// Record the prompt verbatim as the change's Why — the raw intent has
|
|
@@ -228,6 +232,9 @@ function resumeChange(projectRoot, resumeId) {
|
|
|
228
232
|
}
|
|
229
233
|
|
|
230
234
|
// ASCII-folded kebab-case of the prompt ("Faça o login" -> "faca-o-login").
|
|
235
|
+
// Over-long slugs are cut at a WORD boundary, not mid-token — a 900-char
|
|
236
|
+
// prompt used to produce folder names ending in a half word ("...-scoped-c"),
|
|
237
|
+
// which then leaks into every path, ledger line, and archive name.
|
|
231
238
|
export function slugify(text) {
|
|
232
239
|
let slug = text
|
|
233
240
|
.normalize("NFD")
|
|
@@ -235,7 +242,11 @@ export function slugify(text) {
|
|
|
235
242
|
.toLowerCase()
|
|
236
243
|
.replace(/[^a-z0-9]+/g, "-")
|
|
237
244
|
.replace(/^-+|-+$/g, "");
|
|
238
|
-
if (slug.length > 48)
|
|
245
|
+
if (slug.length > 48) {
|
|
246
|
+
const cut = slug.slice(0, 49); // one past the cap: a "-" here means 48 ends a word
|
|
247
|
+
const boundary = cut.lastIndexOf("-");
|
|
248
|
+
slug = (boundary > 0 ? cut.slice(0, boundary) : cut.slice(0, 48)).replace(/-+$/g, "");
|
|
249
|
+
}
|
|
239
250
|
return slug.length > 0 ? slug : "task";
|
|
240
251
|
}
|
|
241
252
|
|
|
@@ -292,7 +303,7 @@ export function rankCapabilities(projectRoot, prompt) {
|
|
|
292
303
|
// file scores its capability when the file sits under a path segment named for
|
|
293
304
|
// it, or when the spec cites the file as evidence. A deterministic overlap
|
|
294
305
|
// hint for the agent — never a decision (ADR 0005).
|
|
295
|
-
export function rankCapabilitiesByDiff(projectRoot, files) {
|
|
306
|
+
export function rankCapabilitiesByDiff(projectRoot, files, { limit = 3 } = {}) {
|
|
296
307
|
const specsDir = path.join(projectRoot, ".doctrina", "specs");
|
|
297
308
|
if (!files || files.length === 0 || !isDir(specsDir)) return [];
|
|
298
309
|
const norm = files.map((f) => f.replace(/\\/g, "/"));
|
|
@@ -310,7 +321,10 @@ export function rankCapabilitiesByDiff(projectRoot, files) {
|
|
|
310
321
|
}
|
|
311
322
|
if (score > 0) ranked.push({ id: cap, score, path: `.doctrina/specs/${cap}/spec.md` });
|
|
312
323
|
}
|
|
313
|
-
|
|
324
|
+
// `limit` keeps the work-playbook hint short (top 3); review passes
|
|
325
|
+
// Infinity — truncating there is how it missed 5 of 8 touched capabilities
|
|
326
|
+
// in the 0.11.0 field session.
|
|
327
|
+
return ranked.sort((a, b) => b.score - a.score || a.id.localeCompare(b.id)).slice(0, limit);
|
|
314
328
|
}
|
|
315
329
|
|
|
316
330
|
// A chore is a spec-less change (review F9): the playbook drops the spec-delta
|
|
@@ -401,6 +415,16 @@ function printPlaybook(projectRoot, { id, prompt, pinned, matches, capability, c
|
|
|
401
415
|
console.log(c.gray(" <EARS body. Keep the two axes honest (Status vs Implementation),"));
|
|
402
416
|
console.log(c.gray(" keep aspiration under ## Maturity → Future, and cite the proof per"));
|
|
403
417
|
console.log(c.gray(" criterion: \"1. [unverified] <signal> — verified by `path/to/test`\">"));
|
|
418
|
+
console.log(c.gray(" New capability? `spec new` then an ADDED delta with the FULL body — apply"));
|
|
419
|
+
console.log(c.gray(" replaces the untouched scaffold. Bookkeeping edits? MODIFIED with an ops"));
|
|
420
|
+
console.log(c.gray(" block, applied mechanically (all ops or none):"));
|
|
421
|
+
console.log(c.gray(" ```ops"));
|
|
422
|
+
console.log(c.gray(" set-header Implementation: verified — `src/x.js`"));
|
|
423
|
+
console.log(c.gray(" bump-version minor"));
|
|
424
|
+
console.log(c.gray(" set-criterion 1: verified"));
|
|
425
|
+
console.log(c.gray(" append-criterion [unverified] new signal — verified by `test/y.test.js`"));
|
|
426
|
+
console.log(c.gray(" ```"));
|
|
427
|
+
console.log(c.gray(" Only prose rewrites stay a by-hand merge."));
|
|
404
428
|
if (fromDiff) {
|
|
405
429
|
console.log(c.gray(" --from-diff: the code already exists — describe its CURRENT behaviour, and"));
|
|
406
430
|
console.log(c.gray(" mark each criterion [unverified] until a test proves it (don't assume the"));
|
|
@@ -414,19 +438,22 @@ function printPlaybook(projectRoot, { id, prompt, pinned, matches, capability, c
|
|
|
414
438
|
console.log(" spec's Implementation: planned → partial → implemented as code lands.");
|
|
415
439
|
console.log(" If the prompt is genuinely ambiguous, ask the user before assuming.");
|
|
416
440
|
console.log("");
|
|
417
|
-
console.log(`6.
|
|
418
|
-
console.log(
|
|
419
|
-
console.log(
|
|
420
|
-
console.log(
|
|
421
|
-
console.log(
|
|
422
|
-
console.log(`
|
|
423
|
-
console.log("
|
|
441
|
+
console.log(`6. ADR checkpoint — does this change decide something structural`);
|
|
442
|
+
console.log(" (an architecture, a boundary, a trade-off a future session must");
|
|
443
|
+
console.log(` not relitigate)? If yes: ${c.cyan("doctrina decision new \"<title>\"")} now,`);
|
|
444
|
+
console.log(" before closing — the definition of done requires it recorded.");
|
|
445
|
+
console.log("");
|
|
446
|
+
console.log(`7. Close in one attested pass (preferred — runs every gate and stops`);
|
|
447
|
+
console.log(" at the first failure with the exact rerun command):");
|
|
448
|
+
console.log(` ${c.cyan(`doctrina close ${id}`)}`);
|
|
449
|
+
console.log(c.gray(" (equivalent, step by step: ") +
|
|
450
|
+
c.gray(`analyze → change apply → verify → coverage → trace → change archive → validate)`));
|
|
451
|
+
console.log(" Before it: check the proposal's ## Verification boxes and every task,");
|
|
424
452
|
console.log(" closing steps included, and bump Implementation to verified.");
|
|
425
453
|
console.log("");
|
|
426
|
-
console.log(`
|
|
427
|
-
console.log(
|
|
428
|
-
console.log(c.gray("
|
|
429
|
-
console.log(c.gray(" a recurring convention), capture it: ") + c.cyan("doctrina skill new <slug>") + c.gray("."));
|
|
454
|
+
console.log(`8. ${c.cyan("doctrina next")} for the follow-up.`);
|
|
455
|
+
console.log(c.gray(" If this change taught a reusable lesson (a fix you'd hate to relearn,"));
|
|
456
|
+
console.log(c.gray(" a recurring convention), capture it: ") + c.cyan("doctrina skill new <slug>") + c.gray("."));
|
|
430
457
|
}
|
|
431
458
|
|
|
432
459
|
export const help = `
|
|
@@ -447,6 +474,8 @@ already open, work suggests resuming it instead of opening a junk change
|
|
|
447
474
|
named after that word (review G1). Use --resume to do so directly.
|
|
448
475
|
|
|
449
476
|
Options:
|
|
477
|
+
--title "<short>" Short display title: drives the slug and the proposal
|
|
478
|
+
H1; the full prompt still lands under ## Why
|
|
450
479
|
--capability <cap> Pin the capability instead of ranking matches
|
|
451
480
|
--id <id> Override the derived change id
|
|
452
481
|
--resume <id> Reprint the playbook for an open change; create nothing
|
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,29 @@ 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";
|
|
42
|
+
import * as intent from "./commands/intent.js";
|
|
43
|
+
import * as upgrade from "./commands/upgrade.js";
|
|
35
44
|
|
|
36
45
|
const COMMANDS = {
|
|
37
46
|
init, spec, change, decision, validate, hooks, analyze, clarify,
|
|
38
47
|
templates, skill, index: indexCmd, next, metrics, context, search,
|
|
39
48
|
intake, work, coverage, verify, contract, trace,
|
|
40
49
|
status, close, review, watch, why, constitution,
|
|
50
|
+
prime, handoff, show, doctor, report, completion,
|
|
51
|
+
intent, upgrade,
|
|
41
52
|
};
|
|
42
53
|
|
|
43
54
|
const TOP_HELP = `
|
|
44
55
|
Usage: doctrina <command> [options]
|
|
45
56
|
|
|
46
57
|
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)
|
|
58
|
+
${surfaceHelp()}
|
|
87
59
|
|
|
88
60
|
Global flags:
|
|
89
61
|
--help, -h Show this message (or per-command help if after a command)
|
|
@@ -91,11 +63,17 @@ Global flags:
|
|
|
91
63
|
`;
|
|
92
64
|
|
|
93
65
|
async function main(argv) {
|
|
66
|
+
// `--version` is only "print the CLI version" when NO command is given.
|
|
67
|
+
// With a command it is that command's flag to consume — the old global
|
|
68
|
+
// intercept made `doctrina spec set x --version 0.10.0` print the CLI
|
|
69
|
+
// version and exit, looking exactly like the spec's version had been set
|
|
70
|
+
// (0.11.0 field-review papercut). `-v` stays global either way.
|
|
71
|
+
const versionOnly = argv.every((t) => t === "--version" || t === "-v") && argv.length > 0;
|
|
94
72
|
const { positional, flags } = parseArgs(argv, {
|
|
95
|
-
boolean: ["help", "h", "
|
|
73
|
+
boolean: ["help", "h", "v", "force", "non-interactive", "check", "save", "bug", "write", "all", "concat", "archive", "strict", "list", "init", "fix", "once", "clean", "json"],
|
|
96
74
|
});
|
|
97
75
|
|
|
98
|
-
if (
|
|
76
|
+
if (versionOnly || flags.get("v") === true) {
|
|
99
77
|
console.log(cliVersion());
|
|
100
78
|
return 0;
|
|
101
79
|
}
|
package/src/lib/commands.js
CHANGED
|
@@ -13,15 +13,87 @@ export const COMMAND_NAMES = [
|
|
|
13
13
|
// bootstrap / day-to-day
|
|
14
14
|
"init", "intake", "work",
|
|
15
15
|
// authoring
|
|
16
|
-
"spec", "change", "contract", "decision", "skill",
|
|
16
|
+
"spec", "change", "contract", "decision", "skill", "intent",
|
|
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", "upgrade",
|
|
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
|
+
["intent add", "Append a new product intent anchor post-intake (- [SC5] ...)"],
|
|
60
|
+
["intent list", "List product.md intent anchors in document order"],
|
|
61
|
+
["analyze", "Inspect a change folder before applying"],
|
|
62
|
+
["clarify", "Smell-test a Markdown file for ambiguity (--all for the tree)"],
|
|
63
|
+
["prime", "Session primer: gates, rules, open work, next steps in one read"],
|
|
64
|
+
["context", "Print the context pack for a task in read order"],
|
|
65
|
+
["show", "Point-read a requirement/criterion/ADR (cli-R12, cli-C3, 0007)"],
|
|
66
|
+
["search", "Search the artifact tree, grouped by category"],
|
|
67
|
+
["validate", "Run schema and structural checks"],
|
|
68
|
+
["coverage", "Report acceptance criteria with linked evidence (--strict gates)"],
|
|
69
|
+
["trace", "Report intent provenance: product intent → specs (--strict gates)"],
|
|
70
|
+
["review", "Conformance review of your changes vs specs/ADRs/contracts"],
|
|
71
|
+
["verify", "Run project-declared typecheck/test/build checks (the real gate)"],
|
|
72
|
+
["close", "Run the whole close sequence for a change in one pass"],
|
|
73
|
+
["doctor", "Aggregate diagnostic with per-finding remediation (a driver)"],
|
|
74
|
+
["status", "One-glance project health dashboard"],
|
|
75
|
+
["why", "Explain provenance: a capability's chain, or an anchor's (SC1)"],
|
|
76
|
+
["handoff", "Markdown handoff note: open work, task state, resume command"],
|
|
77
|
+
["constitution", "Print the standing rules: accepted ADRs + product non-goals"],
|
|
78
|
+
["templates list", "List the templates shipped by the installed CLI"],
|
|
79
|
+
["templates check", "Compare the project against the recommended template shape"],
|
|
80
|
+
["templates update", "Additive fixer for check findings (preview; --write applies)"],
|
|
81
|
+
["hooks install", "Install the pre-commit hook"],
|
|
82
|
+
["index rebuild", "Regenerate index.json from the artifacts on disk"],
|
|
83
|
+
["next", "Print the recommended next workflow actions"],
|
|
84
|
+
["watch", "Re-run validate --fix + next on every change (--once for one pass)"],
|
|
85
|
+
["metrics", "Local git-derived adoption metrics (no network)"],
|
|
86
|
+
["report", "Markdown digest for a period: changes, gates, git summary"],
|
|
87
|
+
["completion", "Print bash/zsh/pwsh completions (generated from the catalog)"],
|
|
88
|
+
["upgrade", "Bring an existing project up to the installed CLI (--write applies)"],
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
// The Commands block of `doctrina --help`, generated from OPERATIONS so the
|
|
92
|
+
// printed surface can never omit an operation the catalog knows about.
|
|
93
|
+
export function surfaceHelp() {
|
|
94
|
+
return OPERATIONS.map(([op, summary]) => ` ${op.padEnd(21)}${summary}`).join("\n");
|
|
95
|
+
}
|
|
96
|
+
|
|
25
97
|
// Extract the set of top-level `doctrina <command>` references that appear in
|
|
26
98
|
// CODE context (fenced blocks or inline backtick spans) of a Markdown file —
|
|
27
99
|
// AGENTS.md, typically. Restricting to code context avoids matching prose like
|
package/src/lib/scan.js
CHANGED
|
@@ -25,6 +25,15 @@ export function listHeader(text, name) {
|
|
|
25
25
|
return m ? m[1].trim() : null;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
// The machine-readable "**Depends on:** defects, decisions" spec header: the
|
|
29
|
+
// sibling capabilities this spec builds on. Returns [] when absent, "n/a", or
|
|
30
|
+
// empty. One parser so index, why, context, and review agree.
|
|
31
|
+
export function parseDependsOn(text) {
|
|
32
|
+
const raw = specHeader(text, "Depends on");
|
|
33
|
+
if (!raw || /^n\/a\b/i.test(raw.trim()) || raw.trim() === "—") return [];
|
|
34
|
+
return raw.match(/[a-z][a-z0-9][a-z0-9-]*/g) ?? [];
|
|
35
|
+
}
|
|
36
|
+
|
|
28
37
|
function dirEntries(dir) {
|
|
29
38
|
if (!isDir(dir)) return [];
|
|
30
39
|
return readdirSync(dir).filter((e) => !e.startsWith(".")).sort();
|
|
@@ -87,6 +96,13 @@ export function deriveIndex(projectRoot, current) {
|
|
|
87
96
|
const realizesRaw = specHeader(text, "Realizes");
|
|
88
97
|
const realizes = realizesRaw ? (realizesRaw.match(/[A-Z]+\d+/g) ?? []) : prev?.realizes;
|
|
89
98
|
if (realizes && realizes.length) entry.realizes = realizes;
|
|
99
|
+
// Depends on: sibling capabilities this spec builds on (0.11.0 field
|
|
100
|
+
// review — specs cited each other only in prose, unreadable by machine).
|
|
101
|
+
// Stored as a capability-id list so `why` can show the graph, `context`
|
|
102
|
+
// can pull dependencies into the read pack, and `review` can flag
|
|
103
|
+
// dependents of a touched capability.
|
|
104
|
+
const depends = specHeader(text, "Depends on") !== null ? parseDependsOn(text) : prev?.depends_on;
|
|
105
|
+
if (depends && depends.length) entry.depends_on = depends;
|
|
90
106
|
out.artifacts.specs.push(entry);
|
|
91
107
|
}
|
|
92
108
|
|
|
@@ -42,17 +42,21 @@ so prefer it over writing artifacts by hand. Full list: `doctrina --help`.
|
|
|
42
42
|
existing code) · `--chore` (no-spec infra/docs/build)
|
|
43
43
|
- **Scaffold (manual creation stays available):** `doctrina spec new <cap>`
|
|
44
44
|
(`--bug`) · `doctrina decision new "<title>"` · `doctrina change new <id>`
|
|
45
|
-
· `doctrina contract new <id>` · `doctrina skill new <slug>`
|
|
45
|
+
· `doctrina contract new <id>` · `doctrina skill new <slug>` · `doctrina
|
|
46
|
+
intent add "<text>"` (new product anchor post-intake)
|
|
46
47
|
- **Advance / close:** `doctrina change apply|archive|diff <id>` · `doctrina
|
|
47
48
|
decision accept|land|supersede <n>` · `doctrina spec set <cap> [...]` ·
|
|
48
|
-
`doctrina close <id>` (whole close in one pass
|
|
49
|
+
`doctrina close <id>` (whole close in one pass, coverage scoped to the
|
|
50
|
+
change)
|
|
49
51
|
- **Gates (ground truth, not guesses):** `doctrina validate` (`--fix`) ·
|
|
50
52
|
`doctrina verify` · `doctrina review` (conformance vs specs/ADRs) ·
|
|
51
|
-
`doctrina coverage --strict`
|
|
52
|
-
|
|
53
|
+
`doctrina coverage --strict` (`--run` executes cited proof) ·
|
|
54
|
+
`doctrina trace --strict` · `doctrina contract check` ·
|
|
55
|
+
`doctrina clarify --all` · `doctrina analyze <id>`
|
|
53
56
|
|
|
54
57
|
Continuous: `doctrina watch` re-syncs and re-orients on every save.
|
|
55
58
|
Capture lessons: `doctrina skill suggest` surfaces skills worth writing.
|
|
59
|
+
After a CLI update: `doctrina upgrade --write` brings this project up.
|
|
56
60
|
|
|
57
61
|
## Stack and tooling
|
|
58
62
|
|
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,
|
|
@@ -24,7 +24,15 @@ ADR 0006). Tag product.md success-criteria bullets with an anchor —
|
|
|
24
24
|
"- [SC1] ..." — and list the ones this spec delivers above. An "active"
|
|
25
25
|
spec with no Realizes: header warns under `doctrina validate`; if the
|
|
26
26
|
capability genuinely maps to no product anchor (internal tooling), keep
|
|
27
|
-
the header and write "n/a — <why>" to record that on purpose.
|
|
27
|
+
the header and write "n/a — <why>" to record that on purpose. Need a NEW
|
|
28
|
+
anchor for intent born after the intake? `doctrina intent add "<text>"`.
|
|
29
|
+
|
|
30
|
+
Optional: declare the sibling capabilities this spec builds on with a
|
|
31
|
+
machine-readable header — "**Depends on:** defects, decisions". It feeds
|
|
32
|
+
`doctrina why` (the dependency graph), `doctrina context <cap>` (the
|
|
33
|
+
dependencies join the read pack), and `doctrina review` (dependents of a
|
|
34
|
+
touched capability are flagged). Citing them only in prose keeps every
|
|
35
|
+
tool blind to the link.
|
|
28
36
|
-->
|
|
29
37
|
|
|
30
38
|
## Purpose
|