prizmkit 1.1.86 → 1.1.88

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 (45) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +462 -4
  3. package/bundled/dev-pipeline/scripts/prizmkit-test-gate.py +446 -0
  4. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +1 -1
  5. package/bundled/dev-pipeline/templates/bootstrap-tier1.md +14 -0
  6. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +18 -3
  7. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +18 -3
  8. package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +2 -2
  9. package/bundled/dev-pipeline/templates/sections/phase-commit.md +2 -2
  10. package/bundled/dev-pipeline/templates/sections/phase-prizmkit-test.md +39 -0
  11. package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +5 -1
  12. package/bundled/dev-pipeline/templates/sections/phase-review-full.md +5 -1
  13. package/bundled/dev-pipeline/tests/conftest.py +1 -0
  14. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +434 -0
  15. package/bundled/dev-pipeline/tests/test_prizmkit_test_gate.py +107 -0
  16. package/bundled/dev-pipeline-windows/scripts/generate-bootstrap-prompt.py +462 -4
  17. package/bundled/dev-pipeline-windows/scripts/prizmkit-test-gate.py +446 -0
  18. package/bundled/dev-pipeline-windows/templates/bootstrap-prompt.md +1 -1
  19. package/bundled/dev-pipeline-windows/templates/bootstrap-tier1.md +14 -0
  20. package/bundled/dev-pipeline-windows/templates/bootstrap-tier2.md +18 -3
  21. package/bundled/dev-pipeline-windows/templates/bootstrap-tier3.md +18 -3
  22. package/bundled/dev-pipeline-windows/templates/sections/phase-commit-full.md +2 -2
  23. package/bundled/dev-pipeline-windows/templates/sections/phase-commit.md +2 -2
  24. package/bundled/dev-pipeline-windows/templates/sections/phase-prizmkit-test.md +39 -0
  25. package/bundled/dev-pipeline-windows/templates/sections/phase-review-agent.md +5 -1
  26. package/bundled/dev-pipeline-windows/templates/sections/phase-review-full.md +5 -1
  27. package/bundled/skills/_metadata.json +1 -1
  28. package/bundled/skills/prizmkit-code-review/SKILL.md +7 -2
  29. package/bundled/skills/prizmkit-code-review/references/reviewer-agent-prompt.md +6 -2
  30. package/bundled/skills/prizmkit-test/SKILL.md +106 -14
  31. package/bundled/skills/prizmkit-test/references/boundary-coverage-protocol.md +220 -0
  32. package/bundled/skills/prizmkit-test/references/examples.md +82 -9
  33. package/bundled/skills/prizmkit-test/references/test-generation-steps.md +68 -12
  34. package/bundled/skills/prizmkit-test/references/test-report-template.md +73 -9
  35. package/bundled/skills/prizmkit-test/scripts/validate_boundary_report.py +347 -0
  36. package/bundled/skills-windows/prizmkit-code-review/SKILL.md +7 -2
  37. package/bundled/skills-windows/prizmkit-code-review/references/reviewer-agent-prompt.md +6 -2
  38. package/bundled/skills-windows/prizmkit-test/SKILL.md +106 -14
  39. package/bundled/skills-windows/prizmkit-test/references/boundary-coverage-protocol.md +220 -0
  40. package/bundled/skills-windows/prizmkit-test/references/examples.md +82 -9
  41. package/bundled/skills-windows/prizmkit-test/references/service-boundary-test-catalog.md +4 -4
  42. package/bundled/skills-windows/prizmkit-test/references/test-generation-steps.md +68 -12
  43. package/bundled/skills-windows/prizmkit-test/references/test-report-template.md +73 -9
  44. package/bundled/skills-windows/prizmkit-test/scripts/validate_boundary_report.py +347 -0
  45. package/package.json +1 -1
