doctrina-cli 0.2.1 → 0.2.2
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/package.json +1 -1
- package/src/commands/skill.js +4 -1
- package/src/commands/validate.js +21 -3
- package/src/lib/scan.js +12 -4
- package/templates/AGENTS.md.template +28 -0
package/package.json
CHANGED
package/src/commands/skill.js
CHANGED
|
@@ -54,7 +54,10 @@ function skillNew(args, flags) {
|
|
|
54
54
|
idx.addSkill(index, {
|
|
55
55
|
id: name,
|
|
56
56
|
path: `.doctrina/skills/${name}.md`,
|
|
57
|
-
|
|
57
|
+
// Seed the index from the scaffolded frontmatter so a fresh skill does
|
|
58
|
+
// not immediately trip validate's description-drift check. Matches how
|
|
59
|
+
// deriveIndex (index rebuild) reads the description.
|
|
60
|
+
description: parseFrontmatter(body, "description") ?? "<edit me — one-sentence summary>",
|
|
58
61
|
last_updated: date,
|
|
59
62
|
});
|
|
60
63
|
idx.touch(index, date);
|
package/src/commands/validate.js
CHANGED
|
@@ -67,7 +67,7 @@ export async function run(_positional, _flags) {
|
|
|
67
67
|
for (const s of index.artifacts.specs ?? []) {
|
|
68
68
|
const full = path.join(projectRoot, s.path);
|
|
69
69
|
if (!isFile(full)) continue;
|
|
70
|
-
const m = read(full).match(
|
|
70
|
+
const m = read(full).match(/^(?:-\s+)?\*\*Version:\*\*\s+(\S+)/m);
|
|
71
71
|
if (m && s.version && m[1] !== s.version) {
|
|
72
72
|
warnings.push(`${s.path} declares version ${m[1]} but index.json records ${s.version} (sync the index)`);
|
|
73
73
|
}
|
|
@@ -80,9 +80,24 @@ export async function run(_positional, _flags) {
|
|
|
80
80
|
if (isDir(adrDir)) {
|
|
81
81
|
for (const f of walk(adrDir)) {
|
|
82
82
|
if (!f.endsWith(".md")) continue;
|
|
83
|
+
const base = path.basename(f);
|
|
84
|
+
// ADR filenames must be NNNN-slug.md (four digits). The entire ADR
|
|
85
|
+
// toolchain — decision new/accept/supersede, index derivation, and
|
|
86
|
+
// the orphan check below — keys off this shape; a file like
|
|
87
|
+
// "ADR-001-foo.md" is silently invisible to all of them.
|
|
88
|
+
if (base !== "README.md" && !/^\d{4}-.+\.md$/.test(base)) {
|
|
89
|
+
errors.push(
|
|
90
|
+
`${relPath(projectRoot, f)} has a non-canonical ADR filename ` +
|
|
91
|
+
`(expected four digits + slug, e.g. "0001-jwt-algorithm.md"; ` +
|
|
92
|
+
`the decision and index commands silently ignore other shapes)`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
83
95
|
const text = read(f);
|
|
84
96
|
if (!/^-\s+\*\*Status:\*\*\s+\S+/m.test(text)) {
|
|
85
|
-
errors.push(
|
|
97
|
+
errors.push(
|
|
98
|
+
`${relPath(projectRoot, f)} missing or malformed Status: header ` +
|
|
99
|
+
`(expected a Markdown list item exactly like "- **Status:** accepted")`,
|
|
100
|
+
);
|
|
86
101
|
}
|
|
87
102
|
}
|
|
88
103
|
}
|
|
@@ -105,7 +120,10 @@ export async function run(_positional, _flags) {
|
|
|
105
120
|
if (entry === "archive" || entry.startsWith(".")) continue;
|
|
106
121
|
const proposal = path.join(changesDir, entry, "proposal.md");
|
|
107
122
|
if (!isFile(proposal)) {
|
|
108
|
-
errors.push(
|
|
123
|
+
errors.push(
|
|
124
|
+
`open change "${entry}" missing proposal.md ` +
|
|
125
|
+
`(every dir under .doctrina/changes/ needs a proposal.md — that exact filename)`,
|
|
126
|
+
);
|
|
109
127
|
}
|
|
110
128
|
}
|
|
111
129
|
}
|
package/src/lib/scan.js
CHANGED
|
@@ -5,15 +5,23 @@ import { today } from "./dates.js";
|
|
|
5
5
|
import { parseFrontmatter } from "../commands/skill.js";
|
|
6
6
|
import { parseOperation, parseCapabilityFromDelta } from "../commands/change.js";
|
|
7
7
|
|
|
8
|
-
// Parse a
|
|
8
|
+
// Parse a metadata header line, tolerating BOTH conventions:
|
|
9
|
+
// **Name:** value (spec style)
|
|
10
|
+
// - **Name:** value (ADR / proposal / intake style)
|
|
11
|
+
// The leading "- " is optional so a one-character convention slip (an
|
|
12
|
+
// agent hand-authoring an ADR with the bare spec form, or vice versa)
|
|
13
|
+
// still parses instead of silently falling back to a default value.
|
|
14
|
+
const HEADER_PREFIX = "(?:-\\s+)?";
|
|
15
|
+
|
|
16
|
+
// Parse a spec-style header line: **Name:** value (dash optional).
|
|
9
17
|
export function specHeader(text, name) {
|
|
10
|
-
const m = text.match(new RegExp(
|
|
18
|
+
const m = text.match(new RegExp(`^${HEADER_PREFIX}\\*\\*${name}:\\*\\*\\s+(.+)$`, "m"));
|
|
11
19
|
return m ? m[1].trim() : null;
|
|
12
20
|
}
|
|
13
21
|
|
|
14
|
-
// Parse an ADR/proposal-style header line: - **Name:** value
|
|
22
|
+
// Parse an ADR/proposal-style header line: - **Name:** value (dash optional).
|
|
15
23
|
export function listHeader(text, name) {
|
|
16
|
-
const m = text.match(new RegExp(
|
|
24
|
+
const m = text.match(new RegExp(`^${HEADER_PREFIX}\\*\\*${name}:\\*\\*\\s+(.+)$`, "m"));
|
|
17
25
|
return m ? m[1].trim() : null;
|
|
18
26
|
}
|
|
19
27
|
|
|
@@ -56,6 +56,34 @@ native multi-agent development. The full framework artifacts live under
|
|
|
56
56
|
- Archived changes in `.doctrina/changes/archive/` are out of the default
|
|
57
57
|
read path; consult only when explicitly debugging history.
|
|
58
58
|
|
|
59
|
+
## Artifact invariants (verbatim — `doctrina validate` enforces these)
|
|
60
|
+
|
|
61
|
+
Scaffold artifacts with the CLI (`doctrina decision new`, `doctrina work`),
|
|
62
|
+
which writes from the canonical templates. Do NOT hand-author these files
|
|
63
|
+
from memory. If you must write one by hand, match these exactly:
|
|
64
|
+
|
|
65
|
+
- **Metadata headers come in two forms — do not mix them up:**
|
|
66
|
+
- ADRs, change proposals, and the intake use **list items**:
|
|
67
|
+
`- **Status:** accepted`, `- **Date:** 2026-06-13`.
|
|
68
|
+
- Specs use **bare bold** (no leading `- `):
|
|
69
|
+
`**Status:** active`, `**Version:** 0.1.0`.
|
|
70
|
+
Match the template for the artifact you are writing.
|
|
71
|
+
- **ADR filename** must be `NNNN-slug.md` — four digits, e.g.
|
|
72
|
+
`0001-jwt-algorithm.md`. `ADR-001-...md` or `1-...md` is invisible to
|
|
73
|
+
`doctrina decision accept`, the index, and the orphan check.
|
|
74
|
+
- **Change folder** at `.doctrina/changes/<id>/` must contain a file named
|
|
75
|
+
exactly `proposal.md` (not `change.md`, not `README.md`). `tasks.md` and
|
|
76
|
+
`design.md` are optional siblings.
|
|
77
|
+
- **Spec** lives at `.doctrina/specs/<capability>/spec.md` and its
|
|
78
|
+
`**Version:**` header must equal the `version` recorded for it in
|
|
79
|
+
`.doctrina/index.json`.
|
|
80
|
+
- **Every artifact on disk** must be registered in `.doctrina/index.json`,
|
|
81
|
+
and every path in `index.json` must exist on disk. Update the index in
|
|
82
|
+
the same change that adds or moves a file.
|
|
83
|
+
|
|
84
|
+
Always finish by running `doctrina validate` and resolving every `error:`
|
|
85
|
+
before considering work done.
|
|
86
|
+
|
|
59
87
|
## How to read context efficiently
|
|
60
88
|
|
|
61
89
|
1. Read this `AGENTS.md`.
|