prizmkit 1.1.48 → 1.1.49

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.
Files changed (30) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/scripts/update-checkpoint.py +173 -0
  3. package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +39 -9
  4. package/bundled/dev-pipeline/templates/sections/phase-browser-verification-auto.md +7 -1
  5. package/bundled/dev-pipeline/templates/sections/phase-browser-verification-opencli.md +7 -1
  6. package/bundled/dev-pipeline/templates/sections/phase-browser-verification.md +7 -1
  7. package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +14 -1
  8. package/bundled/dev-pipeline/templates/sections/phase-commit.md +14 -1
  9. package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-agent-suffix.md +7 -1
  10. package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-lite-suffix.md +7 -1
  11. package/bundled/dev-pipeline/templates/sections/phase-critic-plan-full.md +7 -1
  12. package/bundled/dev-pipeline/templates/sections/phase-critic-plan.md +7 -1
  13. package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +7 -1
  14. package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +7 -1
  15. package/bundled/dev-pipeline/templates/sections/phase-implement-lite.md +7 -1
  16. package/bundled/dev-pipeline/templates/sections/phase-plan-agent.md +7 -1
  17. package/bundled/dev-pipeline/templates/sections/phase-plan-lite.md +7 -1
  18. package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +7 -1
  19. package/bundled/dev-pipeline/templates/sections/phase-review-full.md +7 -1
  20. package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +7 -1
  21. package/bundled/dev-pipeline/templates/sections/phase0-init.md +7 -1
  22. package/bundled/dev-pipeline/templates/sections/phase0-test-baseline.md +7 -1
  23. package/bundled/skills/_metadata.json +1 -1
  24. package/bundled/skills/bug-planner/SKILL.md +17 -5
  25. package/bundled/skills/bug-planner/scripts/validate-bug-list.py +234 -64
  26. package/bundled/skills/feature-planner/SKILL.md +10 -4
  27. package/bundled/skills/feature-planner/scripts/validate-and-generate.py +82 -0
  28. package/bundled/skills/refactor-planner/SKILL.md +11 -6
  29. package/bundled/skills/refactor-planner/scripts/validate-and-generate-refactor.py +72 -0
  30. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  {
2
- "frameworkVersion": "1.1.48",
3
- "bundledAt": "2026-05-21T01:37:14.355Z",
4
- "bundledFrom": "a20d996"
2
+ "frameworkVersion": "1.1.49",
3
+ "bundledAt": "2026-05-21T14:22:00.608Z",
4
+ "bundledFrom": "a8dea99"
5
5
  }
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env python3
2
+ """Safely update a workflow-checkpoint.json step status.
3
+
4
+ Instead of having the AI hand-write JSON, this script reads the checkpoint,
5
+ validates the update, and writes it back atomically.
6
+
7
+ Usage:
8
+ python3 update-checkpoint.py --checkpoint-path <path> --step <skill-or-id> --status <status>
9
+ python3 update-checkpoint.py --checkpoint-path <path> --step prizmkit-implement --status completed
10
+ python3 update-checkpoint.py --checkpoint-path <path> --step S04 --status in_progress
11
+ python3 update-checkpoint.py --checkpoint-path <path> --step prizmkit-code-review --status failed --note "max rounds exhausted"
12
+
13
+ Exit codes:
14
+ 0 = success
15
+ 1 = validation error (step not found, invalid status, etc.)
16
+ 2 = file error (not found, corrupted JSON)
17
+
18
+ Output (stdout): JSON with {ok: true, step_id, skill, old_status, new_status} or {ok: false, error}
19
+ """
20
+
21
+ import argparse
22
+ import json
23
+ import os
24
+ import sys
25
+ import tempfile
26
+
27
+ VALID_STATUSES = {"pending", "in_progress", "completed", "skipped", "failed"}
28
+
29
+
30
+ def _load_checkpoint(path):
31
+ """Load checkpoint JSON. Returns (data, error_string)."""
32
+ if not os.path.isfile(path):
33
+ return None, "File not found: {}".format(path)
34
+ try:
35
+ with open(path, "r", encoding="utf-8") as f:
36
+ data = json.load(f)
37
+ except json.JSONDecodeError as e:
38
+ return None, "Corrupted JSON: {}".format(e)
39
+ except IOError as e:
40
+ return None, "Cannot read file: {}".format(e)
41
+
42
+ if "steps" not in data or not isinstance(data["steps"], list):
43
+ return None, "Invalid checkpoint: missing 'steps' array"
44
+ return data, None
45
+
46
+
47
+ def _write_checkpoint(path, data):
48
+ """Write checkpoint JSON atomically using a temp file + rename."""
49
+ parent = os.path.dirname(os.path.abspath(path))
50
+ if parent and not os.path.isdir(parent):
51
+ os.makedirs(parent, exist_ok=True)
52
+
53
+ tmp_path = None
54
+ try:
55
+ fd, tmp_path = tempfile.mkstemp(
56
+ dir=parent, suffix=".tmp", prefix=".checkpoint-"
57
+ )
58
+ with os.fdopen(fd, "w", encoding="utf-8") as f:
59
+ json.dump(data, f, indent=2, ensure_ascii=False)
60
+ f.write("\n")
61
+ os.replace(tmp_path, os.path.abspath(path))
62
+ except (IOError, OSError) as e:
63
+ # Clean up temp file on error
64
+ if tmp_path is not None and os.path.exists(tmp_path):
65
+ os.unlink(tmp_path)
66
+ return "Cannot write file: {}".format(e)
67
+ return None
68
+
69
+
70
+ def _find_step(steps, identifier):
71
+ """Find a step by skill name or step ID.
72
+
73
+ Returns (index, step_dict) or (None, None).
74
+ """
75
+ for i, step in enumerate(steps):
76
+ if step.get("skill") == identifier or step.get("id") == identifier:
77
+ return i, step
78
+ return None, None
79
+
80
+
81
+ def update_checkpoint(checkpoint_path, step_identifier, new_status, note=None):
82
+ """Update a single step's status in the checkpoint file.
83
+
84
+ Returns a result dict: {ok, step_id, skill, old_status, new_status} or {ok, error}.
85
+ """
86
+ if new_status not in VALID_STATUSES:
87
+ return {
88
+ "ok": False,
89
+ "error": "Invalid status '{}'. Must be one of: {}".format(
90
+ new_status, ", ".join(sorted(VALID_STATUSES))
91
+ ),
92
+ }
93
+
94
+ data, err = _load_checkpoint(checkpoint_path)
95
+ if err:
96
+ return {"ok": False, "error": err}
97
+
98
+ idx, step = _find_step(data["steps"], step_identifier)
99
+ if step is None:
100
+ available = [
101
+ "{} ({})".format(s.get("id", "?"), s.get("skill", "?"))
102
+ for s in data["steps"]
103
+ ]
104
+ return {
105
+ "ok": False,
106
+ "error": "Step '{}' not found. Available: {}".format(
107
+ step_identifier, ", ".join(available)
108
+ ),
109
+ }
110
+
111
+ old_status = step.get("status", "unknown")
112
+ step["status"] = new_status
113
+
114
+ if note:
115
+ step["note"] = note
116
+
117
+ err = _write_checkpoint(checkpoint_path, data)
118
+ if err:
119
+ return {"ok": False, "error": err}
120
+
121
+ return {
122
+ "ok": True,
123
+ "step_id": step.get("id"),
124
+ "skill": step.get("skill"),
125
+ "old_status": old_status,
126
+ "new_status": new_status,
127
+ }
128
+
129
+
130
+ def main():
131
+ parser = argparse.ArgumentParser(
132
+ description="Safely update a workflow-checkpoint.json step status.",
133
+ formatter_class=argparse.RawDescriptionHelpFormatter,
134
+ epilog=(
135
+ "Examples:\n"
136
+ " %(prog)s --checkpoint-path .prizmkit/specs/my-feature/workflow-checkpoint.json "
137
+ "--step prizmkit-implement --status completed\n"
138
+ " %(prog)s --checkpoint-path .prizmkit/bugfix/B-001/workflow-checkpoint.json "
139
+ "--step S04 --status in_progress\n"
140
+ " %(prog)s --checkpoint-path .prizmkit/refactor/R-001/workflow-checkpoint.json "
141
+ "--step prizmkit-code-review --status failed --note 'max rounds exhausted'"
142
+ ),
143
+ )
144
+ parser.add_argument(
145
+ "--checkpoint-path", required=True, help="Path to workflow-checkpoint.json"
146
+ )
147
+ parser.add_argument(
148
+ "--step",
149
+ required=True,
150
+ help="Step identifier (skill name like 'prizmkit-implement' or step ID like 'S04')",
151
+ )
152
+ parser.add_argument(
153
+ "--status",
154
+ required=True,
155
+ choices=sorted(VALID_STATUSES),
156
+ help="New status for the step",
157
+ )
158
+ parser.add_argument(
159
+ "--note",
160
+ default=None,
161
+ help="Optional note to attach to the step (e.g., failure reason)",
162
+ )
163
+
164
+ args = parser.parse_args()
165
+ result = update_checkpoint(
166
+ args.checkpoint_path, args.step, args.status, args.note
167
+ )
168
+ print(json.dumps(result, indent=2, ensure_ascii=False))
169
+ return 0 if result.get("ok") else (2 if "File not found" in result.get("error", "") or "Corrupted" in result.get("error", "") else 1)
170
+
171
+
172
+ if __name__ == "__main__":
173
+ sys.exit(main())
@@ -8,23 +8,53 @@ A checkpoint file tracks your progress through this workflow:
8
8
 
