prizmkit 1.1.10 → 1.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundled/VERSION.json +3 -3
- package/bundled/dev-pipeline/README.md +10 -46
- package/bundled/dev-pipeline/reset-bug.sh +84 -10
- package/bundled/dev-pipeline/reset-feature.sh +86 -10
- package/bundled/dev-pipeline/reset-refactor.sh +68 -4
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +47 -46
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +7 -12
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +124 -20
- package/bundled/dev-pipeline/scripts/utils.py +20 -0
- package/bundled/dev-pipeline/templates/agent-prompts/dev-implement.md +13 -7
- package/bundled/dev-pipeline/templates/bootstrap-tier1.md +62 -66
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +37 -40
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +35 -48
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +135 -182
- package/bundled/dev-pipeline/templates/feature-list-schema.json +6 -21
- package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +9 -9
- package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +1 -1
- package/bundled/dev-pipeline/templates/sections/feature-context.md +4 -0
- package/bundled/dev-pipeline/templates/sections/phase-browser-verification.md +41 -24
- package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +4 -12
- package/bundled/dev-pipeline/templates/sections/phase-deploy-verification.md +9 -17
- package/bundled/dev-pipeline/templates/sections/phase-implement-lite.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-plan-agent.md +3 -2
- package/bundled/dev-pipeline/templates/sections/phase-plan-lite.md +4 -2
- package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +0 -18
- package/bundled/dev-pipeline/templates/sections/session-context.md +1 -2
- package/bundled/dev-pipeline/templates/sections/test-failure-recovery-agent.md +75 -0
- package/bundled/dev-pipeline/templates/sections/test-failure-recovery-lite.md +66 -0
- package/bundled/skills/_metadata.json +1 -1
- package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +3 -8
- package/bundled/skills/feature-pipeline-launcher/SKILL.md +4 -16
- package/bundled/skills/feature-planner/SKILL.md +8 -4
- package/bundled/skills/feature-planner/assets/planning-guide.md +16 -11
- package/bundled/skills/feature-planner/references/browser-interaction.md +9 -8
- package/bundled/skills/feature-planner/references/completeness-review.md +1 -1
- package/bundled/skills/feature-planner/references/error-recovery.md +1 -1
- package/bundled/skills/feature-planner/references/incremental-feature-planning.md +1 -1
- package/bundled/skills/feature-planner/scripts/validate-and-generate.py +10 -7
- package/bundled/skills/feature-workflow/SKILL.md +61 -34
- package/bundled/skills/prizmkit-retrospective/references/structural-sync-steps.md +3 -7
- package/bundled/skills/recovery-workflow/SKILL.md +3 -3
- package/bundled/skills/refactor-pipeline-launcher/SKILL.md +4 -15
- package/bundled/skills/refactor-workflow/SKILL.md +72 -66
- package/package.json +1 -1
- package/bundled/dev-pipeline/retry-bugfix.sh +0 -429
- package/bundled/dev-pipeline/retry-feature.sh +0 -445
- package/bundled/dev-pipeline/retry-refactor.sh +0 -441
- package/bundled/dev-pipeline/templates/sections/failure-log-check.md +0 -9
- package/bundled/dev-pipeline/templates/sections/resume-header.md +0 -5
- package/bundled/dev-pipeline/templates/sections/test-failure-recovery.md +0 -75
|
@@ -26,7 +26,7 @@ import os
|
|
|
26
26
|
import re
|
|
27
27
|
import sys
|
|
28
28
|
|
|
29
|
-
from utils import enrich_global_context, load_json_file, setup_logging
|
|
29
|
+
from utils import enrich_global_context, load_json_file, read_platform_conventions, setup_logging
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
DEFAULT_MAX_RETRIES = 3
|
|
@@ -407,27 +407,25 @@ def resolve_project_root(script_dir):
|
|
|
407
407
|
|
|
408
408
|
|
|
409
409
|
def process_conditional_blocks(content, resume_phase):
|
|
410
|
-
"""Handle conditional blocks based on resume_phase
|
|
410
|
+
"""Handle conditional blocks based on resume_phase.
|
|
411
411
|
|
|
412
412
|
Supports:
|
|
413
413
|
- {{IF_FRESH_START}} / {{END_IF_FRESH_START}}
|
|
414
414
|
- {{IF_RESUME}} / {{END_IF_RESUME}}
|
|
415
|
-
- {{
|
|
416
|
-
- {{IF_INIT_DONE}} / {{END_IF_INIT_DONE}}
|
|
417
|
-
- {{IF_MODE_LITE}} / {{END_IF_MODE_LITE}}
|
|
418
|
-
- {{IF_MODE_STANDARD}} / {{END_IF_MODE_STANDARD}}
|
|
419
|
-
- {{IF_MODE_FULL}} / {{END_IF_MODE_FULL}}
|
|
415
|
+
- {{IF_RETRY}} / {{END_IF_RETRY}}
|
|
420
416
|
"""
|
|
421
417
|
is_resume = resume_phase != "null"
|
|
422
418
|
|
|
423
419
|
if is_resume:
|
|
424
|
-
|
|
425
|
-
content = re.sub(r"\{\{END_IF_RESUME\}\}\n?", "", content)
|
|
420
|
+
# Remove fresh-start blocks, keep resume blocks
|
|
426
421
|
content = re.sub(
|
|
427
422
|
r"\{\{IF_FRESH_START\}\}.*?\{\{END_IF_FRESH_START\}\}\n?",
|
|
428
423
|
"", content, flags=re.DOTALL,
|
|
429
424
|
)
|
|
425
|
+
content = re.sub(r"\{\{IF_RESUME\}\}\n?", "", content)
|
|
426
|
+
content = re.sub(r"\{\{END_IF_RESUME\}\}\n?", "", content)
|
|
430
427
|
else:
|
|
428
|
+
# Keep fresh-start blocks, remove resume blocks
|
|
431
429
|
content = re.sub(r"\{\{IF_FRESH_START\}\}\n?", "", content)
|
|
432
430
|
content = re.sub(r"\{\{END_IF_FRESH_START\}\}\n?", "", content)
|
|
433
431
|
content = re.sub(
|
|
@@ -848,7 +846,7 @@ def _tier_reminders(pipeline_mode, critic_enabled=False):
|
|
|
848
846
|
|
|
849
847
|
|
|
850
848
|
def assemble_sections(pipeline_mode, sections_dir, init_done, is_resume,
|
|
851
|
-
critic_enabled, browser_enabled):
|
|
849
|
+
critic_enabled, browser_enabled, retry_count=0):
|
|
852
850
|
"""Assemble prompt sections based on tier and conditions.
|
|
853
851
|
|
|
854
852
|
Uses Python code for conditional logic instead of regex-based
|
|
@@ -933,15 +931,6 @@ def assemble_sections(pipeline_mode, sections_dir, init_done, is_resume,
|
|
|
933
931
|
sections.append(("phase0-skip",
|
|
934
932
|
"### Phase 0: SKIP (already initialized)\n"))
|
|
935
933
|
|
|
936
|
-
# --- Resume header (if resuming) ---
|
|
937
|
-
if is_resume:
|
|
938
|
-
sections.append(("resume-header",
|
|
939
|
-
load_section(sections_dir, "resume-header.md")))
|
|
940
|
-
|
|
941
|
-
# --- Failure log check ---
|
|
942
|
-
sections.append(("failure-log-check",
|
|
943
|
-
load_section(sections_dir, "failure-log-check.md")))
|
|
944
|
-
|
|
945
934
|
# --- Context Snapshot + Plan (tier-dependent) ---
|
|
946
935
|
if pipeline_mode == "full":
|
|
947
936
|
# Tier 3: full specify + plan workflow
|
|
@@ -995,9 +984,15 @@ def assemble_sections(pipeline_mode, sections_dir, init_done, is_resume,
|
|
|
995
984
|
load_section(sections_dir,
|
|
996
985
|
"phase-implement-agent.md")))
|
|
997
986
|
|
|
998
|
-
# --- Test Failure Recovery Protocol (
|
|
999
|
-
|
|
1000
|
-
|
|
987
|
+
# --- Test Failure Recovery Protocol (tier-specific) ---
|
|
988
|
+
if pipeline_mode == "lite":
|
|
989
|
+
sections.append(("test-failure-recovery",
|
|
990
|
+
load_section(sections_dir,
|
|
991
|
+
"test-failure-recovery-lite.md")))
|
|
992
|
+
else:
|
|
993
|
+
sections.append(("test-failure-recovery",
|
|
994
|
+
load_section(sections_dir,
|
|
995
|
+
"test-failure-recovery-agent.md")))
|
|
1001
996
|
|
|
1002
997
|
# --- Critic: Code Challenge (only if critic enabled, agent tiers) ---
|
|
1003
998
|
if critic_enabled and pipeline_mode in ("standard", "full"):
|
|
@@ -1213,8 +1208,6 @@ def build_replacements(args, feature, features, global_context, script_dir):
|
|
|
1213
1208
|
# Make it absolute from project root
|
|
1214
1209
|
session_status_abs = os.path.join(project_root, session_status_path)
|
|
1215
1210
|
|
|
1216
|
-
prev_status = get_prev_session_status(args.state_dir, args.feature_id)
|
|
1217
|
-
|
|
1218
1211
|
# Compute feature slug for per-feature directory naming
|
|
1219
1212
|
feature_slug = compute_feature_slug(
|
|
1220
1213
|
args.feature_id, feature.get("title", "")
|
|
@@ -1223,7 +1216,10 @@ def build_replacements(args, feature, features, global_context, script_dir):
|
|
|
1223
1216
|
# Detect project state
|
|
1224
1217
|
init_done = detect_init_status(project_root)
|
|
1225
1218
|
artifacts = detect_existing_artifacts(project_root, feature_slug)
|
|
1226
|
-
complexity = feature.get(
|
|
1219
|
+
complexity = feature.get(
|
|
1220
|
+
"estimated_complexity",
|
|
1221
|
+
feature.get("complexity", "medium"),
|
|
1222
|
+
)
|
|
1227
1223
|
if args.mode:
|
|
1228
1224
|
pipeline_mode = args.mode
|
|
1229
1225
|
else:
|
|
@@ -1273,8 +1269,6 @@ def build_replacements(args, feature, features, global_context, script_dir):
|
|
|
1273
1269
|
# Browser interaction - extract from feature if present and playwright-cli available
|
|
1274
1270
|
browser_interaction = feature.get("browser_interaction")
|
|
1275
1271
|
browser_enabled = False
|
|
1276
|
-
browser_url = ""
|
|
1277
|
-
browser_setup_command = ""
|
|
1278
1272
|
browser_verify_steps = ""
|
|
1279
1273
|
|
|
1280
1274
|
browser_verify_env = os.environ.get("BROWSER_VERIFY", "").lower()
|
|
@@ -1282,18 +1276,27 @@ def build_replacements(args, feature, features, global_context, script_dir):
|
|
|
1282
1276
|
browser_interaction = None
|
|
1283
1277
|
|
|
1284
1278
|
if browser_interaction and isinstance(browser_interaction, dict):
|
|
1285
|
-
|
|
1286
|
-
|
|
1279
|
+
# browser_interaction only needs verify_steps — AI auto-detects
|
|
1280
|
+
# dev server command, URL, and port from project config
|
|
1281
|
+
steps = browser_interaction.get("verify_steps", [])
|
|
1282
|
+
if isinstance(browser_interaction, bool) and browser_interaction:
|
|
1283
|
+
# Simple boolean: browser verification enabled, no specific goals
|
|
1287
1284
|
browser_enabled = True
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
)
|
|
1295
|
-
|
|
1296
|
-
|
|
1285
|
+
browser_verify_steps = (
|
|
1286
|
+
" # (no specific verify goals — explore the app and "
|
|
1287
|
+
"verify the feature works as expected)")
|
|
1288
|
+
elif steps:
|
|
1289
|
+
browser_enabled = True
|
|
1290
|
+
browser_verify_steps = "\n".join(
|
|
1291
|
+
" # Goal {}: {}".format(i + 1, step)
|
|
1292
|
+
for i, step in enumerate(steps)
|
|
1293
|
+
)
|
|
1294
|
+
elif browser_interaction.get("url") or browser_interaction.get("enabled", True):
|
|
1295
|
+
# Backward compat: old format had url/setup_command fields
|
|
1296
|
+
browser_enabled = True
|
|
1297
|
+
browser_verify_steps = (
|
|
1298
|
+
" # (no specific verify goals — explore the app and "
|
|
1299
|
+
"verify the feature works as expected)")
|
|
1297
1300
|
|
|
1298
1301
|
# Auto-detect test commands from project structure
|
|
1299
1302
|
test_cmd = detect_test_commands(project_root)
|
|
@@ -1318,10 +1321,6 @@ def build_replacements(args, feature, features, global_context, script_dir):
|
|
|
1318
1321
|
"{{FEATURE_ID}}": args.feature_id,
|
|
1319
1322
|
"{{FEATURE_LIST_PATH}}": os.path.abspath(args.feature_list),
|
|
1320
1323
|
"{{FEATURE_TITLE}}": feature.get("title", ""),
|
|
1321
|
-
"{{RETRY_COUNT}}": str(args.retry_count),
|
|
1322
|
-
"{{MAX_RETRIES}}": str(DEFAULT_MAX_RETRIES),
|
|
1323
|
-
"{{PREV_SESSION_STATUS}}": prev_status,
|
|
1324
|
-
"{{RESUME_PHASE}}": args.resume_phase,
|
|
1325
1324
|
"{{FEATURE_DESCRIPTION}}": feature.get("description", ""),
|
|
1326
1325
|
"{{ACCEPTANCE_CRITERIA}}": format_acceptance_criteria(
|
|
1327
1326
|
feature.get("acceptance_criteria", [])
|
|
@@ -1331,6 +1330,7 @@ def build_replacements(args, feature, features, global_context, script_dir):
|
|
|
1331
1330
|
),
|
|
1332
1331
|
"{{GLOBAL_CONTEXT}}": format_global_context(global_context, project_root),
|
|
1333
1332
|
"{{PROJECT_BRIEF}}": _read_project_brief(project_root),
|
|
1333
|
+
"{{PLATFORM_CONVENTIONS}}": read_platform_conventions(project_root),
|
|
1334
1334
|
"{{TEAM_CONFIG_PATH}}": team_config_path,
|
|
1335
1335
|
"{{DEV_SUBAGENT_PATH}}": dev_subagent,
|
|
1336
1336
|
"{{REVIEWER_SUBAGENT_PATH}}": reviewer_subagent,
|
|
@@ -1349,8 +1349,6 @@ def build_replacements(args, feature, features, global_context, script_dir):
|
|
|
1349
1349
|
"{{INIT_DONE}}": "true" if init_done else "false",
|
|
1350
1350
|
"{{HAS_SPEC}}": "true" if artifacts["has_spec"] else "false",
|
|
1351
1351
|
"{{HAS_PLAN}}": "true" if artifacts["has_plan"] else "false",
|
|
1352
|
-
"{{BROWSER_URL}}": browser_url,
|
|
1353
|
-
"{{BROWSER_SETUP_COMMAND}}": browser_setup_command,
|
|
1354
1352
|
"{{BROWSER_VERIFY_STEPS}}": browser_verify_steps,
|
|
1355
1353
|
"{{AC_CHECKLIST}}": format_ac_checklist(
|
|
1356
1354
|
feature.get("acceptance_criteria", [])
|
|
@@ -1438,7 +1436,6 @@ def main():
|
|
|
1438
1436
|
replacements, effective_resume, browser_enabled = build_replacements(
|
|
1439
1437
|
args, feature, features, global_context, script_dir
|
|
1440
1438
|
)
|
|
1441
|
-
replacements["{{RESUME_PHASE}}"] = effective_resume
|
|
1442
1439
|
|
|
1443
1440
|
# Load agent prompt templates and merge into replacements
|
|
1444
1441
|
agent_prompt_replacements = load_agent_prompts(templates_dir)
|
|
@@ -1460,6 +1457,7 @@ def main():
|
|
|
1460
1457
|
sections = assemble_sections(
|
|
1461
1458
|
pipeline_mode, sections_dir, init_done, is_resume,
|
|
1462
1459
|
critic_enabled, browser_enabled,
|
|
1460
|
+
retry_count=int(args.retry_count),
|
|
1463
1461
|
)
|
|
1464
1462
|
rendered = render_from_sections(sections, replacements)
|
|
1465
1463
|
except FileNotFoundError as exc:
|
|
@@ -1474,7 +1472,10 @@ def main():
|
|
|
1474
1472
|
if args.template:
|
|
1475
1473
|
template_path = args.template
|
|
1476
1474
|
else:
|
|
1477
|
-
complexity = feature.get(
|
|
1475
|
+
complexity = feature.get(
|
|
1476
|
+
"estimated_complexity",
|
|
1477
|
+
feature.get("complexity", "medium"),
|
|
1478
|
+
)
|
|
1478
1479
|
_mode = args.mode or determine_pipeline_mode(complexity)
|
|
1479
1480
|
_tier_file_map = {
|
|
1480
1481
|
"lite": "bootstrap-tier1.md",
|
|
@@ -19,7 +19,7 @@ import os
|
|
|
19
19
|
import re
|
|
20
20
|
import sys
|
|
21
21
|
|
|
22
|
-
from utils import enrich_global_context, load_json_file, setup_logging
|
|
22
|
+
from utils import enrich_global_context, load_json_file, read_platform_conventions, setup_logging
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
DEFAULT_MAX_RETRIES = 3
|
|
@@ -223,8 +223,6 @@ def build_replacements(args, bug, global_context, script_dir):
|
|
|
223
223
|
"sessions", args.session_id, "session-status.json"
|
|
224
224
|
)
|
|
225
225
|
|
|
226
|
-
prev_status = get_prev_session_status(args.state_dir, args.bug_id)
|
|
227
|
-
|
|
228
226
|
# Error source
|
|
229
227
|
error_source = bug.get("error_source", {})
|
|
230
228
|
error_type = error_source.get("type", "unknown") if isinstance(error_source, dict) else "unknown"
|
|
@@ -246,10 +244,6 @@ def build_replacements(args, bug, global_context, script_dir):
|
|
|
246
244
|
"{{BUG_TITLE}}": bug.get("title", ""),
|
|
247
245
|
"{{SEVERITY}}": bug.get("severity", "medium"),
|
|
248
246
|
"{{VERIFICATION_TYPE}}": vtype,
|
|
249
|
-
"{{RETRY_COUNT}}": str(args.retry_count),
|
|
250
|
-
"{{MAX_RETRIES}}": str(DEFAULT_MAX_RETRIES),
|
|
251
|
-
"{{PREV_SESSION_STATUS}}": prev_status,
|
|
252
|
-
"{{RESUME_PHASE}}": args.resume_phase,
|
|
253
247
|
"{{BUG_DESCRIPTION}}": bug.get("description", ""),
|
|
254
248
|
"{{ERROR_SOURCE_TYPE}}": error_type,
|
|
255
249
|
"{{ERROR_SOURCE_DETAILS}}": format_error_source_details(error_source),
|
|
@@ -258,6 +252,7 @@ def build_replacements(args, bug, global_context, script_dir):
|
|
|
258
252
|
),
|
|
259
253
|
"{{ENVIRONMENT}}": format_environment(bug.get("environment")),
|
|
260
254
|
"{{GLOBAL_CONTEXT}}": format_global_context(global_context, project_root),
|
|
255
|
+
"{{PLATFORM_CONVENTIONS}}": read_platform_conventions(project_root),
|
|
261
256
|
"{{TEAM_CONFIG_PATH}}": team_config_path,
|
|
262
257
|
"{{DEV_SUBAGENT_PATH}}": dev_subagent,
|
|
263
258
|
"{{REVIEWER_SUBAGENT_PATH}}": reviewer_subagent,
|
|
@@ -331,13 +326,13 @@ def emit_failure(message):
|
|
|
331
326
|
|
|
332
327
|
BUGFIX_STEPS = [
|
|
333
328
|
("prizmkit-init", "Initialize", []),
|
|
334
|
-
("bug-diagnosis", "
|
|
335
|
-
[".prizmkit/bugfix/{slug}/
|
|
336
|
-
|
|
337
|
-
("
|
|
329
|
+
("bug-diagnosis-and-plan", "Diagnose & Plan",
|
|
330
|
+
[".prizmkit/bugfix/{slug}/spec.md",
|
|
331
|
+
".prizmkit/bugfix/{slug}/plan.md"]),
|
|
332
|
+
("prizmkit-implement", "Implement Fix", []),
|
|
338
333
|
("prizmkit-code-review", "Code Review", []),
|
|
339
334
|
("prizmkit-committer", "Commit", []),
|
|
340
|
-
("bug-report", "Generate Fix Report
|
|
335
|
+
("bug-report", "Generate Fix Report",
|
|
341
336
|
[".prizmkit/bugfix/{slug}/fix-report.md"]),
|
|
342
337
|
]
|
|
343
338
|
|
|
@@ -19,7 +19,7 @@ import os
|
|
|
19
19
|
import re
|
|
20
20
|
import sys
|
|
21
21
|
|
|
22
|
-
from utils import enrich_global_context, load_json_file, setup_logging
|
|
22
|
+
from utils import enrich_global_context, load_json_file, read_platform_conventions, setup_logging
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
DEFAULT_MAX_RETRIES = 3
|
|
@@ -27,6 +27,97 @@ DEFAULT_MAX_RETRIES = 3
|
|
|
27
27
|
LOGGER = setup_logging("generate-refactor-prompt")
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
# Refactor pipeline checkpoint steps (skill_key, display_name, required_artifacts)
|
|
31
|
+
# Artifacts use {slug} placeholder, replaced with refactor_id at runtime.
|
|
32
|
+
REFACTOR_STEPS = [
|
|
33
|
+
("prizmkit-init", "Initialize",
|
|
34
|
+
[".prizmkit/refactor/{slug}"]),
|
|
35
|
+
("prizmkit-plan", "Plan — Specification & Plan Generation",
|
|
36
|
+
[".prizmkit/refactor/{slug}/spec.md",
|
|
37
|
+
".prizmkit/refactor/{slug}/plan.md"]),
|
|
38
|
+
("prizmkit-implement", "Implement — Behavior-Preserving Refactoring",
|
|
39
|
+
[".prizmkit/refactor/{slug}/plan.md"]),
|
|
40
|
+
("prizmkit-code-review", "Review — Code Review & Behavior Verification",
|
|
41
|
+
[".prizmkit/refactor/{slug}/review-report.md"]),
|
|
42
|
+
("prizmkit-committer", "Commit",
|
|
43
|
+
[]),
|
|
44
|
+
("refactor-report", "Generate Refactor Report",
|
|
45
|
+
[".prizmkit/refactor/{slug}/refactor-report.md"]),
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def generate_refactor_checkpoint(refactor_id, session_id):
|
|
50
|
+
"""Generate a checkpoint definition for refactor pipeline.
|
|
51
|
+
|
|
52
|
+
Returns a dict suitable for writing as workflow-checkpoint.json.
|
|
53
|
+
"""
|
|
54
|
+
steps = []
|
|
55
|
+
prev_id = None
|
|
56
|
+
for i, (skill, name, artifacts) in enumerate(REFACTOR_STEPS, 1):
|
|
57
|
+
step_id = "S{:02d}".format(i)
|
|
58
|
+
steps.append({
|
|
59
|
+
"id": step_id,
|
|
60
|
+
"skill": skill,
|
|
61
|
+
"name": name,
|
|
62
|
+
"status": "pending",
|
|
63
|
+
"required_artifacts": [a.replace("{slug}", refactor_id) for a in artifacts],
|
|
64
|
+
"depends_on": prev_id,
|
|
65
|
+
})
|
|
66
|
+
prev_id = step_id
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
"version": 1,
|
|
70
|
+
"workflow_type": "refactor-pipeline",
|
|
71
|
+
"pipeline_mode": "standard",
|
|
72
|
+
"item_id": refactor_id,
|
|
73
|
+
"item_slug": refactor_id,
|
|
74
|
+
"session_id": session_id,
|
|
75
|
+
"steps": steps,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def merge_refactor_checkpoint_state(existing, fresh, project_root):
|
|
80
|
+
"""Merge existing refactor checkpoint state into fresh definition.
|
|
81
|
+
|
|
82
|
+
Same logic as feature/bugfix pipelines: validate artifacts, break chain
|
|
83
|
+
on first invalid step.
|
|
84
|
+
"""
|
|
85
|
+
existing_status = {}
|
|
86
|
+
existing_artifacts = {}
|
|
87
|
+
for step in existing.get("steps", []):
|
|
88
|
+
existing_status[step["skill"]] = step["status"]
|
|
89
|
+
existing_artifacts[step["skill"]] = step.get("required_artifacts", [])
|
|
90
|
+
|
|
91
|
+
valid_completed = set()
|
|
92
|
+
for skill_key, status in existing_status.items():
|
|
93
|
+
if status == "completed":
|
|
94
|
+
artifacts = existing_artifacts.get(skill_key, [])
|
|
95
|
+
if all(os.path.exists(os.path.join(project_root, a))
|
|
96
|
+
for a in artifacts):
|
|
97
|
+
valid_completed.add(skill_key)
|
|
98
|
+
else:
|
|
99
|
+
LOGGER.warning(
|
|
100
|
+
"Step '%s' was completed but artifacts missing — "
|
|
101
|
+
"resetting to pending", skill_key
|
|
102
|
+
)
|
|
103
|
+
elif status == "skipped":
|
|
104
|
+
valid_completed.add(skill_key)
|
|
105
|
+
|
|
106
|
+
chain_broken = False
|
|
107
|
+
for step in fresh["steps"]:
|
|
108
|
+
if chain_broken:
|
|
109
|
+
step["status"] = "pending"
|
|
110
|
+
continue
|
|
111
|
+
prev = existing_status.get(step["skill"])
|
|
112
|
+
if step["skill"] in valid_completed:
|
|
113
|
+
step["status"] = prev
|
|
114
|
+
else:
|
|
115
|
+
chain_broken = True
|
|
116
|
+
step["status"] = "pending"
|
|
117
|
+
|
|
118
|
+
return fresh
|
|
119
|
+
|
|
120
|
+
|
|
30
121
|
def parse_args():
|
|
31
122
|
parser = argparse.ArgumentParser(
|
|
32
123
|
description=(
|
|
@@ -281,8 +372,6 @@ def build_replacements(args, refactor, refactors, global_context, script_dir):
|
|
|
281
372
|
"sessions", args.session_id, "session-status.json"
|
|
282
373
|
)
|
|
283
374
|
|
|
284
|
-
prev_status = get_prev_session_status(args.state_dir, args.refactor_id)
|
|
285
|
-
|
|
286
375
|
# Scope
|
|
287
376
|
scope = refactor.get("scope", {})
|
|
288
377
|
|
|
@@ -321,10 +410,6 @@ def build_replacements(args, refactor, refactors, global_context, script_dir):
|
|
|
321
410
|
"{{NEW_TESTS_NEEDED}}": new_tests_str,
|
|
322
411
|
"{{PRIORITY}}": refactor.get("priority", "medium"),
|
|
323
412
|
"{{COMPLEXITY}}": refactor.get("complexity", "medium"),
|
|
324
|
-
"{{RETRY_COUNT}}": str(args.retry_count),
|
|
325
|
-
"{{MAX_RETRIES}}": str(DEFAULT_MAX_RETRIES),
|
|
326
|
-
"{{PREV_SESSION_STATUS}}": prev_status,
|
|
327
|
-
"{{RESUME_PHASE}}": args.resume_phase,
|
|
328
413
|
"{{REFACTOR_DESCRIPTION}}": refactor.get("description", ""),
|
|
329
414
|
"{{ACCEPTANCE_CRITERIA}}": format_acceptance_criteria(
|
|
330
415
|
refactor.get("acceptance_criteria", [])
|
|
@@ -342,6 +427,7 @@ def build_replacements(args, refactor, refactors, global_context, script_dir):
|
|
|
342
427
|
".prizmkit", "refactor", args.refactor_id, "workflow-checkpoint.json",
|
|
343
428
|
),
|
|
344
429
|
"{{TIMESTAMP}}": "", # Placeholder — agent fills in timestamp
|
|
430
|
+
"{{PLATFORM_CONVENTIONS}}": read_platform_conventions(project_root),
|
|
345
431
|
}
|
|
346
432
|
|
|
347
433
|
return replacements
|
|
@@ -350,33 +436,20 @@ def build_replacements(args, refactor, refactors, global_context, script_dir):
|
|
|
350
436
|
def process_conditional_blocks(content, resume_phase):
|
|
351
437
|
"""Handle conditional blocks based on resume_phase.
|
|
352
438
|
|
|
353
|
-
- {{IF_RESUME}}...{{END_IF_RESUME}} — include only when resuming (resume_phase != "null")
|
|
354
439
|
- {{IF_FRESH_START}}...{{END_IF_FRESH_START}} — include only on fresh start (resume_phase == "null")
|
|
355
440
|
"""
|
|
356
441
|
is_resume = resume_phase != "null"
|
|
357
442
|
|
|
358
443
|
if is_resume:
|
|
359
|
-
# Keep IF_RESUME content, strip markers
|
|
360
|
-
content = content.replace("{{IF_RESUME}}\n", "")
|
|
361
|
-
content = content.replace("{{IF_RESUME}}", "")
|
|
362
|
-
content = content.replace("{{END_IF_RESUME}}\n", "")
|
|
363
|
-
content = content.replace("{{END_IF_RESUME}}", "")
|
|
364
|
-
# Remove IF_FRESH_START blocks entirely
|
|
365
444
|
content = re.sub(
|
|
366
445
|
r"\{\{IF_FRESH_START\}\}.*?\{\{END_IF_FRESH_START\}\}\n?",
|
|
367
446
|
"", content, flags=re.DOTALL,
|
|
368
447
|
)
|
|
369
448
|
else:
|
|
370
|
-
# Keep IF_FRESH_START content, strip markers
|
|
371
449
|
content = content.replace("{{IF_FRESH_START}}\n", "")
|
|
372
450
|
content = content.replace("{{IF_FRESH_START}}", "")
|
|
373
451
|
content = content.replace("{{END_IF_FRESH_START}}\n", "")
|
|
374
452
|
content = content.replace("{{END_IF_FRESH_START}}", "")
|
|
375
|
-
# Remove IF_RESUME blocks entirely
|
|
376
|
-
content = re.sub(
|
|
377
|
-
r"\{\{IF_RESUME\}\}.*?\{\{END_IF_RESUME\}\}\n?",
|
|
378
|
-
"", content, flags=re.DOTALL,
|
|
379
|
-
)
|
|
380
453
|
|
|
381
454
|
return content
|
|
382
455
|
|
|
@@ -470,6 +543,37 @@ def main():
|
|
|
470
543
|
if err:
|
|
471
544
|
emit_failure(err)
|
|
472
545
|
|
|
546
|
+
# Generate checkpoint file
|
|
547
|
+
project_root = resolve_project_root(script_dir)
|
|
548
|
+
checkpoint_rel = os.path.join(
|
|
549
|
+
".prizmkit", "refactor", args.refactor_id, "workflow-checkpoint.json",
|
|
550
|
+
)
|
|
551
|
+
checkpoint_path = os.path.join(project_root, checkpoint_rel)
|
|
552
|
+
checkpoint_dir = os.path.dirname(checkpoint_path)
|
|
553
|
+
os.makedirs(checkpoint_dir, exist_ok=True)
|
|
554
|
+
|
|
555
|
+
checkpoint = generate_refactor_checkpoint(args.refactor_id, args.session_id)
|
|
556
|
+
|
|
557
|
+
is_resume = args.resume_phase != "null"
|
|
558
|
+
if is_resume and os.path.exists(checkpoint_path):
|
|
559
|
+
try:
|
|
560
|
+
with open(checkpoint_path, "r", encoding="utf-8") as f:
|
|
561
|
+
existing = json.load(f)
|
|
562
|
+
checkpoint = merge_refactor_checkpoint_state(
|
|
563
|
+
existing, checkpoint, project_root,
|
|
564
|
+
)
|
|
565
|
+
LOGGER.info("Merged existing refactor checkpoint from %s",
|
|
566
|
+
checkpoint_path)
|
|
567
|
+
except (json.JSONDecodeError, KeyError) as exc:
|
|
568
|
+
LOGGER.warning(
|
|
569
|
+
"Existing refactor checkpoint corrupted (%s) — generating fresh",
|
|
570
|
+
exc,
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
with open(checkpoint_path, "w", encoding="utf-8") as f:
|
|
574
|
+
json.dump(checkpoint, f, indent=2, ensure_ascii=False)
|
|
575
|
+
LOGGER.info("Wrote refactor checkpoint to %s", checkpoint_path)
|
|
576
|
+
|
|
473
577
|
# Resolve critic and mode
|
|
474
578
|
refactor_critic = refactor.get("critic", False)
|
|
475
579
|
if args.critic is not None:
|
|
@@ -518,3 +518,23 @@ def enrich_global_context(global_context, project_root):
|
|
|
518
518
|
global_context[ctx_key] = detected[det_key] + " (auto-detected)"
|
|
519
519
|
|
|
520
520
|
return global_context
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def read_platform_conventions(project_root):
|
|
524
|
+
"""Resolve the path to CLAUDE.md or CODEBUDDY.md for project-level conventions.
|
|
525
|
+
|
|
526
|
+
Returns a path reference for the AI agent to read at runtime,
|
|
527
|
+
rather than inlining the full file content into the prompt.
|
|
528
|
+
"""
|
|
529
|
+
platform = os.environ.get("PRIZMKIT_PLATFORM", "claude")
|
|
530
|
+
if platform == "codebuddy":
|
|
531
|
+
candidates = ["CODEBUDDY.md", "CLAUDE.md"]
|
|
532
|
+
else:
|
|
533
|
+
candidates = ["CLAUDE.md", "CODEBUDDY.md"]
|
|
534
|
+
|
|
535
|
+
for filename in candidates:
|
|
536
|
+
filepath = os.path.join(project_root, filename)
|
|
537
|
+
if os.path.isfile(filepath):
|
|
538
|
+
return "`{}`".format(filename)
|
|
539
|
+
|
|
540
|
+
return "(No project conventions file found — CLAUDE.md or CODEBUDDY.md)"
|
|
@@ -14,14 +14,20 @@ Update the AC Verification Checklist in context-snapshot.md by marking each item
|
|
|
14
14
|
- All [ ] must become [x] — if any AC remains unverified, the feature is incomplete
|
|
15
15
|
- Document any AC that cannot be verified due to test failures
|
|
16
16
|
|
|
17
|
-
## Test Failure Recovery (
|
|
17
|
+
## Test Failure Recovery (Convergence-Based)
|
|
18
18
|
|
|
19
|
-
If tests fail during implementation:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
19
|
+
If tests fail during implementation, use convergence-based recovery — keep fixing as long as progress is being made:
|
|
20
|
+
|
|
21
|
+
1. **Run tests, record results**: count failures, note which tests failed (exclude baseline failures)
|
|
22
|
+
2. **Check termination**:
|
|
23
|
+
- All tests pass → Done
|
|
24
|
+
- Plateau: same failures for 3 consecutive rounds → Cannot resolve, document and stop
|
|
25
|
+
- Failures decreased → Continue fixing
|
|
26
|
+
3. **Fix and iterate**: analyze, apply fix, re-run `$TEST_CMD`, go back to step 1
|
|
27
|
+
|
|
28
|
+
**Key rule**: If failures decrease (even by 1), the plateau counter resets to 0.
|
|
29
|
+
**Do NOT block completion** if unable to resolve — only NEW REGRESSIONS (not in baseline) require fixing.
|
|
30
|
+
**If any AC cannot be verified** due to test failure: the feature is incomplete, add to failure notes.
|
|
25
31
|
|
|
26
32
|
4. Do NOT execute any git commands (no git add/commit/reset/push).
|
|
27
33
|
Do NOT exit until all tasks are [x], the '## Implementation Log' section is written, and AC Verification Checklist is 100% complete in context-snapshot.md."
|