ai-runtime-kit 0.5.0 → 0.10.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/CHANGELOG.md +462 -0
- package/README.md +114 -0
- package/bin/cli.js +4 -0
- package/package.json +2 -1
- package/runtime/INDEX.md +139 -11
- package/runtime/agents/executor.md +7 -0
- package/runtime/agents/feature-writer.md +57 -0
- package/runtime/agents/planner.md +60 -0
- package/runtime/agents/prd-writer.md +59 -0
- package/runtime/agents/reviewer.md +60 -0
- package/runtime/agents/spec-writer.md +60 -0
- package/runtime/agents/tdd-writer.md +57 -0
- package/runtime/agents/verifier.md +8 -1
- package/runtime/features/_template.md +75 -0
- package/runtime/plans/_template.md +5 -2
- package/runtime/reviews/_template.md +8 -0
- package/runtime/specs/_template/spec.md +9 -0
- package/runtime/specs/_template-bug-fix/spec.md +9 -0
- package/runtime/tasks/_template.md +25 -2
- package/runtime/workflows/feature-development.md +86 -6
- package/src/init.js +1 -0
- package/src/validate.js +235 -0
|
@@ -6,6 +6,15 @@ DRAFT
|
|
|
6
6
|
<!-- Allowed: DRAFT | APPROVED | REJECTED | SUPERSEDED.
|
|
7
7
|
See .ai/runtime/workflows/bug-fix.md for lifecycle rules. -->
|
|
8
8
|
|
|
9
|
+
## Parent Feature
|
|
10
|
+
|
|
11
|
+
(none — bug-fix workflow)
|
|
12
|
+
<!-- Bug-fix workflow skips Step 0.5 (no feature slicing) — the
|
|
13
|
+
fix is corrective, not a sliced PRD feature. Override with
|
|
14
|
+
a real path only if the bug fix happens to be part of a
|
|
15
|
+
larger PRD-driven effort. See .ai/runtime/INDEX.md
|
|
16
|
+
§ Traceability for the full chain. -->
|
|
17
|
+
|
|
9
18
|
## 1. Goal
|
|
10
19
|
|
|
11
20
|
Describe the corrective outcome in one paragraph. Focus on what
|
|
@@ -6,9 +6,20 @@ Describe the specific engineering goal.
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Parent Spec
|
|
10
10
|
|
|
11
|
-
-
|
|
11
|
+
`.ai/project/specs/YYYY-MM-DD-<slug>/spec.md`
|
|
12
|
+
<!-- Required. Single path. -->
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Parent Plan
|
|
17
|
+
|
|
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. -->
|
|
12
23
|
|
|
13
24
|
---
|
|
14
25
|
|
|
@@ -64,6 +75,18 @@ TODO
|
|
|
64
75
|
|
|
65
76
|
executor
|
|
66
77
|
|
|
78
|
+
## TDD-Applies
|
|
79
|
+
|
|
80
|
+
false
|
|
81
|
+
<!-- Allowed: true | false.
|
|
82
|
+
Rule: true if the task introduces or modifies runtime
|
|
83
|
+
behavior (code that runs); false for documentation, refactor
|
|
84
|
+
with no behavior change, or config / packaging / metadata
|
|
85
|
+
only. When true, follow workflow Step 1.5 — a failing-test
|
|
86
|
+
commit must precede the implementation commit. When false,
|
|
87
|
+
state a one-line reason here so the skip is explicit
|
|
88
|
+
rather than implicit. -->
|
|
89
|
+
|
|
67
90
|
## Blocked By
|
|
68
91
|
|
|
69
92
|
None.
|
|
@@ -50,6 +50,12 @@ Metrics, User Stories, Out of Scope, Open Questions,
|
|
|
50
50
|
Stakeholders. Engineering details (architecture, data flow, code
|
|
51
51
|
contracts) belong in the downstream spec, not the PRD.
|
|
52
52
|
|
|
53
|
+
Read `.ai/runtime/agents/prd-writer.md` to anchor the role — it
|
|
54
|
+
scopes the agent's responsibilities, inputs, outputs, and
|
|
55
|
+
constraints. The procedural depth (the 11-step elicit-then-write
|
|
56
|
+
flow) lives in a project-side skill referenced from the agent
|
|
57
|
+
file; concept lattice is **agent = WHO, skill = HOW.**
|
|
58
|
+
|
|
53
59
|
Skip this step when:
|
|
54
60
|
|
|
55
61
|
- the change is corrective (use `bug-fix.md` instead — bug fixes
|
|
@@ -60,9 +66,43 @@ Skip this step when:
|
|
|
60
66
|
intent.
|
|
61
67
|
|
|
62
68
|
PRD lifecycle mirrors specs: `DRAFT → APPROVED → REJECTED →
|
|
63
|
-
SUPERSEDED`. An APPROVED PRD authorizes
|
|
64
|
-
|
|
65
|
-
|
|
69
|
+
SUPERSEDED`. An APPROVED PRD authorizes feature slicing (Step
|
|
70
|
+
0.5).
|
|
71
|
+
|
|
72
|
+
### 0.5. Slice into Features
|
|
73
|
+
|
|
74
|
+
When Step 0 ran (a PRD exists), slice it into **≥1 feature
|
|
75
|
+
docs** under:
|
|
76
|
+
|
|
77
|
+
```txt
|
|
78
|
+
.ai/project/features/YYYY-MM-DD-<slug>/feature.md
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
One feature per discrete capability that can be specced and
|
|
82
|
+
implemented independently. The feature doc answers "what slice
|
|
83
|
+
of the PRD does this satisfy, and what does done look like" —
|
|
84
|
+
mid-level between PRD ("what & why") and spec ("how").
|
|
85
|
+
Engineering details (architecture, contracts, test plan) belong
|
|
86
|
+
in the spec, not the feature.
|
|
87
|
+
|
|
88
|
+
Use `.ai/runtime/features/_template.md` as the starting point.
|
|
89
|
+
Each feature must cite its parent PRD in `## Parent PRD`; the
|
|
90
|
+
PRD's `## Downstream Spec` section lists the features that
|
|
91
|
+
derived from it.
|
|
92
|
+
|
|
93
|
+
**Mandatory whenever Step 0 ran.** Same skip criteria as Step 0:
|
|
94
|
+
bug-fix workflow and engineering-only changes skip Step 0
|
|
95
|
+
entirely, so they skip Step 0.5 too.
|
|
96
|
+
|
|
97
|
+
Single-feature PRDs still produce **one feature doc with the
|
|
98
|
+
full template structure** — sections may contain "see parent
|
|
99
|
+
PRD" pointers when the content is fully captured upstream, but
|
|
100
|
+
no 5-line stub allowance (consistent shape protects
|
|
101
|
+
traceability tooling and audit clarity).
|
|
102
|
+
|
|
103
|
+
Feature lifecycle mirrors specs: `DRAFT → APPROVED → REJECTED →
|
|
104
|
+
SUPERSEDED`. An APPROVED feature authorizes spec drafting for
|
|
105
|
+
that feature.
|
|
66
106
|
|
|
67
107
|
### 1. Define Spec
|
|
68
108
|
|
|
@@ -72,9 +112,12 @@ Create a feature spec under:
|
|
|
72
112
|
.ai/project/specs/YYYY-MM-DD-feature-name/spec.md
|
|
73
113
|
```
|
|
74
114
|
|
|
75
|
-
If the spec is downstream of a
|
|
76
|
-
the
|
|
77
|
-
PRD
|
|
115
|
+
If the spec is downstream of a feature (Step 0.5), §1 Goal must
|
|
116
|
+
cite the feature path (the feature in turn cites its parent
|
|
117
|
+
PRD, so the chain assembles upward). Reviewers walk the chain to
|
|
118
|
+
check that the spec covers the feature's requirements without
|
|
119
|
+
quietly expanding scope and that the feature covers its share of
|
|
120
|
+
the PRD's metrics.
|
|
78
121
|
|
|
79
122
|
Spec must include:
|
|
80
123
|
|
|
@@ -86,8 +129,45 @@ Spec must include:
|
|
|
86
129
|
- Verification Commands
|
|
87
130
|
- Rollback Plan
|
|
88
131
|
|
|
132
|
+
### 1.5. TDD Phase (per task, when applicable)
|
|
133
|
+
|
|
134
|
+
For each task produced under the spec's plan: if the task
|
|
135
|
+
carries `TDD-Applies: true` (see
|
|
136
|
+
`.ai/runtime/tasks/_template.md` § TDD-Applies), a **failing-
|
|
137
|
+
test commit must land before** that task's implementation
|
|
138
|
+
commit. The role file for this step is
|
|
139
|
+
`.ai/runtime/agents/tdd-writer.md`.
|
|
140
|
+
|
|
141
|
+
**Trigger.** Task's `TDD-Applies` value is `true`. The
|
|
142
|
+
boundary follows parent-PRD-style rules: behavior-changing
|
|
143
|
+
tasks → `true`; doc / refactor-with-no-behavior-change /
|
|
144
|
+
config-only → `false`.
|
|
145
|
+
|
|
146
|
+
**Required artifact.** A separate commit whose diff contains
|
|
147
|
+
only the failing test(s) for the task. Verify red (test
|
|
148
|
+
actually fails) before declaring the step done. The
|
|
149
|
+
implementation commit lands on a distinct commit hash with a
|
|
150
|
+
later timestamp, satisfying the test that was just authored.
|
|
151
|
+
|
|
152
|
+
**Skip rule.** Tasks with `TDD-Applies: false` skip this step
|
|
153
|
+
entirely. The skip is explicit on the task itself (the
|
|
154
|
+
`## TDD-Applies` section in the task file states the boolean
|
|
155
|
+
plus a one-line reason); never implicit.
|
|
156
|
+
|
|
157
|
+
**Per-task semantics.** This is not a single workflow-wide
|
|
158
|
+
pause. Step 1.5 runs once *per applicable task*, interleaved
|
|
159
|
+
with that task's implementation. A feature with N
|
|
160
|
+
TDD-applicable tasks fires Step 1.5 N times.
|
|
161
|
+
|
|
89
162
|
### 2. Execute with Claude Code
|
|
90
163
|
|
|
164
|
+
> Step 1.5 is a per-task prerequisite for any task with
|
|
165
|
+
> `TDD-Applies: true`. The execute step below covers plan
|
|
166
|
+
> authoring, task production, implementation, and verification
|
|
167
|
+
> as one umbrella; the TDD discipline runs *inside* that
|
|
168
|
+
> umbrella, per applicable task, before each task's
|
|
169
|
+
> implementation commit.
|
|
170
|
+
|
|
91
171
|
Claude Code must read:
|
|
92
172
|
|
|
93
173
|
- `.ai/runtime/agents/executor.md`
|
package/src/init.js
CHANGED
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 };
|