ai-runtime-kit 0.9.0 → 0.10.1

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/CHANGELOG.md CHANGED
@@ -10,6 +10,114 @@ kit.
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [0.10.1] - 2026-05-14
14
+
15
+ Bundled cleanup of four doc-only follow-ups from v0.9.0 and
16
+ v0.10.0 reviews. Engineering-only spec; no PRD/feature parent.
17
+
18
+ ### Changed
19
+ - `runtime/workflows/feature-development.md` — cross-references
20
+ `.ai/runtime/INDEX.md § Traceability` for the canonical
21
+ `## Parent <Type>` rules and `(none — <reason>)` rendering
22
+ convention. Also references `ai-runtime-kit validate` as the
23
+ audit tool.
24
+ - `runtime/tasks/_template.md` — `## Parent Plan` comment now
25
+ shows the explicit `(none — direct task)` literal rendering
26
+ for hot-fix tasks.
27
+ - `runtime/agents/spec-writer.md` — appends one `## Must Not`
28
+ bullet: "Approve specs whose acceptance asserts metrics on
29
+ state not yet in place (drift hit v0.5.1, v0.7.0, v0.10.0)."
30
+ File 1680 → 1794 bytes (under the spec's revised 1900
31
+ ceiling — 4th size-ceiling drift recorded; future fix
32
+ should make budgets ranges, not hard caps).
33
+ - `README.md` — Walkthrough 3 per-step bullets gain annotations
34
+ showing which `## Parent <Type>` field anchors each step's
35
+ upward link. Closing line mentions `ai-runtime-kit validate`
36
+ as the audit step before merge.
37
+
38
+ ### Process
39
+ - Eighth fire of `pre-executor/runtime-scoped-preflight` hook;
40
+ eighth clean pass.
41
+ - Second engineering-only spec on this kit (after v0.8.1).
42
+ The streamlined no-PRD/no-feature path is now well-trodden.
43
+ - `node bin/cli.js validate` self-test post-edit:
44
+ 10 specs / 9 reviews on the kit's tree, all PASS.
45
+
46
+ ## [0.10.0] - 2026-05-14
47
+
48
+ **First M4 data point.** Kit's first `src/**` feature shipped
49
+ under the v0.6.0+ PRD → Feature → Spec → Plan → Task → TDD →
50
+ Implement → Verify → Review workflow with `TDD-Applies: true`
51
+ tasks. Two TDD pairs (test-commit-before-impl-commit) at 100%
52
+ test-first ordering.
53
+
54
+ ### Added
55
+ - **`validate` command** — checks `.ai/project/` tree
56
+ structural integrity. Walks every artifact (PRD / feature /
57
+ spec / plan / task / review) and verifies:
58
+ - Required `## Parent <Type>` sections per v0.9.0 INDEX §
59
+ Traceability rules.
60
+ - Cited parent paths resolve to real files on disk.
61
+ - `Status` values are in the allowed set per artifact type.
62
+ Reports errors (missing-parent / empty-parent /
63
+ unresolved-parent) and warnings (unexpected-status). Exits
64
+ 0 on clean, 1 on any error.
65
+ - `--json` flag for machine-parseable output.
66
+ - `--cwd <dir>` flag for non-default project root.
67
+ - `src/validate.js` (NEW) — pure validator module.
68
+ - `test/validate.test.js` (NEW) — 6 unit tests (clean tree,
69
+ missing parent, broken path, `(none — ...)` rendering,
70
+ JSON output, live dogfood smoke).
71
+
72
+ ### Changed
73
+ - `bin/cli.js` — dispatcher gains `validate` subcommand;
74
+ HELP lists it alongside `init` and `upgrade`.
75
+ - 7 historical specs (v0.4.0 / v0.5.0 / v0.5.1 / v0.6.0 /
76
+ v0.7.0 / v0.8.0 / v0.8.1) retrofitted with `## Parent
77
+ Feature` sections (pre-v0.6.0 use `(none —
78
+ pre-feature-layer)`; v0.8.1 uses `(none —
79
+ engineering-only)`; the rest cite real feature paths).
80
+ - 7 historical reviews retrofitted with `## Parent Spec`
81
+ sections. v0.9.0's spec/review already had theirs (the
82
+ convention F4 shipped was in use immediately on its own
83
+ review). Retrofit is project-side and not subject to
84
+ preflight.
85
+
86
+ ### Process
87
+ - **Kit's first `src/**` feature** under the new pipeline.
88
+ Branch was `feat/validate-cli` (not `chore/runtime-*`)
89
+ because no `runtime/**` paths in scope.
90
+ - **First non-runtime-scoped feature in the v0.6.0+
91
+ workflow** — preflight hook did not fire (correctly; no
92
+ `runtime/**` paths to gate on). The kit's discipline now
93
+ cleanly distinguishes governance-scoped (preflight gated)
94
+ from kit-code-scoped (regular feature branches).
95
+ - **First M4 data point.** Two TDD pairs:
96
+ - T1 (src/validate.js): test commit 16:07:26 → impl
97
+ 16:08:21 (Δ +55s).
98
+ - T2 (bin/cli.js): test commit 16:09:47 → impl 16:11:53
99
+ (Δ +2m6s).
100
+ Both pairs satisfy "test-commit precedes impl-commit." M4
101
+ score for this feature: 2/2 = 100%.
102
+ - **Validator caught a real concern during dogfood.** The
103
+ first run reported `TASK_STATUS.md` as a missing-parent
104
+ error; this file is a top-level status tracker (output of
105
+ `init`), not a task instance. Fix landed in C4: validator
106
+ now skips `EXCLUDED_FILENAMES`. The tool catching its own
107
+ edge case in its first ship cycle is the dogfood loop
108
+ working as intended.
109
+ - **Spec amended mid-implementation** (third occurrence —
110
+ v0.5.1, v0.7.0, and now v0.10.0). Original spec excluded
111
+ retrofitting historical artifacts; the validator's
112
+ credibility depends on its first dogfood pass being
113
+ clean, so the spec was amended pre-impl to include
114
+ retrofit work. Recorded in spec § Status with a comment.
115
+ - **24/24 tests pass** (18 prior + 6 new).
116
+ - **`validate` against this repo's `.ai/` tree**:
117
+ 3 PRDs / 5 features / 9 specs / 0 plans / 0 tasks /
118
+ 8 reviews / **0 errors / 0 warnings / Result: PASS**.
119
+ VM1 satisfied live on first ship.
120
+
13
121
  ## [0.9.0] - 2026-05-14