9
9
  1. **Before each skill**: Read `workflow-checkpoint.json`, verify the previous step has `status: "completed"` or `status: "skipped"`. If it is still `"pending"` or `"in_progress"`, you MUST complete it first before moving on.
10
10
 
11
- 2. **Starting a skill**: Update the current step to `status: "in_progress"`.
11
+ 2. **Starting a skill**: Use the update script to set the current step to `status: "in_progress"`.
12
12
 
13
- 3. **After each skill completes**: Update the current step to `status: "completed"`. Then immediately re-read the file to verify the JSON is valid. If the read fails, re-write the file with correct JSON.
13
+ 3. **After each skill completes**: Use the update script to set the current step to `status: "completed"`.
14
14
 
15
- 4. **On failure**: Set the step to `status: "failed"` and continue to the next step if possible, or halt and write failure-log.md.
15
+ 4. **On failure**: Use the update script to set the step to `status: "failed"` and continue to the next step if possible, or halt and write failure-log.md.
16
16
 
17
17
  5. **On session exit**: The checkpoint file reflects your actual progress. Do NOT manually set future steps to "completed".
18
18
 
19
19
  ### Checkpoint Update Pattern
20
20
 
21
- After completing each skill:
21
+ **IMPORTANT: NEVER hand-write or edit `workflow-checkpoint.json` directly. Always use the update script.**
22
22
 
