kspec 1.0.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/README.md +93 -0
- package/bin/kspec.js +3 -0
- package/package.json +36 -0
- package/src/index.js +677 -0
package/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# kspec — Spec-Driven Development for Kiro CLI
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/kspec-cli)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
Spec-driven development workflow for Kiro CLI with verification at every step.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install -g kspec-cli
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
kspec init # Interactive setup
|
|
18
|
+
kspec analyse # Analyse codebase
|
|
19
|
+
kspec spec "User Auth API" # Create specification
|
|
20
|
+
kspec verify-spec # Verify spec is complete
|
|
21
|
+
kspec tasks # Generate tasks
|
|
22
|
+
kspec verify-tasks # Verify tasks cover spec
|
|
23
|
+
kspec build # Execute with TDD
|
|
24
|
+
kspec verify # Verify implementation
|
|
25
|
+
kspec done # Complete & harvest memory
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Workflow
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
init → analyse → spec → verify-spec → tasks → verify-tasks → build → verify → done
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Commands
|
|
35
|
+
|
|
36
|
+
| Command | Description |
|
|
37
|
+
|---------|-------------|
|
|
38
|
+
| `kspec init` | Interactive setup (date format, execution mode) |
|
|
39
|
+
| `kspec analyse` | Analyse codebase, update steering docs |
|
|
40
|
+
| `kspec spec "Name"` | Create spec.md + spec-lite.md |
|
|
41
|
+
| `kspec verify-spec` | Verify spec covers requirements |
|
|
42
|
+
| `kspec tasks` | Generate tasks.md from spec |
|
|
43
|
+
| `kspec verify-tasks` | Verify tasks cover spec |
|
|
44
|
+
| `kspec build` | Execute tasks with TDD |
|
|
45
|
+
| `kspec verify` | Verify implementation matches spec |
|
|
46
|
+
| `kspec done` | Complete spec, harvest memory |
|
|
47
|
+
| `kspec review` | Code review |
|
|
48
|
+
| `kspec list` | List all specs |
|
|
49
|
+
| `kspec status` | Current status |
|
|
50
|
+
|
|
51
|
+
## Agents & Shortcuts
|
|
52
|
+
|
|
53
|
+
| Agent | Shortcut | Purpose |
|
|
54
|
+
|-------|----------|---------|
|
|
55
|
+
| kspec-analyse | Ctrl+A | Analyse codebase |
|
|
56
|
+
| kspec-spec | Ctrl+S | Create specifications |
|
|
57
|
+
| kspec-tasks | Ctrl+T | Generate tasks |
|
|
58
|
+
| kspec-build | Ctrl+B | Execute with TDD |
|
|
59
|
+
| kspec-verify | Ctrl+V | Verify spec/tasks/impl |
|
|
60
|
+
| kspec-review | Ctrl+R | Code review |
|
|
61
|
+
|
|
62
|
+
## Structure
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
.kspec/
|
|
66
|
+
├── config.json # User preferences
|
|
67
|
+
├── memory.md # Project learnings
|
|
68
|
+
└── specs/
|
|
69
|
+
└── 2026-01-22-feature/
|
|
70
|
+
├── spec.md # Full specification
|
|
71
|
+
├── spec-lite.md # Concise (for context compression)
|
|
72
|
+
├── tasks.md # Implementation tasks
|
|
73
|
+
└── memory.md # Feature learnings
|
|
74
|
+
|
|
75
|
+
.kiro/
|
|
76
|
+
└── steering/ # Project rules (Kiro native)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Configuration
|
|
80
|
+
|
|
81
|
+
Set during `kspec init`:
|
|
82
|
+
|
|
83
|
+
- **Date format**: YYYY-MM-DD, DD-MM-YYYY, or MM-DD-YYYY
|
|
84
|
+
- **Auto-execute**: ask (default), auto, or dry-run
|
|
85
|
+
|
|
86
|
+
## Requirements
|
|
87
|
+
|
|
88
|
+
- Node.js >= 18
|
|
89
|
+
- Kiro CLI or Amazon Q CLI
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
MIT
|
package/bin/kspec.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kspec",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Spec-driven development workflow for Kiro CLI",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"kspec": "bin/kspec.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node --test",
|
|
11
|
+
"lint": "eslint src bin"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"kiro",
|
|
15
|
+
"cli",
|
|
16
|
+
"spec-driven",
|
|
17
|
+
"tdd",
|
|
18
|
+
"development",
|
|
19
|
+
"workflow",
|
|
20
|
+
"ai"
|
|
21
|
+
],
|
|
22
|
+
"author": "Sanjeev Kumar Badrinath",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/sanjeevkumarraob/kspec.git"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18.0.0"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"bin",
|
|
33
|
+
"src",
|
|
34
|
+
"templates"
|
|
35
|
+
]
|
|
36
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { execSync, spawn } = require('child_process');
|
|
4
|
+
const readline = require('readline');
|
|
5
|
+
|
|
6
|
+
const KSPEC_DIR = '.kspec';
|
|
7
|
+
const STEERING_DIR = '.kiro/steering';
|
|
8
|
+
const AGENTS_DIR = '.kiro/agents';
|
|
9
|
+
const CONFIG_FILE = path.join(KSPEC_DIR, 'config.json');
|
|
10
|
+
|
|
11
|
+
// Default config
|
|
12
|
+
const defaultConfig = {
|
|
13
|
+
dateFormat: 'YYYY-MM-DD',
|
|
14
|
+
autoExecute: 'ask',
|
|
15
|
+
initialized: false
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function loadConfig() {
|
|
19
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
20
|
+
return { ...defaultConfig, ...JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')) };
|
|
21
|
+
}
|
|
22
|
+
return defaultConfig;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function saveConfig(cfg) {
|
|
26
|
+
ensureDir(KSPEC_DIR);
|
|
27
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const config = loadConfig();
|
|
31
|
+
|
|
32
|
+
// Helpers
|
|
33
|
+
function log(msg) { console.log(`[kspec] ${msg}`); }
|
|
34
|
+
function die(msg) { console.error(`Error: ${msg}`); process.exit(1); }
|
|
35
|
+
function ensureDir(dir) { if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); }
|
|
36
|
+
|
|
37
|
+
function formatDate(format) {
|
|
38
|
+
const d = new Date();
|
|
39
|
+
const pad = n => n.toString().padStart(2, '0');
|
|
40
|
+
const parts = { YYYY: d.getFullYear(), MM: pad(d.getMonth() + 1), DD: pad(d.getDate()) };
|
|
41
|
+
return format.replace(/YYYY|MM|DD/g, m => parts[m]);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function slugify(text) {
|
|
45
|
+
return text.slice(0, 50).toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '').replace(/^-+|-+$/g, '');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function detectCli() {
|
|
49
|
+
try { execSync('kiro-cli --version', { stdio: 'ignore' }); return 'kiro-cli'; } catch {}
|
|
50
|
+
try { execSync('q --version', { stdio: 'ignore' }); return 'q'; } catch {}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function requireCli() {
|
|
55
|
+
const cli = requireCli();
|
|
56
|
+
if (!cli) die("Neither 'kiro-cli' nor 'q' found. Install Kiro CLI first.");
|
|
57
|
+
return cli;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function prompt(question, choices) {
|
|
61
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
62
|
+
|
|
63
|
+
return new Promise(resolve => {
|
|
64
|
+
if (choices) {
|
|
65
|
+
console.log(`\n${question}`);
|
|
66
|
+
choices.forEach((c, i) => console.log(` ${i + 1}) ${c.label}`));
|
|
67
|
+
rl.question('\nChoice: ', answer => {
|
|
68
|
+
rl.close();
|
|
69
|
+
const idx = parseInt(answer) - 1;
|
|
70
|
+
resolve(choices[idx]?.value || choices[0].value);
|
|
71
|
+
});
|
|
72
|
+
} else {
|
|
73
|
+
rl.question(question, answer => { rl.close(); resolve(answer); });
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function confirm(question) {
|
|
79
|
+
const answer = await prompt(`${question} (Y/n): `);
|
|
80
|
+
return !answer || answer.toLowerCase() === 'y';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function chat(message, agent) {
|
|
84
|
+
const cli = requireCli();
|
|
85
|
+
const args = agent ? ['chat', '--agent', agent, message] : ['chat', message];
|
|
86
|
+
const child = spawn(cli, args, { stdio: 'inherit', shell: true });
|
|
87
|
+
return new Promise(resolve => child.on('close', resolve));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Spec management
|
|
91
|
+
function getSpecsDir() { return path.join(KSPEC_DIR, 'specs'); }
|
|
92
|
+
|
|
93
|
+
function getCurrentSpec() {
|
|
94
|
+
const file = path.join(KSPEC_DIR, '.current');
|
|
95
|
+
if (fs.existsSync(file)) {
|
|
96
|
+
const spec = fs.readFileSync(file, 'utf8').trim();
|
|
97
|
+
if (fs.existsSync(spec)) return spec;
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function setCurrentSpec(folder) {
|
|
103
|
+
ensureDir(KSPEC_DIR);
|
|
104
|
+
fs.writeFileSync(path.join(KSPEC_DIR, '.current'), folder);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function findSpec(name) {
|
|
108
|
+
const specsDir = getSpecsDir();
|
|
109
|
+
if (!fs.existsSync(specsDir)) return null;
|
|
110
|
+
|
|
111
|
+
const slug = slugify(name);
|
|
112
|
+
const matches = fs.readdirSync(specsDir)
|
|
113
|
+
.filter(d => d.includes(slug) && fs.statSync(path.join(specsDir, d)).isDirectory())
|
|
114
|
+
.sort().reverse();
|
|
115
|
+
|
|
116
|
+
if (matches.length === 1) return path.join(specsDir, matches[0]);
|
|
117
|
+
if (matches.length > 1) {
|
|
118
|
+
console.log('Multiple matches:');
|
|
119
|
+
matches.forEach((m, i) => console.log(` ${i + 1}) ${m}`));
|
|
120
|
+
die('Be more specific.');
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function getOrSelectSpec(name) {
|
|
126
|
+
if (name) {
|
|
127
|
+
const found = findSpec(name);
|
|
128
|
+
if (found) return found;
|
|
129
|
+
die(`Spec "${name}" not found.`);
|
|
130
|
+
}
|
|
131
|
+
const current = getCurrentSpec();
|
|
132
|
+
if (current) return current;
|
|
133
|
+
die('No current spec. Run: kspec spec "Feature Name"');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function getTaskStats(folder) {
|
|
137
|
+
const tasksFile = path.join(folder, 'tasks.md');
|
|
138
|
+
if (!fs.existsSync(tasksFile)) return null;
|
|
139
|
+
|
|
140
|
+
const content = fs.readFileSync(tasksFile, 'utf8');
|
|
141
|
+
const total = (content.match(/^-\s*\[[ x]\]/gm) || []).length;
|
|
142
|
+
const done = (content.match(/^-\s*\[x\]/gim) || []).length;
|
|
143
|
+
return { total, done, remaining: total - done };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Templates
|
|
147
|
+
const steeringTemplates = {
|
|
148
|
+
'product.md': `# Product Overview
|
|
149
|
+
|
|
150
|
+
## Purpose
|
|
151
|
+
[Define your product's purpose and target users]
|
|
152
|
+
|
|
153
|
+
## Key Features
|
|
154
|
+
[List main features and capabilities]
|
|
155
|
+
|
|
156
|
+
## Success Metrics
|
|
157
|
+
[How success is measured]`,
|
|
158
|
+
|
|
159
|
+
'tech.md': `# Technology Stack
|
|
160
|
+
|
|
161
|
+
## Languages & Runtime
|
|
162
|
+
[Primary language and version]
|
|
163
|
+
|
|
164
|
+
## Frameworks
|
|
165
|
+
[Web framework, testing framework, key libraries]
|
|
166
|
+
|
|
167
|
+
## Tools
|
|
168
|
+
[Build tools, package managers, linters]`,
|
|
169
|
+
|
|
170
|
+
'testing.md': `# Testing Standards
|
|
171
|
+
|
|
172
|
+
## Approach
|
|
173
|
+
TDD: Red → Green → Refactor
|
|
174
|
+
|
|
175
|
+
## Test Types
|
|
176
|
+
- Unit: Individual functions
|
|
177
|
+
- Integration: Component interactions
|
|
178
|
+
- E2E: User flows
|
|
179
|
+
|
|
180
|
+
## Coverage
|
|
181
|
+
[Minimum thresholds]`
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const agentTemplates = {
|
|
185
|
+
'kspec-analyse.json': {
|
|
186
|
+
name: 'kspec-analyse',
|
|
187
|
+
description: 'Analyse codebase and update steering docs',
|
|
188
|
+
prompt: `You are the kspec analyser. Your job:
|
|
189
|
+
1. Analyse the codebase structure, tech stack, patterns
|
|
190
|
+
2. Review .kiro/steering/ docs
|
|
191
|
+
3. Suggest updates to steering based on actual codebase
|
|
192
|
+
4. Identify risks, tech debt, improvement areas
|
|
193
|
+
|
|
194
|
+
Output a clear analysis report. Propose specific steering doc updates.`,
|
|
195
|
+
allowedTools: ['read', 'write'],
|
|
196
|
+
keyboardShortcut: 'ctrl+a',
|
|
197
|
+
welcomeMessage: 'Analysing codebase...',
|
|
198
|
+
toolsSettings: {
|
|
199
|
+
read: { allowedPaths: ['./**'] },
|
|
200
|
+
write: { allowedPaths: ['.kiro/steering/**'] }
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
'kspec-spec.json': {
|
|
205
|
+
name: 'kspec-spec',
|
|
206
|
+
description: 'Create feature specifications',
|
|
207
|
+
prompt: `You are the kspec specification writer. Your job:
|
|
208
|
+
1. Read .kiro/steering/ for project context
|
|
209
|
+
2. Create a comprehensive spec.md with:
|
|
210
|
+
- Problem/Context
|
|
211
|
+
- Requirements (functional + non-functional)
|
|
212
|
+
- Constraints
|
|
213
|
+
- High-level design
|
|
214
|
+
- Acceptance criteria
|
|
215
|
+
3. IMMEDIATELY after spec.md, create spec-lite.md:
|
|
216
|
+
- Concise version (under 500 words)
|
|
217
|
+
- Key requirements only
|
|
218
|
+
- Used for context after compression
|
|
219
|
+
|
|
220
|
+
Always create both files. spec-lite.md is critical for context retention.`,
|
|
221
|
+
allowedTools: ['read', 'write'],
|
|
222
|
+
keyboardShortcut: 'ctrl+s',
|
|
223
|
+
welcomeMessage: 'Ready to create specification.',
|
|
224
|
+
toolsSettings: {
|
|
225
|
+
read: { allowedPaths: ['./**'] },
|
|
226
|
+
write: { allowedPaths: ['.kspec/specs/**'] }
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
'kspec-tasks.json': {
|
|
231
|
+
name: 'kspec-tasks',
|
|
232
|
+
description: 'Generate implementation tasks from spec',
|
|
233
|
+
prompt: `You are the kspec task generator. Your job:
|
|
234
|
+
1. Read spec.md and spec-lite.md from the spec folder
|
|
235
|
+
2. Generate tasks.md with:
|
|
236
|
+
- Checkbox format: "- [ ] Task description"
|
|
237
|
+
- TDD approach: test first, then implement
|
|
238
|
+
- Logical ordering (models → services → API → UI)
|
|
239
|
+
- Dependencies noted
|
|
240
|
+
- File paths where changes occur
|
|
241
|
+
|
|
242
|
+
Tasks must be atomic and independently verifiable.`,
|
|
243
|
+
allowedTools: ['read', 'write'],
|
|
244
|
+
keyboardShortcut: 'ctrl+t',
|
|
245
|
+
welcomeMessage: 'Generating tasks from spec...',
|
|
246
|
+
toolsSettings: {
|
|
247
|
+
read: { allowedPaths: ['./**'] },
|
|
248
|
+
write: { allowedPaths: ['.kspec/specs/**'] }
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
'kspec-build.json': {
|
|
253
|
+
name: 'kspec-build',
|
|
254
|
+
description: 'Execute tasks with TDD',
|
|
255
|
+
prompt: `You are the kspec builder. Your job:
|
|
256
|
+
1. Read tasks.md, find first uncompleted task (- [ ])
|
|
257
|
+
2. For each task:
|
|
258
|
+
a) Write test first (TDD)
|
|
259
|
+
b) Implement minimal code to pass
|
|
260
|
+
c) Run tests
|
|
261
|
+
d) Mark task complete: change "- [ ]" to "- [x]"
|
|
262
|
+
e) Update tasks.md file
|
|
263
|
+
3. Commit after each task
|
|
264
|
+
|
|
265
|
+
CRITICAL: Always update tasks.md after completing each task.
|
|
266
|
+
NEVER delete .kiro or .kspec folders.
|
|
267
|
+
Use non-interactive flags for commands (--yes, -y).`,
|
|
268
|
+
allowedTools: ['read', 'write', 'shell'],
|
|
269
|
+
keyboardShortcut: 'ctrl+b',
|
|
270
|
+
welcomeMessage: 'Building from tasks...',
|
|
271
|
+
toolsSettings: {
|
|
272
|
+
read: { allowedPaths: ['./**'] },
|
|
273
|
+
write: { allowedPaths: ['./**'] },
|
|
274
|
+
shell: { autoAllowReadonly: true }
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
'kspec-verify.json': {
|
|
279
|
+
name: 'kspec-verify',
|
|
280
|
+
description: 'Verify spec, tasks, or implementation',
|
|
281
|
+
prompt: `You are the kspec verifier. Based on what you're asked to verify:
|
|
282
|
+
|
|
283
|
+
VERIFY-SPEC:
|
|
284
|
+
- Check spec covers all requirements
|
|
285
|
+
- Identify gaps or ambiguities
|
|
286
|
+
- Suggest splitting large requirements
|
|
287
|
+
- Confirm implementability with current codebase
|
|
288
|
+
|
|
289
|
+
VERIFY-TASKS:
|
|
290
|
+
- Check tasks cover all spec requirements
|
|
291
|
+
- Verify task completion status
|
|
292
|
+
- Check test coverage for completed tasks
|
|
293
|
+
- Report: X/Y tasks done, coverage %
|
|
294
|
+
|
|
295
|
+
VERIFY-IMPLEMENTATION:
|
|
296
|
+
- Check implementation matches spec requirements
|
|
297
|
+
- Check all tasks marked complete
|
|
298
|
+
- Run tests, report results
|
|
299
|
+
- List any gaps between spec and implementation
|
|
300
|
+
|
|
301
|
+
Output a clear verification report with pass/fail status.`,
|
|
302
|
+
allowedTools: ['read', 'shell'],
|
|
303
|
+
keyboardShortcut: 'ctrl+v',
|
|
304
|
+
welcomeMessage: 'What should I verify?',
|
|
305
|
+
toolsSettings: {
|
|
306
|
+
read: { allowedPaths: ['./**'] },
|
|
307
|
+
shell: { autoAllowReadonly: true }
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
|
|
311
|
+
'kspec-review.json': {
|
|
312
|
+
name: 'kspec-review',
|
|
313
|
+
description: 'Code review',
|
|
314
|
+
prompt: `You are the kspec code reviewer. Your job:
|
|
315
|
+
1. Review code changes (git diff or specified files)
|
|
316
|
+
2. Check compliance with .kiro/steering/
|
|
317
|
+
3. Evaluate:
|
|
318
|
+
- Code quality and readability
|
|
319
|
+
- Test coverage
|
|
320
|
+
- Security concerns
|
|
321
|
+
- Performance implications
|
|
322
|
+
4. Provide actionable feedback
|
|
323
|
+
|
|
324
|
+
Output: APPROVE / REQUEST_CHANGES with specific issues.`,
|
|
325
|
+
allowedTools: ['read', 'shell'],
|
|
326
|
+
keyboardShortcut: 'ctrl+r',
|
|
327
|
+
welcomeMessage: 'Ready to review. What should I look at?',
|
|
328
|
+
toolsSettings: {
|
|
329
|
+
read: { allowedPaths: ['./**'] },
|
|
330
|
+
shell: { allowedCommands: ['git diff*', 'git log*', 'git status*', 'git show*'], autoAllowReadonly: true }
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
// Commands
|
|
336
|
+
const commands = {
|
|
337
|
+
async init() {
|
|
338
|
+
console.log('\n🚀 Welcome to kspec!\n');
|
|
339
|
+
|
|
340
|
+
const dateFormat = await prompt('Date format for spec folders:', [
|
|
341
|
+
{ label: 'YYYY-MM-DD (2026-01-22) - sorts chronologically', value: 'YYYY-MM-DD' },
|
|
342
|
+
{ label: 'DD-MM-YYYY (22-01-2026)', value: 'DD-MM-YYYY' },
|
|
343
|
+
{ label: 'MM-DD-YYYY (01-22-2026)', value: 'MM-DD-YYYY' }
|
|
344
|
+
]);
|
|
345
|
+
|
|
346
|
+
const autoExecute = await prompt('Command execution during build:', [
|
|
347
|
+
{ label: 'Ask for permission (recommended)', value: 'ask' },
|
|
348
|
+
{ label: 'Auto-execute (faster)', value: 'auto' },
|
|
349
|
+
{ label: 'Dry-run only (show, don\'t run)', value: 'dry' }
|
|
350
|
+
]);
|
|
351
|
+
|
|
352
|
+
const createSteering = await confirm('Create steering doc templates?');
|
|
353
|
+
|
|
354
|
+
// Save config
|
|
355
|
+
const cfg = { dateFormat, autoExecute, initialized: true };
|
|
356
|
+
saveConfig(cfg);
|
|
357
|
+
Object.assign(config, cfg);
|
|
358
|
+
|
|
359
|
+
// Create directories
|
|
360
|
+
ensureDir(path.join(KSPEC_DIR, 'specs'));
|
|
361
|
+
ensureDir(AGENTS_DIR);
|
|
362
|
+
|
|
363
|
+
// Create steering templates
|
|
364
|
+
if (createSteering) {
|
|
365
|
+
ensureDir(STEERING_DIR);
|
|
366
|
+
for (const [file, content] of Object.entries(steeringTemplates)) {
|
|
367
|
+
const p = path.join(STEERING_DIR, file);
|
|
368
|
+
if (!fs.existsSync(p)) {
|
|
369
|
+
fs.writeFileSync(p, content);
|
|
370
|
+
log(`Created ${p}`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Create agents
|
|
376
|
+
for (const [file, content] of Object.entries(agentTemplates)) {
|
|
377
|
+
const p = path.join(AGENTS_DIR, file);
|
|
378
|
+
fs.writeFileSync(p, JSON.stringify(content, null, 2));
|
|
379
|
+
log(`Created ${p}`);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
console.log('\n✅ kspec initialized!\n');
|
|
383
|
+
console.log('Next: kspec analyse');
|
|
384
|
+
},
|
|
385
|
+
|
|
386
|
+
async analyse() {
|
|
387
|
+
log('Analysing codebase...');
|
|
388
|
+
await chat(`Analyse this codebase:
|
|
389
|
+
1. Identify tech stack, architecture, patterns
|
|
390
|
+
2. Review existing .kiro/steering/ docs (if any)
|
|
391
|
+
3. Suggest updates to steering docs
|
|
392
|
+
4. Identify risks and improvement areas
|
|
393
|
+
|
|
394
|
+
Update steering docs as needed.`, 'kspec-analyse');
|
|
395
|
+
},
|
|
396
|
+
|
|
397
|
+
async spec(args) {
|
|
398
|
+
const feature = args.join(' ');
|
|
399
|
+
if (!feature) die('Usage: kspec spec "Feature Name"');
|
|
400
|
+
|
|
401
|
+
const date = formatDate(config.dateFormat || 'YYYY-MM-DD');
|
|
402
|
+
const folder = path.join(getSpecsDir(), `${date}-${slugify(feature)}`);
|
|
403
|
+
ensureDir(folder);
|
|
404
|
+
setCurrentSpec(folder);
|
|
405
|
+
|
|
406
|
+
log(`Spec folder: ${folder}`);
|
|
407
|
+
await chat(`Create specification for: ${feature}
|
|
408
|
+
|
|
409
|
+
Folder: ${folder}
|
|
410
|
+
|
|
411
|
+
1. Read .kiro/steering/ for context
|
|
412
|
+
2. Create ${folder}/spec.md with full specification
|
|
413
|
+
3. IMMEDIATELY create ${folder}/spec-lite.md (concise version, <500 words)
|
|
414
|
+
|
|
415
|
+
spec-lite.md is critical - it's loaded after context compression.`, 'kspec-spec');
|
|
416
|
+
},
|
|
417
|
+
|
|
418
|
+
async 'verify-spec'(args) {
|
|
419
|
+
const folder = getOrSelectSpec(args.join(' '));
|
|
420
|
+
log(`Verifying spec: ${folder}`);
|
|
421
|
+
|
|
422
|
+
await chat(`Verify the specification in ${folder}/spec.md:
|
|
423
|
+
|
|
424
|
+
1. Does it cover all requirements clearly?
|
|
425
|
+
2. Are there gaps or ambiguities?
|
|
426
|
+
3. Should large requirements be split into smaller chunks?
|
|
427
|
+
4. Is it implementable with the current codebase?
|
|
428
|
+
|
|
429
|
+
Read the codebase to check implementability.
|
|
430
|
+
Report: PASS/FAIL with specific issues.`, 'kspec-verify');
|
|
431
|
+
},
|
|
432
|
+
|
|
433
|
+
async tasks(args) {
|
|
434
|
+
const folder = getOrSelectSpec(args.join(' '));
|
|
435
|
+
log(`Generating tasks: ${folder}`);
|
|
436
|
+
|
|
437
|
+
await chat(`Generate tasks from specification.
|
|
438
|
+
|
|
439
|
+
Spec folder: ${folder}
|
|
440
|
+
Read: ${folder}/spec.md and ${folder}/spec-lite.md
|
|
441
|
+
|
|
442
|
+
Create ${folder}/tasks.md with:
|
|
443
|
+
- Checkbox format: "- [ ] Task description"
|
|
444
|
+
- TDD approach (test first)
|
|
445
|
+
- Logical order
|
|
446
|
+
- File paths for each task`, 'kspec-tasks');
|
|
447
|
+
},
|
|
448
|
+
|
|
449
|
+
async 'verify-tasks'(args) {
|
|
450
|
+
const folder = getOrSelectSpec(args.join(' '));
|
|
451
|
+
const stats = getTaskStats(folder);
|
|
452
|
+
|
|
453
|
+
log(`Verifying tasks: ${folder}`);
|
|
454
|
+
if (stats) log(`Progress: ${stats.done}/${stats.total} tasks completed`);
|
|
455
|
+
|
|
456
|
+
await chat(`Verify tasks in ${folder}/tasks.md:
|
|
457
|
+
|
|
458
|
+
1. Do tasks cover ALL requirements from spec.md?
|
|
459
|
+
2. Check completion status of each task
|
|
460
|
+
3. For completed tasks, verify test coverage
|
|
461
|
+
4. Identify any missing tasks
|
|
462
|
+
|
|
463
|
+
Report: X/Y tasks done, gaps found, coverage assessment.`, 'kspec-verify');
|
|
464
|
+
},
|
|
465
|
+
|
|
466
|
+
async build(args) {
|
|
467
|
+
const folder = getOrSelectSpec(args.join(' '));
|
|
468
|
+
const stats = getTaskStats(folder);
|
|
469
|
+
|
|
470
|
+
log(`Building: ${folder}`);
|
|
471
|
+
if (stats) {
|
|
472
|
+
if (stats.remaining === 0) {
|
|
473
|
+
log('All tasks completed! Run: kspec verify');
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
log(`Progress: ${stats.done}/${stats.total} (${stats.remaining} remaining)`);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const execMode = config.autoExecute || 'ask';
|
|
480
|
+
const execNote = execMode === 'auto' ? 'Auto-execute enabled.' :
|
|
481
|
+
execMode === 'dry' ? 'Dry-run mode - show commands only.' :
|
|
482
|
+
'Ask before executing commands.';
|
|
483
|
+
|
|
484
|
+
await chat(`Execute tasks from ${folder}/tasks.md
|
|
485
|
+
|
|
486
|
+
${execNote}
|
|
487
|
+
|
|
488
|
+
1. Find first uncompleted task (- [ ])
|
|
489
|
+
2. Write test first (TDD)
|
|
490
|
+
3. Implement to pass test
|
|
491
|
+
4. Run tests
|
|
492
|
+
5. Mark complete: change "- [ ]" to "- [x]" in tasks.md
|
|
493
|
+
6. Save tasks.md after each completion
|
|
494
|
+
7. Continue to next task
|
|
495
|
+
|
|
496
|
+
CRITICAL: Update tasks.md after each task completion.
|
|
497
|
+
NEVER delete .kiro or .kspec folders.`, 'kspec-build');
|
|
498
|
+
},
|
|
499
|
+
|
|
500
|
+
async verify(args) {
|
|
501
|
+
const folder = getOrSelectSpec(args.join(' '));
|
|
502
|
+
const stats = getTaskStats(folder);
|
|
503
|
+
|
|
504
|
+
log(`Verifying implementation: ${folder}`);
|
|
505
|
+
if (stats) log(`Tasks: ${stats.done}/${stats.total}`);
|
|
506
|
+
|
|
507
|
+
await chat(`Verify implementation for ${folder}:
|
|
508
|
+
|
|
509
|
+
1. Read spec.md - list all requirements
|
|
510
|
+
2. Read tasks.md - check all marked [x]
|
|
511
|
+
3. Check codebase - does implementation match spec?
|
|
512
|
+
4. Run tests - do they pass?
|
|
513
|
+
5. Check coverage - are requirements tested?
|
|
514
|
+
|
|
515
|
+
Report:
|
|
516
|
+
- Requirements: X/Y implemented
|
|
517
|
+
- Tasks: X/Y completed
|
|
518
|
+
- Tests: PASS/FAIL
|
|
519
|
+
- Gaps: [list any]`, 'kspec-verify');
|
|
520
|
+
},
|
|
521
|
+
|
|
522
|
+
async done(args) {
|
|
523
|
+
const folder = getOrSelectSpec(args.join(' '));
|
|
524
|
+
const stats = getTaskStats(folder);
|
|
525
|
+
|
|
526
|
+
if (stats && stats.remaining > 0) {
|
|
527
|
+
const proceed = await confirm(`${stats.remaining} tasks remaining. Mark done anyway?`);
|
|
528
|
+
if (!proceed) return;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
log(`Completing: ${folder}`);
|
|
532
|
+
|
|
533
|
+
// Create memory
|
|
534
|
+
await chat(`Harvest learnings from ${folder}:
|
|
535
|
+
|
|
536
|
+
1. Read spec.md, tasks.md, and implementation
|
|
537
|
+
2. Create ${folder}/memory.md with:
|
|
538
|
+
- Key decisions made
|
|
539
|
+
- Patterns used
|
|
540
|
+
- Lessons learned
|
|
541
|
+
- Follow-ups needed
|
|
542
|
+
|
|
543
|
+
3. Update .kspec/memory.md (project-level) with:
|
|
544
|
+
- New glossary terms
|
|
545
|
+
- Reusable patterns
|
|
546
|
+
- Cross-cutting learnings`, 'kspec-analyse');
|
|
547
|
+
|
|
548
|
+
log('Spec completed!');
|
|
549
|
+
},
|
|
550
|
+
|
|
551
|
+
async review(args) {
|
|
552
|
+
const target = args.join(' ') || 'recent changes (git diff HEAD~1)';
|
|
553
|
+
await chat(`Review: ${target}
|
|
554
|
+
|
|
555
|
+
Check compliance with .kiro/steering/
|
|
556
|
+
Evaluate quality, tests, security.
|
|
557
|
+
Output: APPROVE or REQUEST_CHANGES with specifics.`, 'kspec-review');
|
|
558
|
+
},
|
|
559
|
+
|
|
560
|
+
list() {
|
|
561
|
+
const specsDir = getSpecsDir();
|
|
562
|
+
if (!fs.existsSync(specsDir)) {
|
|
563
|
+
console.log('No specs yet. Run: kspec spec "Feature Name"');
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const current = getCurrentSpec();
|
|
568
|
+
const specs = fs.readdirSync(specsDir)
|
|
569
|
+
.filter(d => fs.statSync(path.join(specsDir, d)).isDirectory())
|
|
570
|
+
.sort().reverse();
|
|
571
|
+
|
|
572
|
+
if (specs.length === 0) {
|
|
573
|
+
console.log('No specs yet. Run: kspec spec "Feature Name"');
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
console.log('\nSpecs:\n');
|
|
578
|
+
specs.forEach(s => {
|
|
579
|
+
const folder = path.join(specsDir, s);
|
|
580
|
+
const isCurrent = folder === current;
|
|
581
|
+
const stats = getTaskStats(folder);
|
|
582
|
+
const progress = stats ? `[${stats.done}/${stats.total}]` : '[no tasks]';
|
|
583
|
+
console.log(` ${isCurrent ? '→' : ' '} ${s} ${progress}`);
|
|
584
|
+
});
|
|
585
|
+
console.log('');
|
|
586
|
+
},
|
|
587
|
+
|
|
588
|
+
status() {
|
|
589
|
+
const current = getCurrentSpec();
|
|
590
|
+
|
|
591
|
+
console.log('\nkspec Status\n');
|
|
592
|
+
console.log(`CLI: ${detectCli() || '(not installed)'}`);
|
|
593
|
+
console.log(`Initialized: ${config.initialized ? 'yes' : 'no'}`);
|
|
594
|
+
console.log(`Date format: ${config.dateFormat || 'YYYY-MM-DD'}`);
|
|
595
|
+
console.log(`Auto-execute: ${config.autoExecute || 'ask'}`);
|
|
596
|
+
|
|
597
|
+
if (current) {
|
|
598
|
+
console.log(`\nCurrent spec: ${path.basename(current)}`);
|
|
599
|
+
const stats = getTaskStats(current);
|
|
600
|
+
if (stats) {
|
|
601
|
+
console.log(`Tasks: ${stats.done}/${stats.total} completed`);
|
|
602
|
+
if (stats.remaining > 0) {
|
|
603
|
+
console.log(`\nNext: kspec build`);
|
|
604
|
+
} else {
|
|
605
|
+
console.log(`\nNext: kspec verify`);
|
|
606
|
+
}
|
|
607
|
+
} else {
|
|
608
|
+
console.log(`\nNext: kspec tasks`);
|
|
609
|
+
}
|
|
610
|
+
} else {
|
|
611
|
+
console.log(`\nNo current spec. Run: kspec spec "Feature Name"`);
|
|
612
|
+
}
|
|
613
|
+
console.log('');
|
|
614
|
+
},
|
|
615
|
+
|
|
616
|
+
agents() {
|
|
617
|
+
console.log(`
|
|
618
|
+
kspec Agents
|
|
619
|
+
|
|
620
|
+
Agent Shortcut Purpose
|
|
621
|
+
─────────────────────────────────────────────
|
|
622
|
+
kspec-analyse Ctrl+A Analyse codebase, update steering
|
|
623
|
+
kspec-spec Ctrl+S Create specifications
|
|
624
|
+
kspec-tasks Ctrl+T Generate tasks from spec
|
|
625
|
+
kspec-build Ctrl+B Execute tasks with TDD
|
|
626
|
+
kspec-verify Ctrl+V Verify spec/tasks/implementation
|
|
627
|
+
kspec-review Ctrl+R Code review
|
|
628
|
+
|
|
629
|
+
Switch: /agent swap or use keyboard shortcuts
|
|
630
|
+
`);
|
|
631
|
+
},
|
|
632
|
+
|
|
633
|
+
help() {
|
|
634
|
+
console.log(`
|
|
635
|
+
kspec - Spec-driven development for Kiro CLI
|
|
636
|
+
|
|
637
|
+
Workflow:
|
|
638
|
+
kspec init Interactive setup
|
|
639
|
+
kspec analyse Analyse codebase, update steering
|
|
640
|
+
kspec spec "Feature" Create specification
|
|
641
|
+
kspec verify-spec Verify spec is complete
|
|
642
|
+
kspec tasks Generate tasks from spec
|
|
643
|
+
kspec verify-tasks Verify tasks cover spec
|
|
644
|
+
kspec build Execute tasks with TDD
|
|
645
|
+
kspec verify Verify implementation
|
|
646
|
+
kspec done Complete spec, harvest memory
|
|
647
|
+
|
|
648
|
+
Other:
|
|
649
|
+
kspec review [target] Code review
|
|
650
|
+
kspec list List all specs
|
|
651
|
+
kspec status Current status
|
|
652
|
+
kspec agents List agents
|
|
653
|
+
kspec help Show this help
|
|
654
|
+
|
|
655
|
+
Examples:
|
|
656
|
+
kspec init
|
|
657
|
+
kspec spec "User Authentication"
|
|
658
|
+
kspec tasks
|
|
659
|
+
kspec build
|
|
660
|
+
kspec verify
|
|
661
|
+
kspec done
|
|
662
|
+
`);
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
async function run(args) {
|
|
667
|
+
const cmd = (args[0] || 'help').replace(/^\//, '');
|
|
668
|
+
const cmdArgs = args.slice(1);
|
|
669
|
+
|
|
670
|
+
if (commands[cmd]) {
|
|
671
|
+
await commands[cmd](cmdArgs);
|
|
672
|
+
} else {
|
|
673
|
+
die(`Unknown command: ${cmd}\nRun 'kspec help' for usage.`);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
module.exports = { run, commands, loadConfig };
|