14
122
 
15
123
  **Closes the v0.6.0 nine-phase-workflow PRD's 4-feature
package/README.md CHANGED
@@ -10,6 +10,22 @@ npx ai-runtime-kit upgrade # existing kit consumer
10
10
 
11
11
  ## Status
12
12
 
13
+ **v0.10.1** — Bundled cleanup of 4 doc-only follow-ups from
14
+ v0.9.0/v0.10.0 reviews: workflow cross-ref to `INDEX § Traceability`,
15
+ hot-fix example in task template, new `spec-writer.md` Must Not
16
+ bullet on metric reachability, Walkthrough 3 per-step
17
+ `## Parent <Type>` annotations.
18
+
19
+ **v0.10.0** — Adds the **`validate` command** — checks
20
+ `.ai/project/` tree structural integrity (every artifact carries
21
+ its required `## Parent <Type>` section; every cited parent path
22
+ resolves on disk; Status values are valid). Reports errors and
23
+ warnings; exits 0 on clean / 1 on errors; `--json` for machine
24
+ output. **First `src/**` feature shipped under the v0.6.0+
25
+ PRD → Feature → Spec → Plan → Task → TDD → Implement → Verify
26
+ → Review pipeline; first M4 data point — 2/2 TDD pairs at
27
+ 100% test-first ordering.**
28
+
13
29
  **v0.9.0** — Formalizes the **`## Parent <Type>` traceability
