prizmkit 1.1.48 → 1.1.51
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/bundled/VERSION.json +3 -3
- package/bundled/dev-pipeline/scripts/update-checkpoint.py +173 -0
- package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +39 -9
- package/bundled/dev-pipeline/templates/sections/phase-browser-verification-auto.md +7 -1
- package/bundled/dev-pipeline/templates/sections/phase-browser-verification-opencli.md +7 -1
- package/bundled/dev-pipeline/templates/sections/phase-browser-verification.md +7 -1
- package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +14 -1
- package/bundled/dev-pipeline/templates/sections/phase-commit.md +14 -1
- package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-agent-suffix.md +7 -1
- package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-lite-suffix.md +7 -1
- package/bundled/dev-pipeline/templates/sections/phase-critic-plan-full.md +7 -1
- package/bundled/dev-pipeline/templates/sections/phase-critic-plan.md +7 -1
- package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +7 -1
- package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +7 -1
- package/bundled/dev-pipeline/templates/sections/phase-implement-lite.md +7 -1
- package/bundled/dev-pipeline/templates/sections/phase-plan-agent.md +7 -1
- package/bundled/dev-pipeline/templates/sections/phase-plan-lite.md +7 -1
- package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +7 -1
- package/bundled/dev-pipeline/templates/sections/phase-review-full.md +7 -1
- package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +7 -1
- package/bundled/dev-pipeline/templates/sections/phase0-init.md +7 -1
- package/bundled/dev-pipeline/templates/sections/phase0-test-baseline.md +7 -1
- package/bundled/skills/_metadata.json +9 -1
- package/bundled/skills/bug-planner/SKILL.md +17 -5
- package/bundled/skills/bug-planner/scripts/validate-bug-list.py +234 -64
- package/bundled/skills/feature-planner/SKILL.md +10 -4
- package/bundled/skills/feature-planner/scripts/validate-and-generate.py +82 -0
- package/bundled/skills/prizmkit-implement/SKILL.md +3 -0
- package/bundled/skills/prizmkit-test/SKILL.md +281 -0
- package/bundled/skills/refactor-planner/SKILL.md +11 -6
- package/bundled/skills/refactor-planner/scripts/validate-and-generate-refactor.py +72 -0
- package/package.json +1 -1
- package/src/scaffold.js +5 -0
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
|
-
Validate .prizmkit/plans/bug-fix-list.json
|
|
3
|
+
Validate and generate .prizmkit/plans/bug-fix-list.json files
|
|
4
|
+
for the dev-pipeline system.
|
|
5
|
+
|
|
6
|
+
Commands:
|
|
7
|
+
validate Validate an existing bug-fix-list.json
|
|
8
|
+
generate Validate a draft JSON and generate final bug-fix-list.json with defaults
|
|
4
9
|
|
|
5
10
|
Usage:
|
|
6
|
-
|
|
11
|
+
python3 validate-bug-list.py validate .prizmkit/plans/bug-fix-list.json [--feature-list .prizmkit/plans/feature-list.json]
|
|
12
|
+
python3 validate-bug-list.py generate --input draft.json --output .prizmkit/plans/bug-fix-list.json
|
|
7
13
|
|
|
8
14
|
Exit codes:
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
15
|
+
0 = valid / generated
|
|
16
|
+
1 = validation errors found
|
|
17
|
+
2 = file not found or JSON parse error
|
|
18
|
+
|
|
19
|
+
Python 3.6+ required. No external dependencies.
|
|
12
20
|
"""
|
|
13
21
|
|
|
22
|
+
import argparse
|
|
14
23
|
import json
|
|
15
24
|
import sys
|
|
16
25
|
import os
|
|
17
26
|
import re
|
|
27
|
+
from datetime import datetime, timezone
|
|
18
28
|
|
|
19
29
|
VALID_SEVERITIES = {"critical", "high", "medium", "low"}
|
|
20
30
|
VALID_SOURCE_TYPES = {"stack_trace", "user_report", "failed_test", "log_pattern", "monitoring_alert"}
|
|
@@ -24,129 +34,289 @@ BUG_ID_PATTERN = re.compile(r"^B-\d{3}$")
|
|
|
24
34
|
SCHEMA_VERSION = "dev-pipeline-bug-fix-list-v1"
|
|
25
35
|
|
|
26
36
|
|
|
27
|
-
def
|
|
28
|
-
|
|
29
|
-
|
|
37
|
+
def _err(msg):
|
|
38
|
+
"""Print an error message to stderr."""
|
|
39
|
+
print("ERROR: {}".format(msg), file=sys.stderr)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _warn(msg):
|
|
43
|
+
"""Print a warning message to stderr."""
|
|
44
|
+
print("WARN: {}".format(msg), file=sys.stderr)
|
|
45
|
+
|
|
30
46
|
|
|
31
|
-
|
|
47
|
+
def _info(msg):
|
|
48
|
+
"""Print an informational message to stderr."""
|
|
49
|
+
print("INFO: {}".format(msg), file=sys.stderr)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _load_json(path):
|
|
53
|
+
"""Load and return parsed JSON from a file. Returns (data, error_string)."""
|
|
54
|
+
if not os.path.isfile(path):
|
|
55
|
+
return None, "File not found: {}".format(path)
|
|
32
56
|
try:
|
|
33
|
-
with open(
|
|
57
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
34
58
|
data = json.load(f)
|
|
35
|
-
|
|
36
|
-
print(f"ERROR: File not found: {bug_list_path}", file=sys.stderr)
|
|
37
|
-
return 2
|
|
59
|
+
return data, None
|
|
38
60
|
except json.JSONDecodeError as e:
|
|
39
|
-
|
|
40
|
-
|
|
61
|
+
return None, "Invalid JSON in {}: {}".format(path, e)
|
|
62
|
+
except Exception as e:
|
|
63
|
+
return None, "Cannot read {}: {}".format(path, e)
|
|
41
64
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
65
|
+
|
|
66
|
+
def _write_json(path, data):
|
|
67
|
+
"""Write data as pretty-printed JSON to path. Creates parent dirs if needed."""
|
|
68
|
+
parent = os.path.dirname(os.path.abspath(path))
|
|
69
|
+
if parent and not os.path.isdir(parent):
|
|
70
|
+
os.makedirs(parent, exist_ok=True)
|
|
71
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
72
|
+
json.dump(data, f, indent=2, ensure_ascii=False)
|
|
73
|
+
f.write("\n")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def validate(data, feature_ids=None):
|
|
77
|
+
"""Validate a parsed bug-fix-list data structure.
|
|
78
|
+
|
|
79
|
+
Returns a dict with keys: valid, errors, warnings, stats.
|
|
80
|
+
"""
|
|
81
|
+
errors = []
|
|
82
|
+
warnings = []
|
|
51
83
|
|
|
52
84
|
# Top-level required fields
|
|
53
85
|
if "$schema" not in data:
|
|
54
86
|
errors.append("Missing required field: $schema")
|
|
55
87
|
elif data["$schema"] != SCHEMA_VERSION:
|
|
56
|
-
errors.append(
|
|
88
|
+
errors.append("Invalid $schema: expected '{}', got '{}'".format(SCHEMA_VERSION, data["$schema"]))
|
|
57
89
|
|
|
58
90
|
if not data.get("project_name"):
|
|
59
91
|
errors.append("Missing or empty required field: project_name")
|
|
60
92
|
|
|
61
93
|
bugs = data.get("bugs", [])
|
|
62
|
-
if not bugs:
|
|
94
|
+
if not bugs or not isinstance(bugs, list):
|
|
63
95
|
errors.append("Missing or empty required field: bugs")
|
|
96
|
+
return {
|
|
97
|
+
"valid": False,
|
|
98
|
+
"errors": errors,
|
|
99
|
+
"warnings": warnings,
|
|
100
|
+
"stats": {"total_bugs": 0, "severity_distribution": {}},
|
|
101
|
+
}
|
|
64
102
|
|
|
65
103
|
# Per-bug validation
|
|
66
104
|
seen_ids = set()
|
|
67
|
-
|
|
105
|
+
severity_counts = {}
|
|
68
106
|
|
|
69
107
|
for i, bug in enumerate(bugs):
|
|
70
|
-
prefix =
|
|
108
|
+
prefix = "bugs[{}]".format(i)
|
|
71
109
|
|
|
72
110
|
# Required fields
|
|
73
111
|
bug_id = bug.get("id", "")
|
|
74
112
|
if not bug_id:
|
|
75
|
-
errors.append(
|
|
113
|
+
errors.append("{}: missing required field 'id'".format(prefix))
|
|
76
114
|
elif not BUG_ID_PATTERN.match(bug_id):
|
|
77
|
-
errors.append(
|
|
115
|
+
errors.append("{}: id '{}' does not match pattern B-NNN".format(prefix, bug_id))
|
|
78
116
|
|
|
79
117
|
if bug_id in seen_ids:
|
|
80
|
-
errors.append(
|
|
118
|
+
errors.append("{}: duplicate bug id '{}'".format(prefix, bug_id))
|
|
81
119
|
seen_ids.add(bug_id)
|
|
82
120
|
|
|
83
121
|
if not bug.get("title"):
|
|
84
|
-
errors.append(
|
|
122
|
+
errors.append("{} ({}): missing required field 'title'".format(prefix, bug_id))
|
|
85
123
|
|
|
86
124
|
if not bug.get("description"):
|
|
87
|
-
errors.append(
|
|
125
|
+
errors.append("{} ({}): missing required field 'description'".format(prefix, bug_id))
|
|
88
126
|
|
|
89
127
|
severity = bug.get("severity", "")
|
|
90
128
|
if severity not in VALID_SEVERITIES:
|
|
91
|
-
errors.append(
|
|
129
|
+
errors.append("{} ({}): invalid severity '{}' — must be one of {}".format(
|
|
130
|
+
prefix, bug_id, severity, sorted(VALID_SEVERITIES)))
|
|
131
|
+
else:
|
|
132
|
+
severity_counts[severity] = severity_counts.get(severity, 0) + 1
|
|
92
133
|
|
|
93
134
|
# error_source
|
|
94
135
|
error_source = bug.get("error_source", {})
|
|
95
136
|
source_type = error_source.get("type", "") if isinstance(error_source, dict) else ""
|
|
96
137
|
if source_type not in VALID_SOURCE_TYPES:
|
|
97
|
-
errors.append(
|
|
138
|
+
errors.append("{} ({}): invalid error_source.type '{}' — must be one of {}".format(
|
|
139
|
+
prefix, bug_id, source_type, sorted(VALID_SOURCE_TYPES)))
|
|
98
140
|
|
|
99
141
|
# verification_type
|
|
100
142
|
vtype = bug.get("verification_type", "")
|
|
101
143
|
if vtype not in VALID_VERIFICATION_TYPES:
|
|
102
|
-
errors.append(
|
|
144
|
+
errors.append("{} ({}): invalid verification_type '{}' — must be one of {}".format(
|
|
145
|
+
prefix, bug_id, vtype, sorted(VALID_VERIFICATION_TYPES)))
|
|
103
146
|
|
|
104
147
|
# acceptance_criteria
|
|
105
148
|
ac = bug.get("acceptance_criteria", [])
|
|
106
149
|
if not ac or not isinstance(ac, list):
|
|
107
|
-
errors.append(
|
|
150
|
+
errors.append("{} ({}): missing or empty acceptance_criteria array".format(prefix, bug_id))
|
|
108
151
|
|
|
109
152
|
# status
|
|
110
153
|
status = bug.get("status", "")
|
|
111
154
|
if status not in VALID_STATUSES:
|
|
112
|
-
errors.append(
|
|
155
|
+
errors.append("{} ({}): invalid status '{}' — must be one of {}".format(
|
|
156
|
+
prefix, bug_id, status, sorted(VALID_STATUSES)))
|
|
113
157
|
|
|
114
158
|
# Priority validation (optional field)
|
|
115
159
|
priority = bug.get("priority")
|
|
116
160
|
if priority is not None:
|
|
117
161
|
if priority not in ("high", "medium", "low"):
|
|
118
|
-
errors.append(
|
|
162
|
+
errors.append("{} ({}): invalid priority '{}' — must be one of 'high', 'medium', 'low'".format(
|
|
163
|
+
prefix, bug_id, priority))
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
"valid": len(errors) == 0,
|
|
167
|
+
"errors": errors,
|
|
168
|
+
"warnings": warnings,
|
|
169
|
+
"stats": {
|
|
170
|
+
"total_bugs": len(bugs),
|
|
171
|
+
"severity_distribution": severity_counts,
|
|
172
|
+
},
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def cmd_validate(args):
|
|
177
|
+
"""Handle the 'validate' command."""
|
|
178
|
+
bug_list = args.input
|
|
179
|
+
feature_list = args.feature_list
|
|
180
|
+
|
|
181
|
+
data, load_err = _load_json(bug_list)
|
|
182
|
+
if load_err:
|
|
183
|
+
_err(load_err)
|
|
184
|
+
return 2
|
|
185
|
+
|
|
186
|
+
# Load feature-list.json (optional, for cross-reference)
|
|
187
|
+
if feature_list:
|
|
188
|
+
fl_data, fl_err = _load_json(feature_list)
|
|
189
|
+
if not fl_data:
|
|
190
|
+
_warn("Could not load feature-list.json at {}: {}".format(feature_list, fl_err))
|
|
191
|
+
|
|
192
|
+
result = validate(data)
|
|
119
193
|
|
|
194
|
+
# Print results to stdout
|
|
195
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
120
196
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
for
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
197
|
+
if result["valid"]:
|
|
198
|
+
bug_count = result["stats"]["total_bugs"]
|
|
199
|
+
sev = result["stats"]["severity_distribution"]
|
|
200
|
+
sev_str = ", ".join("{}={}".format(k, v) for k, v in sorted(sev.items()))
|
|
201
|
+
_info("VALIDATION PASSED — {} bugs ({})".format(bug_count, sev_str))
|
|
202
|
+
return 0
|
|
203
|
+
else:
|
|
204
|
+
_err("VALIDATION FAILED — {} error(s), {} warning(s)".format(
|
|
205
|
+
len(result["errors"]), len(result["warnings"])))
|
|
206
|
+
for e in result["errors"]:
|
|
207
|
+
_err(" ERROR: {}".format(e))
|
|
208
|
+
for w in result["warnings"]:
|
|
209
|
+
_warn(" WARN: {}".format(w))
|
|
128
210
|
return 1
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def cmd_generate(args):
|
|
214
|
+
"""Handle the 'generate' command.
|
|
215
|
+
|
|
216
|
+
Loads a draft JSON (produced by AI), fills in defaults, validates,
|
|
217
|
+
and writes the final bug-fix-list.json.
|
|
218
|
+
"""
|
|
219
|
+
# Load draft (supports stdin via '-')
|
|
220
|
+
if args.input == "-":
|
|
221
|
+
try:
|
|
222
|
+
data = json.load(sys.stdin)
|
|
223
|
+
except json.JSONDecodeError as exc:
|
|
224
|
+
_err("Invalid JSON from stdin: {}".format(exc))
|
|
225
|
+
return 2
|
|
129
226
|
else:
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
227
|
+
data, load_err = _load_json(args.input)
|
|
228
|
+
if load_err:
|
|
229
|
+
_err(load_err)
|
|
230
|
+
return 2
|
|
231
|
+
|
|
232
|
+
# Fill in defaults
|
|
233
|
+
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
234
|
+
data.setdefault("$schema", SCHEMA_VERSION)
|
|
235
|
+
data.setdefault("created_at", now)
|
|
236
|
+
data.setdefault("created_by", "bug-planner")
|
|
237
|
+
|
|
238
|
+
# Set default status for bugs without one
|
|
239
|
+
for bug in data.get("bugs", []):
|
|
240
|
+
bug.setdefault("status", "pending")
|
|
241
|
+
|
|
242
|
+
# Validate
|
|
243
|
+
result = validate(data)
|
|
244
|
+
|
|
245
|
+
# Output validation result
|
|
246
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
247
|
+
|
|
248
|
+
if result["valid"]:
|
|
249
|
+
_write_json(args.output, data)
|
|
250
|
+
_info("Generated bug-fix-list written to {}".format(args.output))
|
|
140
251
|
return 0
|
|
252
|
+
else:
|
|
253
|
+
_err("Validation failed with {} error(s)".format(len(result["errors"])))
|
|
254
|
+
for e in result["errors"]:
|
|
255
|
+
_err(" ERROR: {}".format(e))
|
|
256
|
+
return 1
|
|
141
257
|
|
|
142
258
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
259
|
+
def main():
|
|
260
|
+
# Backward compatibility: if first arg is a file path (not a subcommand),
|
|
261
|
+
# treat it as 'validate' command
|
|
262
|
+
if len(sys.argv) > 1 and not sys.argv[1].startswith("-") and sys.argv[1] not in ("validate", "generate"):
|
|
263
|
+
sys.argv.insert(1, "validate")
|
|
264
|
+
|
|
265
|
+
parser = argparse.ArgumentParser(
|
|
266
|
+
description="Validate and generate .prizmkit/plans/bug-fix-list.json files.",
|
|
267
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
268
|
+
epilog=(
|
|
269
|
+
"Examples:\n"
|
|
270
|
+
" %(prog)s validate .prizmkit/plans/bug-fix-list.json\n"
|
|
271
|
+
" %(prog)s validate .prizmkit/plans/bug-fix-list.json --feature-list .prizmkit/plans/feature-list.json\n"
|
|
272
|
+
" %(prog)s generate --input draft.json --output .prizmkit/plans/bug-fix-list.json\n"
|
|
273
|
+
),
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
subparsers = parser.add_subparsers(dest="command", help="Command to execute")
|
|
277
|
+
|
|
278
|
+
# -- validate --
|
|
279
|
+
p_validate = subparsers.add_parser(
|
|
280
|
+
"validate",
|
|
281
|
+
help="Validate an existing bug-fix-list.json",
|
|
282
|
+
)
|
|
283
|
+
p_validate.add_argument(
|
|
284
|
+
"input", help="Path to bug-fix-list.json"
|
|
285
|
+
)
|
|
286
|
+
p_validate.add_argument(
|
|
287
|
+
"--feature-list", default=None, help="Path to feature-list.json for cross-reference"
|
|
288
|
+
)
|
|
146
289
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
290
|
+
# -- generate --
|
|
291
|
+
p_generate = subparsers.add_parser(
|
|
292
|
+
"generate",
|
|
293
|
+
help="Validate a draft and generate final bug-fix-list.json with defaults",
|
|
294
|
+
)
|
|
295
|
+
p_generate.add_argument(
|
|
296
|
+
"--input", required=True, help="Path to draft JSON (or '-' for stdin)"
|
|
297
|
+
)
|
|
298
|
+
p_generate.add_argument(
|
|
299
|
+
"--output", required=True, help="Path to write final bug-fix-list.json"
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
args = parser.parse_args()
|
|
303
|
+
|
|
304
|
+
if not args.command:
|
|
305
|
+
parser.print_help(sys.stderr)
|
|
306
|
+
return 2
|
|
151
307
|
|
|
152
|
-
|
|
308
|
+
dispatch = {
|
|
309
|
+
"validate": cmd_validate,
|
|
310
|
+
"generate": cmd_generate,
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
handler = dispatch.get(args.command)
|
|
314
|
+
if handler is None:
|
|
315
|
+
_err("Unknown command: {}".format(args.command))
|
|
316
|
+
return 2
|
|
317
|
+
|
|
318
|
+
return handler(args)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
if __name__ == "__main__":
|
|
322
|
+
sys.exit(main())
|
|
@@ -263,9 +263,12 @@ For simple incremental planning, skip detailed Phase 2-3 analysis:
|
|
|
263
263
|
**NEVER proceed without explicit user selection via `AskUserQuestion`. Do NOT render options as plain text — the user must be able to click/select.**
|
|
264
264
|
3. Generate next sequential feature IDs
|
|
265
265
|
4. Draft features (title + description + acceptance_criteria + dependencies)
|
|
266
|
-
5.
|
|
266
|
+
5. Write draft to `.prizmkit/plans/feature-list.draft.json`, then call the generate script:
|
|
267
|
+
```bash
|
|
268
|
+
python3 ${SKILL_DIR}/scripts/validate-and-generate.py generate --input .prizmkit/plans/feature-list.draft.json --output .prizmkit/plans/feature-list.json --mode incremental
|
|
269
|
+
```
|
|
267
270
|
6. If valid → summarize and recommend next step
|
|
268
|
-
7. If invalid → apply fixes, re-
|
|
271
|
+
7. If invalid → apply fixes to the draft, re-run generate (max 2 attempts, then escalate to full workflow)
|
|
269
272
|
|
|
270
273
|
## Browser Interaction Planning
|
|
271
274
|
|
|
@@ -309,10 +312,13 @@ Key requirements:
|
|
|
309
312
|
- `high` → **standard** (orchestrator + dev + reviewer, 3 agents)
|
|
310
313
|
- `critical` → **full** (full team + critic agents, 5 agents). Use for: architectural changes touching 10+ files, cross-module refactoring with API surface changes, features requiring multi-critic voting
|
|
311
314
|
|
|
312
|
-
|
|
315
|
+
**IMPORTANT: Do NOT hand-write the final JSON file.** Instead:
|
|
316
|
+
1. Write a draft JSON to a temporary path (e.g., `.prizmkit/plans/feature-list.draft.json`)
|
|
317
|
+
2. Call the generate script to validate and produce the final file:
|
|
313
318
|
```bash
|
|
314
|
-
python3 ${SKILL_DIR}/scripts/validate-and-generate.py
|
|
319
|
+
python3 ${SKILL_DIR}/scripts/validate-and-generate.py generate --input .prizmkit/plans/feature-list.draft.json --output .prizmkit/plans/feature-list.json --mode <new|incremental>
|
|
315
320
|
```
|
|
321
|
+
The script fills in defaults (`$schema`, `created_at`, `created_by`), validates all fields, and writes the final file only if validation passes. If validation fails, fix the draft and retry.
|
|
316
322
|
|
|
317
323
|
## Testing Defaults (Phase 8)
|
|
318
324
|
|
|
@@ -6,12 +6,14 @@ for the dev-pipeline system.
|
|
|
6
6
|
Commands:
|
|
7
7
|
validate Validate an existing .prizmkit/plans/feature-list.json
|
|
8
8
|
template Generate a blank template .prizmkit/plans/feature-list.json
|
|
9
|
+
generate Validate a draft JSON and generate final feature-list.json with defaults
|
|
9
10
|
summary Print a summary table of features from a .prizmkit/plans/feature-list.json
|
|
10
11
|
grade Generate grading results from eval runs (for npm run skill:review)
|
|
11
12
|
|
|
12
13
|
Usage:
|
|
13
14
|
python3 validate-and-generate.py validate --input .prizmkit/plans/feature-list.json [--output validated.json] [--mode new|incremental]
|
|
14
15
|
python3 validate-and-generate.py template --output .prizmkit/plans/feature-list.json
|
|
16
|
+
python3 validate-and-generate.py generate --input draft.json --output .prizmkit/plans/feature-list.json [--mode new|incremental]
|
|
15
17
|
python3 validate-and-generate.py summary --input .prizmkit/plans/feature-list.json [--format markdown|json]
|
|
16
18
|
python3 validate-and-generate.py grade --workspace /.codebuddy/skill-evals/feature-planner-workspace --iteration iteration-1
|
|
17
19
|
|
|
@@ -716,6 +718,66 @@ def cmd_template(args):
|
|
|
716
718
|
return 0
|
|
717
719
|
|
|
718
720
|
|
|
721
|
+
def cmd_generate(args):
|
|
722
|
+
"""Handle the 'generate' command.
|
|
723
|
+
|
|
724
|
+
Loads a draft JSON (produced by AI), fills in defaults, validates,
|
|
725
|
+
and writes the final feature-list.json.
|
|
726
|
+
"""
|
|
727
|
+
if not args.input:
|
|
728
|
+
_err("--input is required for the generate command")
|
|
729
|
+
return 2
|
|
730
|
+
if not args.output:
|
|
731
|
+
_err("--output is required for the generate command")
|
|
732
|
+
return 2
|
|
733
|
+
|
|
734
|
+
# Load draft (supports stdin via '-')
|
|
735
|
+
if args.input == "-":
|
|
736
|
+
try:
|
|
737
|
+
data = json.load(sys.stdin)
|
|
738
|
+
except json.JSONDecodeError as exc:
|
|
739
|
+
_err("Invalid JSON from stdin: {}".format(exc))
|
|
740
|
+
return 2
|
|
741
|
+
else:
|
|
742
|
+
data, load_err = _load_json(args.input)
|
|
743
|
+
if load_err:
|
|
744
|
+
_err(load_err)
|
|
745
|
+
return 2
|
|
746
|
+
|
|
747
|
+
# Fill in defaults
|
|
748
|
+
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
749
|
+
data.setdefault("$schema", SCHEMA_VERSION)
|
|
750
|
+
data.setdefault("created_at", now)
|
|
751
|
+
data.setdefault("created_by", "feature-planner")
|
|
752
|
+
|
|
753
|
+
# Ensure project_name from app_name if needed
|
|
754
|
+
if "project_name" not in data and "app_name" in data:
|
|
755
|
+
data["project_name"] = data["app_name"]
|
|
756
|
+
|
|
757
|
+
# Set default status for features without one
|
|
758
|
+
for feature in data.get("features", []):
|
|
759
|
+
feature.setdefault("status", "pending")
|
|
760
|
+
|
|
761
|
+
# Validate
|
|
762
|
+
mode = getattr(args, "mode", "new") or "new"
|
|
763
|
+
result = validate_feature_list(data, planning_mode=mode)
|
|
764
|
+
|
|
765
|
+
# Output validation result
|
|
766
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
767
|
+
|
|
768
|
+
if result["valid"]:
|
|
769
|
+
_write_json(args.output, data)
|
|
770
|
+
_info("Generated feature-list written to {}".format(args.output))
|
|
771
|
+
return 0
|
|
772
|
+
else:
|
|
773
|
+
_err("Validation failed with {} error(s)".format(len(result["errors"])))
|
|
774
|
+
for e in result["errors"]:
|
|
775
|
+
_err(" " + e)
|
|
776
|
+
for w in result.get("warnings", []):
|
|
777
|
+
_warn(" " + w)
|
|
778
|
+
return 1
|
|
779
|
+
|
|
780
|
+
|
|
719
781
|
def cmd_summary(args):
|
|
720
782
|
"""Handle the 'summary' command."""
|
|
721
783
|
if not args.input:
|
|
@@ -857,6 +919,7 @@ def main():
|
|
|
857
919
|
" %(prog)s validate --input .prizmkit/plans/feature-list.json --mode incremental\n"
|
|
858
920
|
" %(prog)s validate --input .prizmkit/plans/feature-list.json --output validated.json\n"
|
|
859
921
|
" %(prog)s template --output .prizmkit/plans/feature-list.json\n"
|
|
922
|
+
" %(prog)s generate --input draft.json --output .prizmkit/plans/feature-list.json\n"
|
|
860
923
|
" %(prog)s summary --input .prizmkit/plans/feature-list.json\n"
|
|
861
924
|
" %(prog)s summary --input .prizmkit/plans/feature-list.json --format json\n"
|
|
862
925
|
),
|
|
@@ -891,6 +954,24 @@ def main():
|
|
|
891
954
|
"--output", required=True, help="Path to write template file"
|
|
892
955
|
)
|
|
893
956
|
|
|
957
|
+
# -- generate --
|
|
958
|
+
p_generate = subparsers.add_parser(
|
|
959
|
+
"generate",
|
|
960
|
+
help="Validate a draft and generate final feature-list.json with defaults filled in",
|
|
961
|
+
)
|
|
962
|
+
p_generate.add_argument(
|
|
963
|
+
"--input", required=True, help="Path to draft JSON (or '-' for stdin)"
|
|
964
|
+
)
|
|
965
|
+
p_generate.add_argument(
|
|
966
|
+
"--output", required=True, help="Path to write final feature-list.json"
|
|
967
|
+
)
|
|
968
|
+
p_generate.add_argument(
|
|
969
|
+
"--mode",
|
|
970
|
+
choices=["new", "incremental"],
|
|
971
|
+
default="new",
|
|
972
|
+
help="Validation mode (default: new)",
|
|
973
|
+
)
|
|
974
|
+
|
|
894
975
|
# -- summary --
|
|
895
976
|
p_summary = subparsers.add_parser(
|
|
896
977
|
"summary",
|
|
@@ -931,6 +1012,7 @@ def main():
|
|
|
931
1012
|
dispatch = {
|
|
932
1013
|
"validate": cmd_validate,
|
|
933
1014
|
"template": cmd_template,
|
|
1015
|
+
"generate": cmd_generate,
|
|
934
1016
|
"summary": cmd_summary,
|
|
935
1017
|
"grade": cmd_grade,
|
|
936
1018
|
}
|
|
@@ -40,6 +40,9 @@ For each unchecked task in plan.md, in order:
|
|
|
40
40
|
|
|
41
41
|
1. Read L1/L2 doc for the target file's module — check TRAPS and DECISIONS before modifying files
|
|
42
42
|
2. Apply TDD where applicable: write a failing test first, then implement until it passes. For UI components or configuration changes where unit tests don't apply, skip the test-first step.
|
|
43
|
+
- **Cover three paths for each function**: happy path (valid inputs producing expected behavior), edge cases (boundary values specific to the parameter type and domain — e.g., zero/min/max for numeric, empty collection, boundary index), and error conditions (inputs that should trigger error handling). Determine edge cases from the function's parameter types and domain logic, not from a fixed checklist. If a function has no edge or error paths, don't force them.
|
|
44
|
+
- **No redundant tests**: Check if a test for this behavior already exists before writing. Each test must verify a uniquely different code path — don't write multiple tests that exercise the same logic.
|
|
45
|
+
- **Test your own code only**: Don't test framework behavior, third-party library internals, or language built-ins. For library calls, test the integration point (correct parameters passed, return value correctly handled), not the library itself.
|
|
43
46
|
3. Mark task as `[x]` in `plan.md` immediately after completion — not batched at the end. Immediate marking means plan.md always reflects true progress, even if the session is interrupted.
|
|
44
47
|
4. **Parallel tasks**: If task has `[P]` marker, it can run in parallel with other `[P]` tasks in the same group. Sequential tasks stop on failure (later tasks may depend on this one). Parallel `[P]` tasks continue — report all failures at the end.
|
|
45
48
|
5. **Checkpoint tasks** (`CP:` prefix in plan.md): When a checkpoint task is reached, verify build passes and tests pass before continuing. Checkpoints catch integration errors early — skipping them means cascading failures in later phases.
|