23
- 1. Read `{{CHECKPOINT_PATH}}`
24
- 2. Update the current step `"status": "completed"`
25
- 3. Update the next step `"status": "in_progress"`
26
- 4. Write the updated JSON back to `{{CHECKPOINT_PATH}}`
27
- 5. Verify: `python3 -c "import json; json.load(open('{{CHECKPOINT_PATH}}'))"` — if this fails, re-write
23
+ After completing each skill, run:
24
+
25
+ ```bash
26
+ python3 $PIPELINE_DIR/scripts/update-checkpoint.py \
27
+ --checkpoint-path {{CHECKPOINT_PATH}} \
28
+ --step <SKILL_NAME> \
29
+ --status completed
30
+ ```
31
+
32
+ For example, after completing `prizmkit-implement`:
33
+ ```bash
34
+ python3 $PIPELINE_DIR/scripts/update-checkpoint.py \
35
+ --checkpoint-path {{CHECKPOINT_PATH}} \
36
+ --step prizmkit-implement \
37
+ --status completed
38
+ ```
39
+
40
+ Before starting the next skill:
41
+ ```bash
42
+ python3 $PIPELINE_DIR/scripts/update-checkpoint.py \
43
+ --checkpoint-path {{CHECKPOINT_PATH}} \
44
+ --step <NEXT_SKILL_NAME> \
45
+ --status in_progress
46
+ ```
47
+
48
+ On failure:
49
+ ```bash
50
+ python3 $PIPELINE_DIR/scripts/update-checkpoint.py \
51
+ --checkpoint-path {{CHECKPOINT_PATH}} \
52
+ --step <SKILL_NAME> \
53
+ --status failed \
54
+ --note "brief failure reason"
55
+ ```
56
+
57
+ The script outputs JSON: `{"ok": true, ...}` on success, `{"ok": false, "error": "..."}` on failure.
28
58
 
29
59
  ### Resume Behavior
30
60
 
@@ -150,4 +150,10 @@ Append results to `context-snapshot.md`:
150
150
  If verification fails, log the failure details but continue to commit. Failures do NOT block the commit, but you MUST attempt verification and MUST clean up the dev server.
151
151
 
152
152
 
153
- **Checkpoint update**: Update `workflow-checkpoint.json` set step `browser-verification` to `"completed"`.
153
+ **Checkpoint update**: Run the update script to set step `browser-verification` to `"completed"`:
154
+ ```bash
155
+ python3 $PIPELINE_DIR/scripts/update-checkpoint.py \
156
+ --checkpoint-path {{CHECKPOINT_PATH}} \
157
+ --step browser-verification \
158
+ --status completed
159
+ ```
@@ -121,4 +121,10 @@ Append results to `context-snapshot.md`:
121
121
  If verification fails, log the failure details but continue to commit. Failures do NOT block the commit, but you MUST attempt verification and MUST clean up the dev server.
122
122
 
123
123
 
124
- **Checkpoint update**: Update `workflow-checkpoint.json` set step `browser-verification` to `"completed"`.
124
+ **Checkpoint update**: Run the update script to set step `browser-verification` to `"completed"`:
125
+ ```bash
126
+ python3 $PIPELINE_DIR/scripts/update-checkpoint.py \
127
+ --checkpoint-path {{CHECKPOINT_PATH}} \
128
+ --step browser-verification \
129
+ --status completed
130
+ ```
@@ -143,4 +143,10 @@ Browser cleanup: confirmed
143
143
  If verification fails, log the failure details but continue to commit. Failures do NOT block the commit, but you MUST attempt verification and MUST clean up the dev server.
144
144
 
145
145
 
146
- **Checkpoint update**: Update `workflow-checkpoint.json` set step `browser-verification` to `"completed"`.
146
+ **Checkpoint update**: Run the update script to set step `browser-verification` to `"completed"`:
147
+ ```bash
148
+ python3 $PIPELINE_DIR/scripts/update-checkpoint.py \
149
+ --checkpoint-path {{CHECKPOINT_PATH}} \
150
+ --step browser-verification \
151
+ --status completed
152
+ ```
@@ -55,4 +55,17 @@ Rules for writing completion notes:
55
55
  - If this feature has no downstream dependents, still write the summary (it serves as documentation)
56
56
 
57
57
 
58
- **Checkpoint update**: After `/prizmkit-retrospective` completes, update `workflow-checkpoint.json` set step `prizmkit-retrospective` to `"completed"`. After `/prizmkit-committer` completes, set step `prizmkit-committer` to `"completed"`.
58
+ **Checkpoint update**: After `/prizmkit-retrospective` completes, run the update script to set step `prizmkit-retrospective` to `"completed"`:
59
+ ```bash
60
+ python3 $PIPELINE_DIR/scripts/update-checkpoint.py \
61
+ --checkpoint-path {{CHECKPOINT_PATH}} \
62
+ --step prizmkit-retrospective \
63
+ --status completed
64
+ ```
65
+ After `/prizmkit-committer` completes, set step `prizmkit-committer` to `"completed"`:
66
+ ```bash
67
+ python3 $PIPELINE_DIR/scripts/update-checkpoint.py \
68
+ --checkpoint-path {{CHECKPOINT_PATH}} \
69
+ --step prizmkit-committer \
70
+ --status completed
71
+ ```
@@ -48,4 +48,17 @@ Rules for writing completion notes:
48
48
  - If this feature has no downstream dependents, still write the summary (it serves as documentation)