14
30
  convention** across all artifact templates. Every kit artifact
15
31
  (spec / plan / task / review) now carries a structural
@@ -306,19 +322,19 @@ commit → task → plan → spec → feature → PRD
306
322
  Quick lifecycle, with the agent roles invoked at each step:
307
323
 
308
324
  ```bash
309
- # Step 0 — author a PRD (prd-writer)
325
+ # Step 0 — author a PRD (prd-writer) [chain root]
310
326
  # .ai/project/prds/YYYY-MM-DD-<slug>/prd.md
311
327
  # Problem / Target Users / Success Metrics / User Stories /
312
328
  # Out of Scope / Open Questions / Stakeholders
313
329
 
314
330
  # Step 0.5 — slice the PRD into ≥1 features (feature-writer)
315
331
  # .ai/project/features/YYYY-MM-DD-<feature-slug>/feature.md
316
- # Each cites the parent PRD; maps to PRD success metrics.
332
+ # Each feature has ## Parent PRD citing the PRD by path.
317
333
  # Mandatory whenever Step 0 ran.
318
334
 
319
335
  # Step 1 — draft an engineering spec per feature (spec-writer)
320
336
  # .ai/project/specs/YYYY-MM-DD-<feature-slug>/spec.md
321
- # §1 Goal cites the parent feature path.
337
+ # Spec has ## Parent Feature citing the feature.
322
338
  # §2 Scope enumerates every touched runtime/** path (preflight).
323
339
 
324
340
  # Step 1.5 — TDD phase (tdd-writer, per applicable task)
@@ -326,8 +342,10 @@ Quick lifecycle, with the agent roles invoked at each step:
326
342
  # before the implementation commit.
327
343
 
328
344
  # Step 2 — plan, tasks, implementation (planner + executor)
329
- # .ai/project/plans/...
330
- # .ai/project/tasks/... (each task carries TDD-Applies)
345
+ # .ai/project/plans/... plan has ## Parent Spec.
346
+ # .ai/project/tasks/... each task has
347
+ # ## Parent Spec + ## Parent Plan +
348
+ # ## TDD-Applies.
331
349
  # Implementation lands on a chore/runtime-<slug> branch if
332
350
  # the spec touches runtime/**; otherwise a feature branch.
333
351
 
@@ -336,9 +354,11 @@ Quick lifecycle, with the agent roles invoked at each step:
336
354
 
337
355
  # Step 4 — review (reviewer)
338
356
  # .ai/project/reviews/YYYY-MM-DD-<slug>.md
357
+ # Review has ## Parent Spec.
339
358
  # Maps PRD success metrics to delivered artifacts.
340
359
 
341
- # Then: merge, push, tag, optionally npm publish.
360
+ # Then: ai-runtime-kit validate (audit the chain),
361
+ # merge, push, tag, optionally npm publish.
342
362
  ```
343
363
 
344
364
  For each step, the corresponding kit-shipped agent role file
package/bin/cli.js CHANGED
@@ -10,6 +10,7 @@ Usage: ai-runtime-kit <command> [options]
10
10
  Commands:
11
11
  init Initialize .ai/ runtime in the current directory.
12
12
  upgrade Upgrade .ai/runtime/ to the kit's current version.
13
+ validate Check .ai/project/ tree structural integrity.
13
14
  --help, -h Show this help.
14
15
  --version Show kit version.
15
16
 