@@ -0,0 +1,446 @@
1
+ #!/usr/bin/env python3
2
+ """Scoped PrizmKit test gate for feature pipeline prompts.
3
+
4
+ The feature bootstrap prompt calls this script instead of embedding the full
5
+ Bash/PowerShell report validation logic inline. It intentionally prints compact
6
+ `GATE:*` lines so the AI session can decide whether to continue, retry, or stop.
7
+ """
8
+
9
+ import argparse
10
+ import os
11
+ import subprocess
12
+ import sys
13
+ from pathlib import Path
14
+
15
+ REQUIRED_BOUNDARY_COLUMNS = [
16
+ "Module",
17
+ "Interface",
18
+ "Service Type",
19
+ "Happy Path",
20
+ "Request Validation",
21
+ "Auth / Permission / Ownership",
22
+ "Domain Invariants",
23
+ "Collection / Pagination",
24
+ "Date / Time",
25
+ "State Transitions",
26
+ "Dependency Failure",
27
+ "Response Contract",
28
+ "N/A Reasons",
29
+ "Final Status",
30
+ ]
31
+
32
+ BOUNDARY_VALIDATOR_CANDIDATES = [
33
+ ".claude/command-assets/prizmkit-test/scripts/validate_boundary_report.py",
34
+ ".codebuddy/skills/prizmkit-test/scripts/validate_boundary_report.py",
35
+ ".agents/skills/prizmkit-test/scripts/validate_boundary_report.py",
36
+ ]
37
+
38
+ OPENAPI_CANDIDATES = [
39
+ "openapi.yaml",
40
+ "openapi.yml",
41
+ "swagger.yaml",
42
+ "swagger.yml",
43
+ "docs/openapi.yaml",
44
+ "docs/openapi.yml",
45
+ "docs/swagger.yaml",
46
+ "docs/swagger.yml",
47
+ "api/openapi.yaml",
48
+ "api/openapi.yml",
49
+ "api/swagger.yaml",
50
+ "api/swagger.yml",
51
+ ]
52
+
53
+ IGNORED_SCOPE_VALUES = {"none", "n/a", "not applicable", "(none)", "{...}", "..."}
54
+
55
+
56
+ def default_artifact_dir(feature_slug):
57
+ return ".prizmkit/specs/{}/".format(feature_slug)
58
+
59
+
60
+ def marker_path(feature_slug):
61
+ return os.path.join(default_artifact_dir(feature_slug), ".prizmkit-test-started")
62
+
63
+
64
+ def pointer_path(feature_slug):
65
+ return os.path.join(default_artifact_dir(feature_slug), "test-report-path.txt")
66
+
67
+
68
+ def normalize_artifact_dir(path):
69
+ return str(path or "").strip().replace("\\", "/").rstrip("/")
70
+
71
+
72
+ def read_text(path):
73
+ with open(path, "r", encoding="utf-8") as fh:
74
+ return fh.read()
75
+
76
+
77
+ def start_gate(feature_slug):
78
+ marker = marker_path(feature_slug)
79
+ os.makedirs(os.path.dirname(marker), exist_ok=True)
80
+ Path(marker).touch()
81
+ return {"gate": "GATE:START", "marker": marker}
82
+
83
+
84
+ def newest_report_after_marker(marker):
85
+ if not os.path.isfile(marker):
86
+ return ""
87
+ try:
88
+ marker_mtime = os.path.getmtime(marker)
89
+ except OSError:
90
+ return ""
91
+
92
+ matches = []
93
+ for root, _dirs, files in os.walk(os.path.join(".prizmkit", "test")):
94
+ if "test-report.md" not in files:
95
+ continue
96
+ path = os.path.join(root, "test-report.md")
97
+ try:
98
+ report_mtime = os.path.getmtime(path)
99
+ except OSError:
100
+ continue
101
+ if report_mtime >= marker_mtime:
102
+ matches.append((report_mtime, path))
103
+
104
+ if not matches:
105
+ return ""
106
+ return sorted(matches)[-1][1]
107
+
108
+
109
+ def extract_section(report_text, heading):
110
+ in_section = False
111
+ lines = []
112
+ for raw_line in report_text.splitlines():
113
+ line = raw_line.strip()
114
+ if line == heading:
115
+ in_section = True
116
+ continue
117
+ if in_section and line.startswith("## "):
118
+ break
119
+ if in_section:
120
+ lines.append(raw_line)
121
+ return lines
122
+
123
+
124
+ def extract_scope_field(report_text, field_name):
125
+ prefix = "- {}:".format(field_name)
126
+ for raw_line in extract_section(report_text, "## Scope"):
127
+ line = raw_line.strip()
128
+ if line.startswith(prefix):
129
+ return line[len(prefix):].strip()
130
+ return ""
131
+
132
+
133
+ def extract_verdict(report_text):
134
+ for raw_line in extract_section(report_text, "## Verdict"):
135
+ line = raw_line.strip()
136
+ if line:
137
+ return line
138
+ return ""
139
+
140
+
141
+ def extract_key_value(section_lines, field_name):
142
+ prefix = "{}:".format(field_name).lower()
143
+ for raw_line in section_lines:
144
+ line = raw_line.strip()
145
+ if line.startswith("-"):
146
+ line = line[1:].strip()
147
+ if line.lower().startswith(prefix):
148
+ return line[len(prefix):].strip()
149
+ return ""
150
+
151
+
152
+ def parse_markdown_cells(line):
153
+ stripped = line.strip()
154
+ if not stripped.startswith("|"):
155
+ return []
156
+ return [cell.strip() for cell in stripped.strip("|").split("|")]
157
+
158
+
159
+ def boundary_columns_ok(matrix_lines):
160
+ for line in matrix_lines:
161
+ cells = parse_markdown_cells(line)
162
+ if cells and all(column in cells for column in REQUIRED_BOUNDARY_COLUMNS):
163
+ return True
164
+ return False
165
+
166
+
167
+ def boundary_incomplete(matrix_lines):
168
+ text = "\n".join(matrix_lines).lower()
169
+ return any(token in text for token in ["boundary-missing", "happy-path-only", "unresolved"])
170
+
171
+
172
+ def discover_openapi_file():
173
+ for candidate in OPENAPI_CANDIDATES:
174
+ if os.path.isfile(candidate):
175
+ return candidate
176
+ return ""
177
+
178
+
179
+ def boundary_validator_passed(report_path, openapi_file):
180
+ for validator in BOUNDARY_VALIDATOR_CANDIDATES:
181
+ if not os.path.isfile(validator):
182
+ continue
183
+ args = [sys.executable, validator, "--report", report_path]
184
+ if openapi_file:
185
+ args.extend(["--openapi", openapi_file])
186
+ result = subprocess.run(
187
+ args,
188
+ stdout=subprocess.DEVNULL,
189
+ stderr=subprocess.DEVNULL,
190
+ check=False,
191
+ )
192
+ if result.returncode == 0:
193
+ return True
194
+ return False
195
+
196
+
197
+ def scope_list(report_text, name):
198
+ values = []
199
+ in_list = False
200
+ marker = "- {}:".format(name)
201
+ for raw_line in extract_section(report_text, "## Scope"):
202
+ line = raw_line.strip()
203
+ if line == marker:
204
+ in_list = True
205
+ continue
206
+ if not in_list:
207
+ continue
208
+ if raw_line.startswith(" -") or raw_line.startswith("\t-"):
209
+ value = line[2:].strip().strip("`")
210
+ if value and value not in {"{...}", "..."}:
211
+ values.append(value)
212
+ continue
213
+ if line.startswith("- "):
214
+ break
215
+ return values
216
+
217
+
218
+ def path_is_inside_project(project_root, candidate):
219
+ try:
220
+ return os.path.commonpath([project_root, candidate]) == project_root
221
+ except ValueError:
222
+ return False
223
+
224
+
225
+ def scope_fresh_ok(report_path, marker, report_text):
226
+ try:
227
+ report_mtime = os.path.getmtime(report_path)
228
+ marker_mtime = os.path.getmtime(marker)
229
+ except OSError:
230
+ return False
231
+ if report_mtime < marker_mtime:
232
+ return False
233
+
234
+ source_files = scope_list(report_text, "In-Scope Source Files")
235
+ test_files = scope_list(report_text, "In-Scope Test Files")
236
+ if not source_files or not test_files:
237
+ return False
238
+
239
+ project_root = os.path.realpath(os.getcwd())
240
+ for listed_file in source_files + test_files:
241
+ normalized = listed_file.strip().strip("`")
242
+ if not normalized or normalized.lower() in IGNORED_SCOPE_VALUES:
243
+ return False
244
+ candidate = normalized if os.path.isabs(normalized) else os.path.join(os.getcwd(), normalized)
245
+ candidate_real = os.path.realpath(candidate)
246
+ if not path_is_inside_project(project_root, candidate_real):
247
+ return False
248
+ if not os.path.isfile(candidate_real):
249
+ return False
250
+ if os.path.getmtime(candidate_real) > report_mtime:
251
+ return False
252
+ return True
253
+
254
+
255
+ def write_report_pointer(feature_slug, report_path):
256
+ path = pointer_path(feature_slug)
257
+ os.makedirs(os.path.dirname(path), exist_ok=True)
258
+ with open(path, "w", encoding="utf-8") as fh:
259
+ fh.write(report_path + "\n")
260
+ return path
261
+
262
+
263
+ def update_checkpoint(checkpoint_path):
264
+ script_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "update-checkpoint.py")
265
+ if not checkpoint_path:
266
+ return True, ""
267
+ result = subprocess.run(
268
+ [
269
+ sys.executable,
270
+ script_path,
271
+ "--checkpoint-path",
272
+ checkpoint_path,
273
+ "--step",
274
+ "prizmkit-test",
275
+ "--status",
276
+ "completed",
277
+ ],
278
+ stdout=subprocess.PIPE,
279
+ stderr=subprocess.STDOUT,
280
+ text=True,
281
+ check=False,
282
+ )
283
+ if result.returncode == 0:
284
+ return True, ""
285
+ output = " ".join((result.stdout or "").split())
286
+ return False, output[:500]
287
+
288
+
289
+ def validate_report(report_path, marker, feature_slug, expected_artifact_dir):
290
+ try:
291
+ report_text = read_text(report_path)
292
+ except OSError as exc:
293
+ return {
294
+ "status": "FAIL",
295
+ "reason": "unreadable_report",
296
+ "error": str(exc),
297
+ "report": report_path,
298
+ }
299
+
300
+ report_mode = extract_scope_field(report_text, "Mode")
301
+ report_artifact_dir = extract_scope_field(report_text, "Artifact Dir")
302
+ report_verdict = extract_verdict(report_text)
303
+
304
+ boundary_gate = extract_section(report_text, "## Boundary Completion Gate")
305
+ boundary_matrix = extract_section(report_text, "## Boundary Matrix")
306
+ boundary_validation = extract_section(report_text, "## Boundary Validation")
307
+
308
+ gate_passed = extract_key_value(boundary_gate, "Completion gate passed")
309
+ boundary_missing = extract_key_value(boundary_gate, "Boundary-missing")
310
+ happy_path_only = extract_key_value(boundary_gate, "Happy-path-only")
311
+ validation_result = extract_key_value(boundary_validation, "Validator result").lower()
312
+
313
+ columns_ok = boundary_columns_ok(boundary_matrix)
314
+ incomplete = boundary_incomplete(boundary_matrix)
315
+ openapi_file = discover_openapi_file()
316
+ validator_ok = boundary_validator_passed(report_path, openapi_file)
317
+ fresh_ok = scope_fresh_ok(report_path, marker, report_text)
318
+
319
+ artifact_ok = normalize_artifact_dir(report_artifact_dir) == normalize_artifact_dir(expected_artifact_dir)
320
+ boundary_ok = (
321
+ columns_ok
322
+ and not incomplete
323
+ and gate_passed == "yes"
324
+ and boundary_missing == "0"
325
+ and happy_path_only == "0"
326
+ and validation_result == "passed"
327
+ and validator_ok
328
+ and fresh_ok
329
+ )
330
+
331
+ result = {
332
+ "status": "PASS" if report_mode == "this-change" and artifact_ok and report_verdict == "PASS" and boundary_ok else "FAIL",
333
+ "verdict": report_verdict,
334
+ "mode": report_mode,
335
+ "artifact_dir": report_artifact_dir,
336
+ "boundary_gate": gate_passed,
337
+ "boundary_missing": boundary_missing,
338
+ "happy_path_only": happy_path_only,
339
+ "boundary_validation": validation_result,
340
+ "validator_passed": "1" if validator_ok else "0",
341
+ "scope_fresh": "1" if fresh_ok else "0",
342
+ "openapi": openapi_file,
343
+ "report": report_path,
344
+ "feature_slug": feature_slug,
345
+ }
346
+ if not columns_ok:
347
+ result["reason"] = "boundary_columns_missing"
348
+ elif incomplete:
349
+ result["reason"] = "boundary_incomplete"
350
+ elif not fresh_ok:
351
+ result["reason"] = "scope_not_fresh"
352
+ elif not artifact_ok:
353
+ result["reason"] = "wrong_artifact_dir"
354
+ elif report_mode != "this-change":
355
+ result["reason"] = "wrong_mode"
356
+ elif report_verdict != "PASS":
357
+ result["reason"] = "verdict_not_pass"
358
+ elif not validator_ok:
359
+ result["reason"] = "validator_failed"
360
+ elif not boundary_ok:
361
+ result["reason"] = "boundary_gate_failed"
362
+ return result
363
+
364
+
365
+ def check_gate(feature_slug, artifact_dir=None, checkpoint_path=None):
366
+ expected_artifact_dir = artifact_dir or default_artifact_dir(feature_slug)
367
+ marker = marker_path(feature_slug)
368
+ report_path = newest_report_after_marker(marker)
369
+ if not report_path:
370
+ return {"status": "MISSING", "marker": marker}
371
+
372
+ result = validate_report(report_path, marker, feature_slug, expected_artifact_dir)
373
+ if result["status"] != "PASS":
374
+ return result
375
+
376
+ write_report_pointer(feature_slug, report_path)
377
+ checkpoint_ok, checkpoint_error = update_checkpoint(checkpoint_path)
378
+ if not checkpoint_ok:
379
+ result["status"] = "FAIL"
380
+ result["reason"] = "checkpoint_update_failed"
381
+ result["checkpoint_error"] = checkpoint_error
382
+ return result
383
+
384
+
385
+ def format_result(result):
386
+ status = result.get("status") or result.get("gate", "")
387
+ if status == "GATE:START":
388
+ return "GATE:START marker={}".format(result.get("marker", ""))
389
+ if status == "MISSING":
390
+ return "GATE:MISSING marker={}".format(result.get("marker", ""))
391
+ if status == "PASS":
392
+ return "GATE:PASS {}".format(result.get("report", ""))
393
+
394
+ ordered = [
395
+ "reason",
396
+ "verdict",
397
+ "mode",
398
+ "artifact_dir",
399
+ "boundary_gate",
400
+ "boundary_missing",
401
+ "happy_path_only",
402
+ "boundary_validation",
403
+ "validator_passed",
404
+ "scope_fresh",
405
+ "openapi",
406
+ "report",
407
+ "checkpoint_error",
408
+ "error",
409
+ ]
410
+ parts = []
411
+ for key in ordered:
412
+ value = result.get(key)
413
+ if value is not None and value != "":
414
+ parts.append("{}={}".format(key, value))
415
+ return "GATE:FAIL {}".format(" ".join(parts)).rstrip()
416
+
417
+
418
+ def build_parser():
419
+ parser = argparse.ArgumentParser(
420
+ description="Run the scoped PrizmKit feature test gate.",
421
+ )
422
+ subparsers = parser.add_subparsers(dest="command", required=True)
423
+
424
+ start = subparsers.add_parser("start", help="Create the test gate marker")
425
+ start.add_argument("--feature-slug", required=True)
426
+
427
+ check = subparsers.add_parser("check", help="Validate the latest scoped test report")
428
+ check.add_argument("--feature-slug", required=True)
429
+ check.add_argument("--artifact-dir", default="")
430
+ check.add_argument("--checkpoint-path", default="")
431
+ return parser
432
+
433
+
434
+ def main(argv=None):
435
+ parser = build_parser()
436
+ args = parser.parse_args(argv)
437
+ if args.command == "start":
438
+ result = start_gate(args.feature_slug)
439
+ else:
440
+ result = check_gate(args.feature_slug, args.artifact_dir, args.checkpoint_path)
441
+ print(format_result(result))
442
+ return 0
443
+
444
+
445
+ if __name__ == "__main__":
446
+ sys.exit(main())
@@ -30,7 +30,7 @@ Infer what needs to be done from the feature context above and follow the standa
30
30
 
