prizmkit 1.1.1 → 1.1.4
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/adapters/claude/agent-adapter.js +18 -0
- package/bundled/adapters/claude/command-adapter.js +1 -27
- package/bundled/agents/prizm-dev-team-critic.md +2 -0
- package/bundled/agents/prizm-dev-team-dev.md +2 -0
- package/bundled/agents/prizm-dev-team-reviewer.md +2 -0
- package/bundled/dev-pipeline/README.md +63 -63
- package/bundled/dev-pipeline/assets/feature-list-example.json +1 -1
- package/bundled/dev-pipeline/assets/prizm-dev-team-integration.md +1 -1
- package/bundled/dev-pipeline/{launch-daemon.sh → launch-feature-daemon.sh} +33 -33
- package/bundled/dev-pipeline/launch-refactor-daemon.sh +454 -0
- package/bundled/dev-pipeline/lib/branch.sh +1 -1
- package/bundled/dev-pipeline/reset-feature.sh +3 -3
- package/bundled/dev-pipeline/reset-refactor.sh +312 -0
- package/bundled/dev-pipeline/{retry-bug.sh → retry-bugfix.sh} +47 -59
- package/bundled/dev-pipeline/retry-feature.sh +41 -54
- package/bundled/dev-pipeline/retry-refactor.sh +358 -0
- package/bundled/dev-pipeline/run-bugfix.sh +41 -0
- package/bundled/dev-pipeline/{run.sh → run-feature.sh} +64 -31
- package/bundled/dev-pipeline/run-refactor.sh +787 -0
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +398 -10
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +124 -0
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +419 -0
- package/bundled/dev-pipeline/scripts/init-refactor-pipeline.py +393 -0
- package/bundled/dev-pipeline/scripts/update-refactor-status.py +726 -0
- package/bundled/dev-pipeline/templates/agent-prompts/critic-code-challenge.md +13 -0
- package/bundled/dev-pipeline/templates/agent-prompts/critic-plan-challenge.md +7 -0
- package/bundled/dev-pipeline/templates/agent-prompts/dev-fix.md +7 -0
- package/bundled/dev-pipeline/templates/agent-prompts/dev-implement.md +27 -0
- package/bundled/dev-pipeline/templates/agent-prompts/dev-resume.md +5 -0
- package/bundled/dev-pipeline/templates/agent-prompts/reviewer-analyze.md +5 -0
- package/bundled/dev-pipeline/templates/agent-prompts/reviewer-review.md +12 -0
- package/bundled/dev-pipeline/templates/bootstrap-tier1.md +33 -2
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +13 -9
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +16 -12
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +22 -4
- package/bundled/dev-pipeline/templates/feature-list-schema.json +1 -1
- package/bundled/dev-pipeline/templates/refactor-list-schema.json +159 -0
- package/bundled/dev-pipeline/templates/sections/ac-verification-checklist.md +13 -0
- package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +36 -0
- package/bundled/dev-pipeline/templates/sections/failure-log-check.md +2 -1
- package/bundled/dev-pipeline/templates/sections/feature-context.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-analyze-agent.md +11 -7
- package/bundled/dev-pipeline/templates/sections/phase-analyze-full.md +11 -7
- package/bundled/dev-pipeline/templates/sections/phase-browser-verification.md +5 -1
- package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +3 -0
- package/bundled/dev-pipeline/templates/sections/phase-commit.md +3 -0
- package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-agent-suffix.md +3 -0
- package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-lite-suffix.md +3 -0
- package/bundled/dev-pipeline/templates/sections/phase-critic-code.md +11 -10
- package/bundled/dev-pipeline/templates/sections/phase-critic-plan-full.md +12 -10
- package/bundled/dev-pipeline/templates/sections/phase-critic-plan.md +11 -9
- package/bundled/dev-pipeline/templates/sections/phase-deploy-verification.md +3 -0
- package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +10 -10
- package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +12 -16
- package/bundled/dev-pipeline/templates/sections/phase-implement-lite.md +3 -0
- package/bundled/dev-pipeline/templates/sections/phase-plan-agent.md +3 -0
- package/bundled/dev-pipeline/templates/sections/phase-plan-lite.md +3 -0
- package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +11 -13
- package/bundled/dev-pipeline/templates/sections/phase-review-full.md +12 -20
- package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +3 -0
- package/bundled/dev-pipeline/templates/sections/phase0-init.md +3 -0
- package/bundled/dev-pipeline/templates/sections/phase0-test-baseline.md +3 -0
- package/bundled/dev-pipeline/templates/sections/resume-header.md +4 -1
- package/bundled/dev-pipeline/templates/sections/test-failure-recovery.md +75 -0
- package/bundled/rules/prizm/prizm-commit-workflow.md +1 -0
- package/bundled/rules/prizm/prizm-documentation.md +15 -15
- package/bundled/rules/prizm/prizm-progressive-loading.md +2 -1
- package/bundled/skills/_metadata.json +33 -6
- package/bundled/skills/app-planner/SKILL.md +105 -320
- package/bundled/skills/app-planner/assets/app-design-guide.md +101 -0
- package/bundled/skills/app-planner/references/frontend-design-guide.md +1 -1
- package/bundled/skills/app-planner/references/project-brief-guide.md +49 -80
- package/bundled/skills/bug-fix-workflow/SKILL.md +2 -2
- package/bundled/skills/bug-planner/SKILL.md +68 -5
- package/bundled/skills/bug-planner/scripts/validate-bug-list.py +3 -2
- package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +19 -5
- package/bundled/skills/{dev-pipeline-launcher → feature-pipeline-launcher}/SKILL.md +32 -32
- package/bundled/skills/feature-planner/SKILL.md +337 -0
- package/bundled/skills/{app-planner → feature-planner}/assets/evaluation-guide.md +4 -4
- package/bundled/skills/{app-planner → feature-planner}/assets/planning-guide.md +3 -171
- package/bundled/skills/{app-planner → feature-planner}/references/browser-interaction.md +6 -5
- package/bundled/skills/feature-planner/references/decomposition-patterns.md +75 -0
- package/bundled/skills/{app-planner → feature-planner}/references/error-recovery.md +8 -8
- package/bundled/skills/{app-planner → feature-planner}/references/incremental-feature-planning.md +1 -1
- package/bundled/skills/{app-planner/references/new-app-planning.md → feature-planner/references/new-project-planning.md} +1 -1
- package/bundled/skills/{app-planner → feature-planner}/scripts/validate-and-generate.py +4 -4
- package/bundled/skills/feature-workflow/SKILL.md +23 -23
- package/bundled/skills/prizm-kit/SKILL.md +1 -3
- package/bundled/skills/prizm-kit/assets/project-memory-template.md +4 -2
- package/bundled/skills/prizmkit-analyze/SKILL.md +2 -5
- package/bundled/skills/prizmkit-code-review/SKILL.md +2 -2
- package/bundled/skills/prizmkit-committer/SKILL.md +32 -8
- package/bundled/skills/prizmkit-deploy/SKILL.md +1 -5
- package/bundled/skills/prizmkit-implement/SKILL.md +5 -51
- package/bundled/skills/prizmkit-init/SKILL.md +7 -78
- package/bundled/skills/prizmkit-plan/SKILL.md +1 -12
- package/bundled/skills/prizmkit-prizm-docs/SKILL.md +13 -28
- package/bundled/skills/prizmkit-prizm-docs/assets/PRIZM-SPEC.md +52 -1
- package/bundled/skills/prizmkit-retrospective/SKILL.md +12 -117
- package/bundled/skills/recovery-workflow/SKILL.md +168 -316
- package/bundled/skills/recovery-workflow/evals/evals.json +29 -13
- package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +232 -274
- package/bundled/skills/refactor-pipeline-launcher/SKILL.md +352 -0
- package/bundled/skills/refactor-planner/SKILL.md +436 -0
- package/bundled/skills/refactor-planner/assets/planning-guide.md +292 -0
- package/bundled/skills/refactor-planner/references/behavior-preservation.md +301 -0
- package/bundled/skills/refactor-planner/references/refactor-scoping-guide.md +221 -0
- package/bundled/skills/refactor-planner/scripts/validate-and-generate-refactor.py +786 -0
- package/bundled/skills/refactor-workflow/SKILL.md +299 -319
- package/bundled/team/prizm-dev-team.json +1 -1
- package/package.json +1 -1
- package/src/clean.js +3 -3
- package/src/scaffold.js +6 -6
- package/bundled/skills/prizmkit-plan/assets/spec-template.md +0 -56
- package/bundled/skills/prizmkit-plan/references/clarify-guide.md +0 -67
- package/src/config.js +0 -504
- package/src/prompts.js +0 -210
- /package/bundled/skills/{dev-pipeline-launcher → feature-pipeline-launcher}/scripts/preflight-check.py +0 -0
|
@@ -101,6 +101,12 @@ def parse_args():
|
|
|
101
101
|
default=None,
|
|
102
102
|
help="Override critic enablement (default: read from feature field)",
|
|
103
103
|
)
|
|
104
|
+
parser.add_argument(
|
|
105
|
+
"--extract-baselines",
|
|
106
|
+
action="store_true",
|
|
107
|
+
help="Run tests and extract baseline failures (slower, optional)",
|
|
108
|
+
)
|
|
109
|
+
|
|
104
110
|
return parser.parse_args()
|
|
105
111
|
|
|
106
112
|
|
|
@@ -154,6 +160,101 @@ def format_acceptance_criteria(criteria):
|
|
|
154
160
|
lines.append("- {}".format(item))
|
|
155
161
|
return "\n".join(lines)
|
|
156
162
|
|
|
163
|
+
def detect_test_commands(project_root):
|
|
164
|
+
"""
|
|
165
|
+
Auto-detect test commands based on project structure.
|
|
166
|
+
|
|
167
|
+
Returns: space-separated string of test commands (e.g., "npm test go test ./...")
|
|
168
|
+
"""
|
|
169
|
+
test_commands = []
|
|
170
|
+
|
|
171
|
+
# Check for npm/package.json
|
|
172
|
+
if os.path.exists(os.path.join(project_root, "package.json")):
|
|
173
|
+
test_commands.append("npm test")
|
|
174
|
+
|
|
175
|
+
# Check for Go
|
|
176
|
+
if os.path.exists(os.path.join(project_root, "go.mod")):
|
|
177
|
+
test_commands.append("go test ./...")
|
|
178
|
+
|
|
179
|
+
# Check for Rust/Cargo
|
|
180
|
+
if os.path.exists(os.path.join(project_root, "Cargo.toml")):
|
|
181
|
+
test_commands.append("cargo test")
|
|
182
|
+
|
|
183
|
+
# Check for Python pytest
|
|
184
|
+
if os.path.exists(os.path.join(project_root, "pytest.ini")) or \
|
|
185
|
+
os.path.exists(os.path.join(project_root, "setup.py")):
|
|
186
|
+
test_commands.append("pytest")
|
|
187
|
+
|
|
188
|
+
# Check for Make test target
|
|
189
|
+
makefile_path = os.path.join(project_root, "Makefile")
|
|
190
|
+
if os.path.exists(makefile_path):
|
|
191
|
+
try:
|
|
192
|
+
with open(makefile_path, 'r') as f:
|
|
193
|
+
if 'test:' in f.read():
|
|
194
|
+
test_commands.append("make test")
|
|
195
|
+
except Exception:
|
|
196
|
+
pass
|
|
197
|
+
|
|
198
|
+
# Return deduplicated space-separated list
|
|
199
|
+
return " ".join(dict.fromkeys(test_commands)) if test_commands else ""
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def extract_baseline_failures(test_cmd, project_root):
|
|
203
|
+
"""
|
|
204
|
+
Run test command and extract failing tests.
|
|
205
|
+
|
|
206
|
+
Returns: semicolon-separated list of failing test names
|
|
207
|
+
"""
|
|
208
|
+
if not test_cmd or test_cmd.startswith("(auto-detection"):
|
|
209
|
+
return ""
|
|
210
|
+
|
|
211
|
+
try:
|
|
212
|
+
import subprocess
|
|
213
|
+
original_cwd = os.getcwd()
|
|
214
|
+
os.chdir(project_root)
|
|
215
|
+
|
|
216
|
+
result = subprocess.run(
|
|
217
|
+
test_cmd,
|
|
218
|
+
shell=True,
|
|
219
|
+
capture_output=True,
|
|
220
|
+
text=True,
|
|
221
|
+
timeout=120
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
os.chdir(original_cwd)
|
|
225
|
+
|
|
226
|
+
output = result.stdout + result.stderr
|
|
227
|
+
failures = []
|
|
228
|
+
|
|
229
|
+
for line in output.split('\n'):
|
|
230
|
+
if 'FAILED' in line and '::' in line:
|
|
231
|
+
parts = line.split('FAILED')
|
|
232
|
+
if len(parts) > 1:
|
|
233
|
+
test_name = parts[1].strip().split(' ')[0]
|
|
234
|
+
if test_name and test_name not in failures:
|
|
235
|
+
failures.append(test_name)
|
|
236
|
+
|
|
237
|
+
return ";".join(failures) if failures else ""
|
|
238
|
+
|
|
239
|
+
except Exception as e:
|
|
240
|
+
return f"(error: {str(e)})"
|
|
241
|
+
finally:
|
|
242
|
+
try:
|
|
243
|
+
os.chdir(original_cwd)
|
|
244
|
+
except Exception:
|
|
245
|
+
pass
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def format_ac_checklist(acceptance_criteria):
|
|
249
|
+
"""Format acceptance criteria as a markdown checkbox list."""
|
|
250
|
+
if not acceptance_criteria:
|
|
251
|
+
return "- [ ] (no acceptance criteria specified)"
|
|
252
|
+
lines = []
|
|
253
|
+
for item in acceptance_criteria:
|
|
254
|
+
lines.append("- [ ] {}".format(item))
|
|
255
|
+
return "\n".join(lines)
|
|
256
|
+
|
|
257
|
+
|
|
157
258
|
|
|
158
259
|
def format_global_context(global_context, project_root=None):
|
|
159
260
|
"""Format global_context dict as a key-value list.
|
|
@@ -251,8 +352,9 @@ def _read_project_brief(project_root):
|
|
|
251
352
|
|
|
252
353
|
Returns the file content as a string, or a fallback message if absent.
|
|
253
354
|
This brief is generated by app-planner during interactive planning and
|
|
254
|
-
captures
|
|
255
|
-
|
|
355
|
+
captures the user's product ideas as a checklist. Each line is one idea,
|
|
356
|
+
marked [ ] for pending or [x] for completed. Feature sessions should mark
|
|
357
|
+
items [x] and append key file paths when implementing relevant ideas.
|
|
256
358
|
"""
|
|
257
359
|
brief_path = os.path.join(project_root, "project-brief.md")
|
|
258
360
|
if os.path.isfile(brief_path):
|
|
@@ -428,6 +530,171 @@ def determine_pipeline_mode(complexity):
|
|
|
428
530
|
return mapping.get(complexity, "lite")
|
|
429
531
|
|
|
430
532
|
|
|
533
|
+
# ============================================================
|
|
534
|
+
# Checkpoint generation
|
|
535
|
+
# ============================================================
|
|
536
|
+
|
|
537
|
+
# Mapping: section name -> (skill_key, display_name, required_artifacts)
|
|
538
|
+
# skill_key is a unique identifier in the checkpoint, not necessarily the
|
|
539
|
+
# prizmkit skill name. This ensures each section has a distinct key so
|
|
540
|
+
# merge_checkpoint_state() never collides.
|
|
541
|
+
SECTION_TO_SKILL = {
|
|
542
|
+
"phase0-init": ("prizmkit-init", "Project Bootstrap",
|
|
543
|
+
[".prizm-docs/root.prizm", ".prizmkit/config.json"]),
|
|
544
|
+
"phase0-test-baseline": ("test-baseline", "Test Baseline", []),
|
|
545
|
+
"phase-context-snapshot": ("context-snapshot", "Build Context Snapshot",
|
|
546
|
+
[".prizmkit/specs/{slug}/context-snapshot.md"]),
|
|
547
|
+
"phase-specify-plan": ("context-snapshot-and-plan", "Specify & Plan",
|
|
548
|
+
[".prizmkit/specs/{slug}/context-snapshot.md",
|
|
549
|
+
".prizmkit/specs/{slug}/plan.md"]),
|
|
550
|
+
"phase-plan": ("prizmkit-plan", "Plan & Tasks",
|
|
551
|
+
[".prizmkit/specs/{slug}/plan.md"]),
|
|
552
|
+
"phase-analyze": ("prizmkit-analyze", "Analyze", []),
|
|
553
|
+
"phase-critic-plan": ("critic-plan-review", "Critic: Plan Review", []),
|
|
554
|
+
"phase-implement": ("prizmkit-implement", "Implement + Test", []),
|
|
555
|
+
"phase-critic-code": ("critic-code-review", "Critic: Code Review", []),
|
|
556
|
+
"phase-review": ("prizmkit-code-review", "Code Review", []),
|
|
557
|
+
"phase-browser": ("browser-verification", "Browser Verification", []),
|
|
558
|
+
"phase-deploy": ("deploy-verification", "Deploy Verification", []),
|
|
559
|
+
"phase-commit": None, # special: split into retrospective + committer
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
# phase-commit is split into two steps
|
|
563
|
+
_COMMIT_STEPS = [
|
|
564
|
+
("prizmkit-retrospective", "Retrospective", []),
|
|
565
|
+
("prizmkit-committer", "Commit", []),
|
|
566
|
+
]
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def _resolve_artifacts(artifact_templates, slug):
|
|
570
|
+
"""Replace {slug} placeholder with the actual feature slug."""
|
|
571
|
+
return [a.replace("{slug}", slug) for a in artifact_templates]
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
def generate_checkpoint_definition(sections, pipeline_mode, workflow_type,
|
|
575
|
+
item_id, item_slug, session_id,
|
|
576
|
+
init_done=False):
|
|
577
|
+
"""Derive checkpoint step definitions from the assembled sections list.
|
|
578
|
+
|
|
579
|
+
Args:
|
|
580
|
+
sections: list of (name, content) tuples from assemble_sections()
|
|
581
|
+
pipeline_mode: "lite" | "standard" | "full"
|
|
582
|
+
workflow_type: "feature-pipeline"
|
|
583
|
+
item_id: feature ID (e.g. "F-001")
|
|
584
|
+
item_slug: feature slug (e.g. "001-user-auth")
|
|
585
|
+
session_id: current session ID
|
|
586
|
+
init_done: whether project is already initialized (Phase 0 skip)
|
|
587
|
+
|
|
588
|
+
Returns:
|
|
589
|
+
dict suitable for writing as workflow-checkpoint.json
|
|
590
|
+
"""
|
|
591
|
+
steps = []
|
|
592
|
+
step_counter = 1
|
|
593
|
+
prev_step_id = None
|
|
594
|
+
|
|
595
|
+
for section_name, _content in sections:
|
|
596
|
+
if section_name not in SECTION_TO_SKILL:
|
|
597
|
+
continue
|
|
598
|
+
|
|
599
|
+
mapping = SECTION_TO_SKILL[section_name]
|
|
600
|
+
|
|
601
|
+
if mapping is None:
|
|
602
|
+
# phase-commit -> split into retrospective + committer
|
|
603
|
+
for skill, name, artifacts in _COMMIT_STEPS:
|
|
604
|
+
step_id = "S{:02d}".format(step_counter)
|
|
605
|
+
steps.append({
|
|
606
|
+
"id": step_id,
|
|
607
|
+
"skill": skill,
|
|
608
|
+
"name": name,
|
|
609
|
+
"status": "pending",
|
|
610
|
+
"required_artifacts": _resolve_artifacts(artifacts, item_slug),
|
|
611
|
+
"depends_on": prev_step_id,
|
|
612
|
+
})
|
|
613
|
+
prev_step_id = step_id
|
|
614
|
+
step_counter += 1
|
|
615
|
+
continue
|
|
616
|
+
|
|
617
|
+
skill, name, artifacts = mapping
|
|
618
|
+
step_id = "S{:02d}".format(step_counter)
|
|
619
|
+
|
|
620
|
+
status = "pending"
|
|
621
|
+
if init_done and section_name in ("phase0-init", "phase0-test-baseline"):
|
|
622
|
+
status = "skipped"
|
|
623
|
+
|
|
624
|
+
steps.append({
|
|
625
|
+
"id": step_id,
|
|
626
|
+
"skill": skill,
|
|
627
|
+
"name": name,
|
|
628
|
+
"status": status,
|
|
629
|
+
"required_artifacts": _resolve_artifacts(artifacts, item_slug),
|
|
630
|
+
"depends_on": prev_step_id,
|
|
631
|
+
})
|
|
632
|
+
|
|
633
|
+
prev_step_id = step_id
|
|
634
|
+
step_counter += 1
|
|
635
|
+
|
|
636
|
+
return {
|
|
637
|
+
"version": 1,
|
|
638
|
+
"workflow_type": workflow_type,
|
|
639
|
+
"pipeline_mode": pipeline_mode,
|
|
640
|
+
"item_id": item_id,
|
|
641
|
+
"item_slug": item_slug,
|
|
642
|
+
"session_id": session_id,
|
|
643
|
+
"steps": steps,
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
def merge_checkpoint_state(existing, fresh, project_root):
|
|
648
|
+
"""Merge existing checkpoint state into a freshly generated definition.
|
|
649
|
+
|
|
650
|
+
Matching is by skill_key (not step ID), since tier changes across retries
|
|
651
|
+
may shift step IDs.
|
|
652
|
+
|
|
653
|
+
Merge rules:
|
|
654
|
+
1. Only keep completed steps whose required_artifacts all exist on disk
|
|
655
|
+
2. Keep skipped steps unconditionally
|
|
656
|
+
3. Once a step is NOT completed/skipped, break the dependency chain:
|
|
657
|
+
all subsequent steps reset to pending
|
|
658
|
+
"""
|
|
659
|
+
existing_status = {}
|
|
660
|
+
existing_artifacts = {}
|
|
661
|
+
for step in existing.get("steps", []):
|
|
662
|
+
existing_status[step["skill"]] = step["status"]
|
|
663
|
+
existing_artifacts[step["skill"]] = step.get("required_artifacts", [])
|
|
664
|
+
|
|
665
|
+
# Determine which completed steps have valid artifacts
|
|
666
|
+
valid_completed = set()
|
|
667
|
+
for skill_key, status in existing_status.items():
|
|
668
|
+
if status == "completed":
|
|
669
|
+
artifacts = existing_artifacts.get(skill_key, [])
|
|
670
|
+
if all(os.path.exists(os.path.join(project_root, a))
|
|
671
|
+
for a in artifacts):
|
|
672
|
+
valid_completed.add(skill_key)
|
|
673
|
+
else:
|
|
674
|
+
LOGGER.warning(
|
|
675
|
+
"Step '%s' was completed but artifacts missing — "
|
|
676
|
+
"resetting to pending", skill_key
|
|
677
|
+
)
|
|
678
|
+
elif status == "skipped":
|
|
679
|
+
valid_completed.add(skill_key)
|
|
680
|
+
|
|
681
|
+
# Apply to fresh steps; break chain on first non-valid step
|
|
682
|
+
chain_broken = False
|
|
683
|
+
for step in fresh["steps"]:
|
|
684
|
+
if chain_broken:
|
|
685
|
+
step["status"] = "pending"
|
|
686
|
+
continue
|
|
687
|
+
|
|
688
|
+
prev = existing_status.get(step["skill"])
|
|
689
|
+
if step["skill"] in valid_completed:
|
|
690
|
+
step["status"] = prev # completed or skipped
|
|
691
|
+
else:
|
|
692
|
+
chain_broken = True
|
|
693
|
+
step["status"] = "pending"
|
|
694
|
+
|
|
695
|
+
return fresh
|
|
696
|
+
|
|
697
|
+
|
|
431
698
|
# ============================================================
|
|
432
699
|
# Section Assembly (new modular approach)
|
|
433
700
|
# ============================================================
|
|
@@ -445,6 +712,38 @@ def load_section(sections_dir, name):
|
|
|
445
712
|
return f.read()
|
|
446
713
|
|
|
447
714
|
|
|
715
|
+
def load_agent_prompts(templates_dir):
|
|
716
|
+
"""Load agent prompt templates from agent-prompts/ directory.
|
|
717
|
+
|
|
718
|
+
Returns a dict of {{AGENT_PROMPT_XXX}} -> prompt content replacements.
|
|
719
|
+
If the directory does not exist, returns an empty dict (backward compat).
|
|
720
|
+
"""
|
|
721
|
+
agent_prompts_dir = os.path.join(templates_dir, "agent-prompts")
|
|
722
|
+
if not os.path.isdir(agent_prompts_dir):
|
|
723
|
+
LOGGER.debug("No agent-prompts/ directory found, skipping")
|
|
724
|
+
return {}
|
|
725
|
+
|
|
726
|
+
# Map filename -> placeholder name
|
|
727
|
+
# e.g. dev-implement.md -> {{AGENT_PROMPT_DEV_IMPLEMENT}}
|
|
728
|
+
prompt_map = {}
|
|
729
|
+
for filename in sorted(os.listdir(agent_prompts_dir)):
|
|
730
|
+
if not filename.endswith(".md"):
|
|
731
|
+
continue
|
|
732
|
+
stem = filename[:-3] # remove .md
|
|
733
|
+
placeholder = "{{{{AGENT_PROMPT_{}}}}}".format(
|
|
734
|
+
stem.upper().replace("-", "_")
|
|
735
|
+
)
|
|
736
|
+
filepath = os.path.join(agent_prompts_dir, filename)
|
|
737
|
+
try:
|
|
738
|
+
with open(filepath, "r", encoding="utf-8") as f:
|
|
739
|
+
prompt_map[placeholder] = f.read().strip()
|
|
740
|
+
LOGGER.debug("Loaded agent prompt: %s -> %s", filename, placeholder)
|
|
741
|
+
except IOError as exc:
|
|
742
|
+
LOGGER.warning("Failed to load agent prompt %s: %s", filename, exc)
|
|
743
|
+
|
|
744
|
+
return prompt_map
|
|
745
|
+
|
|
746
|
+
|
|
448
747
|
def _tier_header(pipeline_mode):
|
|
449
748
|
"""Return the tier-specific header and mission description."""
|
|
450
749
|
headers = {
|
|
@@ -587,6 +886,12 @@ def assemble_sections(pipeline_mode, sections_dir, init_done, is_resume,
|
|
|
587
886
|
load_section(sections_dir,
|
|
588
887
|
"subagent-timeout-recovery.md")))
|
|
589
888
|
|
|
889
|
+
# --- Checkpoint System ---
|
|
890
|
+
checkpoint_section_path = os.path.join(sections_dir, "checkpoint-system.md")
|
|
891
|
+
if os.path.isfile(checkpoint_section_path):
|
|
892
|
+
sections.append(("checkpoint-system",
|
|
893
|
+
load_section(sections_dir, "checkpoint-system.md")))
|
|
894
|
+
|
|
590
895
|
# --- Execution header ---
|
|
591
896
|
sections.append(("execution-header", "---\n\n## Execution\n"))
|
|
592
897
|
|
|
@@ -741,9 +1046,13 @@ def render_from_sections(sections, replacements):
|
|
|
741
1046
|
"""
|
|
742
1047
|
content = "\n".join(text for _, text in sections)
|
|
743
1048
|
|
|
744
|
-
# Replace all placeholders
|
|
745
|
-
|
|
746
|
-
|
|
1049
|
+
# Replace all placeholders — run twice to handle agent prompt templates
|
|
1050
|
+
# that contain their own {{PLACEHOLDER}} variables. First pass injects
|
|
1051
|
+
# agent prompt content (e.g. {{AGENT_PROMPT_DEV_IMPLEMENT}} expands to a
|
|
1052
|
+
# block containing {{FEATURE_ID}}). Second pass replaces the inner vars.
|
|
1053
|
+
for _pass in range(2):
|
|
1054
|
+
for placeholder, value in replacements.items():
|
|
1055
|
+
content = content.replace(placeholder, value)
|
|
747
1056
|
|
|
748
1057
|
return content
|
|
749
1058
|
|
|
@@ -954,11 +1263,28 @@ def build_replacements(args, feature, features, global_context, script_dir):
|
|
|
954
1263
|
steps = browser_interaction.get("verify_steps", [])
|
|
955
1264
|
if steps:
|
|
956
1265
|
browser_verify_steps = "\n".join(
|
|
957
|
-
" #
|
|
1266
|
+
" # Goal {}: {}".format(i + 1, step)
|
|
958
1267
|
for i, step in enumerate(steps)
|
|
959
1268
|
)
|
|
960
1269
|
else:
|
|
961
|
-
browser_verify_steps = " # (no specific verify
|
|
1270
|
+
browser_verify_steps = " # (no specific verify goals — just open and screenshot)"
|
|
1271
|
+
|
|
1272
|
+
# Auto-detect test commands from project structure
|
|
1273
|
+
test_cmd = detect_test_commands(project_root)
|
|
1274
|
+
if not test_cmd:
|
|
1275
|
+
test_cmd = "(auto-detection found no standard test commands; manually specify TEST_CMD)"
|
|
1276
|
+
|
|
1277
|
+
# Optionally extract baseline failures from test execution
|
|
1278
|
+
baseline_failures = ""
|
|
1279
|
+
if args.extract_baselines:
|
|
1280
|
+
baseline_failures = extract_baseline_failures(test_cmd, project_root)
|
|
1281
|
+
|
|
1282
|
+
# Extract coverage target from feature.testing field (new in v2)
|
|
1283
|
+
coverage_target = "80" # Default coverage target
|
|
1284
|
+
testing_config = feature.get("testing", {})
|
|
1285
|
+
if isinstance(testing_config, dict):
|
|
1286
|
+
coverage_target = str(testing_config.get("coverage_target", 80))
|
|
1287
|
+
|
|
962
1288
|
|
|
963
1289
|
replacements = {
|
|
964
1290
|
"{{RUN_ID}}": args.run_id,
|
|
@@ -988,6 +1314,9 @@ def build_replacements(args, feature, features, global_context, script_dir):
|
|
|
988
1314
|
"{{SESSION_STATUS_PATH}}": session_status_abs,
|
|
989
1315
|
"{{PROJECT_ROOT}}": project_root,
|
|
990
1316
|
"{{FEATURE_SLUG}}": feature_slug,
|
|
1317
|
+
"{{CHECKPOINT_PATH}}": os.path.join(
|
|
1318
|
+
".prizmkit", "specs", feature_slug, "workflow-checkpoint.json",
|
|
1319
|
+
),
|
|
991
1320
|
"{{PIPELINE_MODE}}": pipeline_mode,
|
|
992
1321
|
"{{COMPLEXITY}}": complexity,
|
|
993
1322
|
"{{CRITIC_ENABLED}}": "true" if critic_enabled else "false",
|
|
@@ -999,6 +1328,12 @@ def build_replacements(args, feature, features, global_context, script_dir):
|
|
|
999
1328
|
"{{BROWSER_URL}}": browser_url,
|
|
1000
1329
|
"{{BROWSER_SETUP_COMMAND}}": browser_setup_command,
|
|
1001
1330
|
"{{BROWSER_VERIFY_STEPS}}": browser_verify_steps,
|
|
1331
|
+
"{{AC_CHECKLIST}}": format_ac_checklist(
|
|
1332
|
+
feature.get("acceptance_criteria", [])
|
|
1333
|
+
),
|
|
1334
|
+
"{{TEST_CMD}}": test_cmd,
|
|
1335
|
+
"{{BASELINE_FAILURES}}": baseline_failures,
|
|
1336
|
+
"{{COVERAGE_TARGET}}": coverage_target,
|
|
1002
1337
|
}
|
|
1003
1338
|
|
|
1004
1339
|
return replacements, effective_resume, browser_enabled
|
|
@@ -1016,9 +1351,11 @@ def render_template(template_content, replacements, resume_phase, browser_enable
|
|
|
1016
1351
|
content = process_mode_blocks(content, pipeline_mode, init_done, critic_enabled,
|
|
1017
1352
|
browser_enabled)
|
|
1018
1353
|
|
|
1019
|
-
# Step 3: Replace all {{PLACEHOLDER}} variables
|
|
1020
|
-
|
|
1021
|
-
|
|
1354
|
+
# Step 3: Replace all {{PLACEHOLDER}} variables (two passes for nested
|
|
1355
|
+
# agent prompt templates that may contain their own placeholders)
|
|
1356
|
+
for _pass in range(2):
|
|
1357
|
+
for placeholder, value in replacements.items():
|
|
1358
|
+
content = content.replace(placeholder, value)
|
|
1022
1359
|
|
|
1023
1360
|
return content
|
|
1024
1361
|
|
|
@@ -1079,6 +1416,10 @@ def main():
|
|
|
1079
1416
|
)
|
|
1080
1417
|
replacements["{{RESUME_PHASE}}"] = effective_resume
|
|
1081
1418
|
|
|
1419
|
+
# Load agent prompt templates and merge into replacements
|
|
1420
|
+
agent_prompt_replacements = load_agent_prompts(templates_dir)
|
|
1421
|
+
replacements.update(agent_prompt_replacements)
|
|
1422
|
+
|
|
1082
1423
|
# Extract state needed for assembly
|
|
1083
1424
|
pipeline_mode = replacements.get("{{PIPELINE_MODE}}", "lite")
|
|
1084
1425
|
init_done = replacements.get("{{INIT_DONE}}", "false") == "true"
|
|
@@ -1147,6 +1488,52 @@ def main():
|
|
|
1147
1488
|
if err:
|
|
1148
1489
|
emit_failure(err)
|
|
1149
1490
|
|
|
1491
|
+
# ── Generate checkpoint file ──────────────────────────────────────
|
|
1492
|
+
project_root = resolve_project_root(
|
|
1493
|
+
os.path.dirname(os.path.abspath(__file__))
|
|
1494
|
+
)
|
|
1495
|
+
feature_slug = replacements.get("{{FEATURE_SLUG}}", "")
|
|
1496
|
+
checkpoint_path = ""
|
|
1497
|
+
|
|
1498
|
+
if use_sections and feature_slug:
|
|
1499
|
+
checkpoint = generate_checkpoint_definition(
|
|
1500
|
+
sections=sections,
|
|
1501
|
+
pipeline_mode=pipeline_mode,
|
|
1502
|
+
workflow_type="feature-pipeline",
|
|
1503
|
+
item_id=args.feature_id,
|
|
1504
|
+
item_slug=feature_slug,
|
|
1505
|
+
session_id=args.session_id,
|
|
1506
|
+
init_done=init_done,
|
|
1507
|
+
)
|
|
1508
|
+
|
|
1509
|
+
checkpoint_dir = os.path.join(
|
|
1510
|
+
project_root, ".prizmkit", "specs", feature_slug,
|
|
1511
|
+
)
|
|
1512
|
+
os.makedirs(checkpoint_dir, exist_ok=True)
|
|
1513
|
+
checkpoint_path = os.path.join(
|
|
1514
|
+
checkpoint_dir, "workflow-checkpoint.json",
|
|
1515
|
+
)
|
|
1516
|
+
|
|
1517
|
+
# On resume, merge existing completed state (with artifact validation)
|
|
1518
|
+
if is_resume and os.path.exists(checkpoint_path):
|
|
1519
|
+
try:
|
|
1520
|
+
with open(checkpoint_path, "r", encoding="utf-8") as f:
|
|
1521
|
+
existing = json.load(f)
|
|
1522
|
+
checkpoint = merge_checkpoint_state(
|
|
1523
|
+
existing, checkpoint, project_root,
|
|
1524
|
+
)
|
|
1525
|
+
LOGGER.info("Merged existing checkpoint state from %s",
|
|
1526
|
+
checkpoint_path)
|
|
1527
|
+
except (json.JSONDecodeError, KeyError) as exc:
|
|
1528
|
+
LOGGER.warning(
|
|
1529
|
+
"Existing checkpoint corrupted (%s) — generating fresh",
|
|
1530
|
+
exc,
|
|
1531
|
+
)
|
|
1532
|
+
|
|
1533
|
+
with open(checkpoint_path, "w", encoding="utf-8") as f:
|
|
1534
|
+
json.dump(checkpoint, f, indent=2, ensure_ascii=False)
|
|
1535
|
+
LOGGER.info("Wrote checkpoint to %s", checkpoint_path)
|
|
1536
|
+
|
|
1150
1537
|
# ── Success JSON ───────────────────────────────────────────────────
|
|
1151
1538
|
feature_model = feature.get("model", "")
|
|
1152
1539
|
mode_agent_counts = {"lite": 1, "standard": 3, "full": 3}
|
|
@@ -1164,6 +1551,7 @@ def main():
|
|
|
1164
1551
|
"render_mode": "sections" if use_sections else "legacy",
|
|
1165
1552
|
"validation_warnings": len(warnings),
|
|
1166
1553
|
"validation_errors": len(errors),
|
|
1554
|
+
"checkpoint_path": checkpoint_path,
|
|
1167
1555
|
}
|
|
1168
1556
|
print(json.dumps(output, indent=2, ensure_ascii=False))
|
|
1169
1557
|
sys.exit(0)
|
|
@@ -324,6 +324,95 @@ def emit_failure(message):
|
|
|
324
324
|
sys.exit(1)
|
|
325
325
|
|
|
326
326
|
|
|
327
|
+
# ============================================================
|
|
328
|
+
# Checkpoint generation for bugfix pipeline
|
|
329
|
+
# ============================================================
|
|
330
|
+
|
|
331
|
+
BUGFIX_STEPS = [
|
|
332
|
+
("prizmkit-init", "Initialize", []),
|
|
333
|
+
("bug-diagnosis", "Bug Diagnosis & Fix Plan",
|
|
334
|
+
[".prizmkit/bugfix/{slug}/fix-plan.md"]),
|
|
335
|
+
("bug-reproduce", "Write Reproduction Test", []),
|
|
336
|
+
("bug-fix", "Implement Fix", []),
|
|
337
|
+
("prizmkit-code-review", "Code Review", []),
|
|
338
|
+
("prizmkit-committer", "Commit", []),
|
|
339
|
+
("bug-report", "Generate Fix Report & Update TRAPS",
|
|
340
|
+
[".prizmkit/bugfix/{slug}/fix-report.md"]),
|
|
341
|
+
]
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def generate_bugfix_checkpoint(bug_id, session_id):
|
|
345
|
+
"""Generate a checkpoint definition for bugfix pipeline.
|
|
346
|
+
|
|
347
|
+
Returns a dict suitable for writing as workflow-checkpoint.json.
|
|
348
|
+
"""
|
|
349
|
+
steps = []
|
|
350
|
+
prev_id = None
|
|
351
|
+
for i, (skill, name, artifacts) in enumerate(BUGFIX_STEPS, 1):
|
|
352
|
+
step_id = "S{:02d}".format(i)
|
|
353
|
+
steps.append({
|
|
354
|
+
"id": step_id,
|
|
355
|
+
"skill": skill,
|
|
356
|
+
"name": name,
|
|
357
|
+
"status": "pending",
|
|
358
|
+
"required_artifacts": [a.replace("{slug}", bug_id) for a in artifacts],
|
|
359
|
+
"depends_on": prev_id,
|
|
360
|
+
})
|
|
361
|
+
prev_id = step_id
|
|
362
|
+
|
|
363
|
+
return {
|
|
364
|
+
"version": 1,
|
|
365
|
+
"workflow_type": "bugfix-pipeline",
|
|
366
|
+
"pipeline_mode": "single",
|
|
367
|
+
"item_id": bug_id,
|
|
368
|
+
"item_slug": bug_id,
|
|
369
|
+
"session_id": session_id,
|
|
370
|
+
"steps": steps,
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def merge_bugfix_checkpoint_state(existing, fresh, project_root):
|
|
375
|
+
"""Merge existing bugfix checkpoint state into fresh definition.
|
|
376
|
+
|
|
377
|
+
Same logic as feature pipeline: validate artifacts, break chain on
|
|
378
|
+
first invalid step.
|
|
379
|
+
"""
|
|
380
|
+
existing_status = {}
|
|
381
|
+
existing_artifacts = {}
|
|
382
|
+
for step in existing.get("steps", []):
|
|
383
|
+
existing_status[step["skill"]] = step["status"]
|
|
384
|
+
existing_artifacts[step["skill"]] = step.get("required_artifacts", [])
|
|
385
|
+
|
|
386
|
+
valid_completed = set()
|
|
387
|
+
for skill_key, status in existing_status.items():
|
|
388
|
+
if status == "completed":
|
|
389
|
+
artifacts = existing_artifacts.get(skill_key, [])
|
|
390
|
+
if all(os.path.exists(os.path.join(project_root, a))
|
|
391
|
+
for a in artifacts):
|
|
392
|
+
valid_completed.add(skill_key)
|
|
393
|
+
else:
|
|
394
|
+
LOGGER.warning(
|
|
395
|
+
"Step '%s' was completed but artifacts missing — "
|
|
396
|
+
"resetting to pending", skill_key
|
|
397
|
+
)
|
|
398
|
+
elif status == "skipped":
|
|
399
|
+
valid_completed.add(skill_key)
|
|
400
|
+
|
|
401
|
+
chain_broken = False
|
|
402
|
+
for step in fresh["steps"]:
|
|
403
|
+
if chain_broken:
|
|
404
|
+
step["status"] = "pending"
|
|
405
|
+
continue
|
|
406
|
+
prev = existing_status.get(step["skill"])
|
|
407
|
+
if step["skill"] in valid_completed:
|
|
408
|
+
step["status"] = prev
|
|
409
|
+
else:
|
|
410
|
+
chain_broken = True
|
|
411
|
+
step["status"] = "pending"
|
|
412
|
+
|
|
413
|
+
return fresh
|
|
414
|
+
|
|
415
|
+
|
|
327
416
|
def main():
|
|
328
417
|
args = parse_args()
|
|
329
418
|
|
|
@@ -366,6 +455,12 @@ def main():
|
|
|
366
455
|
# Build replacements
|
|
367
456
|
replacements = build_replacements(args, bug, global_context, script_dir)
|
|
368
457
|
|
|
458
|
+
# Add checkpoint path to replacements
|
|
459
|
+
checkpoint_rel = os.path.join(
|
|
460
|
+
".prizmkit", "bugfix", args.bug_id, "workflow-checkpoint.json",
|
|
461
|
+
)
|
|
462
|
+
replacements["{{CHECKPOINT_PATH}}"] = checkpoint_rel
|
|
463
|
+
|
|
369
464
|
# Render the template
|
|
370
465
|
rendered = render_template(template_content, replacements, bug)
|
|
371
466
|
|
|
@@ -374,10 +469,39 @@ def main():
|
|
|
374
469
|
if err:
|
|
375
470
|
emit_failure(err)
|
|
376
471
|
|
|
472
|
+
# Generate checkpoint file
|
|
473
|
+
project_root = resolve_project_root(script_dir)
|
|
474
|
+
checkpoint_path = os.path.join(project_root, checkpoint_rel)
|
|
475
|
+
checkpoint_dir = os.path.dirname(checkpoint_path)
|
|
476
|
+
os.makedirs(checkpoint_dir, exist_ok=True)
|
|
477
|
+
|
|
478
|
+
checkpoint = generate_bugfix_checkpoint(args.bug_id, args.session_id)
|
|
479
|
+
|
|
480
|
+
is_resume = args.resume_phase != "null"
|
|
481
|
+
if is_resume and os.path.exists(checkpoint_path):
|
|
482
|
+
try:
|
|
483
|
+
with open(checkpoint_path, "r", encoding="utf-8") as f:
|
|
484
|
+
existing = json.load(f)
|
|
485
|
+
checkpoint = merge_bugfix_checkpoint_state(
|
|
486
|
+
existing, checkpoint, project_root,
|
|
487
|
+
)
|
|
488
|
+
LOGGER.info("Merged existing bugfix checkpoint from %s",
|
|
489
|
+
checkpoint_path)
|
|
490
|
+
except (json.JSONDecodeError, KeyError) as exc:
|
|
491
|
+
LOGGER.warning(
|
|
492
|
+
"Existing bugfix checkpoint corrupted (%s) — generating fresh",
|
|
493
|
+
exc,
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
with open(checkpoint_path, "w", encoding="utf-8") as f:
|
|
497
|
+
json.dump(checkpoint, f, indent=2, ensure_ascii=False)
|
|
498
|
+
LOGGER.info("Wrote bugfix checkpoint to %s", checkpoint_path)
|
|
499
|
+
|
|
377
500
|
# Success
|
|
378
501
|
output = {
|
|
379
502
|
"success": True,
|
|
380
503
|
"output_path": os.path.abspath(args.output),
|
|
504
|
+
"checkpoint_path": checkpoint_path,
|
|
381
505
|
}
|
|
382
506
|
print(json.dumps(output, indent=2, ensure_ascii=False))
|
|
383
507
|
sys.exit(0)
|