49
49
 
50
50
 
51
- **Checkpoint update**: After `/prizmkit-retrospective` completes, update `workflow-checkpoint.json` set step `prizmkit-retrospective` to `"completed"`. After `/prizmkit-committer` completes, set step `prizmkit-committer` to `"completed"`.
51
+ **Checkpoint update**: After `/prizmkit-retrospective` completes, run the update script to set step `prizmkit-retrospective` to `"completed"`:
52
+ ```bash
53
+ python3 $PIPELINE_DIR/scripts/update-checkpoint.py \
54
+ --checkpoint-path {{CHECKPOINT_PATH}} \
55
+ --step prizmkit-retrospective \
56
+ --status completed
57
+ ```
58
+ After `/prizmkit-committer` completes, set step `prizmkit-committer` to `"completed"`:
59
+ ```bash
60
+ python3 $PIPELINE_DIR/scripts/update-checkpoint.py \
61
+ --checkpoint-path {{CHECKPOINT_PATH}} \
62
+ --step prizmkit-committer \
63
+ --status completed
64
+ ```
@@ -14,4 +14,10 @@
14
14
  - <trap entries extracted from L1/L2 docs>
15
15
 
16
16
 
17
- **Checkpoint update**: Update `workflow-checkpoint.json` set step `context-snapshot` to `"completed"`.
17
+ **Checkpoint update**: Run the update script to set step `context-snapshot` to `"completed"`:
18
+ ```bash
19
+ python3 $PIPELINE_DIR/scripts/update-checkpoint.py \
20
+ --checkpoint-path {{CHECKPOINT_PATH}} \
21
+ --step context-snapshot \
22
+ --status completed
23
+ ```
@@ -3,4 +3,10 @@
3
3
  - **Section 5 — Existing Tests**: full content of related test files as code block
4
4
 
5
5
 
6
- **Checkpoint update**: Update `workflow-checkpoint.json` set step `context-snapshot` to `"completed"`.
6
+ **Checkpoint update**: Run the update script to set step `context-snapshot` to `"completed"`:
7
+ ```bash
8
+ python3 $PIPELINE_DIR/scripts/update-checkpoint.py \
9
+ --checkpoint-path {{CHECKPOINT_PATH}} \
10
+ --step context-snapshot \
11
+ --status completed
12
+ ```
@@ -44,4 +44,10 @@ After all critics return, read all 3 reports:
44
44
  **CP-2.5**: Plan challenges reviewed and resolved.
45
45
 
46
46
 
47
- **Checkpoint update**: Update `workflow-checkpoint.json` set step `critic-plan-review` to `"completed"`.
47
+ **Checkpoint update**: Run the update script to set step `critic-plan-review` to `"completed"`:
48
+ ```bash
49
+ python3 $PIPELINE_DIR/scripts/update-checkpoint.py \
50
+ --checkpoint-path {{CHECKPOINT_PATH}} \
51
+ --step critic-plan-review \
52
+ --status completed
53
+ ```
@@ -23,4 +23,10 @@ Wait for Critic to return.
23
23
  **CP-2.5**: Plan challenges reviewed and resolved.
24
24
 
25
25
 
26
- **Checkpoint update**: Update `workflow-checkpoint.json` set step `critic-plan-review` to `"completed"`.
26
+ **Checkpoint update**: Run the update script to set step `critic-plan-review` to `"completed"`:
27
+ ```bash
28
+ python3 $PIPELINE_DIR/scripts/update-checkpoint.py \
29
+ --checkpoint-path {{CHECKPOINT_PATH}} \
30
+ --step critic-plan-review \
31
+ --status completed
32
+ ```
@@ -28,4 +28,10 @@ grep -q "## Implementation Log" .prizmkit/specs/{{FEATURE_SLUG}}/context-snapsho
28
28
  If GATE:MISSING — send message to Dev (re-spawn if needed): "Write the '## Implementation Log' section to context-snapshot.md before I can proceed to review. Include: files changed/created, key decisions, deviations from plan, notable discoveries."
29
29
 
30
30
 
31
- **Checkpoint update**: Update `workflow-checkpoint.json` set step `prizmkit-implement` to `"completed"`.
31
+ **Checkpoint update**: Run the update script to set step `prizmkit-implement` to `"completed"`:
32
+ ```bash
33
+ python3 $PIPELINE_DIR/scripts/update-checkpoint.py \
34
+ --checkpoint-path {{CHECKPOINT_PATH}} \
35
+ --step prizmkit-implement \
36
+ --status completed
37
+ ```
@@ -41,4 +41,10 @@ Wait for Dev to return. **If Dev times out before all tasks are `[x]`**:
41
41
  All tasks `[x]`, tests pass.
42
42
 
43
43
 
44
- **Checkpoint update**: Update `workflow-checkpoint.json` set step `prizmkit-implement` to `"completed"`.
44
+ **Checkpoint update**: Run the update script to set step `prizmkit-implement` to `"completed"`:
45
+ ```bash
46
+ python3 $PIPELINE_DIR/scripts/update-checkpoint.py \
47
+ --checkpoint-path {{CHECKPOINT_PATH}} \
48
+ --step prizmkit-implement \
49
+ --status completed
50
+ ```
@@ -39,4 +39,10 @@ You know this project's tech stack. Identify ALL test commands that apply (e.g.,
39
39
  **CP-2**: All acceptance criteria met, all tests pass.