31
31
  3. **Implement**: Run `/prizmkit-implement` with `artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` to execute the plan using TDD (write tests first, then implement).
32
32
 
33
- 4. **Test**: Run the project test suite to verify all tests pass with no regressions.
33
+ 4. **Test**: Run `/prizmkit-test scope=this-change artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` to generate and verify tests only for this feature's changed scope. Do not use bugfix/refactor artifact directories for this gate.
34
34
 
35
35
  5. **Review**: Run `/prizmkit-code-review` with `artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` to review and auto-fix changes against the spec (internal review-fix loop, max 3 rounds).
36
36
 
@@ -163,6 +163,20 @@ When tests fail, use convergence recovery — keep fixing while progress is bein
163
163
  **Key rule**: If failures decrease (even by 1), plateau counter resets. Do NOT block commit for unresolved failures — document and defer to next session.
164
164
 
165
165
 
166
+ ### Scoped Feature Test Gate — PrizmKit Test
167
+
168
+ Run `/prizmkit-test scope=this-change artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` after implementation and before review/commit.
169
+
170
+ Before invoking the skill, create `.prizmkit/specs/{{FEATURE_SLUG}}/.prizmkit-test-started` so stale reports are ignored.
171
+
172
+ Gate requirements:
173
+ - Generate or update tests only for this feature's changed files, changed public interfaces, and acceptance criteria.
174
+ - Do NOT use `.prizmkit/bugfix/` or `.prizmkit/refactor/` artifact directories.
175
+ - Record unrelated historical gaps under `Out of Scope Gaps`; do not generate tests for them and do not fail this feature because of them.
176
+ - Accept only a current-run `.prizmkit/test/*/test-report.md` whose `Scope` mode is `this-change`, whose `Artifact Dir` matches `.prizmkit/specs/{{FEATURE_SLUG}}/`, whose `Verdict` is `PASS`, whose `Boundary Completion Gate` reports `Completion gate passed: yes` with zero `Boundary-missing` and zero `Happy-path-only`, and whose `Boundary Validation` result is `passed`.
177
+ - Only after a valid `PASS`, write the report path to `.prizmkit/specs/{{FEATURE_SLUG}}/test-report-path.txt` and append a 3-5 bullet `## PrizmKit Test Gate` summary to `context-snapshot.md`.
178
+ - If the report verdict is `NEEDS_FIXES`, fix in-scope implementation/test issues and rerun `/prizmkit-test`. If the report is `BLOCKED`, missing, stale, or for the wrong scope/artifact dir, write `failure-log.md` and stop for recovery.
179
+
166
180
  {{IF_BROWSER_INTERACTION}}