@@ -38,6 +39,9 @@ async function main() {
38
39
  case 'upgrade':
39
40
  await require('../src/upgrade').run(rest);
40
41
  return;
42
+ case 'validate':
43
+ require('../src/validate').run(rest);
44
+ return;
41
45
  default:
42
46
  console.error(`ai-runtime-kit: unknown command '${cmd}'`);
43
47
  console.error('');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-runtime-kit",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
4
4
  "description": "Reusable AI engineering runtime: BOOTSTRAP, workflows, safety, hooks, and templates for software projects driven by AI agents.",
5
5
  "bin": {
6
6
  "ai-runtime-kit": "./bin/cli.js"
@@ -51,6 +51,8 @@ One file at `.ai/project/specs/YYYY-MM-DD-<slug>/spec.md`.
51
51
  - Lock hard byte-budgets on new template/role files without
52
52
  prototyping — use ranges or revisit during impl
53
53
  (drift hit v0.5.1 + v0.7.0).
54
+ - Approve specs whose acceptance asserts metrics on state
55
+ not yet in place (drift hit v0.5.1, v0.7.0, v0.10.0).
54
56
 
55
57
  ---
56
58
 
@@ -16,10 +16,12 @@ Describe the specific engineering goal.
16
16
  ## Parent Plan
17
17
 
18
18
  `.ai/project/plans/YYYY-MM-DD-<slug>/plan.md`
19
- <!-- Required. Single path. For hot-fix tasks created outside
20
- the normal plan-first workflow, render
21
- `(none direct task)`. See .ai/runtime/INDEX.md
22
- § Traceability for the full chain. -->
19
+ <!-- Required. Single path.
20
+ For hot-fix tasks created outside the normal plan-first
21
+ workflow, render literally:
22
+ (none direct task)
23
+ See .ai/runtime/INDEX.md § Traceability for the full
24
+ chain and the (none — <reason>) rendering convention. -->
23
25
 
24
26
  ---
25
27
 
@@ -119,6 +119,12 @@ check that the spec covers the feature's requirements without
119
119
  quietly expanding scope and that the feature covers its share of
120
120
  the PRD's metrics.
121
121
 
122
+ The canonical rules for `## Parent <Type>` sections per artifact
123
+ type — including the `(none — <reason>)` rendering convention
124
+ for engineering-only specs, bug-fix specs, and hot-fix tasks —
125
+ live at `.ai/runtime/INDEX.md` § Traceability. Use
126
+ `ai-runtime-kit validate` to verify a project's tree conforms.
127
+
122
128
  Spec must include:
123
129
 
124
130
  - Goal
@@ -0,0 +1,235 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const { parseArgs } = require('node:util');
6
+
7
+ // Per v0.9.0 INDEX § Traceability:
8
+ // PRD — no parent required (chain root)
9
+ // Feature — ## Parent PRD
10
+ // Spec — ## Parent Feature
11
+ // Plan — ## Parent Spec
12
+ // Task — ## Parent Spec + ## Parent Plan
13
+ // Review — ## Parent Spec
14
+ const ARTIFACT_RULES = {
15
+ prds: { dir: 'prds', required: [] },
16
+ features: { dir: 'features', required: ['Parent PRD'] },
17
+ specs: { dir: 'specs', required: ['Parent Feature'] },
18
+ plans: { dir: 'plans', required: ['Parent Spec'] },
19
+ tasks: { dir: 'tasks', required: ['Parent Spec', 'Parent Plan'] },
20
+ reviews: { dir: 'reviews', required: ['Parent Spec'] },
21
+ };
22
+
23
+ const VALID_STATUSES = {
24
+ prds: ['DRAFT', 'APPROVED', 'REJECTED', 'SUPERSEDED'],
25
+ features: ['DRAFT', 'APPROVED', 'REJECTED', 'SUPERSEDED'],
26
+ specs: ['DRAFT', 'APPROVED', 'REJECTED', 'SUPERSEDED'],
27
+ plans: ['PLANNED', 'IN_PROGRESS', 'DONE'],
28
+ tasks: ['TODO', 'IN_PROGRESS', 'IN_REVIEW', 'DONE'],
29
+ };
30
+
31
+ // Files within an artifact dir that are NOT instances of that
32
+ // artifact type. TASK_STATUS.md is a top-level status tracker
33
+ // produced by `init`, not a per-task instance.
34
+ const EXCLUDED_FILENAMES = new Set(['TASK_STATUS.md']);
35
+
36
+ function walkMd(dir) {
37
+ if (!fs.existsSync(dir)) return [];
38
+ const out = [];
39
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
40
+ if (EXCLUDED_FILENAMES.has(entry.name)) continue;
41
+ const p = path.join(dir, entry.name);
42
+ if (entry.isDirectory()) out.push(...walkMd(p));
43
+ else if (entry.isFile() && entry.name.endsWith('.md')) out.push(p);
44
+ }
45
+ return out;
46
+ }
47
+
48
+ function parseSections(content) {
49
+ // Returns { sectionName: bodyString }.
50
+ // Sections are top-level `## ` headings.
51
+ const lines = content.split('\n');
52
+ const sections = {};
53
+ let cur = null;
54
+ let buf = [];
55
+ for (const line of lines) {
56
+ const m = line.match(/^##\s+(.+?)\s*$/);
57
+ if (m) {
58
+ if (cur !== null) sections[cur] = buf.join('\n').trim();
59
+ cur = m[1].trim();
60
+ buf = [];
61
+ } else if (cur !== null) {
62
+ buf.push(line);
63
+ }
64
+ }
65
+ if (cur !== null) sections[cur] = buf.join('\n').trim();
66
+ return sections;
67
+ }
68
+
69
+ function firstValueLine(body) {
70
+ // Returns first non-empty, non-comment line; strips surrounding backticks.
71
+ for (const raw of body.split('\n')) {
72
+ const line = raw.trim();
73
+ if (!line) continue;
74
+ if (line.startsWith('<!--')) continue;
75
+ return line.replace(/^`/, '').replace(/`$/, '');
76
+ }
77
+ return null;
78
+ }
79
+
80
+ function isNoneRendering(value) {
81
+ return value !== null && value.startsWith('(none');
82
+ }
83
+
84
+ function validate(projectRoot, _options = {}) {
85
+ const errors = [];
86
+ const warnings = [];
87
+ const summary = { prds: 0, features: 0, specs: 0, plans: 0, tasks: 0, reviews: 0 };
88
+
89
+ const aiProject = path.join(projectRoot, '.ai', 'project');
90
+ if (!fs.existsSync(aiProject)) {
91
+ return { errors, warnings, summary };
92
+ }
93
+
94
+ for (const [artifact, rule] of Object.entries(ARTIFACT_RULES)) {
95
+ const dir = path.join(aiProject, rule.dir);
96
+ const files = walkMd(dir);
97
+ summary[artifact] = files.length;
98
+
99
+ for (const file of files) {
100
+ const relFile = path.relative(projectRoot, file);
101
+ const content = fs.readFileSync(file, 'utf8');
102
+ const sections = parseSections(content);
103
+
104
+ for (const parentName of rule.required) {
105
+ const sectionBody = sections[parentName];
106
+ if (sectionBody === undefined) {
107
+ errors.push({
108
+ artifact,
109
+ file: relFile,
110
+ type: 'missing-parent',
111
+ message: `Missing required ## ${parentName} section`,
112
+ });
113
+ continue;
114
+ }
115
+ const value = firstValueLine(sectionBody);
116
+ if (value === null) {
117
+ errors.push({
118
+ artifact,
119
+ file: relFile,
120
+ type: 'empty-parent',
121
+ message: `## ${parentName} section is empty`,
122
+ });
123
+ continue;
124
+ }
125
+ if (isNoneRendering(value)) continue;
126
+ const resolved = path.join(projectRoot, value);
127
+ if (!fs.existsSync(resolved)) {
128
+ errors.push({
129
+ artifact,
130
+ file: relFile,
131
+ type: 'unresolved-parent',
132
+ message: `## ${parentName} points to non-existent path: ${value}`,
133
+ });
134
+ }
135
+ }
136
+
137
+ const allowedStatuses = VALID_STATUSES[artifact];
138
+ if (allowedStatuses && sections.Status !== undefined) {
139
+ const statusValue = firstValueLine(sections.Status);
140
+ if (statusValue !== null && !allowedStatuses.includes(statusValue)) {
141
+ warnings.push({
142
+ artifact,
143
+ file: relFile,
144
+ type: 'unexpected-status',
145
+ message: `Status "${statusValue}" not in allowed values: ${allowedStatuses.join(', ')}`,
146
+ });
147
+ }
148
+ }
149
+ }
150
+ }
151
+
152
+ return { errors, warnings, summary };
153
+ }
154
+
155
+ function run(argv) {
156
+ let parsed;
157
+ try {
158
+ parsed = parseArgs({
159
+ args: argv,
160
+ options: {
161
+ cwd: { type: 'string' },
162
+ json: { type: 'boolean', default: false },
163
+ help: { type: 'boolean', short: 'h', default: false },
164
+ },
165
+ strict: true,
166
+ allowPositionals: false,
167
+ });
168
+ } catch (e) {
169
+ console.error(`validate: ${e.message}`);
170
+ process.exit(1);
171
+ }
172
+
173
+ if (parsed.values.help) {
174
+ printHelp();
175
+ return;
176
+ }
177
+
178
+ const cwd = path.resolve(parsed.values.cwd ?? process.cwd());
179
+ const result = validate(cwd);
180
+ const status = result.errors.length === 0 ? 'PASS' : 'FAIL';
181
+
182
+ if (parsed.values.json) {
183
+ console.log(JSON.stringify({ ...result, result: status }, null, 2));
184
+ } else {
185
+ console.log(`Validating .ai/project/ at ${cwd}\n`);
186
+ for (const [name, count] of Object.entries(result.summary)) {
187
+ console.log(` ${name.padEnd(10)} ${count}`);
188
+ }
189
+ console.log('');
190
+ if (result.errors.length > 0) {
191
+ console.log(`Errors: ${result.errors.length}`);
192
+ for (const e of result.errors) {
193
+ console.log(` - ${e.file}: ${e.type}: ${e.message}`);
194
+ }
195
+ } else {
196
+ console.log('Errors: none');
197
+ }
198
+ if (result.warnings.length > 0) {
199
+ console.log(`Warnings: ${result.warnings.length}`);
200
+ for (const w of result.warnings) {
201
+ console.log(` - ${w.file}: ${w.type}: ${w.message}`);
202
+ }
203
+ } else {
204
+ console.log('Warnings: none');
205
+ }
206
+ console.log('');
207
+ console.log(
208
+ `Result: ${status === 'PASS' ? 'PASS (clean tree)' : `FAIL (${result.errors.length} errors)`}`,
209
+ );
210
+ }
211
+
212
+ process.exit(result.errors.length === 0 ? 0 : 1);
213
+ }
214
+
215
+ function printHelp() {
216
+ console.log(`ai-runtime-kit validate [options]
217
+
218
+ Validate the .ai/project/ tree's structural integrity. Walks
219
+ every artifact and checks that:
220
+
221
+ - Required ## Parent <Type> sections are present per the
222
+ v0.9.0 INDEX § Traceability rules.
223
+ - Cited parent paths resolve to real files on disk.
224
+ - Status values are in the allowed set per artifact type.
225
+
226
+ Options:
227
+ --cwd <dir> Target project root (default: process.cwd())
228
+ --json Output structured JSON instead of human text
229
+ -h, --help Show this help
230
+
231
+ Exits 0 on no errors (warnings allowed and printed); 1 on any
232
+ error. See .ai/runtime/INDEX.md § Traceability for the rules.`);
233
+ }
234
+
235
+ module.exports = { validate, run, printHelp };