km-spec-driven-development 0.1.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/LICENSE +25 -0
- package/README.md +282 -0
- package/bin/km-spec +995 -0
- package/package.json +25 -0
- package/templates/.specs/codebase/CONVENTIONS.md +125 -0
- package/templates/.specs/features/.gitkeep +0 -0
- package/templates/.specs/project/PROJECT.md +36 -0
- package/templates/.specs/project/ROADMAP.md +26 -0
- package/templates/.specs/project/STATE.md +37 -0
- package/templates/.specs/quick/.gitkeep +0 -0
- package/templates/AGENTS.md +42 -0
- package/templates/SDD.md +1873 -0
package/bin/km-spec
ADDED
|
@@ -0,0 +1,995 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Resolve symlinks to find the real script location (works on Linux and macOS)
|
|
5
|
+
_SOURCE="${BASH_SOURCE[0]}"
|
|
6
|
+
while [[ -L "$_SOURCE" ]]; do
|
|
7
|
+
_DIR="$(cd -P "$(dirname "$_SOURCE")" && pwd)"
|
|
8
|
+
_SOURCE="$(readlink "$_SOURCE")"
|
|
9
|
+
[[ "$_SOURCE" != /* ]] && _SOURCE="$_DIR/$_SOURCE"
|
|
10
|
+
done
|
|
11
|
+
SCRIPT_DIR="$(cd -P "$(dirname "$_SOURCE")" && pwd)"
|
|
12
|
+
TEMPLATES_DIR="${SCRIPT_DIR}/../templates"
|
|
13
|
+
|
|
14
|
+
VERSION="0.1.0"
|
|
15
|
+
|
|
16
|
+
# ─── Logo & Help ──────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
print_logo() {
|
|
19
|
+
cat <<'EOF'
|
|
20
|
+
_ __ __ __ ____
|
|
21
|
+
| |/ /| \/ | / ___| _ __ ___ ___
|
|
22
|
+
| ' / | |\/| |____\___ \| '_ \ / _ \/ __|
|
|
23
|
+
| . \ | | | |_____|__) | |_) | __/ (__
|
|
24
|
+
|_|\_\|_| |_| |____/| .__/ \___|\___|
|
|
25
|
+
|_|
|
|
26
|
+
|
|
27
|
+
KM Spec Driven Development
|
|
28
|
+
Lightweight Spec Driven Development for AI-assisted projects
|
|
29
|
+
EOF
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
print_help() {
|
|
33
|
+
print_logo
|
|
34
|
+
cat <<'EOF'
|
|
35
|
+
|
|
36
|
+
Usage:
|
|
37
|
+
km-spec <command> [options]
|
|
38
|
+
|
|
39
|
+
Commands:
|
|
40
|
+
init Initialize SDD structure
|
|
41
|
+
project Configure .specs/project/PROJECT.md
|
|
42
|
+
roadmap Configure .specs/project/ROADMAP.md
|
|
43
|
+
state Update .specs/project/STATE.md
|
|
44
|
+
stack Update Technical Context in CONVENTIONS.md
|
|
45
|
+
architecture Update Architecture in CONVENTIONS.md
|
|
46
|
+
structure Update Project Structure in CONVENTIONS.md
|
|
47
|
+
testing Update Testing in CONVENTIONS.md
|
|
48
|
+
integrations Update Integrations in CONVENTIONS.md
|
|
49
|
+
concerns Update Concerns in CONVENTIONS.md
|
|
50
|
+
feature <name> Create a feature spec
|
|
51
|
+
quick <name> Create a quick task
|
|
52
|
+
status Show SDD status
|
|
53
|
+
doctor Validate SDD structure
|
|
54
|
+
help Show this help
|
|
55
|
+
EOF
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
slugify() {
|
|
61
|
+
local s
|
|
62
|
+
s="$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')"
|
|
63
|
+
s="$(printf '%s' "$s" | sed \
|
|
64
|
+
-e 's/[áàãâä]/a/g' \
|
|
65
|
+
-e 's/[éèêë]/e/g' \
|
|
66
|
+
-e 's/[íìîï]/i/g' \
|
|
67
|
+
-e 's/[óòõôö]/o/g' \
|
|
68
|
+
-e 's/[úùûü]/u/g' \
|
|
69
|
+
-e 's/[ç]/c/g' \
|
|
70
|
+
-e 's/[ñ]/n/g')"
|
|
71
|
+
s="$(printf '%s' "$s" | sed 's/[^a-z0-9]/-/g; s/-\{2,\}/-/g; s/^-//; s/-$//')"
|
|
72
|
+
printf '%s' "$s"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
require_templates() {
|
|
76
|
+
if [[ ! -d "$TEMPLATES_DIR" ]]; then
|
|
77
|
+
printf 'Error: templates directory not found at: %s\n' "$TEMPLATES_DIR" >&2
|
|
78
|
+
exit 1
|
|
79
|
+
fi
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
require_init() {
|
|
83
|
+
if [[ ! -d ".specs" ]]; then
|
|
84
|
+
printf 'Error: .specs/ not found in current directory.\nRun: km-spec init\n' >&2
|
|
85
|
+
exit 1
|
|
86
|
+
fi
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
ok() { printf ' \033[32mOK \033[0m %s\n' "$1"; }
|
|
90
|
+
warn() { printf ' \033[33mWARN \033[0m %s\n' "$1"; }
|
|
91
|
+
err() { printf ' \033[31mERROR\033[0m %s\n' "$1"; }
|
|
92
|
+
info() { printf ' %s\n' "$1"; }
|
|
93
|
+
|
|
94
|
+
confirm() {
|
|
95
|
+
local reply
|
|
96
|
+
read -r -p "$1 [y/N] " reply || true
|
|
97
|
+
[[ "$reply" =~ ^[Yy]$ ]]
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# update_section FILE HEADING NEW_CONTENT
|
|
101
|
+
# Replaces the body of a ## section with NEW_CONTENT.
|
|
102
|
+
# Appends the section if the heading is not found.
|
|
103
|
+
# Preserves all other sections.
|
|
104
|
+
update_section() {
|
|
105
|
+
local file="$1"
|
|
106
|
+
local heading="$2"
|
|
107
|
+
local content="$3"
|
|
108
|
+
local tmp_content tmp_result
|
|
109
|
+
tmp_content="$(mktemp)"
|
|
110
|
+
tmp_result="$(mktemp)"
|
|
111
|
+
|
|
112
|
+
printf '%s\n' "$content" > "$tmp_content"
|
|
113
|
+
|
|
114
|
+
awk -v heading="$heading" -v cf="$tmp_content" '
|
|
115
|
+
BEGIN {
|
|
116
|
+
in_section=0; inserted=0; n=0
|
|
117
|
+
while ((getline line < cf) > 0) lines[++n] = line
|
|
118
|
+
close(cf)
|
|
119
|
+
}
|
|
120
|
+
$0 == heading {
|
|
121
|
+
in_section=1; inserted=1
|
|
122
|
+
print $0
|
|
123
|
+
for (i=1; i<=n; i++) print lines[i]
|
|
124
|
+
next
|
|
125
|
+
}
|
|
126
|
+
in_section && /^## / { in_section=0 }
|
|
127
|
+
!in_section { print }
|
|
128
|
+
END {
|
|
129
|
+
if (!inserted) {
|
|
130
|
+
print ""
|
|
131
|
+
print heading
|
|
132
|
+
for (i=1; i<=n; i++) print lines[i]
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
' "$file" > "$tmp_result"
|
|
136
|
+
|
|
137
|
+
cp "$tmp_result" "$file"
|
|
138
|
+
rm -f "$tmp_result" "$tmp_content"
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
# ─── cmd_help ─────────────────────────────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
cmd_help() {
|
|
144
|
+
print_help
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
# ─── cmd_init ─────────────────────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
cmd_init() {
|
|
150
|
+
require_templates
|
|
151
|
+
|
|
152
|
+
local force=false
|
|
153
|
+
[[ "${1:-}" == "--force" ]] && force=true
|
|
154
|
+
|
|
155
|
+
local created=0 skipped=0
|
|
156
|
+
|
|
157
|
+
local files=(
|
|
158
|
+
"AGENTS.md"
|
|
159
|
+
"SDD.md"
|
|
160
|
+
".specs/project/PROJECT.md"
|
|
161
|
+
".specs/project/ROADMAP.md"
|
|
162
|
+
".specs/project/STATE.md"
|
|
163
|
+
".specs/codebase/CONVENTIONS.md"
|
|
164
|
+
".specs/features/.gitkeep"
|
|
165
|
+
".specs/quick/.gitkeep"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
printf '[KM Spec] Initializing SDD structure...\n\n'
|
|
169
|
+
|
|
170
|
+
for f in "${files[@]}"; do
|
|
171
|
+
local src="$TEMPLATES_DIR/$f"
|
|
172
|
+
local dst="./$f"
|
|
173
|
+
local dir
|
|
174
|
+
dir="$(dirname "$dst")"
|
|
175
|
+
|
|
176
|
+
if [[ ! -f "$src" ]]; then
|
|
177
|
+
warn "Template not found: $src"
|
|
178
|
+
continue
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
mkdir -p "$dir"
|
|
182
|
+
|
|
183
|
+
if [[ -f "$dst" ]] && [[ "$force" != true ]]; then
|
|
184
|
+
printf '[KM Spec] Skipped: %s already exists\n' "$f"
|
|
185
|
+
skipped=$((skipped + 1))
|
|
186
|
+
else
|
|
187
|
+
cp "$src" "$dst"
|
|
188
|
+
printf '[KM Spec] Created: %s\n' "$f"
|
|
189
|
+
created=$((created + 1))
|
|
190
|
+
fi
|
|
191
|
+
done
|
|
192
|
+
|
|
193
|
+
printf '\n[KM Spec] Done. Created: %d Skipped: %d\n' "$created" "$skipped"
|
|
194
|
+
printf '\nNext steps:\n'
|
|
195
|
+
printf ' km-spec project — fill in project goals and constraints\n'
|
|
196
|
+
printf ' km-spec stack — document the technical stack\n'
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
# ─── cmd_feature ──────────────────────────────────────────────────────────────
|
|
200
|
+
|
|
201
|
+
cmd_feature() {
|
|
202
|
+
require_init
|
|
203
|
+
|
|
204
|
+
local name="${1:-}"
|
|
205
|
+
local force=false
|
|
206
|
+
|
|
207
|
+
if [[ -z "$name" ]]; then
|
|
208
|
+
printf 'Usage: km-spec feature "Feature Name"\n' >&2
|
|
209
|
+
exit 1
|
|
210
|
+
fi
|
|
211
|
+
|
|
212
|
+
shift || true
|
|
213
|
+
[[ "${1:-}" == "--force" ]] && force=true
|
|
214
|
+
|
|
215
|
+
local slug
|
|
216
|
+
slug="$(slugify "$name")"
|
|
217
|
+
local dir=".specs/features/$slug"
|
|
218
|
+
|
|
219
|
+
if [[ -d "$dir" ]] && [[ "$force" != true ]]; then
|
|
220
|
+
printf 'Feature already exists: %s\nUse --force to overwrite.\n' "$dir"
|
|
221
|
+
exit 1
|
|
222
|
+
fi
|
|
223
|
+
|
|
224
|
+
mkdir -p "$dir"
|
|
225
|
+
|
|
226
|
+
printf '\nCreating feature: %s\n\n' "$name"
|
|
227
|
+
|
|
228
|
+
local goal req1 req2 req3 ac1 ac2 ac3 oos
|
|
229
|
+
goal="" req1="" req2="" req3="" ac1="" ac2="" ac3="" oos=""
|
|
230
|
+
read -r -p "Feature goal: " goal || true
|
|
231
|
+
read -r -p "Requirement 1: " req1 || true
|
|
232
|
+
read -r -p "Requirement 2: " req2 || true
|
|
233
|
+
read -r -p "Requirement 3: " req3 || true
|
|
234
|
+
read -r -p "Acceptance criterion 1: " ac1 || true
|
|
235
|
+
read -r -p "Acceptance criterion 2: " ac2 || true
|
|
236
|
+
read -r -p "Acceptance criterion 3: " ac3 || true
|
|
237
|
+
read -r -p "Out of scope: " oos || true
|
|
238
|
+
|
|
239
|
+
cat > "$dir/spec.md" <<SPEC
|
|
240
|
+
# Feature: $name
|
|
241
|
+
|
|
242
|
+
## Goal
|
|
243
|
+
|
|
244
|
+
$goal
|
|
245
|
+
|
|
246
|
+
## Requirements
|
|
247
|
+
|
|
248
|
+
- REQ-001: $req1
|
|
249
|
+
- REQ-002: $req2
|
|
250
|
+
- REQ-003: $req3
|
|
251
|
+
|
|
252
|
+
## Acceptance Criteria
|
|
253
|
+
|
|
254
|
+
- [ ] AC-001: $ac1
|
|
255
|
+
- [ ] AC-002: $ac2
|
|
256
|
+
- [ ] AC-003: $ac3
|
|
257
|
+
|
|
258
|
+
## Out of Scope
|
|
259
|
+
|
|
260
|
+
- $oos
|
|
261
|
+
SPEC
|
|
262
|
+
|
|
263
|
+
cat > "$dir/tasks.md" <<TASKS
|
|
264
|
+
# Tasks: $name
|
|
265
|
+
|
|
266
|
+
## TASK-001 — Define implementation plan
|
|
267
|
+
|
|
268
|
+
Status: TODO
|
|
269
|
+
|
|
270
|
+
Related requirements:
|
|
271
|
+
|
|
272
|
+
- REQ-001
|
|
273
|
+
- REQ-002
|
|
274
|
+
- REQ-003
|
|
275
|
+
|
|
276
|
+
Objective:
|
|
277
|
+
|
|
278
|
+
Create an implementation plan for this feature based on \`spec.md\`.
|
|
279
|
+
|
|
280
|
+
Implementation steps:
|
|
281
|
+
|
|
282
|
+
1. Read \`.specs/project/STATE.md\`.
|
|
283
|
+
2. Read \`.specs/project/PROJECT.md\`.
|
|
284
|
+
3. Read \`.specs/codebase/CONVENTIONS.md\`.
|
|
285
|
+
4. Inspect the existing code before changing anything.
|
|
286
|
+
5. Break the feature into atomic implementation tasks.
|
|
287
|
+
|
|
288
|
+
Acceptance criteria:
|
|
289
|
+
|
|
290
|
+
- [ ] Implementation tasks are clear and atomic.
|
|
291
|
+
- [ ] Each task references related requirements.
|
|
292
|
+
- [ ] Validation requirements are defined.
|
|
293
|
+
|
|
294
|
+
Validation:
|
|
295
|
+
|
|
296
|
+
- [ ] Build passes, if applicable.
|
|
297
|
+
- [ ] Lint passes, if available.
|
|
298
|
+
- [ ] Typecheck passes, if available.
|
|
299
|
+
- [ ] Tests pass, if available.
|
|
300
|
+
- [ ] Acceptance criteria verified.
|
|
301
|
+
|
|
302
|
+
Notes:
|
|
303
|
+
TASKS
|
|
304
|
+
|
|
305
|
+
cat > "$dir/validation.md" <<VALIDATION
|
|
306
|
+
# Validation: $name
|
|
307
|
+
|
|
308
|
+
## Validation Summary
|
|
309
|
+
|
|
310
|
+
Status: Pending
|
|
311
|
+
|
|
312
|
+
## Evidence
|
|
313
|
+
|
|
314
|
+
| Gate | Result | Command / Notes |
|
|
315
|
+
|------|--------|-----------------|
|
|
316
|
+
| Build | Pending | |
|
|
317
|
+
| Lint | Pending | |
|
|
318
|
+
| Typecheck | Pending | |
|
|
319
|
+
| Tests | Pending | |
|
|
320
|
+
| Manual Review | Pending | |
|
|
321
|
+
|
|
322
|
+
## Issues Found
|
|
323
|
+
|
|
324
|
+
None.
|
|
325
|
+
|
|
326
|
+
## Final Result
|
|
327
|
+
|
|
328
|
+
Pending.
|
|
329
|
+
VALIDATION
|
|
330
|
+
|
|
331
|
+
printf '\n'
|
|
332
|
+
printf '[KM Spec] Created: %s/spec.md\n' "$dir"
|
|
333
|
+
printf '[KM Spec] Created: %s/tasks.md\n' "$dir"
|
|
334
|
+
printf '[KM Spec] Created: %s/validation.md\n' "$dir"
|
|
335
|
+
printf '\nNext step: open %s/spec.md and review the spec.\n' "$dir"
|
|
336
|
+
printf 'Then ask your AI agent to implement TASK-001.\n'
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
# ─── cmd_quick ────────────────────────────────────────────────────────────────
|
|
340
|
+
|
|
341
|
+
cmd_quick() {
|
|
342
|
+
require_init
|
|
343
|
+
|
|
344
|
+
local name="${1:-}"
|
|
345
|
+
local force=false
|
|
346
|
+
|
|
347
|
+
if [[ -z "$name" ]]; then
|
|
348
|
+
printf 'Usage: km-spec quick "Task Name"\n' >&2
|
|
349
|
+
exit 1
|
|
350
|
+
fi
|
|
351
|
+
|
|
352
|
+
shift || true
|
|
353
|
+
[[ "${1:-}" == "--force" ]] && force=true
|
|
354
|
+
|
|
355
|
+
local slug
|
|
356
|
+
slug="$(slugify "$name")"
|
|
357
|
+
local file=".specs/quick/${slug}.md"
|
|
358
|
+
|
|
359
|
+
if [[ -f "$file" ]] && [[ "$force" != true ]]; then
|
|
360
|
+
printf 'Quick task already exists: %s\nUse --force to overwrite.\n' "$file"
|
|
361
|
+
exit 1
|
|
362
|
+
fi
|
|
363
|
+
|
|
364
|
+
mkdir -p ".specs/quick"
|
|
365
|
+
|
|
366
|
+
printf '\nCreating quick task: %s\n\n' "$name"
|
|
367
|
+
|
|
368
|
+
local objective files_affected ac validation_steps
|
|
369
|
+
objective="" files_affected="" ac="" validation_steps=""
|
|
370
|
+
read -r -p "Objective: " objective || true
|
|
371
|
+
read -r -p "Likely affected files: " files_affected || true
|
|
372
|
+
read -r -p "Acceptance criteria: " ac || true
|
|
373
|
+
read -r -p "Validation steps: " validation_steps || true
|
|
374
|
+
|
|
375
|
+
cat > "$file" <<QUICK
|
|
376
|
+
# Quick Task: $name
|
|
377
|
+
|
|
378
|
+
## Objective
|
|
379
|
+
|
|
380
|
+
$objective
|
|
381
|
+
|
|
382
|
+
## Scope
|
|
383
|
+
|
|
384
|
+
Files likely affected:
|
|
385
|
+
|
|
386
|
+
- $files_affected
|
|
387
|
+
|
|
388
|
+
## Acceptance Criteria
|
|
389
|
+
|
|
390
|
+
- [ ] $ac
|
|
391
|
+
|
|
392
|
+
## Validation
|
|
393
|
+
|
|
394
|
+
- [ ] Build passes, if applicable.
|
|
395
|
+
- [ ] Lint passes, if available.
|
|
396
|
+
- [ ] Typecheck passes, if available.
|
|
397
|
+
- [ ] Tests pass, if available.
|
|
398
|
+
- [ ] Manual check completed, if needed.
|
|
399
|
+
|
|
400
|
+
Notes: $validation_steps
|
|
401
|
+
|
|
402
|
+
## Summary
|
|
403
|
+
|
|
404
|
+
[Describe what was changed after implementation.]
|
|
405
|
+
QUICK
|
|
406
|
+
|
|
407
|
+
printf '\n[KM Spec] Created: %s\n' "$file"
|
|
408
|
+
printf '\nNext step: ask your AI agent to complete this task, then update the Summary section.\n'
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
# ─── cmd_project ──────────────────────────────────────────────────────────────
|
|
412
|
+
|
|
413
|
+
cmd_project() {
|
|
414
|
+
require_init
|
|
415
|
+
|
|
416
|
+
local file=".specs/project/PROJECT.md"
|
|
417
|
+
mkdir -p ".specs/project"
|
|
418
|
+
|
|
419
|
+
if [[ -f "$file" ]] && [[ -s "$file" ]]; then
|
|
420
|
+
if ! confirm "PROJECT.md already exists. Overwrite?"; then
|
|
421
|
+
printf 'Aborted.\n'
|
|
422
|
+
exit 0
|
|
423
|
+
fi
|
|
424
|
+
fi
|
|
425
|
+
|
|
426
|
+
printf '\nConfiguring PROJECT.md\n\n'
|
|
427
|
+
|
|
428
|
+
local name desc problem users goals nonneg oos terms
|
|
429
|
+
name="" desc="" problem="" users="" goals="" nonneg="" oos="" terms=""
|
|
430
|
+
read -r -p "Project name: " name || true
|
|
431
|
+
read -r -p "One-line description: " desc || true
|
|
432
|
+
read -r -p "Problem being solved: " problem || true
|
|
433
|
+
read -r -p "Target users (comma-separated): " users || true
|
|
434
|
+
read -r -p "Main goals (comma-separated): " goals || true
|
|
435
|
+
read -r -p "Non-negotiables (comma-separated): " nonneg || true
|
|
436
|
+
read -r -p "Out of scope: " oos || true
|
|
437
|
+
read -r -p "Key terms (term: definition): " terms || true
|
|
438
|
+
|
|
439
|
+
local users_md goals_md nonneg_md
|
|
440
|
+
users_md="$(printf '%s' "$users" | tr ',' '\n' | sed 's/^[[:space:]]*/- /')"
|
|
441
|
+
goals_md="$(printf '%s' "$goals" | tr ',' '\n' | sed 's/^[[:space:]]*/- /')"
|
|
442
|
+
nonneg_md="$(printf '%s' "$nonneg" | tr ',' '\n' | sed 's/^[[:space:]]*/- /')"
|
|
443
|
+
|
|
444
|
+
cat > "$file" <<PROJECT
|
|
445
|
+
# Project
|
|
446
|
+
|
|
447
|
+
## Name
|
|
448
|
+
|
|
449
|
+
$name
|
|
450
|
+
|
|
451
|
+
## One-Line Description
|
|
452
|
+
|
|
453
|
+
$desc
|
|
454
|
+
|
|
455
|
+
## Problem
|
|
456
|
+
|
|
457
|
+
$problem
|
|
458
|
+
|
|
459
|
+
## Target Users
|
|
460
|
+
|
|
461
|
+
$users_md
|
|
462
|
+
|
|
463
|
+
## Goals
|
|
464
|
+
|
|
465
|
+
$goals_md
|
|
466
|
+
|
|
467
|
+
## Non-Negotiables
|
|
468
|
+
|
|
469
|
+
$nonneg_md
|
|
470
|
+
|
|
471
|
+
## Out of Scope
|
|
472
|
+
|
|
473
|
+
- $oos
|
|
474
|
+
|
|
475
|
+
## Key Terms
|
|
476
|
+
|
|
477
|
+
- $terms
|
|
478
|
+
PROJECT
|
|
479
|
+
|
|
480
|
+
printf '\n[KM Spec] Updated: %s\n' "$file"
|
|
481
|
+
printf '\nNext step: km-spec stack\n'
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
# ─── cmd_roadmap ──────────────────────────────────────────────────────────────
|
|
485
|
+
|
|
486
|
+
cmd_roadmap() {
|
|
487
|
+
require_init
|
|
488
|
+
|
|
489
|
+
local file=".specs/project/ROADMAP.md"
|
|
490
|
+
mkdir -p ".specs/project"
|
|
491
|
+
|
|
492
|
+
if [[ -f "$file" ]] && [[ -s "$file" ]]; then
|
|
493
|
+
if ! confirm "ROADMAP.md already exists. Overwrite?"; then
|
|
494
|
+
printf 'Aborted.\n'
|
|
495
|
+
exit 0
|
|
496
|
+
fi
|
|
497
|
+
fi
|
|
498
|
+
|
|
499
|
+
printf '\nConfiguring ROADMAP.md\n\n'
|
|
500
|
+
|
|
501
|
+
local phase ms_name ms_goal ms_status features backlog deferred
|
|
502
|
+
phase="" ms_name="" ms_goal="" ms_status="" features="" backlog="" deferred=""
|
|
503
|
+
read -r -p "Current phase (Discovery/Setup/MVP/Active Development/Stabilization/Maintenance): " phase || true
|
|
504
|
+
read -r -p "Milestone name: " ms_name || true
|
|
505
|
+
read -r -p "Milestone goal: " ms_goal || true
|
|
506
|
+
read -r -p "Milestone status (Planned/In Progress/Done): " ms_status || true
|
|
507
|
+
read -r -p "Features in this milestone (comma-separated): " features || true
|
|
508
|
+
read -r -p "Backlog items (comma-separated): " backlog || true
|
|
509
|
+
read -r -p "Deferred items (comma-separated): " deferred || true
|
|
510
|
+
|
|
511
|
+
local features_md backlog_md deferred_md
|
|
512
|
+
features_md="$(printf '%s' "$features" | tr ',' '\n' | sed 's/^[[:space:]]*/- [ ] /')"
|
|
513
|
+
backlog_md="$(printf '%s' "$backlog" | tr ',' '\n' | sed 's/^[[:space:]]*/- [ ] /')"
|
|
514
|
+
deferred_md="$(printf '%s' "$deferred" | tr ',' '\n' | sed 's/^[[:space:]]*/- [ ] /')"
|
|
515
|
+
|
|
516
|
+
cat > "$file" <<ROADMAP
|
|
517
|
+
# Roadmap
|
|
518
|
+
|
|
519
|
+
## Current Phase
|
|
520
|
+
|
|
521
|
+
$phase
|
|
522
|
+
|
|
523
|
+
## Milestones
|
|
524
|
+
|
|
525
|
+
### Milestone 1 — $ms_name
|
|
526
|
+
|
|
527
|
+
**Status:** $ms_status
|
|
528
|
+
**Goal:** $ms_goal
|
|
529
|
+
|
|
530
|
+
#### Features
|
|
531
|
+
|
|
532
|
+
$features_md
|
|
533
|
+
|
|
534
|
+
## Backlog
|
|
535
|
+
|
|
536
|
+
$backlog_md
|
|
537
|
+
|
|
538
|
+
## Deferred
|
|
539
|
+
|
|
540
|
+
$deferred_md
|
|
541
|
+
ROADMAP
|
|
542
|
+
|
|
543
|
+
printf '\n[KM Spec] Updated: %s\n' "$file"
|
|
544
|
+
printf '\nNext step: km-spec feature "Your first feature"\n'
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
# ─── cmd_state ────────────────────────────────────────────────────────────────
|
|
548
|
+
|
|
549
|
+
cmd_state() {
|
|
550
|
+
require_init
|
|
551
|
+
|
|
552
|
+
local file=".specs/project/STATE.md"
|
|
553
|
+
if [[ ! -f "$file" ]]; then
|
|
554
|
+
printf 'Error: %s not found. Run: km-spec init\n' "$file" >&2
|
|
555
|
+
exit 1
|
|
556
|
+
fi
|
|
557
|
+
|
|
558
|
+
printf '\nUpdating STATE.md\n\n'
|
|
559
|
+
|
|
560
|
+
local focus last_task current_task next_steps decision blocker notes
|
|
561
|
+
focus="" last_task="" current_task="" next_steps="" decision="" blocker="" notes=""
|
|
562
|
+
read -r -p "Current focus: " focus || true
|
|
563
|
+
read -r -p "Last completed task: " last_task || true
|
|
564
|
+
read -r -p "Current task: " current_task || true
|
|
565
|
+
read -r -p "Suggested next steps (comma-separated): " next_steps || true
|
|
566
|
+
read -r -p "Decision to record (Enter to skip): " decision || true
|
|
567
|
+
read -r -p "Blocker to record (Enter to skip): " blocker || true
|
|
568
|
+
read -r -p "Notes for future agents: " notes || true
|
|
569
|
+
|
|
570
|
+
local today
|
|
571
|
+
today="$(date '+%Y-%m-%d %H:%M')"
|
|
572
|
+
|
|
573
|
+
local next_md
|
|
574
|
+
next_md="$(printf '%s' "$next_steps" | tr ',' '\n' | awk 'NR>0{print NR". "$0}' | sed 's/^[0-9]*\.[[:space:]]*/& /')"
|
|
575
|
+
|
|
576
|
+
local decisions_text blockers_text
|
|
577
|
+
decisions_text="$([ -n "$decision" ] && printf '- %s' "$decision" || printf '- None.')"
|
|
578
|
+
blockers_text="$([ -n "$blocker" ] && printf '- %s' "$blocker" || printf '- None.')"
|
|
579
|
+
|
|
580
|
+
cat >> "$file" <<SESSION
|
|
581
|
+
|
|
582
|
+
---
|
|
583
|
+
|
|
584
|
+
## Session — $today
|
|
585
|
+
|
|
586
|
+
**Current Focus:** $focus
|
|
587
|
+
|
|
588
|
+
**Last Completed Task:** $last_task
|
|
589
|
+
|
|
590
|
+
**Current Task:** $current_task
|
|
591
|
+
|
|
592
|
+
**Suggested Next Steps:**
|
|
593
|
+
|
|
594
|
+
$next_md
|
|
595
|
+
|
|
596
|
+
**Decisions:**
|
|
597
|
+
|
|
598
|
+
$decisions_text
|
|
599
|
+
|
|
600
|
+
**Blockers:**
|
|
601
|
+
|
|
602
|
+
$blockers_text
|
|
603
|
+
|
|
604
|
+
**Notes:**
|
|
605
|
+
|
|
606
|
+
$notes
|
|
607
|
+
SESSION
|
|
608
|
+
|
|
609
|
+
printf '\n[KM Spec] Updated: %s\n' "$file"
|
|
610
|
+
printf '\n[KM Spec] Done.\n'
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
# ─── CONVENTIONS.md updaters ──────────────────────────────────────────────────
|
|
614
|
+
|
|
615
|
+
conventions_file=".specs/codebase/CONVENTIONS.md"
|
|
616
|
+
|
|
617
|
+
_require_conventions() {
|
|
618
|
+
require_init
|
|
619
|
+
if [[ ! -f "$conventions_file" ]]; then
|
|
620
|
+
printf 'Error: %s not found. Run: km-spec init\n' "$conventions_file" >&2
|
|
621
|
+
exit 1
|
|
622
|
+
fi
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
cmd_stack() {
|
|
626
|
+
_require_conventions
|
|
627
|
+
printf '\nUpdating Technical Context in CONVENTIONS.md\n\n'
|
|
628
|
+
|
|
629
|
+
local lang framework runtime pkgmgr database libs deploy cicd
|
|
630
|
+
lang="" framework="" runtime="" pkgmgr="" database="" libs="" deploy="" cicd=""
|
|
631
|
+
read -r -p "Language: " lang || true
|
|
632
|
+
read -r -p "Framework: " framework || true
|
|
633
|
+
read -r -p "Runtime: " runtime || true
|
|
634
|
+
read -r -p "Package manager: " pkgmgr || true
|
|
635
|
+
read -r -p "Database: " database || true
|
|
636
|
+
read -r -p "Main libraries: " libs || true
|
|
637
|
+
read -r -p "Deployment: " deploy || true
|
|
638
|
+
read -r -p "CI/CD: " cicd || true
|
|
639
|
+
|
|
640
|
+
local content
|
|
641
|
+
content="
|
|
642
|
+
- Language: $lang
|
|
643
|
+
- Framework: $framework
|
|
644
|
+
- Runtime: $runtime
|
|
645
|
+
- Package manager: $pkgmgr
|
|
646
|
+
- Database: $database
|
|
647
|
+
- Main libraries: $libs
|
|
648
|
+
- Deployment: $deploy
|
|
649
|
+
- CI/CD: $cicd"
|
|
650
|
+
|
|
651
|
+
update_section "$conventions_file" "## 1. Technical Context" "$content"
|
|
652
|
+
printf '\n[KM Spec] Updated: %s\n' "$conventions_file"
|
|
653
|
+
printf '\nNext step: km-spec architecture\n'
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
cmd_architecture() {
|
|
657
|
+
_require_conventions
|
|
658
|
+
printf '\nUpdating Architecture in CONVENTIONS.md\n\n'
|
|
659
|
+
|
|
660
|
+
local modules dataflow patterns decisions
|
|
661
|
+
modules="" dataflow="" patterns="" decisions=""
|
|
662
|
+
read -r -p "Main modules: " modules || true
|
|
663
|
+
read -r -p "Data flow: " dataflow || true
|
|
664
|
+
read -r -p "Architectural patterns: " patterns || true
|
|
665
|
+
read -r -p "Important technical decisions: " decisions || true
|
|
666
|
+
|
|
667
|
+
local content
|
|
668
|
+
content="
|
|
669
|
+
- Main modules: $modules
|
|
670
|
+
- Data flow: $dataflow
|
|
671
|
+
- Architectural patterns: $patterns
|
|
672
|
+
- Important decisions: $decisions"
|
|
673
|
+
|
|
674
|
+
update_section "$conventions_file" "## 2. Architecture" "$content"
|
|
675
|
+
printf '\n[KM Spec] Updated: %s\n' "$conventions_file"
|
|
676
|
+
printf '\nNext step: km-spec structure\n'
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
cmd_structure() {
|
|
680
|
+
_require_conventions
|
|
681
|
+
printf '\nUpdating Project Structure in CONVENTIONS.md\n\n'
|
|
682
|
+
|
|
683
|
+
local srcdir api_dir comp_dir svc_dir test_dir generated
|
|
684
|
+
srcdir="" api_dir="" comp_dir="" svc_dir="" test_dir="" generated=""
|
|
685
|
+
read -r -p "Main source directory: " srcdir || true
|
|
686
|
+
read -r -p "Where API files go: " api_dir || true
|
|
687
|
+
read -r -p "Where components go: " comp_dir || true
|
|
688
|
+
read -r -p "Where services go: " svc_dir || true
|
|
689
|
+
read -r -p "Where tests go: " test_dir || true
|
|
690
|
+
read -r -p "Generated files or folders: " generated || true
|
|
691
|
+
|
|
692
|
+
local content
|
|
693
|
+
content="
|
|
694
|
+
Main source directory: \`$srcdir\`
|
|
695
|
+
|
|
696
|
+
File placement rules:
|
|
697
|
+
|
|
698
|
+
- API files: $api_dir
|
|
699
|
+
- Components: $comp_dir
|
|
700
|
+
- Services: $svc_dir
|
|
701
|
+
- Tests: $test_dir
|
|
702
|
+
- Generated: $generated"
|
|
703
|
+
|
|
704
|
+
update_section "$conventions_file" "## 3. Project Structure" "$content"
|
|
705
|
+
printf '\n[KM Spec] Updated: %s\n' "$conventions_file"
|
|
706
|
+
printf '\nNext step: km-spec testing\n'
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
cmd_testing() {
|
|
710
|
+
_require_conventions
|
|
711
|
+
printf '\nUpdating Testing in CONVENTIONS.md\n\n'
|
|
712
|
+
|
|
713
|
+
local framework pattern unit_rules int_rules e2e_rules commands
|
|
714
|
+
framework="" pattern="" unit_rules="" int_rules="" e2e_rules="" commands=""
|
|
715
|
+
read -r -p "Test framework: " framework || true
|
|
716
|
+
read -r -p "Test file pattern: " pattern || true
|
|
717
|
+
read -r -p "Unit test rules: " unit_rules || true
|
|
718
|
+
read -r -p "Integration test rules: " int_rules || true
|
|
719
|
+
read -r -p "E2E test rules: " e2e_rules || true
|
|
720
|
+
read -r -p "Commands to run tests: " commands || true
|
|
721
|
+
|
|
722
|
+
local content
|
|
723
|
+
content="
|
|
724
|
+
- Test framework: $framework
|
|
725
|
+
- Test file pattern: $pattern
|
|
726
|
+
- Unit test rules: $unit_rules
|
|
727
|
+
- Integration test rules: $int_rules
|
|
728
|
+
- E2E test rules: $e2e_rules
|
|
729
|
+
- Required commands: \`$commands\`
|
|
730
|
+
|
|
731
|
+
Validation gates:
|
|
732
|
+
|
|
733
|
+
- [ ] Build
|
|
734
|
+
- [ ] Lint
|
|
735
|
+
- [ ] Typecheck
|
|
736
|
+
- [ ] Tests
|
|
737
|
+
- [ ] Manual check when needed"
|
|
738
|
+
|
|
739
|
+
update_section "$conventions_file" "## 5. Testing" "$content"
|
|
740
|
+
printf '\n[KM Spec] Updated: %s\n' "$conventions_file"
|
|
741
|
+
printf '\nNext step: km-spec integrations\n'
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
cmd_integrations() {
|
|
745
|
+
_require_conventions
|
|
746
|
+
printf '\nUpdating Integrations in CONVENTIONS.md\n\n'
|
|
747
|
+
|
|
748
|
+
local svc_name purpose auth env_vars limits webhooks
|
|
749
|
+
svc_name="" purpose="" auth="" env_vars="" limits="" webhooks=""
|
|
750
|
+
read -r -p "Service name: " svc_name || true
|
|
751
|
+
read -r -p "Purpose: " purpose || true
|
|
752
|
+
read -r -p "Authentication method: " auth || true
|
|
753
|
+
read -r -p "Environment variables: " env_vars || true
|
|
754
|
+
read -r -p "Rate limits or risks: " limits || true
|
|
755
|
+
read -r -p "Webhook details (Enter to skip): " webhooks || true
|
|
756
|
+
|
|
757
|
+
local content
|
|
758
|
+
content="
|
|
759
|
+
| Field | Value |
|
|
760
|
+
|-------|-------|
|
|
761
|
+
| Service | $svc_name |
|
|
762
|
+
| Purpose | $purpose |
|
|
763
|
+
| Auth | $auth |
|
|
764
|
+
| Env vars | $env_vars |
|
|
765
|
+
| Risks / limits | $limits |
|
|
766
|
+
| Webhooks | ${webhooks:-None} |"
|
|
767
|
+
|
|
768
|
+
update_section "$conventions_file" "## 6. Integrations" "$content"
|
|
769
|
+
printf '\n[KM Spec] Updated: %s\n' "$conventions_file"
|
|
770
|
+
printf '\nNext step: km-spec concerns\n'
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
cmd_concerns() {
|
|
774
|
+
_require_conventions
|
|
775
|
+
printf '\nUpdating Concerns in CONVENTIONS.md\n\n'
|
|
776
|
+
|
|
777
|
+
local risks debt fragile low_cov deprecated
|
|
778
|
+
risks="" debt="" fragile="" low_cov="" deprecated=""
|
|
779
|
+
read -r -p "Known risks: " risks || true
|
|
780
|
+
read -r -p "Technical debt: " debt || true
|
|
781
|
+
read -r -p "Fragile areas: " fragile || true
|
|
782
|
+
read -r -p "Low test coverage areas: " low_cov || true
|
|
783
|
+
read -r -p "Deprecated patterns: " deprecated || true
|
|
784
|
+
|
|
785
|
+
local content
|
|
786
|
+
content="
|
|
787
|
+
- Known risks: $risks
|
|
788
|
+
- Technical debt: $debt
|
|
789
|
+
- Fragile areas: $fragile
|
|
790
|
+
- Low test coverage areas: $low_cov
|
|
791
|
+
- Deprecated patterns: $deprecated"
|
|
792
|
+
|
|
793
|
+
update_section "$conventions_file" "## 7. Concerns" "$content"
|
|
794
|
+
printf '\n[KM Spec] Updated: %s\n' "$conventions_file"
|
|
795
|
+
printf '\nRun km-spec status to review the full setup.\n'
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
# ─── cmd_status ───────────────────────────────────────────────────────────────
|
|
799
|
+
|
|
800
|
+
cmd_status() {
|
|
801
|
+
printf 'KM Spec Status\n\n'
|
|
802
|
+
|
|
803
|
+
printf 'Base files:\n'
|
|
804
|
+
local base_files=("AGENTS.md" "SDD.md" ".specs/project/PROJECT.md"
|
|
805
|
+
".specs/project/ROADMAP.md" ".specs/project/STATE.md"
|
|
806
|
+
".specs/codebase/CONVENTIONS.md")
|
|
807
|
+
|
|
808
|
+
for f in "${base_files[@]}"; do
|
|
809
|
+
if [[ -f "$f" ]]; then
|
|
810
|
+
printf ' Found %s\n' "$f"
|
|
811
|
+
else
|
|
812
|
+
printf ' Missing %s\n' "$f"
|
|
813
|
+
fi
|
|
814
|
+
done
|
|
815
|
+
|
|
816
|
+
printf '\nFeatures:\n'
|
|
817
|
+
local feat_found=false
|
|
818
|
+
if [[ -d ".specs/features" ]]; then
|
|
819
|
+
for d in .specs/features/*/; do
|
|
820
|
+
[[ -d "$d" ]] || continue
|
|
821
|
+
printf ' %s\n' "$(basename "$d")"
|
|
822
|
+
feat_found=true
|
|
823
|
+
done
|
|
824
|
+
fi
|
|
825
|
+
[[ "$feat_found" == true ]] || printf ' (none)\n'
|
|
826
|
+
|
|
827
|
+
printf '\nQuick tasks:\n'
|
|
828
|
+
local quick_found=false
|
|
829
|
+
if [[ -d ".specs/quick" ]]; then
|
|
830
|
+
for f in .specs/quick/*.md; do
|
|
831
|
+
[[ -f "$f" ]] || continue
|
|
832
|
+
printf ' %s\n' "$(basename "$f" .md)"
|
|
833
|
+
quick_found=true
|
|
834
|
+
done
|
|
835
|
+
fi
|
|
836
|
+
[[ "$quick_found" == true ]] || printf ' (none)\n'
|
|
837
|
+
|
|
838
|
+
printf '\nCONVENTIONS.md sections:\n'
|
|
839
|
+
if [[ -f ".specs/codebase/CONVENTIONS.md" ]]; then
|
|
840
|
+
local sections=(
|
|
841
|
+
"## 1. Technical Context"
|
|
842
|
+
"## 2. Architecture"
|
|
843
|
+
"## 3. Project Structure"
|
|
844
|
+
"## 4. Coding Standards"
|
|
845
|
+
"## 5. Testing"
|
|
846
|
+
"## 6. Integrations"
|
|
847
|
+
"## 7. Concerns"
|
|
848
|
+
"## 8. AI Rules"
|
|
849
|
+
"## 9. Optional Expansion"
|
|
850
|
+
)
|
|
851
|
+
for s in "${sections[@]}"; do
|
|
852
|
+
if grep -qF "$s" ".specs/codebase/CONVENTIONS.md" 2>/dev/null; then
|
|
853
|
+
printf ' Found %s\n' "$s"
|
|
854
|
+
else
|
|
855
|
+
printf ' Missing %s\n' "$s"
|
|
856
|
+
fi
|
|
857
|
+
done
|
|
858
|
+
else
|
|
859
|
+
printf ' CONVENTIONS.md not found\n'
|
|
860
|
+
fi
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
# ─── cmd_doctor ───────────────────────────────────────────────────────────────
|
|
864
|
+
|
|
865
|
+
cmd_doctor() {
|
|
866
|
+
printf 'KM Spec Doctor\n\n'
|
|
867
|
+
|
|
868
|
+
local errors=0 warnings=0
|
|
869
|
+
|
|
870
|
+
printf 'Required files:\n'
|
|
871
|
+
local required_files=("AGENTS.md" "SDD.md" ".specs/project/PROJECT.md"
|
|
872
|
+
".specs/project/ROADMAP.md" ".specs/project/STATE.md"
|
|
873
|
+
".specs/codebase/CONVENTIONS.md")
|
|
874
|
+
for f in "${required_files[@]}"; do
|
|
875
|
+
if [[ -f "$f" ]]; then ok "$f"
|
|
876
|
+
else err "$f — missing"; errors=$((errors + 1)); fi
|
|
877
|
+
done
|
|
878
|
+
|
|
879
|
+
printf '\nRequired directories:\n'
|
|
880
|
+
local required_dirs=(".specs/project" ".specs/codebase" ".specs/features" ".specs/quick")
|
|
881
|
+
for d in "${required_dirs[@]}"; do
|
|
882
|
+
if [[ -d "$d" ]]; then ok "$d"
|
|
883
|
+
else err "$d — missing"; errors=$((errors + 1)); fi
|
|
884
|
+
done
|
|
885
|
+
|
|
886
|
+
printf '\nForbidden standalone files (content must be inside CONVENTIONS.md):\n'
|
|
887
|
+
local forbidden=(".specs/codebase/STACK.md" ".specs/codebase/ARCHITECTURE.md"
|
|
888
|
+
".specs/codebase/STRUCTURE.md" ".specs/codebase/TESTING.md"
|
|
889
|
+
".specs/codebase/INTEGRATIONS.md" ".specs/codebase/CONCERNS.md")
|
|
890
|
+
for f in "${forbidden[@]}"; do
|
|
891
|
+
if [[ -f "$f" ]]; then
|
|
892
|
+
warn "$f — consolidate into CONVENTIONS.md"
|
|
893
|
+
warnings=$((warnings + 1))
|
|
894
|
+
else
|
|
895
|
+
ok "$(basename "$f") — not present (correct)"
|
|
896
|
+
fi
|
|
897
|
+
done
|
|
898
|
+
|
|
899
|
+
printf '\nCONVENTIONS.md sections:\n'
|
|
900
|
+
if [[ -f ".specs/codebase/CONVENTIONS.md" ]]; then
|
|
901
|
+
local sections=(
|
|
902
|
+
"## 1. Technical Context" "## 2. Architecture" "## 3. Project Structure"
|
|
903
|
+
"## 4. Coding Standards" "## 5. Testing" "## 6. Integrations"
|
|
904
|
+
"## 7. Concerns" "## 8. AI Rules"
|
|
905
|
+
)
|
|
906
|
+
for s in "${sections[@]}"; do
|
|
907
|
+
if grep -qF "$s" ".specs/codebase/CONVENTIONS.md" 2>/dev/null; then ok "$s"
|
|
908
|
+
else warn "$s — section missing"; warnings=$((warnings + 1)); fi
|
|
909
|
+
done
|
|
910
|
+
fi
|
|
911
|
+
|
|
912
|
+
printf '\nSTATE.md:\n'
|
|
913
|
+
if [[ -f ".specs/project/STATE.md" ]]; then
|
|
914
|
+
if [[ -s ".specs/project/STATE.md" ]]; then ok "STATE.md — not empty"
|
|
915
|
+
else warn "STATE.md — empty"; warnings=$((warnings + 1)); fi
|
|
916
|
+
fi
|
|
917
|
+
|
|
918
|
+
printf '\nAGENTS.md references:\n'
|
|
919
|
+
if [[ -f "AGENTS.md" ]]; then
|
|
920
|
+
local agents_ok=true
|
|
921
|
+
grep -q "STATE.md" "AGENTS.md" 2>/dev/null || agents_ok=false
|
|
922
|
+
grep -q "PROJECT.md" "AGENTS.md" 2>/dev/null || agents_ok=false
|
|
923
|
+
grep -q "CONVENTIONS.md" "AGENTS.md" 2>/dev/null || agents_ok=false
|
|
924
|
+
if [[ "$agents_ok" == true ]]; then
|
|
925
|
+
ok "AGENTS.md — references STATE.md, PROJECT.md, CONVENTIONS.md"
|
|
926
|
+
else
|
|
927
|
+
warn "AGENTS.md — missing one or more required file references"
|
|
928
|
+
warnings=$((warnings + 1))
|
|
929
|
+
fi
|
|
930
|
+
fi
|
|
931
|
+
|
|
932
|
+
printf '\nFeature integrity:\n'
|
|
933
|
+
local feat_checked=false
|
|
934
|
+
if [[ -d ".specs/features" ]]; then
|
|
935
|
+
for d in .specs/features/*/; do
|
|
936
|
+
[[ -d "$d" ]] || continue
|
|
937
|
+
feat_checked=true
|
|
938
|
+
local feat
|
|
939
|
+
feat="$(basename "$d")"
|
|
940
|
+
for req_file in spec.md tasks.md validation.md; do
|
|
941
|
+
if [[ -f "$d$req_file" ]]; then ok "$feat/$req_file"
|
|
942
|
+
else warn "$feat/$req_file — missing"; warnings=$((warnings + 1)); fi
|
|
943
|
+
done
|
|
944
|
+
done
|
|
945
|
+
fi
|
|
946
|
+
[[ "$feat_checked" == true ]] || info "No features found."
|
|
947
|
+
|
|
948
|
+
printf '\nQuick tasks:\n'
|
|
949
|
+
local quick_checked=false
|
|
950
|
+
if [[ -d ".specs/quick" ]]; then
|
|
951
|
+
for f in .specs/quick/*.md; do
|
|
952
|
+
[[ -f "$f" ]] || continue
|
|
953
|
+
quick_checked=true
|
|
954
|
+
ok "$(basename "$f")"
|
|
955
|
+
done
|
|
956
|
+
fi
|
|
957
|
+
[[ "$quick_checked" == true ]] || info "No quick tasks found."
|
|
958
|
+
|
|
959
|
+
printf '\nSummary:\n'
|
|
960
|
+
if [[ $errors -eq 0 ]] && [[ $warnings -eq 0 ]]; then
|
|
961
|
+
printf ' \033[32mAll checks passed.\033[0m\n'
|
|
962
|
+
else
|
|
963
|
+
[[ $errors -gt 0 ]] && printf ' \033[31m%d error(s) found.\033[0m\n' "$errors"
|
|
964
|
+
[[ $warnings -gt 0 ]] && printf ' \033[33m%d warning(s) found.\033[0m\n' "$warnings"
|
|
965
|
+
fi
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
# ─── Main ─────────────────────────────────────────────────────────────────────
|
|
969
|
+
|
|
970
|
+
main() {
|
|
971
|
+
local cmd="${1:-help}"
|
|
972
|
+
shift || true
|
|
973
|
+
|
|
974
|
+
case "$cmd" in
|
|
975
|
+
init) cmd_init "$@" ;;
|
|
976
|
+
feature) cmd_feature "$@" ;;
|
|
977
|
+
quick) cmd_quick "$@" ;;
|
|
978
|
+
project) cmd_project "$@" ;;
|
|
979
|
+
roadmap) cmd_roadmap "$@" ;;
|
|
980
|
+
state) cmd_state "$@" ;;
|
|
981
|
+
stack) cmd_stack "$@" ;;
|
|
982
|
+
architecture) cmd_architecture "$@" ;;
|
|
983
|
+
structure) cmd_structure "$@" ;;
|
|
984
|
+
testing) cmd_testing "$@" ;;
|
|
985
|
+
integrations) cmd_integrations "$@" ;;
|
|
986
|
+
concerns) cmd_concerns "$@" ;;
|
|
987
|
+
status) cmd_status "$@" ;;
|
|
988
|
+
doctor) cmd_doctor "$@" ;;
|
|
989
|
+
help|--help|-h) cmd_help ;;
|
|
990
|
+
version|--version|-v) printf 'km-spec v%s\n' "$VERSION" ;;
|
|
991
|
+
*) printf 'Unknown command: %s\n\n' "$cmd" >&2; cmd_help; exit 1 ;;
|
|
992
|
+
esac
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
main "$@"
|