167
181
  ### Phase 3.5: Browser Verification — MANDATORY
168
182
 
@@ -240,6 +240,20 @@ grep -q "## Implementation Log" .prizmkit/specs/{{FEATURE_SLUG}}/context-snapsho
240
240
  ```
241
241
  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."
242
242
 
243
+ ### Scoped Feature Test Gate — PrizmKit Test
244
+
245
+ Run `/prizmkit-test scope=this-change artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` after implementation and before review/commit.
246
+
247
+ Before invoking the skill, create `.prizmkit/specs/{{FEATURE_SLUG}}/.prizmkit-test-started` so stale reports are ignored.
248
+
249
+ Gate requirements:
250
+ - Generate or update tests only for this feature's changed files, changed public interfaces, and acceptance criteria.
251
+ - Do NOT use `.prizmkit/bugfix/` or `.prizmkit/refactor/` artifact directories.
252
+ - Record unrelated historical gaps under `Out of Scope Gaps`; do not generate tests for them and do not fail this feature because of them.
253
+ - Accept only a current-run `.prizmkit/test/*/test-report.md` whose `Scope` mode is `this-change`, whose `Artifact Dir` matches `.prizmkit/specs/{{FEATURE_SLUG}}/`, whose `Verdict` is `PASS`, whose `Boundary Completion Gate` reports `Completion gate passed: yes` with zero `Boundary-missing` and zero `Happy-path-only`, and whose `Boundary Validation` result is `passed`.
254
+ - Only after a valid `PASS`, write the report path to `.prizmkit/specs/{{FEATURE_SLUG}}/test-report-path.txt` and append a 3-5 bullet `## PrizmKit Test Gate` summary to `context-snapshot.md`.
255
+ - If the report verdict is `NEEDS_FIXES`, fix in-scope implementation/test issues and rerun `/prizmkit-test`. If the report is `BLOCKED`, missing, stale, or for the wrong scope/artifact dir, write `failure-log.md` and stop for recovery.
256
+
243
257
  ### Phase 5: Review + Test — Reviewer Subagent
244
258
 
245
259
  Spawn Reviewer subagent (Agent tool, subagent_type="prizm-dev-team-reviewer", run_in_background=false).
@@ -250,9 +264,10 @@ Prompt:
250
264
  > "Read {{REVIEWER_SUBAGENT_PATH}}. For feature {{FEATURE_ID}} (slug: {{FEATURE_SLUG}}):
251
265
  > 1. Read `.prizmkit/specs/{{FEATURE_SLUG}}/spec.md` for goals and acceptance criteria
252
266
  > 2. Read `.prizmkit/specs/{{FEATURE_SLUG}}/plan.md` for architecture decisions and completed tasks
253
- > 3. Run /prizmkit-code-review with artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/. The skill runs an internal review-fix loop (Reviewer filter Dev fix, max 3 rounds) and writes review-report.md.
254
- > 4. Run the full test suite using `{{TEST_CMD}}`. When running: `({{TEST_CMD}}) 2>&1 | tee /tmp/review-test-out.txt | tail -20`, then grep the file for details do NOT re-run the suite multiple times.
255
- > 5. review-report.md will be written to .prizmkit/specs/{{FEATURE_SLUG}}/ by prizmkit-code-review.
267
+ > 3. Read `.prizmkit/specs/{{FEATURE_SLUG}}/test-report-path.txt`, then read the referenced `/prizmkit-test` report. Review generated/updated tests, `In-Scope Failures`, `Baseline Failures`, `Out of Scope Gaps`, boundary coverage if present, and the report verdict.
268
+ > 4. Run /prizmkit-code-review with artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/. The skill runs an internal review-fix loop (Reviewer filter Dev fix, max 3 rounds) and writes review-report.md.
269
+ > 5. Run the full test suite using `{{TEST_CMD}}`. When running: `({{TEST_CMD}}) 2>&1 | tee /tmp/review-test-out.txt | tail -20`, then grep the file for details — do NOT re-run the suite multiple times.
270
+ > 6. review-report.md will be written to .prizmkit/specs/{{FEATURE_SLUG}}/ by prizmkit-code-review.
256
271
  > Report: verdict (PASS/NEEDS_FIXES), number of rounds, findings fixed/rejected."
257
272
 
258
273
  Wait for Reviewer to return.
@@ -301,6 +301,20 @@ Wait for Dev to return. **If Dev times out before all tasks are `[x]`**:
301
301
 
302
302
  All tasks `[x]`, tests pass.
303
303
 
304
+ ### Scoped Feature Test Gate — PrizmKit Test
305
+
306
+ Run `/prizmkit-test scope=this-change artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` after implementation and before review/commit.
307
+
308
+ Before invoking the skill, create `.prizmkit/specs/{{FEATURE_SLUG}}/.prizmkit-test-started` so stale reports are ignored.
309
+
310
+ Gate requirements:
311
+ - Generate or update tests only for this feature's changed files, changed public interfaces, and acceptance criteria.
312
+ - Do NOT use `.prizmkit/bugfix/` or `.prizmkit/refactor/` artifact directories.
313
+ - Record unrelated historical gaps under `Out of Scope Gaps`; do not generate tests for them and do not fail this feature because of them.
314
+ - Accept only a current-run `.prizmkit/test/*/test-report.md` whose `Scope` mode is `this-change`, whose `Artifact Dir` matches `.prizmkit/specs/{{FEATURE_SLUG}}/`, whose `Verdict` is `PASS`, whose `Boundary Completion Gate` reports `Completion gate passed: yes` with zero `Boundary-missing` and zero `Happy-path-only`, and whose `Boundary Validation` result is `passed`.
315
+ - Only after a valid `PASS`, write the report path to `.prizmkit/specs/{{FEATURE_SLUG}}/test-report-path.txt` and append a 3-5 bullet `## PrizmKit Test Gate` summary to `context-snapshot.md`.
316
+ - If the report verdict is `NEEDS_FIXES`, fix in-scope implementation/test issues and rerun `/prizmkit-test`. If the report is `BLOCKED`, missing, stale, or for the wrong scope/artifact dir, write `failure-log.md` and stop for recovery.
317
+
304
318
  ### Phase 5: Review + Test — Reviewer Agent
