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 +108 -0
- package/README.md +26 -6
- package/bin/cli.js +4 -0
- package/package.json +1 -1
- package/runtime/agents/spec-writer.md +2 -0
- package/runtime/tasks/_template.md +6 -4
- package/runtime/workflows/feature-development.md +6 -0
- package/src/validate.js +235 -0
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
|
|
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
|
-
#
|
|
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/...
|
|
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:
|
|
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
|
@@ -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.
|
|
20
|
-
the normal plan-first
|
|
21
|
-
|
|
22
|
-
|
|
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
|
package/src/validate.js
ADDED
|
@@ -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 };
|