40
40
 
41
41
 
42
- **Checkpoint update**: Update `workflow-checkpoint.json` set step `prizmkit-implement` to `"completed"`.
42
+ **Checkpoint update**: Run the update script to set step `prizmkit-implement` to `"completed"`:
43
+ ```bash
44
+ python3 $PIPELINE_DIR/scripts/update-checkpoint.py \
45
+ --checkpoint-path {{CHECKPOINT_PATH}} \
46
+ --step prizmkit-implement \
47
+ --status completed
48
+ ```
@@ -18,4 +18,10 @@ Before proceeding past CP-1, verify:
18
18
  **CP-1**: plan.md exists with Tasks section.
19
19
 
20
20
 
21
- **Checkpoint update**: Update `workflow-checkpoint.json` set step `prizmkit-plan` to `"completed"`.
21
+ **Checkpoint update**: Run the update script to set step `prizmkit-plan` to `"completed"`:
22
+ ```bash
23
+ python3 $PIPELINE_DIR/scripts/update-checkpoint.py \
24
+ --checkpoint-path {{CHECKPOINT_PATH}} \
25
+ --step prizmkit-plan \
26
+ --status completed
27
+ ```
@@ -18,4 +18,10 @@ Before proceeding past CP-1:
18
18
  **CP-1**: plan.md exists with Tasks section.
19
19
 
20
20
 
21
- **Checkpoint update**: Update `workflow-checkpoint.json` set step `prizmkit-plan` to `"completed"`.
21
+ **Checkpoint update**: Run the update script to set step `prizmkit-plan` to `"completed"`:
22
+ ```bash
23
+ python3 $PIPELINE_DIR/scripts/update-checkpoint.py \
24
+ --checkpoint-path {{CHECKPOINT_PATH}} \
25
+ --step prizmkit-plan \
26
+ --status completed
27
+ ```
@@ -18,4 +18,10 @@ Read `review-report.md` and check the Verdict:
18
18
  **CP-3**: Review complete, report written.
19
19
 
20
20
 