305
319
 
306
320
  Spawn Reviewer agent (Agent tool, subagent_type="prizm-dev-team-reviewer", run_in_background=false).
@@ -311,9 +325,10 @@ Prompt:
311
325
  > "Read {{REVIEWER_SUBAGENT_PATH}}. For feature {{FEATURE_ID}} (slug: {{FEATURE_SLUG}}):
312
326
  > 1. Read `.prizmkit/specs/{{FEATURE_SLUG}}/spec.md` for goals and acceptance criteria
313
327
  > 2. Read `.prizmkit/specs/{{FEATURE_SLUG}}/plan.md` for architecture decisions and completed tasks
314
- > 3. Run /prizmkit-code-review with artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/. The skill runs an internal review-fix loop (Reviewer filter Dev fix, max 3 rounds) and writes review-report.md.
315
- > 4. Run the full test suite using `{{TEST_CMD}}`. When running tests: `({{TEST_CMD}}) 2>&1 | tee /tmp/review-test-out.txt | tail -20`, then grep `/tmp/review-test-out.txt` for details do NOT re-run the suite multiple times.
316
- > 5. review-report.md will be written to .prizmkit/specs/{{FEATURE_SLUG}}/ by prizmkit-code-review.
328
+ > 3. Read `.prizmkit/specs/{{FEATURE_SLUG}}/test-report-path.txt`, then read the referenced `/prizmkit-test` report. Review generated/updated tests, `In-Scope Failures`, `Baseline Failures`, `Out of Scope Gaps`, boundary coverage if present, and the report verdict.
329
+ > 4. Run /prizmkit-code-review with artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/. The skill runs an internal review-fix loop (Reviewer filter Dev fix, max 3 rounds) and writes review-report.md.
330
+ > 5. Run the full test suite using `{{TEST_CMD}}`. When running tests: `({{TEST_CMD}}) 2>&1 | tee /tmp/review-test-out.txt | tail -20`, then grep `/tmp/review-test-out.txt` for details — do NOT re-run the suite multiple times.
331
+ > 6. review-report.md will be written to .prizmkit/specs/{{FEATURE_SLUG}}/ by prizmkit-code-review.
317
332
  > Report: verdict (PASS/NEEDS_FIXES), number of rounds, findings fixed/rejected."
