doctrina-cli 0.13.0 → 0.15.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 +3 -2
- package/src/commands/adapter.js +205 -0
- package/src/commands/analyze.js +120 -17
- package/src/commands/change.js +100 -42
- package/src/commands/clarify.js +8 -1
- package/src/commands/close.js +57 -2
- package/src/commands/completion.js +6 -0
- package/src/commands/constitution.js +8 -1
- package/src/commands/context.js +391 -58
- package/src/commands/contract.js +76 -43
- package/src/commands/coverage.js +126 -12
- package/src/commands/decision.js +172 -40
- package/src/commands/doctor.js +92 -11
- package/src/commands/handoff.js +8 -1
- package/src/commands/hooks.js +6 -0
- package/src/commands/index-rebuild.js +8 -1
- package/src/commands/init.js +139 -6
- package/src/commands/intake.js +11 -2
- package/src/commands/intent.js +8 -1
- package/src/commands/metrics.js +80 -7
- package/src/commands/next.js +38 -3
- package/src/commands/prime.js +8 -1
- package/src/commands/report.js +8 -1
- package/src/commands/review.js +8 -1
- package/src/commands/search.js +8 -1
- package/src/commands/show.js +8 -1
- package/src/commands/skill.js +201 -8
- package/src/commands/spec.js +13 -6
- package/src/commands/status.js +14 -2
- package/src/commands/templates.js +193 -59
- package/src/commands/trace.js +15 -3
- package/src/commands/triage.js +248 -0
- package/src/commands/upgrade.js +8 -1
- package/src/commands/validate.js +148 -5
- package/src/commands/verify.js +126 -5
- package/src/commands/watch.js +8 -1
- package/src/commands/why.js +10 -24
- package/src/commands/work.js +44 -3
- package/src/index.js +61 -6
- package/src/lib/adapters.js +140 -0
- package/src/lib/adr-guard.js +1 -0
- package/src/lib/args.js +10 -0
- package/src/lib/clarity.js +1 -0
- package/src/lib/colors.js +1 -0
- package/src/lib/commands.js +252 -27
- package/src/lib/criteria.js +1 -0
- package/src/lib/dates.js +1 -0
- package/src/lib/diff.js +1 -0
- package/src/lib/doc-model.js +287 -0
- package/src/lib/docs-impact.js +167 -0
- package/src/lib/ears.js +1 -0
- package/src/lib/exit-codes.js +103 -0
- package/src/lib/flag-catalog.js +106 -0
- package/src/lib/fs-ops.js +1 -0
- package/src/lib/gates.js +123 -0
- package/src/lib/git.js +97 -0
- package/src/lib/index-json.js +14 -6
- package/src/lib/json-out.js +83 -0
- package/src/lib/pipeline.js +161 -0
- package/src/lib/prompt.js +18 -1
- package/src/lib/runtime.js +683 -0
- package/src/lib/scan.js +81 -33
- package/src/lib/spec-ops.js +94 -9
- package/src/lib/suggest.js +1 -0
- package/src/lib/templates.js +89 -0
- package/src/lib/usage.js +89 -0
- package/src/lib/version.js +1 -0
- package/templates/AGENTS.md.template +59 -53
- package/templates/contract.md.template +74 -3
- package/templates/spec.md.template +22 -0
package/src/commands/clarify.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
// @ts-check
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import process from "node:process";
|
|
3
4
|
import { readdirSync } from "node:fs";
|
|
4
5
|
import { exists, isDir, isFile, read, relPath, walk } from "../lib/fs-ops.js";
|
|
5
6
|
import { flagBool, flagString } from "../lib/args.js";
|
|
6
7
|
import { c } from "../lib/colors.js";
|
|
8
|
+
import { notADoctrinaProject } from "../lib/exit-codes.js";
|
|
7
9
|
|
|
8
10
|
// Two lexicons, one linter (0.11.0 field review item 3: an English-only
|
|
9
11
|
// clarify is permanently red on a PT-BR project — "some" is the verb *sumir*,
|
|
@@ -98,6 +100,11 @@ function forcedLang(flags) {
|
|
|
98
100
|
return null;
|
|
99
101
|
}
|
|
100
102
|
|
|
103
|
+
// Flags this command accepts. Declared HERE, with the command, so
|
|
104
|
+
// adding a command never requires editing the entrypoint — the gap that
|
|
105
|
+
// let six flags ship undeclared and silently swallow a positional (C3).
|
|
106
|
+
export const flags = { boolean: ["json", "all"], string: ["lang"] };
|
|
107
|
+
|
|
101
108
|
export async function run(positional, flags) {
|
|
102
109
|
const projectRoot = process.cwd();
|
|
103
110
|
|
|
@@ -138,7 +145,7 @@ export async function run(positional, flags) {
|
|
|
138
145
|
// register from prose specs.
|
|
139
146
|
function clarifyAll(projectRoot, lang = null) {
|
|
140
147
|
if (!exists(path.join(projectRoot, ".doctrina"))) {
|
|
141
|
-
throw
|
|
148
|
+
throw notADoctrinaProject();
|
|
142
149
|
}
|
|
143
150
|
const files = [];
|
|
144
151
|
const productPath = path.join(projectRoot, ".doctrina", "product.md");
|
package/src/commands/close.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
// @ts-check
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import process from "node:process";
|
|
4
|
+
import { appendFileSync } from "node:fs";
|
|
3
5
|
import { exists, isFile, read, walk } from "../lib/fs-ops.js";
|
|
4
6
|
import { flagBool } from "../lib/args.js";
|
|
5
7
|
import { c } from "../lib/colors.js";
|
|
6
8
|
import { parseCapabilityFromDelta } from "./change.js";
|
|
7
9
|
import { printAdrCheckpoint } from "../lib/adr-guard.js";
|
|
10
|
+
import { checkDocsImpact } from "../lib/docs-impact.js";
|
|
11
|
+
import { notADoctrinaProject } from "../lib/exit-codes.js";
|
|
8
12
|
import * as analyze from "./analyze.js";
|
|
9
13
|
import * as change from "./change.js";
|
|
10
14
|
import * as verify from "./verify.js";
|
|
@@ -21,6 +25,11 @@ import * as skill from "./skill.js";
|
|
|
21
25
|
// rerun, so the agent makes one call and the human approves once. It is a
|
|
22
26
|
// driver over the existing commands; it adds no new checks of its own.
|
|
23
27
|
|
|
28
|
+
// Flags this command accepts. Declared HERE, with the command, so
|
|
29
|
+
// adding a command never requires editing the entrypoint — the gap that
|
|
30
|
+
// let six flags ship undeclared and silently swallow a positional (C3).
|
|
31
|
+
export const flags = { boolean: ["json", "force"], string: [] };
|
|
32
|
+
|
|
24
33
|
export async function run(positional, flags) {
|
|
25
34
|
if (positional.length === 0) {
|
|
26
35
|
console.error(c.red("error:") + " close requires a change <id> (e.g. doctrina close 0001-add-login)");
|
|
@@ -28,7 +37,7 @@ export async function run(positional, flags) {
|
|
|
28
37
|
}
|
|
29
38
|
const projectRoot = process.cwd();
|
|
30
39
|
if (!exists(path.join(projectRoot, ".doctrina"))) {
|
|
31
|
-
throw
|
|
40
|
+
throw notADoctrinaProject();
|
|
32
41
|
}
|
|
33
42
|
|
|
34
43
|
// Batch close (operator review 2026-07-19 §4.5): each id closes
|
|
@@ -54,6 +63,10 @@ export async function run(positional, flags) {
|
|
|
54
63
|
async function closeOne(projectRoot, id, flags) {
|
|
55
64
|
const force = flagBool(flags, "force", false);
|
|
56
65
|
const archiveFlags = force ? new Map([["force", true]]) : new Map();
|
|
66
|
+
// Set by the docs gate when it fails, so a --force close can record the
|
|
67
|
+
// gap in the ledger after the archive lands.
|
|
68
|
+
/** @type {{ signals: string[] } | null} */
|
|
69
|
+
let docsGap = null;
|
|
57
70
|
|
|
58
71
|
// Scope the coverage gate to the capabilities THIS change touches (its
|
|
59
72
|
// deltas), so one deliberately deferred spec elsewhere in the tree cannot
|
|
@@ -61,6 +74,7 @@ async function closeOne(projectRoot, id, flags) {
|
|
|
61
74
|
// item 4). A change with no deltas (chore / metadata-only) falls back to
|
|
62
75
|
// the whole-tree gate — there is no narrower honest scope for it.
|
|
63
76
|
const touched = touchedCapabilities(projectRoot, id);
|
|
77
|
+
/** @type {FlagMap} */
|
|
64
78
|
const coverageFlags = new Map([["strict", true]]);
|
|
65
79
|
let coverageRerun = "doctrina coverage --strict";
|
|
66
80
|
if (touched.length > 0) {
|
|
@@ -100,11 +114,35 @@ async function closeOne(projectRoot, id, flags) {
|
|
|
100
114
|
// trace is advisory (provenance is a warning, not a hard gate): report it,
|
|
101
115
|
// never let it block the close.
|
|
102
116
|
{ label: "trace", rerun: "doctrina trace", run: async () => { await trace.run([], new Map()); return 0; } },
|
|
117
|
+
// Docs ship inside the change (D2): a change that alters a documented
|
|
118
|
+
// surface — a command, a flag, an exit code — closes only with the
|
|
119
|
+
// documentation that describes it. A blocking gate, because a docs
|
|
120
|
+
// phase scheduled after the work never happens; --force is the same
|
|
121
|
+
// escape hatch archive offers, and records the gap in the ledger.
|
|
122
|
+
{
|
|
123
|
+
label: "docs",
|
|
124
|
+
forceable: true,
|
|
125
|
+
rerun: "edit docs/ (EN + PT), then rerun",
|
|
126
|
+
run: async () => {
|
|
127
|
+
const r = checkDocsImpact(projectRoot, path.join(projectRoot, ".doctrina", "changes", id));
|
|
128
|
+
if (r.ok) {
|
|
129
|
+
console.log(c.green("ok") + ` ${r.reason}`);
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
console.error(c.red("error:") + ` this change ${r.reason}:`);
|
|
133
|
+
for (const s of r.signals) console.error(` - ${s}`);
|
|
134
|
+
console.error(c.gray("hint: ") +
|
|
135
|
+
"document it in docs/en/ AND docs/pt/ (the `keep-docs-en-pt-parity` skill), " +
|
|
136
|
+
"or pass --force to close anyway (records the gap)");
|
|
137
|
+
docsGap = r;
|
|
138
|
+
return 1;
|
|
139
|
+
},
|
|
140
|
+
},
|
|
103
141
|
{ label: "archive", rerun: `doctrina change archive ${id}${force ? " --force" : ""}`, run: () => change.run(["archive", id], archiveFlags) },
|
|
104
142
|
{ label: "validate", rerun: "doctrina validate", run: () => validate.run([], new Map()) },
|
|
105
143
|
];
|
|
106
144
|
|
|
107
|
-
console.log(c.bold(`Closing change ${id}`) + c.gray(" — analyze → ADR checkpoint → apply → verify → coverage → trace → archive → validate"));
|
|
145
|
+
console.log(c.bold(`Closing change ${id}`) + c.gray(" — analyze → ADR checkpoint → apply → verify → coverage → trace → docs → archive → validate"));
|
|
108
146
|
|
|
109
147
|
for (let i = 0; i < steps.length; i++) {
|
|
110
148
|
const step = steps[i];
|
|
@@ -122,6 +160,13 @@ async function closeOne(projectRoot, id, flags) {
|
|
|
122
160
|
console.error(c.red("error:") + ` ${err.message}`);
|
|
123
161
|
}
|
|
124
162
|
if (code !== 0) {
|
|
163
|
+
// A forceable gate under --force warns and continues, matching how
|
|
164
|
+
// `change archive --force` handles incomplete verification: the gap
|
|
165
|
+
// is recorded, not hidden.
|
|
166
|
+
if (code !== 0 && step.forceable && force) {
|
|
167
|
+
console.log(c.yellow("warn:") + ` proceeding past "${step.label}" (--force) — the gap is recorded in the ledger`);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
125
170
|
console.log("");
|
|
126
171
|
console.log(c.red(`✗ close stopped at "${step.label}"`) + c.gray(` (step ${i + 1}/${steps.length})`));
|
|
127
172
|
console.log(c.gray("Fix it, then rerun the step or the whole close:"));
|
|
@@ -131,6 +176,16 @@ async function closeOne(projectRoot, id, flags) {
|
|
|
131
176
|
}
|
|
132
177
|
}
|
|
133
178
|
|
|
179
|
+
// Record a forced docs gap in the ledger, so history shows the change
|
|
180
|
+
// shipped without its documentation rather than showing nothing.
|
|
181
|
+
if (docsGap) {
|
|
182
|
+
const ledgerPath = path.join(projectRoot, ".doctrina", "changes", "archive", "LEDGER.md");
|
|
183
|
+
if (isFile(ledgerPath)) {
|
|
184
|
+
appendFileSync(ledgerPath, ` - docs gap: ${id} closed with --force; ${docsGap.signals.join("; ")} documented nowhere\n`);
|
|
185
|
+
console.log(c.yellow("ledger") + " recorded the docs gap");
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
134
189
|
// Capture the lesson while it is fresh (operator review §4.7): surface the
|
|
135
190
|
// fix-shaped lessons not yet written as skills, right where the operator
|
|
136
191
|
// just proved they finish what they start. Advisory — a listing, never a
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @ts-check
|
|
1
2
|
import { OPERATIONS, COMMAND_NAMES } from "../lib/commands.js";
|
|
2
3
|
import { c } from "../lib/colors.js";
|
|
3
4
|
|
|
@@ -90,6 +91,11 @@ Register-ArgumentCompleter -Native -CommandName doctrina -ScriptBlock {
|
|
|
90
91
|
`;
|
|
91
92
|
}
|
|
92
93
|
|
|
94
|
+
// Flags this command accepts. Declared HERE, with the command, so
|
|
95
|
+
// adding a command never requires editing the entrypoint — the gap that
|
|
96
|
+
// let six flags ship undeclared and silently swallow a positional (C3).
|
|
97
|
+
export const flags = { boolean: ["json"], string: [] };
|
|
98
|
+
|
|
93
99
|
export async function run(positional, _flags) {
|
|
94
100
|
const shell = positional[0];
|
|
95
101
|
const generators = { bash: bashScript, zsh: zshScript, pwsh: pwshScript, powershell: pwshScript };
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
// @ts-check
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import process from "node:process";
|
|
3
4
|
import { readdirSync } from "node:fs";
|
|
4
5
|
import { exists, isDir, isFile, read } from "../lib/fs-ops.js";
|
|
5
6
|
import { listHeader } from "../lib/scan.js";
|
|
6
7
|
import { c } from "../lib/colors.js";
|
|
8
|
+
import { notADoctrinaProject } from "../lib/exit-codes.js";
|
|
7
9
|
|
|
8
10
|
// The project's standing rules in one read — Spec Kit parity for its
|
|
9
11
|
// `constitution.md`, but ASSEMBLED, not a new home for facts. Doctrina's
|
|
@@ -16,10 +18,15 @@ import { c } from "../lib/colors.js";
|
|
|
16
18
|
// To change a principle, supersede its ADR; to change a non-goal, edit
|
|
17
19
|
// product.md. The command never writes — it has nothing of its own to own.
|
|
18
20
|
|
|
21
|
+
// Flags this command accepts. Declared HERE, with the command, so
|
|
22
|
+
// adding a command never requires editing the entrypoint — the gap that
|
|
23
|
+
// let six flags ship undeclared and silently swallow a positional (C3).
|
|
24
|
+
export const flags = { boolean: ["json"], string: [] };
|
|
25
|
+
|
|
19
26
|
export async function run(_positional, _flags) {
|
|
20
27
|
const projectRoot = process.cwd();
|
|
21
28
|
if (!exists(path.join(projectRoot, ".doctrina"))) {
|
|
22
|
-
throw
|
|
29
|
+
throw notADoctrinaProject();
|
|
23
30
|
}
|
|
24
31
|
|
|
25
32
|
const project = projectName(projectRoot);
|