21
- **Checkpoint update**: Update `workflow-checkpoint.json` set step `prizmkit-code-review` to `"completed"`.
21
+ **Checkpoint update**: Run the update script to set step `prizmkit-code-review` to `"completed"`:
22
+ ```bash
23
+ python3 $PIPELINE_DIR/scripts/update-checkpoint.py \
24
+ --checkpoint-path {{CHECKPOINT_PATH}} \
25
+ --step prizmkit-code-review \
26
+ --status completed
27
+ ```
@@ -20,4 +20,10 @@ Run the full test suite: `({{TEST_CMD}}) 2>&1 | tee /tmp/review-test-out.txt | t
20
20
  **CP-3**: Review complete, tests pass, report written.
21
21
 
22
22
 
23
- **Checkpoint update**: Update `workflow-checkpoint.json` set step `prizmkit-code-review` to `"completed"`.
23
+ **Checkpoint update**: Run the update script to set step `prizmkit-code-review` to `"completed"`:
24
+ ```bash
25
+ python3 $PIPELINE_DIR/scripts/update-checkpoint.py \
26
+ --checkpoint-path {{CHECKPOINT_PATH}} \
27
+ --step prizmkit-code-review \
28
+ --status completed
29
+ ```
@@ -64,4 +64,10 @@ Before proceeding past CP-1, verify:
64
64
  **CP-1**: Both spec.md and plan.md exist.
65
65
 
66
66
 
67
- **Checkpoint update**: Update `workflow-checkpoint.json` set step `context-snapshot-and-plan` to `"completed"`.
67
+ **Checkpoint update**: Run the update script to set step `context-snapshot-and-plan` to `"completed"`:
68
+ ```bash
69
+ python3 $PIPELINE_DIR/scripts/update-checkpoint.py \
70
+ --checkpoint-path {{CHECKPOINT_PATH}} \
71
+ --step context-snapshot-and-plan \
72
+ --status completed
73
+ ```
@@ -4,4 +4,10 @@
4
4
  - **CP-0**: Verify `.prizmkit/prizm-docs/root.prizm`, `.prizmkit/config.json` exist
5
5
 
6
6
 
7
- **Checkpoint update**: Update `workflow-checkpoint.json` set step `prizmkit-init` to `"completed"`.
7
+ **Checkpoint update**: Run the update script to set step `prizmkit-init` to `"completed"`:
8
+ ```bash
9
+ python3 $PIPELINE_DIR/scripts/update-checkpoint.py \
10
+ --checkpoint-path {{CHECKPOINT_PATH}} \
11
+ --step prizmkit-init \
12
+ --status completed
13
+ ```
@@ -12,4 +12,10 @@ Save the list of **pre-existing failing tests** (if any) as `BASELINE_FAILURES`.
12
12
  > **Test Output Rule**: Always capture test output to a temp file (`tee /tmp/test-out.txt`). Then grep the file instead of re-running the suite.
13
13
 
14
14
 
15
- **Checkpoint update**: Update `workflow-checkpoint.json` set step `test-baseline` to `"completed"`.
15
+ **Checkpoint update**: Run the update script to set step `test-baseline` to `"completed"`:
16
+ ```bash
17
+ python3 $PIPELINE_DIR/scripts/update-checkpoint.py \
18
+ --checkpoint-path {{CHECKPOINT_PATH}} \
19
+ --step test-baseline \
20
+ --status completed
21
+ ```
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.1.48",
2
+ "version": "1.1.49",
3
3
  "skills": {
4
4
  "prizm-kit": {
5
5
  "description": "Full-lifecycle dev toolkit. Covers spec-driven development, Prizm context docs, code quality, debugging, deployment, and knowledge management.",
@@ -223,9 +223,13 @@ Only proceed to Phase 5 after user confirms.
223
223
 
224
224
  ### Phase 5: Generate & Validate
225
225
 
226
- 1. **Generate `.prizmkit/plans/bug-fix-list.json`**: Conform to `.prizmkit/dev-pipeline/templates/bug-fix-list-schema.json`
227
- 2. **Validate against schema**: Run `python3 ${SKILL_DIR}/scripts/validate-bug-list.py .prizmkit/plans/bug-fix-list.json --feature-list .prizmkit/plans/feature-list.json`. If the script is unavailable, use the checklist in `${SKILL_DIR}/references/schema-validation.md`.
228
- 3. **Write file** to `.prizmkit/plans/` (or user-specified path)
226
+ 1. **Write draft JSON**: Write a draft `.prizmkit/plans/bug-fix-list.draft.json` with all collected bug data. Conform to `.prizmkit/dev-pipeline/templates/bug-fix-list-schema.json`.
227
+ 2. **Generate and validate**: Run the generate script to validate and produce the final file:
228
+ ```bash
229
+ python3 ${SKILL_DIR}/scripts/validate-bug-list.py generate --input .prizmkit/plans/bug-fix-list.draft.json --output .prizmkit/plans/bug-fix-list.json
230
+ ```
231
+ The script fills in defaults (`$schema`, `created_at`, `created_by`), validates all fields, and writes the final file only if validation passes.
232
+ 3. **If validation fails**: Fix the draft and retry (max 3 attempts). If the script is unavailable, use the checklist in `${SKILL_DIR}/references/schema-validation.md`.
229
233
  4. **Output**: File path, summary, and next steps
230
234
 
231
235
  **Gate → CP-BP-5**: `bug-fix-list.json` passes validation script with zero errors.
@@ -269,8 +273,12 @@ Batch-parse error logs to generate bug entries without interactive prompts:
269
273
  - error_source: populated from log content
270
274
  - verification_type: default to `automated`
271
275
  - acceptance_criteria: auto-generate "Error no longer occurs in [scenario]"
272
- 4. Output draft `.prizmkit/plans/bug-fix-list.json` for user review
276
+ 4. Write draft to `.prizmkit/plans/bug-fix-list.draft.json` for user review
273
277
  5. Ask: "Review and confirm? You can edit individual entries."
278
+ 6. After user confirms, call the generate script:
279
+ ```bash
280
+ python3 ${SKILL_DIR}/scripts/validate-bug-list.py generate --input .prizmkit/plans/bug-fix-list.draft.json --output .prizmkit/plans/bug-fix-list.json
281
+ ```
274
282
 
275
283
  ## Operation: From Tests
276
284
 
@@ -280,7 +288,11 @@ Batch-parse failed test output:
280
288
  2. Parse each failed test case as a separate bug entry
281
289
  3. Auto-populate `failed_test_path`, `error_message`
282
290
  4. Set verification_type to `automated` (test already exists)
283
- 5. Output draft `.prizmkit/plans/bug-fix-list.json`
291
+ 5. Write draft to `.prizmkit/plans/bug-fix-list.draft.json`
292
+ 6. After user confirms, call the generate script:
293
+ ```bash
294
+ python3 ${SKILL_DIR}/scripts/validate-bug-list.py generate --input .prizmkit/plans/bug-fix-list.draft.json --output .prizmkit/plans/bug-fix-list.json
295
+ ```
284
296
 
285
297
  ## Operation: Validate
286
298
 
@@ -1,20 +1,30 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- Validate .prizmkit/plans/bug-fix-list.json against the PrizmKit bug-fix-list schema.
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
- python3 validate-bug-list.py [.prizmkit/plans/bug-fix-list.json] [--feature-list .prizmkit/plans/feature-list.json]
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
- 0 = valid
10
- 1 = validation errors found
11
- 2 = file not found or JSON parse error
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 validate(bug_list_path, feature_list_path=None):
28
- errors = []
29
- warnings = []
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
- # Load bug-fix-list.json
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(bug_list_path) as f:
57
+ with open(path, "r", encoding="utf-8") as f:
34
58
  data = json.load(f)
35
- except FileNotFoundError:
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
- print(f"ERROR: Invalid JSON in {bug_list_path}: {e}", file=sys.stderr)
40
- return 2
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
- # Load feature-list.json (optional, for cross-reference)
43
- feature_ids = set()
44
- if feature_list_path:
45
- try:
46
- with open(feature_list_path) as f:
47
- fl_data = json.load(f)
48
- feature_ids = {f.get("id") for f in fl_data.get("features", [])}
49
- except (FileNotFoundError, json.JSONDecodeError):
50
- warnings.append(f"Could not load feature-list.json at {feature_list_path}")
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(f"Invalid $schema: expected '{SCHEMA_VERSION}', got '{data['$schema']}'")
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
- seen_priorities = set()
105
+ severity_counts = {}
68
106
 
69
107
  for i, bug in enumerate(bugs):
70
- prefix = f"bugs[{i}]"
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(f"{prefix}: missing required field 'id'")
113
+ errors.append("{}: missing required field 'id'".format(prefix))
76
114
  elif not BUG_ID_PATTERN.match(bug_id):
77
- errors.append(f"{prefix}: id '{bug_id}' does not match pattern B-NNN")
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(f"{prefix}: duplicate bug id '{bug_id}'")
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(f"{prefix} ({bug_id}): missing required field 'title'")
122
+ errors.append("{} ({}): missing required field 'title'".format(prefix, bug_id))
85
123
 
86
124
  if not bug.get("description"):
87
- errors.append(f"{prefix} ({bug_id}): missing required field 'description'")
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(f"{prefix} ({bug_id}): invalid severity '{severity}' — must be one of {VALID_SEVERITIES}")
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(f"{prefix} ({bug_id}): invalid error_source.type '{source_type}' — must be one of {VALID_SOURCE_TYPES}")
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(f"{prefix} ({bug_id}): invalid verification_type '{vtype}' — must be one of {VALID_VERIFICATION_TYPES}")
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(f"{prefix} ({bug_id}): missing or empty acceptance_criteria array")
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(f"{prefix} ({bug_id}): invalid status '{status}' — must be one of {VALID_STATUSES}")
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(f"{prefix} ({bug_id}): invalid priority '{priority}' — must be one of 'high', 'medium', 'low'")
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
- # Output results
122
- if errors:
123
- print(f"VALIDATION FAILED — {len(errors)} error(s), {len(warnings)} warning(s)\n")
124
- for e in errors:
125
- print(f" ERROR: {e}")
126
- for w in warnings:
127
- print(f" WARN: {w}")
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
- bug_count = len(bugs)
131
- severity_counts = {}
132
- for b in bugs:
133
- s = b.get("severity", "unknown")
134
- severity_counts[s] = severity_counts.get(s, 0) + 1
135
- sev_str = ", ".join(f"{k}={v}" for k, v in sorted(severity_counts.items()))
136
- print(f"VALIDATION PASSED — {bug_count} bugs ({sev_str})")
137
- if warnings:
138
- for w in warnings:
139
- print(f" WARN: {w}")
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
- if __name__ == "__main__":
144
- bug_list = sys.argv[1] if len(sys.argv) > 1 else ".prizmkit/plans/bug-fix-list.json"
145
- feature_list = None
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
- if "--feature-list" in sys.argv:
148
- idx = sys.argv.index("--feature-list")
149
- if idx + 1 < len(sys.argv):
150
- feature_list = sys.argv[idx + 1]
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
- sys.exit(validate(bug_list, feature_list))
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. Run validation script immediately
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-validate (max 2 attempts, then escalate to full workflow)
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
- Run the validation script after generation:
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 validate --input <output-path> --mode <new|incremental>
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
  }
@@ -293,12 +293,14 @@ If issues found, discuss with user and resolve before proceeding.
293
293
 
294
294
  **Goal**: Produce `.prizmkit/plans/refactor-list.json` and validate it.
295
295
 
296
- 1. Generate `.prizmkit/plans/refactor-list.json` at `.prizmkit/plans/`
297
- 2. Run validation:
296
+ **IMPORTANT: Do NOT hand-write the final JSON file.** Instead:
297
+ 1. Write a draft JSON to `.prizmkit/plans/refactor-list.draft.json` with all collected refactor data.
298
+ 2. Call the generate script to validate and produce the final file:
298
299
  ```bash