318
333
 
319
334
  Wait for Reviewer to return.
@@ -70,13 +70,13 @@ python3 $PIPELINE_DIR/scripts/update-checkpoint.py \
70
70
  --status completed
71
71
  ```
72
72
 
73
- ### TERMINAL STOP — S09 Completed
73
+ ### TERMINAL STOP — Commit Completed
74
74
 
75
75
  After all of the following are true, the session is **TERMINAL**:
76
76
 
77
77
  - `/prizmkit-committer` has completed
78
78
  - `git status --short` is clean (except ignored/local-only pipeline artifacts)
79
79
  - `.prizmkit/specs/{{FEATURE_SLUG}}/completion-summary.json` is written
80
- - the S09 / `prizmkit-committer` checkpoint is marked `completed`
80
+ - the `prizmkit-committer` checkpoint is marked `completed`
81
81
 
82
82
  At that point, stop immediately. Do **not** process delayed teammate/reviewer findings, do **not** edit files, do **not** amend the commit, and do **not** start any new tool calls. Any findings that arrive after S09 is completed are follow-up work for a later session, not part of this feature commit.
@@ -63,13 +63,13 @@ python3 $PIPELINE_DIR/scripts/update-checkpoint.py \
63
63
  --status completed
64
64
  ```
65
65
 
