doctrina-cli 0.2.0 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doctrina-cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Doctrina CLI — spec-driven, AGENTS.md-native framework for multi-agent AI development.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -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
- description: "<edit me — one-sentence summary>",
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);
@@ -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(/^\*\*Version:\*\*\s+(\S+)/m);
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(`${relPath(projectRoot, f)} missing or malformed Status: header`);
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(`open change "${entry}" missing proposal.md`);
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
  }
@@ -164,7 +182,7 @@ export async function run(_positional, _flags) {
164
182
 
165
183
  // 10. Orphan detection — files on disk not referenced in index.json.
166
184
  if (index?.artifacts) {
167
- const indexedSpecIds = new Set((index.artifacts.specs ?? []).map((s) => s.id));
185
+ const indexedSpecIds = new Set((index.artifacts.specs ?? []).map((s) => s.id ?? s.capability));
168
186
  if (isDir(specsDir)) {
169
187
  const { readdirSync } = await import("node:fs");
170
188
  for (const cap of readdirSync(specsDir)) {
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 spec-style header line: **Name:** value
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(`^\\*\\*${name}:\\*\\*\\s+(.+)$`, "m"));
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(`^-\\s+\\*\\*${name}:\\*\\*\\s+(.+)$`, "m"));
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`.