299
- python3 ${SKILL_DIR}/scripts/validate-and-generate-refactor.py validate --input .prizmkit/plans/refactor-list.json
300
+ python3 ${SKILL_DIR}/scripts/validate-and-generate-refactor.py generate --input .prizmkit/plans/refactor-list.draft.json --output .prizmkit/plans/refactor-list.json
300
301
  ```
301
- 3. If validation fails -> fix issues and re-validate (max 3 attempts)
302
+ The script fills in defaults (`$schema`, `created_at`, `created_by`), validates all fields, and writes the final file only if validation passes.
303
+ 3. If validation fails -> fix the draft and retry (max 3 attempts)
302
304
  4. If validation passes -> present final summary
303
305
 
304
306
  **CHECKPOINT CP-RP-6**: `.prizmkit/plans/refactor-list.json` generated and validated.
@@ -380,9 +382,12 @@ For simple refactoring with minimal scope:
380
382
 
381
383
  **NEVER proceed without explicit user selection via `AskUserQuestion`. Do NOT render options as plain text — the user must be able to click/select.**
382
384
  3. Draft items (title + type + scope + description + acceptance_criteria + behavior_preservation + dependencies)
383
- 4. Run validation script immediately
385
+ 4. Write draft to `.prizmkit/plans/refactor-list.draft.json`, then call the generate script:
386
+ ```bash
387
+ python3 ${SKILL_DIR}/scripts/validate-and-generate-refactor.py generate --input .prizmkit/plans/refactor-list.draft.json --output .prizmkit/plans/refactor-list.json
388
+ ```
384
389
  5. If valid -> summarize and recommend next step
385
- 6. If invalid -> apply fixes, re-validate (max 2 attempts, then escalate to full workflow)
390
+ 6. If invalid -> apply fixes to the draft, re-run generate (max 2 attempts, then escalate to full workflow)
386
391
 
387
392
  ### When NOT to Use Fast Path
388
393
  - More than 2 refactor items
@@ -6,11 +6,13 @@ for the dev-pipeline system.
6
6
  Commands:
7
7
  validate Validate an existing .prizmkit/plans/refactor-list.json
8
8
  template Generate a blank template .prizmkit/plans/refactor-list.json
9
+ generate Validate a draft JSON and generate final refactor-list.json with defaults
9
10
  summary Print a summary table of refactors from a .prizmkit/plans/refactor-list.json
10
11
 
11
12
  Usage:
12
13
  python3 validate-and-generate-refactor.py validate --input .prizmkit/plans/refactor-list.json [--output validated.json]
13
14
  python3 validate-and-generate-refactor.py template --output .prizmkit/plans/refactor-list.json
15
+ python3 validate-and-generate-refactor.py generate --input draft.json --output .prizmkit/plans/refactor-list.json
14
16
  python3 validate-and-generate-refactor.py summary --input .prizmkit/plans/refactor-list.json [--format markdown|json]
15
17
 
16
18
  Python 3.6+ required. No external dependencies.
@@ -22,6 +24,7 @@ import json
22
24
  import os
23
25
  import re
24
26
  import sys
27
+ from datetime import datetime, timezone
25
28
 
26
29
  # ---------------------------------------------------------------------------
27
30
  # Constants
@@ -687,6 +690,61 @@ def cmd_template(args):
687
690
  return 0
688
691
 
689
692
 
693
+ def cmd_generate(args):
694
+ """Handle the 'generate' command.
695
+
696
+ Loads a draft JSON (produced by AI), fills in defaults, validates,
697
+ and writes the final refactor-list.json.
698
+ """
699
+ if not args.input:
700
+ _err("--input is required for the generate command")
701
+ return 2
702
+ if not args.output:
703
+ _err("--output is required for the generate command")
704
+ return 2
705
+
706
+ # Load draft (supports stdin via '-')
707
+ if args.input == "-":
708
+ try:
709
+ data = json.load(sys.stdin)
710
+ except json.JSONDecodeError as exc:
711
+ _err("Invalid JSON from stdin: {}".format(exc))
712
+ return 2
713
+ else:
714
+ data, load_err = _load_json(args.input)
715
+ if load_err:
716
+ _err(load_err)
717
+ return 2
718
+
719
+ # Fill in defaults
720
+ now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
721
+ data.setdefault("$schema", SCHEMA_VERSION)
722
+ data.setdefault("created_at", now)
723
+ data.setdefault("created_by", "refactor-planner")
724
+
725
+ # Set default status for refactors without one
726
+ for refactor in data.get("refactors", []):
727
+ refactor.setdefault("status", "pending")
728
+
729
+ # Validate
730
+ result = validate_refactor_list(data)
731
+
732
+ # Output validation result
733
+ print(json.dumps(result, indent=2, ensure_ascii=False))
734
+
735
+ if result["valid"]:
736
+ _write_json(args.output, data)
737
+ _info("Generated refactor-list written to {}".format(args.output))
738
+ return 0
739
+ else:
740
+ _err("Validation failed with {} error(s)".format(len(result["errors"])))
741
+ for e in result["errors"]:
742
+ _err(" " + e)
743
+ for w in result.get("warnings", []):
744
+ _warn(" " + w)
745
+ return 1
746
+
747
+
690
748
  def cmd_summary(args):
691
749
  """Handle the 'summary' command."""
692
750
  if not args.input:
@@ -719,6 +777,7 @@ def main():
719
777
  " %(prog)s validate --input .prizmkit/plans/refactor-list.json\n"
720
778
  " %(prog)s validate --input .prizmkit/plans/refactor-list.json --output validated.json\n"
721
779
  " %(prog)s template --output .prizmkit/plans/refactor-list.json\n"
780
+ " %(prog)s generate --input draft.json --output .prizmkit/plans/refactor-list.json\n"
722
781
  " %(prog)s summary --input .prizmkit/plans/refactor-list.json\n"
723
782
  " %(prog)s summary --input .prizmkit/plans/refactor-list.json --format json\n"
724
783
  ),
@@ -747,6 +806,18 @@ def main():
747
806
  "--output", required=True, help="Path to write template file"
748
807
  )
749
808
 
809
+ # -- generate --
810
+ p_generate = subparsers.add_parser(
811
+ "generate",
812
+ help="Validate a draft and generate final refactor-list.json with defaults",
813
+ )
814
+ p_generate.add_argument(
815
+ "--input", required=True, help="Path to draft JSON (or '-' for stdin)"
816
+ )
817
+ p_generate.add_argument(
818
+ "--output", required=True, help="Path to write final refactor-list.json"
819
+ )
820
+
750
821
  # -- summary --
751
822
  p_summary = subparsers.add_parser(
752
823
  "summary",
@@ -771,6 +842,7 @@ def main():
771
842
  dispatch = {
772
843
  "validate": cmd_validate,
773
844
  "template": cmd_template,
845
+ "generate": cmd_generate,
774
846
  "summary": cmd_summary,
775
847
  }
776
848
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prizmkit",
3
- "version": "1.1.48",
3
+ "version": "1.1.49",
4
4
  "description": "Create a new PrizmKit-powered project with clean initialization — no framework dev files, just what you need.",
5
5
  "type": "module",
6
6
  "bin": {