km-spec-driven-development 0.1.0 → 0.2.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 +114 -107
- package/bin/km-spec +616 -205
- package/package.json +4 -1
- package/templates/AGENTS.md +61 -26
- package/templates/HARNESS.md +135 -0
- package/templates/SDD.md +87 -78
- package/templates/features/context.md +69 -0
- package/templates/features/plan.md +71 -0
- package/templates/features/spec.md +88 -0
- package/templates/features/tasks.md +290 -0
- package/templates/features/validation.md +52 -0
- package/templates/quick/SUMMARY.md +53 -0
- package/templates/quick/TASK.md +71 -0
package/bin/km-spec
CHANGED
|
@@ -11,7 +11,8 @@ done
|
|
|
11
11
|
SCRIPT_DIR="$(cd -P "$(dirname "$_SOURCE")" && pwd)"
|
|
12
12
|
TEMPLATES_DIR="${SCRIPT_DIR}/../templates"
|
|
13
13
|
|
|
14
|
-
VERSION="0.
|
|
14
|
+
VERSION="0.2.0"
|
|
15
|
+
TEMPLATE_VERSION="1.0"
|
|
15
16
|
|
|
16
17
|
# ─── Logo & Help ──────────────────────────────────────────────────────────────
|
|
17
18
|
|
|
@@ -31,7 +32,7 @@ EOF
|
|
|
31
32
|
|
|
32
33
|
print_help() {
|
|
33
34
|
print_logo
|
|
34
|
-
cat <<
|
|
35
|
+
cat <<EOF
|
|
35
36
|
|
|
36
37
|
Usage:
|
|
37
38
|
km-spec <command> [options]
|
|
@@ -47,11 +48,36 @@ Commands:
|
|
|
47
48
|
testing Update Testing in CONVENTIONS.md
|
|
48
49
|
integrations Update Integrations in CONVENTIONS.md
|
|
49
50
|
concerns Update Concerns in CONVENTIONS.md
|
|
50
|
-
feature <name>
|
|
51
|
-
quick <name>
|
|
51
|
+
feature <name> [options] Create a feature spec (non-interactive by default)
|
|
52
|
+
quick <name> [options] Create a quick task (non-interactive by default)
|
|
52
53
|
status Show SDD status
|
|
53
54
|
doctor Validate SDD structure
|
|
54
55
|
help Show this help
|
|
56
|
+
version Show version
|
|
57
|
+
|
|
58
|
+
Feature options:
|
|
59
|
+
--description <text> Confirmed overview text for the feature
|
|
60
|
+
--tier standard|complex Planning depth (default: standard)
|
|
61
|
+
--interactive Prompt for fields (legacy interview mode)
|
|
62
|
+
--force Overwrite files in the target feature directory
|
|
63
|
+
|
|
64
|
+
Quick options:
|
|
65
|
+
--description <text> Confirmed objective/description text
|
|
66
|
+
--interactive Prompt for fields (legacy interview mode)
|
|
67
|
+
--force Overwrite files in the target quick-task directory
|
|
68
|
+
|
|
69
|
+
Examples:
|
|
70
|
+
km-spec init
|
|
71
|
+
km-spec feature "User Authentication"
|
|
72
|
+
km-spec feature "User Authentication" --description "Login and session management"
|
|
73
|
+
km-spec feature "SSO Federation" --tier complex
|
|
74
|
+
km-spec feature "User Authentication" --interactive
|
|
75
|
+
km-spec quick "Fix login validation"
|
|
76
|
+
km-spec quick "Fix login validation" --description "Reject expired sessions"
|
|
77
|
+
km-spec doctor
|
|
78
|
+
km-spec status
|
|
79
|
+
|
|
80
|
+
km-spec v${VERSION}
|
|
55
81
|
EOF
|
|
56
82
|
}
|
|
57
83
|
|
|
@@ -97,45 +123,121 @@ confirm() {
|
|
|
97
123
|
[[ "$reply" =~ ^[Yy]$ ]]
|
|
98
124
|
}
|
|
99
125
|
|
|
100
|
-
#
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
local
|
|
106
|
-
|
|
107
|
-
local content="$3"
|
|
108
|
-
local tmp_content tmp_result
|
|
109
|
-
tmp_content="$(mktemp)"
|
|
110
|
-
tmp_result="$(mktemp)"
|
|
126
|
+
# Multi-line safe: use awk for placeholder replacement without eval.
|
|
127
|
+
# render_template SRC DEST KEY=VALUE KEY=VALUE ...
|
|
128
|
+
# Keys are placeholder names without braces (e.g. FEATURE_NAME).
|
|
129
|
+
render_template() {
|
|
130
|
+
local src="$1"
|
|
131
|
+
local dest="$2"
|
|
132
|
+
shift 2
|
|
111
133
|
|
|
112
|
-
|
|
134
|
+
if [[ ! -f "$src" ]]; then
|
|
135
|
+
printf 'Error: template not found: %s\n' "$src" >&2
|
|
136
|
+
exit 1
|
|
137
|
+
fi
|
|
113
138
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
$
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
139
|
+
local tmp
|
|
140
|
+
tmp="$(mktemp)"
|
|
141
|
+
cp "$src" "$tmp"
|
|
142
|
+
|
|
143
|
+
local pair key val
|
|
144
|
+
for pair in "$@"; do
|
|
145
|
+
key="${pair%%=*}"
|
|
146
|
+
val="${pair#*=}"
|
|
147
|
+
# Use awk with ENVIRON-style via -v and index replace for all occurrences.
|
|
148
|
+
# Pass value via file to preserve newlines safely.
|
|
149
|
+
local valfile
|
|
150
|
+
valfile="$(mktemp)"
|
|
151
|
+
printf '%s' "$val" > "$valfile"
|
|
152
|
+
local out
|
|
153
|
+
out="$(mktemp)"
|
|
154
|
+
awk -v key="{{${key}}}" -v vf="$valfile" '
|
|
155
|
+
BEGIN {
|
|
156
|
+
repl = ""
|
|
157
|
+
while ((getline line < vf) > 0) {
|
|
158
|
+
if (repl != "") repl = repl "\n"
|
|
159
|
+
repl = repl line
|
|
160
|
+
}
|
|
161
|
+
# trailing content without final newline still ok
|
|
162
|
+
close(vf)
|
|
133
163
|
}
|
|
134
|
-
|
|
135
|
-
|
|
164
|
+
{
|
|
165
|
+
n = index($0, key)
|
|
166
|
+
if (n == 0) { print; next }
|
|
167
|
+
# replace all occurrences on the line
|
|
168
|
+
line = $0
|
|
169
|
+
out = ""
|
|
170
|
+
while ((n = index(line, key)) > 0) {
|
|
171
|
+
out = out substr(line, 1, n - 1) repl
|
|
172
|
+
line = substr(line, n + length(key))
|
|
173
|
+
}
|
|
174
|
+
print out line
|
|
175
|
+
}
|
|
176
|
+
' "$tmp" > "$out"
|
|
177
|
+
mv "$out" "$tmp"
|
|
178
|
+
rm -f "$valfile"
|
|
179
|
+
done
|
|
136
180
|
|
|
137
|
-
|
|
138
|
-
|
|
181
|
+
mkdir -p "$(dirname "$dest")"
|
|
182
|
+
cp "$tmp" "$dest"
|
|
183
|
+
rm -f "$tmp"
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
next_feature_id() {
|
|
187
|
+
local n=0
|
|
188
|
+
local d
|
|
189
|
+
if [[ -d ".specs/features" ]]; then
|
|
190
|
+
for d in .specs/features/*/; do
|
|
191
|
+
[[ -d "$d" ]] || continue
|
|
192
|
+
n=$((n + 1))
|
|
193
|
+
done
|
|
194
|
+
fi
|
|
195
|
+
# When creating a new dir, this count includes existing only (new not yet created).
|
|
196
|
+
printf 'FEAT-%03d' $((n + 1))
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
complex_sections_text() {
|
|
200
|
+
cat <<'COMPLEX'
|
|
201
|
+
## Component Boundaries
|
|
202
|
+
|
|
203
|
+
| Boundary | Owns | Must not own | Confidence |
|
|
204
|
+
|----------|------|--------------|------------|
|
|
205
|
+
| [NEEDS CLARIFICATION] | | | [NEEDS CLARIFICATION] |
|
|
206
|
+
|
|
207
|
+
## Data Model
|
|
208
|
+
|
|
209
|
+
| Entity / table | Fields (summary) | Notes | Confidence |
|
|
210
|
+
|----------------|------------------|-------|------------|
|
|
211
|
+
| [NEEDS CLARIFICATION] | | | [NEEDS CLARIFICATION] |
|
|
212
|
+
|
|
213
|
+
## API or Interface Contracts
|
|
214
|
+
|
|
215
|
+
### Endpoint / interface: [NEEDS CLARIFICATION]
|
|
216
|
+
|
|
217
|
+
- **Request:** [NEEDS CLARIFICATION]
|
|
218
|
+
- **Response:** [NEEDS CLARIFICATION]
|
|
219
|
+
- **Errors:** [NEEDS CLARIFICATION]
|
|
220
|
+
|
|
221
|
+
## Security
|
|
222
|
+
|
|
223
|
+
- [NEEDS CLARIFICATION]: Authn/authz, input validation, secrets, threat notes.
|
|
224
|
+
|
|
225
|
+
## Performance
|
|
226
|
+
|
|
227
|
+
- [NEEDS CLARIFICATION]: Latency, throughput, N+1, caching, limits.
|
|
228
|
+
|
|
229
|
+
## Migration or Rollout
|
|
230
|
+
|
|
231
|
+
- [NEEDS CLARIFICATION]: Schema migrations, feature flags, phased rollout.
|
|
232
|
+
|
|
233
|
+
## Observability
|
|
234
|
+
|
|
235
|
+
- [NEEDS CLARIFICATION]: Logs, metrics, traces, alerts.
|
|
236
|
+
|
|
237
|
+
## Rollback Strategy
|
|
238
|
+
|
|
239
|
+
- [NEEDS CLARIFICATION]: How to revert safely if the release fails.
|
|
240
|
+
COMPLEX
|
|
139
241
|
}
|
|
140
242
|
|
|
141
243
|
# ─── cmd_help ─────────────────────────────────────────────────────────────────
|
|
@@ -156,6 +258,7 @@ cmd_init() {
|
|
|
156
258
|
|
|
157
259
|
local files=(
|
|
158
260
|
"AGENTS.md"
|
|
261
|
+
"HARNESS.md"
|
|
159
262
|
"SDD.md"
|
|
160
263
|
".specs/project/PROJECT.md"
|
|
161
264
|
".specs/project/ROADMAP.md"
|
|
@@ -192,220 +295,398 @@ cmd_init() {
|
|
|
192
295
|
|
|
193
296
|
printf '\n[KM Spec] Done. Created: %d Skipped: %d\n' "$created" "$skipped"
|
|
194
297
|
printf '\nNext steps:\n'
|
|
195
|
-
printf ' km-spec
|
|
196
|
-
printf ' km-spec
|
|
298
|
+
printf ' km-spec feature "Your Feature" — scaffold a guided feature spec\n'
|
|
299
|
+
printf ' km-spec project — fill in project goals (optional interview)\n'
|
|
300
|
+
printf ' Read HARNESS.md and AGENTS.md before agent work\n'
|
|
197
301
|
}
|
|
198
302
|
|
|
199
303
|
# ─── cmd_feature ──────────────────────────────────────────────────────────────
|
|
200
304
|
|
|
201
305
|
cmd_feature() {
|
|
202
306
|
require_init
|
|
307
|
+
require_templates
|
|
203
308
|
|
|
204
309
|
local name="${1:-}"
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if [[ -z "$name" ]]; then
|
|
208
|
-
printf 'Usage: km-spec feature "Feature Name"\n' >&2
|
|
310
|
+
if [[ -z "$name" || "$name" == --* ]]; then
|
|
311
|
+
printf 'Usage: km-spec feature "Feature Name" [--description TEXT] [--tier standard|complex] [--interactive] [--force]\n' >&2
|
|
209
312
|
exit 1
|
|
210
313
|
fi
|
|
211
|
-
|
|
212
314
|
shift || true
|
|
213
|
-
|
|
315
|
+
|
|
316
|
+
local force=false interactive=false description="" tier="standard"
|
|
317
|
+
|
|
318
|
+
while [[ $# -gt 0 ]]; do
|
|
319
|
+
case "$1" in
|
|
320
|
+
--force) force=true; shift ;;
|
|
321
|
+
--interactive) interactive=true; shift ;;
|
|
322
|
+
--description)
|
|
323
|
+
if [[ -z "${2:-}" || "${2:-}" == --* ]]; then
|
|
324
|
+
printf 'Error: --description requires a value.\n' >&2
|
|
325
|
+
exit 1
|
|
326
|
+
fi
|
|
327
|
+
description="$2"
|
|
328
|
+
shift 2
|
|
329
|
+
;;
|
|
330
|
+
--description=*)
|
|
331
|
+
description="${1#--description=}"
|
|
332
|
+
shift
|
|
333
|
+
;;
|
|
334
|
+
--tier)
|
|
335
|
+
if [[ -z "${2:-}" || "${2:-}" == --* ]]; then
|
|
336
|
+
printf 'Error: --tier requires a value (standard|complex).\n' >&2
|
|
337
|
+
exit 1
|
|
338
|
+
fi
|
|
339
|
+
tier="$2"
|
|
340
|
+
shift 2
|
|
341
|
+
;;
|
|
342
|
+
--tier=*)
|
|
343
|
+
tier="${1#--tier=}"
|
|
344
|
+
shift
|
|
345
|
+
;;
|
|
346
|
+
*)
|
|
347
|
+
printf 'Error: unknown option for feature: %s\n' "$1" >&2
|
|
348
|
+
exit 1
|
|
349
|
+
;;
|
|
350
|
+
esac
|
|
351
|
+
done
|
|
352
|
+
|
|
353
|
+
case "$tier" in
|
|
354
|
+
standard|complex) ;;
|
|
355
|
+
*)
|
|
356
|
+
printf 'Error: unknown tier "%s". Supported tiers: standard, complex.\n' "$tier" >&2
|
|
357
|
+
exit 1
|
|
358
|
+
;;
|
|
359
|
+
esac
|
|
214
360
|
|
|
215
361
|
local slug
|
|
216
362
|
slug="$(slugify "$name")"
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
if [[ -d "$dir" ]] && [[ "$force" != true ]]; then
|
|
220
|
-
printf 'Feature already exists: %s\nUse --force to overwrite.\n' "$dir"
|
|
363
|
+
if [[ -z "$slug" ]]; then
|
|
364
|
+
printf 'Error: could not derive a safe slug from feature name.\n' >&2
|
|
221
365
|
exit 1
|
|
222
366
|
fi
|
|
223
367
|
|
|
224
|
-
|
|
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.
|
|
368
|
+
local dir=".specs/features/$slug"
|
|
301
369
|
|
|
302
|
-
|
|
303
|
-
|
|
370
|
+
# Path safety: slug is already alnum+hyphen only
|
|
371
|
+
if [[ "$slug" == *"/"* || "$slug" == *".."* ]]; then
|
|
372
|
+
printf 'Error: invalid feature slug.\n' >&2
|
|
373
|
+
exit 1
|
|
374
|
+
fi
|
|
304
375
|
|
|
305
|
-
|
|
306
|
-
|
|
376
|
+
if [[ -d "$dir" || -e "$dir" ]] && [[ "$force" != true ]]; then
|
|
377
|
+
printf 'Feature already exists: %s\nUse --force to overwrite generated files in this directory only.\n' "$dir" >&2
|
|
378
|
+
exit 1
|
|
379
|
+
fi
|
|
307
380
|
|
|
308
|
-
|
|
381
|
+
if [[ "$force" == true ]] && [[ -d "$dir" ]]; then
|
|
382
|
+
printf '[KM Spec] --force: will replace generated files in %s:\n' "$dir"
|
|
383
|
+
printf ' - %s/spec.md\n' "$dir"
|
|
384
|
+
printf ' - %s/context.md\n' "$dir"
|
|
385
|
+
printf ' - %s/plan.md\n' "$dir"
|
|
386
|
+
printf ' - %s/tasks.md\n' "$dir"
|
|
387
|
+
printf ' - %s/validation.md\n' "$dir"
|
|
388
|
+
fi
|
|
309
389
|
|
|
310
|
-
|
|
390
|
+
mkdir -p "$dir"
|
|
311
391
|
|
|
312
|
-
|
|
392
|
+
local feature_id created_date
|
|
393
|
+
created_date="$(date '+%Y-%m-%d')"
|
|
394
|
+
feature_id="$(next_feature_id)"
|
|
395
|
+
# If force-overwrite and existing FEAT id present, reuse it
|
|
396
|
+
if [[ -f "$dir/spec.md" ]]; then
|
|
397
|
+
local existing_id
|
|
398
|
+
existing_id="$(grep -E '\*\*Feature ID\*\*' "$dir/spec.md" 2>/dev/null | head -1 | sed -E 's/.*\|\s*(FEAT-[A-Za-z0-9-]+)\s*\|.*/\1/' || true)"
|
|
399
|
+
if [[ "$existing_id" == FEAT-* ]]; then
|
|
400
|
+
feature_id="$existing_id"
|
|
401
|
+
fi
|
|
402
|
+
fi
|
|
313
403
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
404
|
+
local overview_block confirmed_block inferred_block
|
|
405
|
+
if [[ -n "$description" ]]; then
|
|
406
|
+
overview_block="[CONFIRMED]: ${description}"
|
|
407
|
+
confirmed_block="- [CONFIRMED]: Feature name is **${name}**.
|
|
408
|
+
- [CONFIRMED]: Overview description provided at scaffold time (see Overview)."
|
|
409
|
+
else
|
|
410
|
+
overview_block="[NEEDS CLARIFICATION]: Write 1–3 sentences describing what this feature does and why it exists. Do not invent product claims."
|
|
411
|
+
confirmed_block="- [CONFIRMED]: Feature name is **${name}** (scaffold title only).
|
|
412
|
+
- [NEEDS CLARIFICATION]: No overview description was provided at scaffold time."
|
|
413
|
+
fi
|
|
321
414
|
|
|
322
|
-
|
|
415
|
+
inferred_block="- None yet. Do **not** invent product requirements from the feature name.
|
|
416
|
+
- Optional later: list non-binding ideas here only with explicit \`[INFERRED]\` labels, then promote with evidence."
|
|
417
|
+
|
|
418
|
+
# Interactive mode fills optional fields into overview/confirmed without inventing REQs
|
|
419
|
+
if [[ "$interactive" == true ]]; then
|
|
420
|
+
printf '\nInteractive feature setup: %s\n' "$name"
|
|
421
|
+
printf '(Leave blank to keep template placeholders. Requirements are never auto-invented.)\n\n'
|
|
422
|
+
local goal oos
|
|
423
|
+
goal="" oos=""
|
|
424
|
+
read -r -p "Feature goal / overview: " goal || true
|
|
425
|
+
read -r -p "Out of scope (optional): " oos || true
|
|
426
|
+
if [[ -n "$goal" ]]; then
|
|
427
|
+
overview_block="[CONFIRMED]: ${goal}"
|
|
428
|
+
confirmed_block="- [CONFIRMED]: Feature name is **${name}**.
|
|
429
|
+
- [CONFIRMED]: Overview provided interactively (see Overview)."
|
|
430
|
+
description="$goal"
|
|
431
|
+
fi
|
|
432
|
+
if [[ -n "$oos" ]]; then
|
|
433
|
+
# Append note via environment for post-pass; store for sed after render
|
|
434
|
+
:
|
|
435
|
+
fi
|
|
436
|
+
fi
|
|
323
437
|
|
|
324
|
-
|
|
438
|
+
local complex_sections=""
|
|
439
|
+
if [[ "$tier" == "complex" ]]; then
|
|
440
|
+
complex_sections="$(complex_sections_text)"
|
|
441
|
+
else
|
|
442
|
+
complex_sections="<!-- Complex-tier sections omitted (tier=standard). Re-scaffold with --tier complex or add sections manually if complexity grows. -->"
|
|
443
|
+
fi
|
|
325
444
|
|
|
326
|
-
|
|
445
|
+
local desc_context=""
|
|
446
|
+
if [[ -n "$description" ]]; then
|
|
447
|
+
desc_context="Scaffold description ([CONFIRMED] at creation): ${description}"
|
|
448
|
+
else
|
|
449
|
+
desc_context="[NEEDS CLARIFICATION]: No scaffold description provided."
|
|
450
|
+
fi
|
|
327
451
|
|
|
328
|
-
|
|
329
|
-
|
|
452
|
+
printf '\nCreating feature: %s (tier=%s)\n\n' "$name" "$tier"
|
|
453
|
+
|
|
454
|
+
local tdir="$TEMPLATES_DIR/features"
|
|
455
|
+
render_template "$tdir/spec.md" "$dir/spec.md" \
|
|
456
|
+
"FEATURE_NAME=${name}" \
|
|
457
|
+
"FEATURE_SLUG=${slug}" \
|
|
458
|
+
"FEATURE_ID=${feature_id}" \
|
|
459
|
+
"DESCRIPTION=${description}" \
|
|
460
|
+
"CREATED_DATE=${created_date}" \
|
|
461
|
+
"TIER=${tier}" \
|
|
462
|
+
"TEMPLATE_VERSION=${TEMPLATE_VERSION}" \
|
|
463
|
+
"OVERVIEW_BLOCK=${overview_block}" \
|
|
464
|
+
"CONFIRMED_BLOCK=${confirmed_block}" \
|
|
465
|
+
"INFERRED_BLOCK=${inferred_block}"
|
|
466
|
+
|
|
467
|
+
render_template "$tdir/context.md" "$dir/context.md" \
|
|
468
|
+
"FEATURE_NAME=${name}" \
|
|
469
|
+
"FEATURE_SLUG=${slug}" \
|
|
470
|
+
"FEATURE_ID=${feature_id}" \
|
|
471
|
+
"DESCRIPTION=${description}" \
|
|
472
|
+
"CREATED_DATE=${created_date}" \
|
|
473
|
+
"TIER=${tier}" \
|
|
474
|
+
"TEMPLATE_VERSION=${TEMPLATE_VERSION}" \
|
|
475
|
+
"DESCRIPTION_CONTEXT=${desc_context}"
|
|
476
|
+
|
|
477
|
+
render_template "$tdir/plan.md" "$dir/plan.md" \
|
|
478
|
+
"FEATURE_NAME=${name}" \
|
|
479
|
+
"FEATURE_SLUG=${slug}" \
|
|
480
|
+
"FEATURE_ID=${feature_id}" \
|
|
481
|
+
"DESCRIPTION=${description}" \
|
|
482
|
+
"CREATED_DATE=${created_date}" \
|
|
483
|
+
"TIER=${tier}" \
|
|
484
|
+
"TEMPLATE_VERSION=${TEMPLATE_VERSION}" \
|
|
485
|
+
"COMPLEX_SECTIONS=${complex_sections}"
|
|
486
|
+
|
|
487
|
+
render_template "$tdir/tasks.md" "$dir/tasks.md" \
|
|
488
|
+
"FEATURE_NAME=${name}" \
|
|
489
|
+
"FEATURE_SLUG=${slug}" \
|
|
490
|
+
"FEATURE_ID=${feature_id}" \
|
|
491
|
+
"DESCRIPTION=${description}" \
|
|
492
|
+
"CREATED_DATE=${created_date}" \
|
|
493
|
+
"TIER=${tier}" \
|
|
494
|
+
"TEMPLATE_VERSION=${TEMPLATE_VERSION}"
|
|
495
|
+
|
|
496
|
+
render_template "$tdir/validation.md" "$dir/validation.md" \
|
|
497
|
+
"FEATURE_NAME=${name}" \
|
|
498
|
+
"FEATURE_SLUG=${slug}" \
|
|
499
|
+
"FEATURE_ID=${feature_id}" \
|
|
500
|
+
"DESCRIPTION=${description}" \
|
|
501
|
+
"CREATED_DATE=${created_date}" \
|
|
502
|
+
"TIER=${tier}" \
|
|
503
|
+
"TEMPLATE_VERSION=${TEMPLATE_VERSION}"
|
|
504
|
+
|
|
505
|
+
# Interactive optional out-of-scope injection
|
|
506
|
+
if [[ "$interactive" == true ]]; then
|
|
507
|
+
local oos_val="${oos:-}"
|
|
508
|
+
if [[ -n "${oos_val:-}" ]]; then
|
|
509
|
+
# Replace first NEEDS CLARIFICATION out-of-scope bullet with confirmed text
|
|
510
|
+
local tmp
|
|
511
|
+
tmp="$(mktemp)"
|
|
512
|
+
awk -v oos="$oos_val" '
|
|
513
|
+
BEGIN { done=0 }
|
|
514
|
+
!done && /\[NEEDS CLARIFICATION\]: List what this feature/ {
|
|
515
|
+
print "- [CONFIRMED]: " oos
|
|
516
|
+
done=1
|
|
517
|
+
next
|
|
518
|
+
}
|
|
519
|
+
{ print }
|
|
520
|
+
' "$dir/spec.md" > "$tmp"
|
|
521
|
+
mv "$tmp" "$dir/spec.md"
|
|
522
|
+
fi
|
|
523
|
+
fi
|
|
330
524
|
|
|
331
|
-
printf '\n'
|
|
332
525
|
printf '[KM Spec] Created: %s/spec.md\n' "$dir"
|
|
526
|
+
printf '[KM Spec] Created: %s/context.md\n' "$dir"
|
|
527
|
+
printf '[KM Spec] Created: %s/plan.md\n' "$dir"
|
|
333
528
|
printf '[KM Spec] Created: %s/tasks.md\n' "$dir"
|
|
334
529
|
printf '[KM Spec] Created: %s/validation.md\n' "$dir"
|
|
335
|
-
printf '\nNext
|
|
336
|
-
printf '
|
|
530
|
+
printf '\nNext steps:\n'
|
|
531
|
+
printf ' 1. Fill spec.md (requirements stay unresolved until confirmed).\n'
|
|
532
|
+
printf ' 2. Ask your agent to follow AGENTS.md + HARNESS.md starting at TASK-001.\n'
|
|
337
533
|
}
|
|
338
534
|
|
|
339
535
|
# ─── cmd_quick ────────────────────────────────────────────────────────────────
|
|
340
536
|
|
|
341
537
|
cmd_quick() {
|
|
342
538
|
require_init
|
|
539
|
+
require_templates
|
|
343
540
|
|
|
344
541
|
local name="${1:-}"
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
if [[ -z "$name" ]]; then
|
|
348
|
-
printf 'Usage: km-spec quick "Task Name"\n' >&2
|
|
542
|
+
if [[ -z "$name" || "$name" == --* ]]; then
|
|
543
|
+
printf 'Usage: km-spec quick "Task Name" [--description TEXT] [--interactive] [--force]\n' >&2
|
|
349
544
|
exit 1
|
|
350
545
|
fi
|
|
351
|
-
|
|
352
546
|
shift || true
|
|
353
|
-
|
|
547
|
+
|
|
548
|
+
local force=false interactive=false description=""
|
|
549
|
+
|
|
550
|
+
while [[ $# -gt 0 ]]; do
|
|
551
|
+
case "$1" in
|
|
552
|
+
--force) force=true; shift ;;
|
|
553
|
+
--interactive) interactive=true; shift ;;
|
|
554
|
+
--description)
|
|
555
|
+
if [[ -z "${2:-}" || "${2:-}" == --* ]]; then
|
|
556
|
+
printf 'Error: --description requires a value.\n' >&2
|
|
557
|
+
exit 1
|
|
558
|
+
fi
|
|
559
|
+
description="$2"
|
|
560
|
+
shift 2
|
|
561
|
+
;;
|
|
562
|
+
--description=*)
|
|
563
|
+
description="${1#--description=}"
|
|
564
|
+
shift
|
|
565
|
+
;;
|
|
566
|
+
*)
|
|
567
|
+
printf 'Error: unknown option for quick: %s\n' "$1" >&2
|
|
568
|
+
exit 1
|
|
569
|
+
;;
|
|
570
|
+
esac
|
|
571
|
+
done
|
|
354
572
|
|
|
355
573
|
local slug
|
|
356
574
|
slug="$(slugify "$name")"
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
575
|
+
if [[ -z "$slug" ]]; then
|
|
576
|
+
printf 'Error: could not derive a safe slug from task name.\n' >&2
|
|
577
|
+
exit 1
|
|
578
|
+
fi
|
|
579
|
+
if [[ "$slug" == *"/"* || "$slug" == *".."* ]]; then
|
|
580
|
+
printf 'Error: invalid quick-task slug.\n' >&2
|
|
361
581
|
exit 1
|
|
362
582
|
fi
|
|
363
583
|
|
|
364
|
-
|
|
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:
|
|
584
|
+
local dir=".specs/quick/$slug"
|
|
585
|
+
local legacy_file=".specs/quick/${slug}.md"
|
|
385
586
|
|
|
386
|
-
- $
|
|
587
|
+
if [[ -e "$dir" || -f "$legacy_file" ]] && [[ "$force" != true ]]; then
|
|
588
|
+
if [[ -f "$legacy_file" ]]; then
|
|
589
|
+
printf 'Quick task already exists (legacy file): %s\nUse --force to replace with directory layout (legacy file left in place unless removed manually).\n' "$legacy_file" >&2
|
|
590
|
+
else
|
|
591
|
+
printf 'Quick task already exists: %s\nUse --force to overwrite generated files in this directory only.\n' "$dir" >&2
|
|
592
|
+
fi
|
|
593
|
+
exit 1
|
|
594
|
+
fi
|
|
387
595
|
|
|
388
|
-
|
|
596
|
+
if [[ "$force" == true ]]; then
|
|
597
|
+
printf '[KM Spec] --force: will replace generated files in %s:\n' "$dir"
|
|
598
|
+
printf ' - %s/TASK.md\n' "$dir"
|
|
599
|
+
printf ' - %s/SUMMARY.md\n' "$dir"
|
|
600
|
+
fi
|
|
389
601
|
|
|
390
|
-
-
|
|
602
|
+
mkdir -p "$dir"
|
|
391
603
|
|
|
392
|
-
|
|
604
|
+
local created_date
|
|
605
|
+
created_date="$(date '+%Y-%m-%d')"
|
|
393
606
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
607
|
+
local quick_objective quick_description
|
|
608
|
+
if [[ -n "$description" ]]; then
|
|
609
|
+
quick_objective="[CONFIRMED]: ${description}"
|
|
610
|
+
quick_description="[CONFIRMED]: ${description}"
|
|
611
|
+
else
|
|
612
|
+
quick_objective="[NEEDS CLARIFICATION]: State the objective in one sentence. Do not invent scope."
|
|
613
|
+
quick_description="[NEEDS CLARIFICATION]: Add brief context for the change. Inspect the repository before listing files."
|
|
614
|
+
fi
|
|
399
615
|
|
|
400
|
-
|
|
616
|
+
if [[ "$interactive" == true ]]; then
|
|
617
|
+
printf '\nInteractive quick task setup: %s\n\n' "$name"
|
|
618
|
+
local objective files_affected ac validation_steps
|
|
619
|
+
objective="" files_affected="" ac="" validation_steps=""
|
|
620
|
+
read -r -p "Objective: " objective || true
|
|
621
|
+
read -r -p "Likely affected files: " files_affected || true
|
|
622
|
+
read -r -p "Acceptance criteria: " ac || true
|
|
623
|
+
read -r -p "Validation steps: " validation_steps || true
|
|
624
|
+
if [[ -n "$objective" ]]; then
|
|
625
|
+
quick_objective="[CONFIRMED]: ${objective}"
|
|
626
|
+
quick_description="[CONFIRMED]: ${objective}"
|
|
627
|
+
fi
|
|
628
|
+
fi
|
|
401
629
|
|
|
402
|
-
|
|
630
|
+
printf '\nCreating quick task: %s\n\n' "$name"
|
|
403
631
|
|
|
404
|
-
|
|
405
|
-
|
|
632
|
+
local tdir="$TEMPLATES_DIR/quick"
|
|
633
|
+
render_template "$tdir/TASK.md" "$dir/TASK.md" \
|
|
634
|
+
"FEATURE_NAME=${name}" \
|
|
635
|
+
"FEATURE_SLUG=${slug}" \
|
|
636
|
+
"FEATURE_ID=" \
|
|
637
|
+
"DESCRIPTION=${description}" \
|
|
638
|
+
"CREATED_DATE=${created_date}" \
|
|
639
|
+
"TIER=quick" \
|
|
640
|
+
"TEMPLATE_VERSION=${TEMPLATE_VERSION}" \
|
|
641
|
+
"QUICK_OBJECTIVE=${quick_objective}" \
|
|
642
|
+
"QUICK_DESCRIPTION=${quick_description}"
|
|
643
|
+
|
|
644
|
+
render_template "$tdir/SUMMARY.md" "$dir/SUMMARY.md" \
|
|
645
|
+
"FEATURE_NAME=${name}" \
|
|
646
|
+
"FEATURE_SLUG=${slug}" \
|
|
647
|
+
"FEATURE_ID=" \
|
|
648
|
+
"DESCRIPTION=${description}" \
|
|
649
|
+
"CREATED_DATE=${created_date}" \
|
|
650
|
+
"TIER=quick" \
|
|
651
|
+
"TEMPLATE_VERSION=${TEMPLATE_VERSION}"
|
|
652
|
+
|
|
653
|
+
# Interactive: inject optional fields into TASK.md if provided
|
|
654
|
+
if [[ "$interactive" == true ]]; then
|
|
655
|
+
if [[ -n "${files_affected:-}" ]]; then
|
|
656
|
+
local tmp
|
|
657
|
+
tmp="$(mktemp)"
|
|
658
|
+
awk -v f="$files_affected" '
|
|
659
|
+
/\[NEEDS CLARIFICATION\]: `path\/to\/file`/ && !done {
|
|
660
|
+
print "- [CONFIRMED]: " f
|
|
661
|
+
done=1
|
|
662
|
+
next
|
|
663
|
+
}
|
|
664
|
+
{ print }
|
|
665
|
+
' "$dir/TASK.md" > "$tmp"
|
|
666
|
+
mv "$tmp" "$dir/TASK.md"
|
|
667
|
+
fi
|
|
668
|
+
if [[ -n "${ac:-}" ]]; then
|
|
669
|
+
local tmp
|
|
670
|
+
tmp="$(mktemp)"
|
|
671
|
+
awk -v a="$ac" '
|
|
672
|
+
/\[NEEDS CLARIFICATION\]: Observable outcome/ && !done {
|
|
673
|
+
print "- [ ] [CONFIRMED]: " a
|
|
674
|
+
done=1
|
|
675
|
+
next
|
|
676
|
+
}
|
|
677
|
+
{ print }
|
|
678
|
+
' "$dir/TASK.md" > "$tmp"
|
|
679
|
+
mv "$tmp" "$dir/TASK.md"
|
|
680
|
+
fi
|
|
681
|
+
if [[ -n "${validation_steps:-}" ]]; then
|
|
682
|
+
printf '\n## Interactive validation notes\n\n%s\n' "$validation_steps" >> "$dir/TASK.md"
|
|
683
|
+
fi
|
|
684
|
+
fi
|
|
406
685
|
|
|
407
|
-
printf '
|
|
408
|
-
printf '
|
|
686
|
+
printf '[KM Spec] Created: %s/TASK.md\n' "$dir"
|
|
687
|
+
printf '[KM Spec] Created: %s/SUMMARY.md\n' "$dir"
|
|
688
|
+
printf '\nNext step: ask your agent to complete TASK.md following AGENTS.md and HARNESS.md,\n'
|
|
689
|
+
printf 'then fill SUMMARY.md and update STATE.md.\n'
|
|
409
690
|
}
|
|
410
691
|
|
|
411
692
|
# ─── cmd_project ──────────────────────────────────────────────────────────────
|
|
@@ -478,7 +759,7 @@ $nonneg_md
|
|
|
478
759
|
PROJECT
|
|
479
760
|
|
|
480
761
|
printf '\n[KM Spec] Updated: %s\n' "$file"
|
|
481
|
-
printf '\nNext step: km-spec
|
|
762
|
+
printf '\nNext step: km-spec feature "Your first feature"\n'
|
|
482
763
|
}
|
|
483
764
|
|
|
484
765
|
# ─── cmd_roadmap ──────────────────────────────────────────────────────────────
|
|
@@ -574,8 +855,17 @@ cmd_state() {
|
|
|
574
855
|
next_md="$(printf '%s' "$next_steps" | tr ',' '\n' | awk 'NR>0{print NR". "$0}' | sed 's/^[0-9]*\.[[:space:]]*/& /')"
|
|
575
856
|
|
|
576
857
|
local decisions_text blockers_text
|
|
577
|
-
|
|
578
|
-
|
|
858
|
+
# Use printf -- so values starting with '-' are not treated as options
|
|
859
|
+
if [[ -n "$decision" ]]; then
|
|
860
|
+
decisions_text="$(printf -- '- %s' "$decision")"
|
|
861
|
+
else
|
|
862
|
+
decisions_text="- None."
|
|
863
|
+
fi
|
|
864
|
+
if [[ -n "$blocker" ]]; then
|
|
865
|
+
blockers_text="$(printf -- '- %s' "$blocker")"
|
|
866
|
+
else
|
|
867
|
+
blockers_text="- None."
|
|
868
|
+
fi
|
|
579
869
|
|
|
580
870
|
cat >> "$file" <<SESSION
|
|
581
871
|
|
|
@@ -795,13 +1085,51 @@ cmd_concerns() {
|
|
|
795
1085
|
printf '\nRun km-spec status to review the full setup.\n'
|
|
796
1086
|
}
|
|
797
1087
|
|
|
1088
|
+
# update_section FILE HEADING NEW_CONTENT
|
|
1089
|
+
update_section() {
|
|
1090
|
+
local file="$1"
|
|
1091
|
+
local heading="$2"
|
|
1092
|
+
local content="$3"
|
|
1093
|
+
local tmp_content tmp_result
|
|
1094
|
+
tmp_content="$(mktemp)"
|
|
1095
|
+
tmp_result="$(mktemp)"
|
|
1096
|
+
|
|
1097
|
+
printf '%s\n' "$content" > "$tmp_content"
|
|
1098
|
+
|
|
1099
|
+
awk -v heading="$heading" -v cf="$tmp_content" '
|
|
1100
|
+
BEGIN {
|
|
1101
|
+
in_section=0; inserted=0; n=0
|
|
1102
|
+
while ((getline line < cf) > 0) lines[++n] = line
|
|
1103
|
+
close(cf)
|
|
1104
|
+
}
|
|
1105
|
+
$0 == heading {
|
|
1106
|
+
in_section=1; inserted=1
|
|
1107
|
+
print $0
|
|
1108
|
+
for (i=1; i<=n; i++) print lines[i]
|
|
1109
|
+
next
|
|
1110
|
+
}
|
|
1111
|
+
in_section && /^## / { in_section=0 }
|
|
1112
|
+
!in_section { print }
|
|
1113
|
+
END {
|
|
1114
|
+
if (!inserted) {
|
|
1115
|
+
print ""
|
|
1116
|
+
print heading
|
|
1117
|
+
for (i=1; i<=n; i++) print lines[i]
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
' "$file" > "$tmp_result"
|
|
1121
|
+
|
|
1122
|
+
cp "$tmp_result" "$file"
|
|
1123
|
+
rm -f "$tmp_result" "$tmp_content"
|
|
1124
|
+
}
|
|
1125
|
+
|
|
798
1126
|
# ─── cmd_status ───────────────────────────────────────────────────────────────
|
|
799
1127
|
|
|
800
1128
|
cmd_status() {
|
|
801
1129
|
printf 'KM Spec Status\n\n'
|
|
802
1130
|
|
|
803
1131
|
printf 'Base files:\n'
|
|
804
|
-
local base_files=("AGENTS.md" "SDD.md" ".specs/project/PROJECT.md"
|
|
1132
|
+
local base_files=("AGENTS.md" "HARNESS.md" "SDD.md" ".specs/project/PROJECT.md"
|
|
805
1133
|
".specs/project/ROADMAP.md" ".specs/project/STATE.md"
|
|
806
1134
|
".specs/codebase/CONVENTIONS.md")
|
|
807
1135
|
|
|
@@ -816,10 +1144,34 @@ cmd_status() {
|
|
|
816
1144
|
printf '\nFeatures:\n'
|
|
817
1145
|
local feat_found=false
|
|
818
1146
|
if [[ -d ".specs/features" ]]; then
|
|
1147
|
+
local d
|
|
819
1148
|
for d in .specs/features/*/; do
|
|
820
1149
|
[[ -d "$d" ]] || continue
|
|
821
|
-
printf ' %s\n' "$(basename "$d")"
|
|
822
1150
|
feat_found=true
|
|
1151
|
+
local feat layout tier
|
|
1152
|
+
feat="$(basename "$d")"
|
|
1153
|
+
layout="legacy"
|
|
1154
|
+
if [[ -f "$d/spec.md" && -f "$d/context.md" && -f "$d/plan.md" && -f "$d/tasks.md" && -f "$d/validation.md" ]]; then
|
|
1155
|
+
layout="standard"
|
|
1156
|
+
if grep -qE '\|\s*\*\*Tier\*\*\s*\|\s*complex\s*\|' "$d/spec.md" 2>/dev/null \
|
|
1157
|
+
|| grep -qE '\*\*Tier\*\*.*complex' "$d/plan.md" 2>/dev/null \
|
|
1158
|
+
|| grep -q '## Component Boundaries' "$d/plan.md" 2>/dev/null; then
|
|
1159
|
+
if grep -qiE 'Tier[[:space:]]*\|\s*complex|tier.*complex|\*\*Tier\*\*.*complex' "$d/spec.md" "$d/plan.md" 2>/dev/null; then
|
|
1160
|
+
layout="complex"
|
|
1161
|
+
fi
|
|
1162
|
+
fi
|
|
1163
|
+
# Prefer explicit tier field
|
|
1164
|
+
if grep -qE '\| \*\*Tier\*\* \| complex \|' "$d/spec.md" 2>/dev/null; then
|
|
1165
|
+
layout="complex"
|
|
1166
|
+
elif grep -qE '\| \*\*Tier\*\* \| standard \|' "$d/spec.md" 2>/dev/null; then
|
|
1167
|
+
layout="standard"
|
|
1168
|
+
fi
|
|
1169
|
+
elif [[ -f "$d/spec.md" && -f "$d/tasks.md" && -f "$d/validation.md" ]]; then
|
|
1170
|
+
layout="legacy"
|
|
1171
|
+
else
|
|
1172
|
+
layout="incomplete"
|
|
1173
|
+
fi
|
|
1174
|
+
printf ' %s [%s]\n' "$feat" "$layout"
|
|
823
1175
|
done
|
|
824
1176
|
fi
|
|
825
1177
|
[[ "$feat_found" == true ]] || printf ' (none)\n'
|
|
@@ -827,10 +1179,16 @@ cmd_status() {
|
|
|
827
1179
|
printf '\nQuick tasks:\n'
|
|
828
1180
|
local quick_found=false
|
|
829
1181
|
if [[ -d ".specs/quick" ]]; then
|
|
1182
|
+
local d f
|
|
1183
|
+
for d in .specs/quick/*/; do
|
|
1184
|
+
[[ -d "$d" ]] || continue
|
|
1185
|
+
quick_found=true
|
|
1186
|
+
printf ' %s/ [directory]\n' "$(basename "$d")"
|
|
1187
|
+
done
|
|
830
1188
|
for f in .specs/quick/*.md; do
|
|
831
1189
|
[[ -f "$f" ]] || continue
|
|
832
|
-
printf ' %s\n' "$(basename "$f" .md)"
|
|
833
1190
|
quick_found=true
|
|
1191
|
+
printf ' %s [legacy-file]\n' "$(basename "$f")"
|
|
834
1192
|
done
|
|
835
1193
|
fi
|
|
836
1194
|
[[ "$quick_found" == true ]] || printf ' (none)\n'
|
|
@@ -876,6 +1234,14 @@ cmd_doctor() {
|
|
|
876
1234
|
else err "$f — missing"; errors=$((errors + 1)); fi
|
|
877
1235
|
done
|
|
878
1236
|
|
|
1237
|
+
printf '\nRecommended files:\n'
|
|
1238
|
+
if [[ -f "HARNESS.md" ]]; then
|
|
1239
|
+
ok "HARNESS.md"
|
|
1240
|
+
else
|
|
1241
|
+
warn "HARNESS.md — missing (recommended for agent lifecycle; run km-spec init or copy from package templates)"
|
|
1242
|
+
warnings=$((warnings + 1))
|
|
1243
|
+
fi
|
|
1244
|
+
|
|
879
1245
|
printf '\nRequired directories:\n'
|
|
880
1246
|
local required_dirs=(".specs/project" ".specs/codebase" ".specs/features" ".specs/quick")
|
|
881
1247
|
for d in "${required_dirs[@]}"; do
|
|
@@ -927,20 +1293,50 @@ cmd_doctor() {
|
|
|
927
1293
|
warn "AGENTS.md — missing one or more required file references"
|
|
928
1294
|
warnings=$((warnings + 1))
|
|
929
1295
|
fi
|
|
1296
|
+
if ! grep -q "HARNESS.md" "AGENTS.md" 2>/dev/null; then
|
|
1297
|
+
warn "AGENTS.md — does not reference HARNESS.md (recommended)"
|
|
1298
|
+
warnings=$((warnings + 1))
|
|
1299
|
+
fi
|
|
930
1300
|
fi
|
|
931
1301
|
|
|
932
1302
|
printf '\nFeature integrity:\n'
|
|
933
1303
|
local feat_checked=false
|
|
934
1304
|
if [[ -d ".specs/features" ]]; then
|
|
1305
|
+
local d
|
|
935
1306
|
for d in .specs/features/*/; do
|
|
936
1307
|
[[ -d "$d" ]] || continue
|
|
937
1308
|
feat_checked=true
|
|
938
1309
|
local feat
|
|
939
1310
|
feat="$(basename "$d")"
|
|
940
|
-
|
|
1311
|
+
|
|
1312
|
+
# Core files: spec + tasks required; validation expected
|
|
1313
|
+
for req_file in spec.md tasks.md; do
|
|
941
1314
|
if [[ -f "$d$req_file" ]]; then ok "$feat/$req_file"
|
|
942
|
-
else
|
|
1315
|
+
else err "$feat/$req_file — missing"; errors=$((errors + 1)); fi
|
|
943
1316
|
done
|
|
1317
|
+
|
|
1318
|
+
if [[ -f "$d/validation.md" ]]; then ok "$feat/validation.md"
|
|
1319
|
+
else warn "$feat/validation.md — missing"; warnings=$((warnings + 1)); fi
|
|
1320
|
+
|
|
1321
|
+
# New layout files: warn if missing (legacy compatible)
|
|
1322
|
+
if [[ -f "$d/context.md" ]]; then ok "$feat/context.md"
|
|
1323
|
+
else warn "$feat/context.md — missing (legacy layout; recommended for 0.2+)"; warnings=$((warnings + 1)); fi
|
|
1324
|
+
|
|
1325
|
+
if [[ -f "$d/plan.md" ]]; then ok "$feat/plan.md"
|
|
1326
|
+
else warn "$feat/plan.md — missing (legacy layout; recommended for 0.2+; replaces design.md role)"; warnings=$((warnings + 1)); fi
|
|
1327
|
+
|
|
1328
|
+
# Lightweight content warnings
|
|
1329
|
+
if [[ -f "$d/spec.md" ]]; then
|
|
1330
|
+
if grep -qE '\{\{[A-Z0-9_]+\}\}' "$d/spec.md" 2>/dev/null; then
|
|
1331
|
+
warn "$feat/spec.md — unresolved template placeholders {{…}}"
|
|
1332
|
+
warnings=$((warnings + 1))
|
|
1333
|
+
fi
|
|
1334
|
+
# Empty requirement table rows that never left NEEDS CLARIFICATION only — informational
|
|
1335
|
+
if ! grep -qE 'REQ-[0-9]+' "$d/spec.md" 2>/dev/null; then
|
|
1336
|
+
warn "$feat/spec.md — no REQ-XXX identifiers found"
|
|
1337
|
+
warnings=$((warnings + 1))
|
|
1338
|
+
fi
|
|
1339
|
+
fi
|
|
944
1340
|
done
|
|
945
1341
|
fi
|
|
946
1342
|
[[ "$feat_checked" == true ]] || info "No features found."
|
|
@@ -948,10 +1344,22 @@ cmd_doctor() {
|
|
|
948
1344
|
printf '\nQuick tasks:\n'
|
|
949
1345
|
local quick_checked=false
|
|
950
1346
|
if [[ -d ".specs/quick" ]]; then
|
|
1347
|
+
local d f
|
|
1348
|
+
for d in .specs/quick/*/; do
|
|
1349
|
+
[[ -d "$d" ]] || continue
|
|
1350
|
+
quick_checked=true
|
|
1351
|
+
local q
|
|
1352
|
+
q="$(basename "$d")"
|
|
1353
|
+
if [[ -f "$d/TASK.md" ]]; then ok "quick/$q/TASK.md"
|
|
1354
|
+
else warn "quick/$q/TASK.md — missing"; warnings=$((warnings + 1)); fi
|
|
1355
|
+
if [[ -f "$d/SUMMARY.md" ]]; then ok "quick/$q/SUMMARY.md"
|
|
1356
|
+
else warn "quick/$q/SUMMARY.md — missing"; warnings=$((warnings + 1)); fi
|
|
1357
|
+
done
|
|
951
1358
|
for f in .specs/quick/*.md; do
|
|
952
1359
|
[[ -f "$f" ]] || continue
|
|
953
1360
|
quick_checked=true
|
|
954
|
-
|
|
1361
|
+
warn "$(basename "$f") — legacy single-file quick task (prefer quick/<slug>/TASK.md + SUMMARY.md)"
|
|
1362
|
+
warnings=$((warnings + 1))
|
|
955
1363
|
done
|
|
956
1364
|
fi
|
|
957
1365
|
[[ "$quick_checked" == true ]] || info "No quick tasks found."
|
|
@@ -963,6 +1371,9 @@ cmd_doctor() {
|
|
|
963
1371
|
[[ $errors -gt 0 ]] && printf ' \033[31m%d error(s) found.\033[0m\n' "$errors"
|
|
964
1372
|
[[ $warnings -gt 0 ]] && printf ' \033[33m%d warning(s) found.\033[0m\n' "$warnings"
|
|
965
1373
|
fi
|
|
1374
|
+
|
|
1375
|
+
# Exit non-zero only on errors (warnings are informational)
|
|
1376
|
+
[[ $errors -eq 0 ]]
|
|
966
1377
|
}
|
|
967
1378
|
|
|
968
1379
|
# ─── Main ─────────────────────────────────────────────────────────────────────
|