66
- ### TERMINAL STOP — S09 Completed
66
+ ### TERMINAL STOP — Commit Completed
67
67
 
68
68
  After all of the following are true, the session is **TERMINAL**:
69
69
 
70
70
  - `/prizmkit-committer` has completed
71
71
  - `git status --short` is clean (except ignored/local-only pipeline artifacts)
72
72
  - `.prizmkit/specs/{{FEATURE_SLUG}}/completion-summary.json` is written
73
- - the S09 / `prizmkit-committer` checkpoint is marked `completed`
73
+ - the `prizmkit-committer` checkpoint is marked `completed`
74
74
 
75
75
  At that point, stop immediately. Do **not** process delayed teammate/reviewer findings, do **not** edit files, do **not** amend the commit, and do **not** start any new tool calls. Any findings that arrive after S09 is completed are follow-up work for a later session, not part of this feature commit.
@@ -0,0 +1,39 @@
1
+ ### Scoped Feature Test Gate — PrizmKit Test
2
+
3
+ **Goal**: Generate and verify tests for this feature's changed scope before code review without embedding the full gate implementation in this prompt.
4
+
5
+ Create a start marker immediately before invoking the skill so the gate can reject stale reports from older sessions:
6
+
7
+ ```bash
8
+ python3 "$PIPELINE_DIR/scripts/prizmkit-test-gate.py" start \
9
+ --feature-slug "{{FEATURE_SLUG}}"
10
+ ```
11
+
12
+ Run `/prizmkit-test` with the feature artifact directory:
13
+
14
+ ```text
15
+ /prizmkit-test scope=this-change artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/
16
+ ```
17
+
18
+ Rules:
19
+ - This gate is feature-pipeline only. Do NOT use `.prizmkit/bugfix/` or `.prizmkit/refactor/` artifact directories here.
20
+ - Generate or update tests only for this feature's changed files, changed public interfaces, and acceptance criteria.
21
+ - Record unrelated historical coverage gaps under `Out of Scope Gaps`; do not generate tests for them and do not fail this feature because of them.
22
+ - If generated tests fail because implementation is wrong, fix implementation within this feature scope and rerun the affected tests.
23
+ - If failures are baseline or unrelated to this feature, document them in the report and continue only when no in-scope failure remains unexplained.
24
+
25
+ **Gate Check — Test Report**:
26
+ After `/prizmkit-test` returns, run the gate script to locate the newest current-run report, validate scope/freshness/boundary coverage, write `test-report-path.txt`, and mark the `prizmkit-test` checkpoint completed only on `GATE:PASS`:
27
+
28
+ ```bash
29
+ python3 "$PIPELINE_DIR/scripts/prizmkit-test-gate.py" check \
30
+ --feature-slug "{{FEATURE_SLUG}}" \
31
+ --artifact-dir ".prizmkit/specs/{{FEATURE_SLUG}}/" \
32
+ --checkpoint-path "{{CHECKPOINT_PATH}}"
33
+ ```
34
+
35
+ Gate outcomes:
36
+ - `GATE:PASS` → append the report path and a 3-5 bullet summary to `context-snapshot.md` under `## PrizmKit Test Gate`, then proceed to the next checkpoint step. The gate script already wrote `test-report-path.txt` and marked `prizmkit-test` completed.
37
+ - `GATE:FAIL` with `NEEDS_FIXES` → read the report's `In-Scope Failures`, fix the feature implementation or generated tests within this feature scope, rerun `/prizmkit-test`, and repeat the gate check. Do not proceed to code review while the verdict is not `PASS`.
38
+ - `GATE:FAIL` with `BLOCKED`, wrong mode, wrong artifact dir, unreadable report, stale scope, boundary failure, validator failure, or checkpoint update failure → write `.prizmkit/specs/{{FEATURE_SLUG}}/failure-log.md` with the scoped test failure and stop for recovery.
39
+ - `GATE:MISSING` → perform one bounded status check: inspect `/prizmkit-test` output and `.prizmkit/test/` for a report directory. If no current-run report exists, write `failure-log.md` and stop for recovery.
@@ -1,4 +1,8 @@
1
- ### Review + Test — Code Review
1
+ ### Review — Code Review
2
+
3
+ Precondition: the `prizmkit-test` checkpoint is completed and `.prizmkit/specs/{{FEATURE_SLUG}}/test-report-path.txt` points to the scoped test report.
4
+
5
+ Before invoking review, read `.prizmkit/specs/{{FEATURE_SLUG}}/test-report-path.txt`, read the referenced `/prizmkit-test` report, and confirm the summary in `context-snapshot.md` under `## PrizmKit Test Gate` matches it. The code-review skill must receive this context through `artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` and the Reviewer must assess generated/updated test quality, `In-Scope Failures`, `Baseline Failures`, `Out of Scope Gaps`, boundary coverage if present, and the scoped report verdict.
2
6
 
