doctrina-cli 0.6.0 → 0.8.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/close.js +104 -0
- package/src/commands/coverage.js +27 -0
- package/src/commands/hooks.js +9 -3
- package/src/commands/next.js +56 -3
- package/src/commands/review.js +177 -0
- package/src/commands/skill.js +112 -2
- package/src/commands/status.js +178 -0
- package/src/commands/trace.js +31 -0
- package/src/commands/validate.js +21 -0
- package/src/commands/verify.js +83 -12
- package/src/commands/watch.js +97 -0
- package/src/commands/why.js +182 -0
- package/src/commands/work.js +9 -0
- package/src/index.js +13 -1
- package/templates/AGENTS.md.template +49 -22
- package/templates/hooks/pre-commit.sample +21 -4
- package/templates/spec.md.template +7 -3
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { readdirSync } from "node:fs";
|
|
4
|
+
import { exists, isDir, isFile, read } from "../lib/fs-ops.js";
|
|
5
|
+
import { specHeader, listHeader } from "../lib/scan.js";
|
|
6
|
+
import { c } from "../lib/colors.js";
|
|
7
|
+
import { suggest } from "../lib/suggest.js";
|
|
8
|
+
|
|
9
|
+
// Provenance explainer (review 2026-06-27 passive-user feature #5): answer
|
|
10
|
+
// "why does this capability exist / why was it built this way?" by assembling
|
|
11
|
+
// the chain the framework already records — product intent (the [SC1] anchors
|
|
12
|
+
// it Realizes) → the capability's purpose → the acceptance criteria that prove
|
|
13
|
+
// it → the accepted ADRs that decided it — into one read. The human (or agent)
|
|
14
|
+
// asks; the tool answers from the artifacts, instead of grepping by hand.
|
|
15
|
+
// Read-only. Builds on the Realizes:/trace provenance (ADR 0006/0011).
|
|
16
|
+
|
|
17
|
+
export async function run(positional, _flags) {
|
|
18
|
+
const cap = positional[0];
|
|
19
|
+
const projectRoot = process.cwd();
|
|
20
|
+
if (!exists(path.join(projectRoot, ".doctrina"))) {
|
|
21
|
+
throw new Error("not a Doctrina project (no .doctrina/ in cwd). Run `doctrina init` first.");
|
|
22
|
+
}
|
|
23
|
+
const specsDir = path.join(projectRoot, ".doctrina", "specs");
|
|
24
|
+
const known = isDir(specsDir) ? readdirSync(specsDir).filter((e) => isFile(path.join(specsDir, e, "spec.md"))) : [];
|
|
25
|
+
|
|
26
|
+
if (!cap) {
|
|
27
|
+
console.error(c.red("error:") + " why requires a <capability>");
|
|
28
|
+
if (known.length) console.error(c.gray("known: ") + known.sort().join(", "));
|
|
29
|
+
return 2;
|
|
30
|
+
}
|
|
31
|
+
const specPath = path.join(specsDir, cap, "spec.md");
|
|
32
|
+
if (!isFile(specPath)) {
|
|
33
|
+
console.error(c.red("error:") + ` no spec for capability "${cap}"`);
|
|
34
|
+
const guess = suggest(cap, known);
|
|
35
|
+
if (guess) console.error(c.gray("hint: ") + `did you mean "${guess}"?`);
|
|
36
|
+
else if (known.length) console.error(c.gray("known: ") + known.sort().join(", "));
|
|
37
|
+
return 1;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const text = read(specPath);
|
|
41
|
+
console.log(c.bold(`Why "${cap}"`) + c.gray(" — provenance chain (intent → capability → proof → decisions)"));
|
|
42
|
+
console.log("");
|
|
43
|
+
|
|
44
|
+
// 1. Product intent it realizes.
|
|
45
|
+
const realizesRaw = specHeader(text, "Realizes");
|
|
46
|
+
const anchorIds = realizesRaw ? (realizesRaw.match(/[A-Z]+\d+/g) ?? []) : [];
|
|
47
|
+
console.log(c.bold(" Product intent"));
|
|
48
|
+
if (realizesRaw && /^n\/a\b/i.test(realizesRaw.trim())) {
|
|
49
|
+
console.log(` ${c.gray("n/a — " + realizesRaw.trim().replace(/^n\/a\s*[—-]?\s*/i, ""))}`);
|
|
50
|
+
} else if (anchorIds.length === 0) {
|
|
51
|
+
console.log(` ${c.yellow("none declared")} ${c.gray("— add a **Realizes:** header (doctrina trace)")}`);
|
|
52
|
+
} else {
|
|
53
|
+
const anchors = productAnchors(projectRoot);
|
|
54
|
+
for (const id of anchorIds) {
|
|
55
|
+
const line = anchors.get(id);
|
|
56
|
+
console.log(` ${c.cyan(id)} ${line ? line : c.yellow("(no matching anchor in product.md)")}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 2. The capability itself.
|
|
61
|
+
console.log("");
|
|
62
|
+
console.log(c.bold(" Capability"));
|
|
63
|
+
const status = specHeader(text, "Status") ?? "—";
|
|
64
|
+
const impl = specHeader(text, "Implementation") ?? "—";
|
|
65
|
+
console.log(` ${c.gray("status:")} ${status} ${c.gray("implementation:")} ${impl}`);
|
|
66
|
+
const purpose = sectionParagraph(text, "Purpose");
|
|
67
|
+
if (purpose) console.log(" " + purpose.replace(/\s+/g, " ").trim());
|
|
68
|
+
|
|
69
|
+
// 3. The proof (acceptance criteria with cited evidence).
|
|
70
|
+
console.log("");
|
|
71
|
+
console.log(c.bold(" Proof"));
|
|
72
|
+
const criteria = acceptanceWithEvidence(text);
|
|
73
|
+
if (criteria.length === 0) {
|
|
74
|
+
console.log(` ${c.gray("no acceptance criteria declared")}`);
|
|
75
|
+
} else {
|
|
76
|
+
for (const cr of criteria) {
|
|
77
|
+
const proof = cr.proof.length ? c.gray(cr.proof.map((p) => `\`${p}\``).join(", ")) : c.yellow("no evidence cited");
|
|
78
|
+
console.log(` ${cr.mark} #${cr.n} ${proof}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 4. The decisions that shaped it (accepted ADRs naming the capability).
|
|
83
|
+
console.log("");
|
|
84
|
+
console.log(c.bold(" Decisions"));
|
|
85
|
+
const adrs = decisionsMentioning(projectRoot, cap);
|
|
86
|
+
if (adrs.length === 0) {
|
|
87
|
+
console.log(` ${c.gray("no accepted ADR names this capability")}`);
|
|
88
|
+
} else {
|
|
89
|
+
for (const a of adrs) console.log(` ${c.cyan("ADR " + a.id)} ${a.title}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Map of anchor id -> the product.md bullet text that declares it.
|
|
96
|
+
function productAnchors(projectRoot) {
|
|
97
|
+
const out = new Map();
|
|
98
|
+
const p = path.join(projectRoot, ".doctrina", "product.md");
|
|
99
|
+
if (!isFile(p)) return out;
|
|
100
|
+
for (const line of read(p).split(/\r?\n/)) {
|
|
101
|
+
const m = line.match(/^\s*[-*]\s+\[([A-Z]+\d+)\]\s+(.+)$/);
|
|
102
|
+
if (m && !out.has(m[1])) out.set(m[1], m[2].trim());
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// First non-comment paragraph under a "## <name>" heading.
|
|
108
|
+
function sectionParagraph(text, name) {
|
|
109
|
+
const lines = text.split(/\r?\n/);
|
|
110
|
+
let inSection = false;
|
|
111
|
+
const buf = [];
|
|
112
|
+
for (const line of lines) {
|
|
113
|
+
if (/^##\s+/.test(line)) {
|
|
114
|
+
if (inSection) break;
|
|
115
|
+
if (new RegExp(`^##\\s+${name}\\b`, "i").test(line)) inSection = true;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (!inSection) continue;
|
|
119
|
+
if (/^\s*<!--/.test(line) || /-->/.test(line)) continue;
|
|
120
|
+
if (line.trim() === "") {
|
|
121
|
+
if (buf.length) break;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
buf.push(line.trim());
|
|
125
|
+
}
|
|
126
|
+
return buf.join(" ");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Numbered acceptance criteria with their [mark] and any backtick proof paths.
|
|
130
|
+
function acceptanceWithEvidence(text) {
|
|
131
|
+
const lines = text.split(/\r?\n/);
|
|
132
|
+
let inSection = false;
|
|
133
|
+
const out = [];
|
|
134
|
+
for (const line of lines) {
|
|
135
|
+
if (/^##\s+/.test(line)) {
|
|
136
|
+
inSection = /^##\s+Acceptance criteria\b/i.test(line);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (!inSection) continue;
|
|
140
|
+
const m = line.match(/^\s*(\d+)\.\s+(.*)$/);
|
|
141
|
+
if (!m) continue;
|
|
142
|
+
const body = m[2];
|
|
143
|
+
const markMatch = body.match(/^\[([^\]]+)\]/);
|
|
144
|
+
const mark = markMatch
|
|
145
|
+
? (/^verified/i.test(markMatch[1]) ? c.green("✓") : c.yellow("○"))
|
|
146
|
+
: c.gray("·");
|
|
147
|
+
const proof = [...body.matchAll(/`([^`]+)`/g)].map((x) => x[1]).filter((s) => s.includes("/") || /\.[a-z0-9]{1,8}$/i.test(s));
|
|
148
|
+
out.push({ n: m[1], mark, proof });
|
|
149
|
+
}
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Accepted ADRs whose body names the capability (word-ish boundary).
|
|
154
|
+
function decisionsMentioning(projectRoot, cap) {
|
|
155
|
+
const adrDir = path.join(projectRoot, ".doctrina", "decisions");
|
|
156
|
+
const out = [];
|
|
157
|
+
if (!isDir(adrDir)) return out;
|
|
158
|
+
const re = new RegExp(`\\b${cap.replace(/[-]/g, "[- ]?")}\\b`, "i");
|
|
159
|
+
for (const f of readdirSync(adrDir).sort()) {
|
|
160
|
+
const m = f.match(/^(\d{4})-.*\.md$/);
|
|
161
|
+
if (!m) continue;
|
|
162
|
+
const text = read(path.join(adrDir, f));
|
|
163
|
+
const status = (listHeader(text, "Status") ?? "").toLowerCase();
|
|
164
|
+
if (status !== "accepted") continue;
|
|
165
|
+
if (!re.test(text)) continue;
|
|
166
|
+
const titleMatch = text.match(/^#\s+ADR\s+\d{4}\s*[—-]\s*(.+)$/m);
|
|
167
|
+
out.push({ id: m[1], title: titleMatch ? titleMatch[1].trim() : f });
|
|
168
|
+
}
|
|
169
|
+
return out;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export const help = `
|
|
173
|
+
Usage: doctrina why <capability>
|
|
174
|
+
|
|
175
|
+
Explain a capability's provenance by assembling the chain Doctrina already
|
|
176
|
+
records into one read: the product intent it **Realizes:** ([SC1] anchors),
|
|
177
|
+
the capability's purpose and status, the acceptance criteria that prove it
|
|
178
|
+
(with cited evidence), and the accepted ADRs that name it. Read-only.
|
|
179
|
+
|
|
180
|
+
Builds on the intent provenance (ADR 0006/0011) — answer "why was X built,
|
|
181
|
+
and built this way?" without grepping the tree by hand.
|
|
182
|
+
`;
|
package/src/commands/work.js
CHANGED
|
@@ -333,6 +333,8 @@ function printChorePlaybook(projectRoot, { id, prompt }) {
|
|
|
333
333
|
console.log(` spec delta, it is not a chore — reopen with ${c.cyan("doctrina work \"<prompt>\"")}.`);
|
|
334
334
|
console.log(`4. ${c.cyan(`doctrina change apply ${id}`)} (zero deltas → flips to applied).`);
|
|
335
335
|
console.log(`5. ${c.cyan(`doctrina change archive ${id}`)}, then ${c.cyan("doctrina validate")}.`);
|
|
336
|
+
console.log(c.gray(" If this fix taught a lesson worth not relearning, capture it: ") +
|
|
337
|
+
c.cyan("doctrina skill new <slug>") + c.gray("."));
|
|
336
338
|
}
|
|
337
339
|
|
|
338
340
|
function printPlaybook(projectRoot, { id, prompt, pinned, matches, capability, clarity, fromDiff = false, diffMatches = [] }) {
|
|
@@ -385,6 +387,10 @@ function printPlaybook(projectRoot, { id, prompt, pinned, matches, capability, c
|
|
|
385
387
|
console.log("");
|
|
386
388
|
console.log("2. If the capability has no spec yet:");
|
|
387
389
|
console.log(` ${c.cyan("doctrina spec new <capability>")}`);
|
|
390
|
+
console.log(c.gray(" Trace it to product intent: tag the product.md success-criteria bullet"));
|
|
391
|
+
console.log(c.gray(" it serves with an anchor (\"- [SC1] ...\") and set the spec's"));
|
|
392
|
+
console.log(c.gray(" \"**Realizes:** SC1\" header (or \"n/a — <why>\"). `doctrina validate` warns"));
|
|
393
|
+
console.log(c.gray(" on an active spec with no Realizes; `doctrina trace` reports the link."));
|
|
388
394
|
console.log("");
|
|
389
395
|
console.log("3. Write one delta per affected capability at");
|
|
390
396
|
console.log(` .doctrina/changes/${id}/specs/<capability>/delta.md:`);
|
|
@@ -413,11 +419,14 @@ function printPlaybook(projectRoot, { id, prompt, pinned, matches, capability, c
|
|
|
413
419
|
console.log("8. Prove it before declaring done (archive refuses unchecked boxes):");
|
|
414
420
|
console.log(` ${c.cyan("doctrina verify")} — the project's typecheck/test/build gate`);
|
|
415
421
|
console.log(` ${c.cyan("doctrina coverage")} — each acceptance criterion cites a real test`);
|
|
422
|
+
console.log(` ${c.cyan("doctrina trace")} — the capability traces to product intent`);
|
|
416
423
|
console.log(" Then check the proposal's ## Verification boxes and every task,");
|
|
417
424
|
console.log(" closing steps included, and bump Implementation to verified.");
|
|
418
425
|
console.log("");
|
|
419
426
|
console.log(`9. ${c.cyan(`doctrina change archive ${id}`)}`);
|
|
420
427
|
console.log(`10. ${c.cyan("doctrina validate")}, then ${c.cyan("doctrina next")} for the follow-up.`);
|
|
428
|
+
console.log(c.gray(" If this change taught a reusable lesson (a fix you'd hate to relearn,"));
|
|
429
|
+
console.log(c.gray(" a recurring convention), capture it: ") + c.cyan("doctrina skill new <slug>") + c.gray("."));
|
|
421
430
|
}
|
|
422
431
|
|
|
423
432
|
export const help = `
|
package/src/index.js
CHANGED
|
@@ -26,11 +26,17 @@ import * as coverage from "./commands/coverage.js";
|
|
|
26
26
|
import * as verify from "./commands/verify.js";
|
|
27
27
|
import * as contract from "./commands/contract.js";
|
|
28
28
|
import * as trace from "./commands/trace.js";
|
|
29
|
+
import * as status from "./commands/status.js";
|
|
30
|
+
import * as close from "./commands/close.js";
|
|
31
|
+
import * as review from "./commands/review.js";
|
|
32
|
+
import * as watch from "./commands/watch.js";
|
|
33
|
+
import * as why from "./commands/why.js";
|
|
29
34
|
|
|
30
35
|
const COMMANDS = {
|
|
31
36
|
init, spec, change, decision, validate, hooks, analyze, clarify,
|
|
32
37
|
templates, skill, index: indexCmd, next, metrics, context, search,
|
|
33
38
|
intake, work, coverage, verify, contract, trace,
|
|
39
|
+
status, close, review, watch, why,
|
|
34
40
|
};
|
|
35
41
|
|
|
36
42
|
const TOP_HELP = `
|
|
@@ -56,6 +62,7 @@ Commands:
|
|
|
56
62
|
skill new Scaffold an on-demand procedural memory skill
|
|
57
63
|
skill list List skills with their descriptions
|
|
58
64
|
skill sync Mirror skill frontmatter descriptions into index.json
|
|
65
|
+
skill suggest Surface fix-shaped lessons worth a skill (--write scaffolds)
|
|
59
66
|
analyze Inspect a change folder before applying
|
|
60
67
|
clarify Smell-test a Markdown file for ambiguity (--all for the tree)
|
|
61
68
|
context Print the context pack for a task in read order
|
|
@@ -63,12 +70,17 @@ Commands:
|
|
|
63
70
|
validate Run schema and structural checks
|
|
64
71
|
coverage Report acceptance criteria with linked evidence (--strict gates)
|
|
65
72
|
trace Report intent provenance: product intent → specs (--strict gates)
|
|
73
|
+
review Conformance review of your changes vs specs/ADRs/contracts
|
|
66
74
|
verify Run project-declared typecheck/test/build checks (the real gate)
|
|
75
|
+
close Run the whole close sequence for a change in one pass
|
|
76
|
+
status One-glance project health dashboard
|
|
77
|
+
why Explain a capability's provenance (intent → proof → ADRs)
|
|
67
78
|
templates list List the templates shipped by the installed CLI
|
|
68
79
|
templates check Compare the project against the recommended template shape
|
|
69
80
|
hooks install Install the pre-commit hook
|
|
70
81
|
index rebuild Regenerate index.json from the artifacts on disk
|
|
71
82
|
next Print the recommended next workflow actions
|
|
83
|
+
watch Re-run validate --fix + next on every change (--once for one pass)
|
|
72
84
|
metrics Local git-derived adoption metrics (no network)
|
|
73
85
|
|
|
74
86
|
Global flags:
|
|
@@ -78,7 +90,7 @@ Global flags:
|
|
|
78
90
|
|
|
79
91
|
async function main(argv) {
|
|
80
92
|
const { positional, flags } = parseArgs(argv, {
|
|
81
|
-
boolean: ["help", "h", "version", "v", "force", "non-interactive", "check", "save", "bug", "write", "all", "concat", "archive", "strict", "list", "init"],
|
|
93
|
+
boolean: ["help", "h", "version", "v", "force", "non-interactive", "check", "save", "bug", "write", "all", "concat", "archive", "strict", "list", "init", "fix", "once", "clean"],
|
|
82
94
|
});
|
|
83
95
|
|
|
84
96
|
if (flags.get("version") || flags.get("v")) {
|
|
@@ -11,20 +11,47 @@ This repository uses the Doctrina framework for spec-driven, AGENTS.md
|
|
|
11
11
|
native multi-agent development. The full framework artifacts live under
|
|
12
12
|
`.doctrina/`.
|
|
13
13
|
|
|
14
|
-
## Working from intent
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
change requirements.
|
|
25
|
-
- **Day-to-day.** Turn
|
|
26
|
-
|
|
27
|
-
|
|
14
|
+
## Working from intent (you drive; the human stays passive)
|
|
15
|
+
|
|
16
|
+
You — the AI agent — run the Doctrina commands. The human gives a brief
|
|
17
|
+
prompt and approves; do not make them run the CLI or hand-author artifacts.
|
|
18
|
+
|
|
19
|
+
- **Bootstrap.** If `.doctrina/intake.md` is `Status: pending`, the specs
|
|
20
|
+
are not written yet: run `doctrina intake` and execute the printed
|
|
21
|
+
playbook in one pass (fill `product.md`, derive capabilities, one EARS
|
|
22
|
+
spec each, `doctrina clarify --all` + `doctrina validate`, then flip the
|
|
23
|
+
intake to `Status: converted`). After conversion the specs are the only
|
|
24
|
+
source of truth — never edit `intake.md` to change requirements.
|
|
25
|
+
- **Day-to-day.** Turn any request into a change with `doctrina work
|
|
26
|
+
"<prompt>"` and follow the printed playbook (spec delta → tasks →
|
|
27
|
+
implement → analyze → apply → archive → validate). Unsure what to do
|
|
28
|
+
next? Run `doctrina next`. Need to read? Run `doctrina context`. Ask the
|
|
29
|
+
human only on genuine ambiguity.
|
|
30
|
+
|
|
31
|
+
## Doctrina command surface (reach for these — don't hand-author)
|
|
32
|
+
|
|
33
|
+
The CLI scaffolds from canonical templates and keeps `index.json` in sync,
|
|
34
|
+
so prefer it over writing artifacts by hand. Full list: `doctrina --help`.
|
|
35
|
+
|
|
36
|
+
- **Read / orient (any task, first & last):** `doctrina status` (health) ·
|
|
37
|
+
`doctrina context [<cap>] --concat` (read pack) · `doctrina next` (what's
|
|
38
|
+
next) · `doctrina why <cap>` (provenance) · `doctrina spec list` ·
|
|
39
|
+
`doctrina decision list` · `doctrina search <query>`
|
|
40
|
+
- **Start:** `doctrina work "<prompt>"` · `--from-diff` (backfill from
|
|
41
|
+
existing code) · `--chore` (no-spec infra/docs/build)
|
|
42
|
+
- **Scaffold (manual creation stays available):** `doctrina spec new <cap>`
|
|
43
|
+
(`--bug`) · `doctrina decision new "<title>"` · `doctrina change new <id>`
|
|
44
|
+
· `doctrina contract new <id>` · `doctrina skill new <slug>`
|
|
45
|
+
- **Advance / close:** `doctrina change apply|archive|diff <id>` · `doctrina
|
|
46
|
+
decision accept|land|supersede <n>` · `doctrina spec set <cap> [...]` ·
|
|
47
|
+
`doctrina close <id>` (whole close in one pass)
|
|
48
|
+
- **Gates (ground truth, not guesses):** `doctrina validate` (`--fix`) ·
|
|
49
|
+
`doctrina verify` · `doctrina review` (conformance vs specs/ADRs) ·
|
|
50
|
+
`doctrina coverage --strict` · `doctrina trace --strict` · `doctrina
|
|
51
|
+
contract check` · `doctrina clarify --all` · `doctrina analyze <id>`
|
|
52
|
+
|
|
53
|
+
Continuous: `doctrina watch` re-syncs and re-orients on every save.
|
|
54
|
+
Capture lessons: `doctrina skill suggest` surfaces skills worth writing.
|
|
28
55
|
|
|
29
56
|
## Stack and tooling
|
|
30
57
|
|
|
@@ -86,14 +113,14 @@ before considering work done.
|
|
|
86
113
|
|
|
87
114
|
## How to read context efficiently
|
|
88
115
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
116
|
+
`doctrina context [<capability>] --concat` assembles the read pack in one
|
|
117
|
+
call, in order: this `AGENTS.md` → `.doctrina/product.md` → the capability
|
|
118
|
+
spec → open `.doctrina/changes/<id>/` → `.doctrina/decisions/` filtered to
|
|
119
|
+
`Status: accepted`. Run it for ANY task, not only `doctrina work`; it skips
|
|
120
|
+
`changes/archive/` (history — read only when debugging it).
|
|
121
|
+
|
|
122
|
+
On demand only: if the task matches a skill in `.doctrina/skills/`, read its
|
|
123
|
+
`description:` / `when:` frontmatter; load the full body when the trigger fires.
|
|
97
124
|
|
|
98
125
|
Keep this file under 150 lines. Density beats prose. Use exact commands.
|
|
99
126
|
|
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env sh
|
|
2
2
|
# Doctrina pre-commit hook.
|
|
3
|
-
# Installed by `doctrina hooks install`.
|
|
4
|
-
#
|
|
5
|
-
#
|
|
3
|
+
# Installed by `doctrina hooks install`. Edit freely after install; the CLI
|
|
4
|
+
# will not overwrite without --force.
|
|
5
|
+
#
|
|
6
|
+
# Runs `doctrina validate --fix`:
|
|
7
|
+
# 1. --fix regenerates index.json from the tree (repairs metadata drift and
|
|
8
|
+
# migrates the framework_version stamp) BEFORE checking, so the single
|
|
9
|
+
# most common gate failure — a hand-edited header that drifted the index —
|
|
10
|
+
# is healed at commit time instead of blocking the commit.
|
|
11
|
+
# 2. validate still exits non-zero (and blocks the commit) on real errors a
|
|
12
|
+
# rebuild cannot heal: missing artifacts, duplicate ADR numbers, broken
|
|
13
|
+
# EARS, ledger/index disagreement, and so on.
|
|
14
|
+
#
|
|
15
|
+
# To gate without auto-repairing (CI-style, fail on any drift), replace the
|
|
16
|
+
# line below with a bare `doctrina validate`.
|
|
6
17
|
|
|
7
|
-
doctrina validate
|
|
18
|
+
doctrina validate --fix || exit 1
|
|
19
|
+
|
|
20
|
+
# Re-stage the index if --fix rewrote it, so the repaired index lands in this
|
|
21
|
+
# commit rather than as a leftover unstaged change. Harmless when unchanged.
|
|
22
|
+
if [ -f .doctrina/index.json ]; then
|
|
23
|
+
git add .doctrina/index.json
|
|
24
|
+
fi
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
**Capability:** {{CAPABILITY}}
|
|
4
4
|
**Status:** draft
|
|
5
5
|
**Implementation:** planned
|
|
6
|
+
**Realizes:** <product.md success-criteria anchors this delivers, or "n/a — <why>">
|
|
6
7
|
**Last updated:** {{DATE}}
|
|
7
8
|
**Version:** 0.1.0
|
|
8
9
|
|
|
@@ -18,9 +19,12 @@ behind it, and `doctrina validate` warns. To record a deliberate gap and
|
|
|
18
19
|
silence the warning, add a note after the value, e.g.
|
|
19
20
|
"**Implementation:** planned — backend deferred to Q3, see ADR 0007".
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
Realizes ties this capability to product intent (`doctrina trace`;
|
|
23
|
+
ADR 0006). Tag product.md success-criteria bullets with an anchor —
|
|
24
|
+
"- [SC1] ..." — and list the ones this spec delivers above. An "active"
|
|
25
|
+
spec with no Realizes: header warns under `doctrina validate`; if the
|
|
26
|
+
capability genuinely maps to no product anchor (internal tooling), keep
|
|
27
|
+
the header and write "n/a — <why>" to record that on purpose.
|
|
24
28
|
-->
|
|
25
29
|
|
|
26
30
|
## Purpose
|