3
7
  Run `/prizmkit-code-review` with `artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/`.
4
8
 
@@ -1,4 +1,8 @@
1
- ### Review + Test — Code Review
1
+ ### Review — Code Review
2
+
3
+ Precondition: the `prizmkit-test` checkpoint is completed and `.prizmkit/specs/{{FEATURE_SLUG}}/test-report-path.txt` points to the scoped test report.
4
+
5
+ Before invoking review, read `.prizmkit/specs/{{FEATURE_SLUG}}/test-report-path.txt`, read the referenced `/prizmkit-test` report, and confirm the summary in `context-snapshot.md` under `## PrizmKit Test Gate` matches it. The code-review skill must receive this context through `artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` and the Reviewer must assess generated/updated test quality, `In-Scope Failures`, `Baseline Failures`, `Out of Scope Gaps`, boundary coverage if present, and the scoped report verdict.
2
6
 
3
7
  Run `/prizmkit-code-review` with `artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/`.
4
8
 
@@ -24,4 +24,5 @@ def _load_hyphenated_module(module_name, filename):
24
24
  # Register hyphenated script files so tests can import them with underscores.
25
25
  _load_hyphenated_module("generate_bootstrap_prompt", "generate-bootstrap-prompt.py")
26
26
  _load_hyphenated_module("generate_bugfix_prompt", "generate-bugfix-prompt.py")
27
+ _load_hyphenated_module("prizmkit_test_gate", "prizmkit-test-gate.py")
27
28
  _load_hyphenated_module("update_feature_status", "update-feature-status.py")