prizmkit 1.1.39 → 1.1.41

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 (34) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/SCHEMA_ANALYSIS.md +1 -1
  3. package/bundled/dev-pipeline/run-bugfix.sh +74 -0
  4. package/bundled/dev-pipeline/run-feature.sh +74 -0
  5. package/bundled/dev-pipeline/run-refactor.sh +74 -0
  6. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +0 -6
  7. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +118 -1
  8. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +123 -8
  9. package/bundled/dev-pipeline/templates/bootstrap-tier1.md +0 -23
  10. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +0 -23
  11. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +0 -23
  12. package/bundled/dev-pipeline/templates/bug-fix-list-schema.json +22 -3
  13. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +56 -0
  14. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +64 -4
  15. package/bundled/dev-pipeline/templates/refactor-list-schema.json +22 -3
  16. package/bundled/dev-pipeline/tests/test-deploy-safety.sh +223 -0
  17. package/bundled/skills/_metadata.json +3 -3
  18. package/bundled/skills/app-planner/SKILL.md +0 -3
  19. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +34 -6
  20. package/bundled/skills/feature-pipeline-launcher/SKILL.md +42 -18
  21. package/bundled/skills/prizmkit-committer/SKILL.md +0 -1
  22. package/bundled/skills/prizmkit-deploy/SKILL.md +491 -209
  23. package/bundled/skills/prizmkit-deploy/references/cloud-platform-deploy.md +93 -0
  24. package/bundled/skills/prizmkit-deploy/references/deploy-config-schema.md +147 -0
  25. package/bundled/skills/prizmkit-deploy/references/deploy-history-schema.md +62 -0
  26. package/bundled/skills/prizmkit-deploy/references/docker-deploy.md +31 -0
  27. package/bundled/skills/prizmkit-deploy/references/nginx-blue-green.md +59 -0
  28. package/bundled/skills/prizmkit-init/SKILL.md +0 -2
  29. package/bundled/skills/prizmkit-plan/SKILL.md +0 -3
  30. package/bundled/skills/recovery-workflow/SKILL.md +96 -7
  31. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +40 -9
  32. package/package.json +1 -1
  33. package/bundled/dev-pipeline/templates/sections/phase-deploy-verification.md +0 -31
  34. package/bundled/skills/prizmkit-deploy/assets/deploy-template.md +0 -187
@@ -1,5 +1,5 @@
1
1
  {
2
- "frameworkVersion": "1.1.39",
3
- "bundledAt": "2026-04-19T14:24:30.167Z",
4
- "bundledFrom": "5d52f7f"
2
+ "frameworkVersion": "1.1.41",
3
+ "bundledAt": "2026-04-30T09:49:08.976Z",
4
+ "bundledFrom": "13a2ce9"
5
5
  }
@@ -325,7 +325,7 @@ pending, in_progress, completed, failed, skipped
325
325
  | `failure-log-check.md` | Failure log diagnostics |
326
326
  | `context-budget-rules.md` | Context window guardrails |
327
327
  | `phase-browser-verification.md` | Playwright UI verification |
328
- | `phase-deploy-verification.md` | Local deployment check |
328
+ | `phase-deploy-verification.md` | [DEPRECATED] Local deployment check (template removed) |
329
329
  | `ac-verification-checklist.md` | Acceptance criteria verification |
330
330
  | `feature-context.md` | Feature brief context |
331
331
  | `phase-specify-plan-full.md` | Full spec + plan workflow |
@@ -50,6 +50,7 @@ MODEL=${MODEL:-""}
50
50
  DEV_BRANCH=${DEV_BRANCH:-""}
51
51
  AUTO_PUSH=${AUTO_PUSH:-0}
52
52
  STOP_ON_FAILURE=${STOP_ON_FAILURE:-0}
53
+ ENABLE_DEPLOY=${ENABLE_DEPLOY:-0}
53
54
 
54
55
  # Source shared common helpers (CLI/platform detection + logs + deps)
55
56
  source "$SCRIPT_DIR/lib/common.sh"
@@ -955,6 +956,79 @@ main() {
955
956
  log_success " Total subagent calls: $total_subagent_calls"
956
957
  log_success "════════════════════════════════════════════════════"
957
958
 
959
+ # ── Deploy session (only if ENABLE_DEPLOY=1 and all bugs fixed) ──
960
+ if [[ "$ENABLE_DEPLOY" == "1" ]]; then
961
+ local incomplete_count
962
+ incomplete_count=$({ python3 -c "
963
+ import json, sys
964
+ with open(sys.argv[1]) as f:
965
+ data = json.load(f)
966
+ bad = [b for b in data.get('bugs', [])
967
+ if b.get('status') not in ('completed', 'skipped', 'needs_info')]
968
+ for b in bad:
969
+ print(f\" {b['id']}: {b.get('status', 'unknown')} — {b.get('title', '')}\")
970
+ print(len(bad))
971
+ " "$bug_list" 2>/dev/null || echo "0"; } | tee /dev/stderr | tail -1)
972
+
973
+ if [[ "$incomplete_count" -gt 0 ]]; then
974
+ echo ""
975
+ log_warn "DEPLOY BLOCKED: $incomplete_count bug(s) not fixed successfully."
976
+ log_warn "Fix failed bugs and re-run, or manually run /prizmkit-deploy."
977
+ else
978
+ echo ""
979
+ log_info "All bugs fixed — starting deploy session..."
980
+ log_info "ENABLE_DEPLOY=1"
981
+
982
+ local deploy_session_id="deploy-$(date +%Y%m%d%H%M%S)"
983
+ local deploy_session_dir="$STATE_DIR/deploy/$deploy_session_id"
984
+ mkdir -p "$deploy_session_dir/logs"
985
+
986
+ local deploy_prompt="$deploy_session_dir/bootstrap-prompt.md"
987
+ local _deploy_branch _deploy_commit
988
+ _deploy_branch=$(git -C "$_proj_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
989
+ _deploy_commit=$(git -C "$_proj_root" rev-parse --short HEAD 2>/dev/null || echo "unknown")
990
+ cat > "$deploy_prompt" << DEPLOY_PROMPT_EOF
991
+ ## Deploy
992
+
993
+ All bugs have been fixed successfully.
994
+
995
+ - Branch: $_deploy_branch
996
+ - Commit: $_deploy_commit
997
+
998
+ Run /prizmkit-deploy to deploy the project. Read .prizmkit/deploy/deploy.config.json
999
+ for deployment configuration. If no deploy config exists, guide the user through
1000
+ setting one up before deploying.
1001
+ DEPLOY_PROMPT_EOF
1002
+
1003
+ log_info "Deploy prompt: $deploy_prompt"
1004
+ log_info "Deploy log: $deploy_session_dir/logs/session.log"
1005
+
1006
+ case "$CLI_CMD" in
1007
+ *claude*)
1008
+ "$CLI_CMD" \
1009
+ -p "$(cat "$deploy_prompt")" \
1010
+ --dangerously-skip-permissions \
1011
+ > "$deploy_session_dir/logs/session.log" 2>&1
1012
+ ;;
1013
+ *)
1014
+ "$CLI_CMD" \
1015
+ --print \
1016
+ -y \
1017
+ < "$deploy_prompt" \
1018
+ > "$deploy_session_dir/logs/session.log" 2>&1
1019
+ ;;
1020
+ esac
1021
+ local deploy_exit=$?
1022
+
1023
+ if [[ $deploy_exit -eq 0 ]]; then
1024
+ log_success "Deploy session completed (exit 0)"
1025
+ else
1026
+ log_warn "Deploy session exited with code $deploy_exit"
1027
+ log_warn "Review log: $deploy_session_dir/logs/session.log"
1028
+ fi
1029
+ fi
1030
+ fi
1031
+
958
1032
  break
959
1033
  fi
960
1034
 
@@ -53,6 +53,7 @@ MODEL=${MODEL:-""}
53
53
  DEV_BRANCH=${DEV_BRANCH:-""}
54
54
  AUTO_PUSH=${AUTO_PUSH:-0}
55
55
  STOP_ON_FAILURE=${STOP_ON_FAILURE:-0}
56
+ ENABLE_DEPLOY=${ENABLE_DEPLOY:-0}
56
57
 
57
58
  # Source shared common helpers (CLI/platform detection + logs + deps)
58
59
  source "$SCRIPT_DIR/lib/common.sh"
@@ -1186,6 +1187,79 @@ print(count)
1186
1187
  log_warn "Run './run-feature.sh unskip' to reset and retry them."
1187
1188
  fi
1188
1189
 
1190
+ # ── Deploy session (only if ENABLE_DEPLOY=1 and all features completed) ──
1191
+ if [[ "$ENABLE_DEPLOY" == "1" ]]; then
1192
+ local incomplete_count
1193
+ incomplete_count=$({ python3 -c "
1194
+ import json, sys
1195
+ with open(sys.argv[1]) as f:
1196
+ data = json.load(f)
1197
+ bad = [f for f in data.get('features', [])
1198
+ if f.get('status') not in ('completed', 'skipped')]
1199
+ for f in bad:
1200
+ print(f\" {f['id']}: {f.get('status', 'unknown')} — {f.get('title', '')}\")
1201
+ print(len(bad))
1202
+ " "$feature_list" 2>/dev/null || echo "0"; } | tee /dev/stderr | tail -1)
1203
+
1204
+ if [[ "$incomplete_count" -gt 0 ]]; then
1205
+ echo ""
1206
+ log_warn "DEPLOY BLOCKED: $incomplete_count task(s) not completed successfully."
1207
+ log_warn "Fix failed tasks and re-run, or manually run /prizmkit-deploy."
1208
+ else
1209
+ echo ""
1210
+ log_info "All tasks completed — starting deploy session..."
1211
+ log_info "ENABLE_DEPLOY=1"
1212
+
1213
+ local deploy_session_id="deploy-$(date +%Y%m%d%H%M%S)"
1214
+ local deploy_session_dir="$STATE_DIR/deploy/$deploy_session_id"
1215
+ mkdir -p "$deploy_session_dir/logs"
1216
+
1217
+ local deploy_prompt="$deploy_session_dir/bootstrap-prompt.md"
1218
+ local _deploy_branch _deploy_commit
1219
+ _deploy_branch=$(git -C "$_proj_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
1220
+ _deploy_commit=$(git -C "$_proj_root" rev-parse --short HEAD 2>/dev/null || echo "unknown")
1221
+ cat > "$deploy_prompt" << DEPLOY_PROMPT_EOF
1222
+ ## Deploy
1223
+
1224
+ All features in the pipeline completed successfully.
1225
+
1226
+ - Branch: $_deploy_branch
1227
+ - Commit: $_deploy_commit
1228
+
1229
+ Run /prizmkit-deploy to deploy the project. Read .prizmkit/deploy/deploy.config.json
1230
+ for deployment configuration. If no deploy config exists, guide the user through
1231
+ setting one up before deploying.
1232
+ DEPLOY_PROMPT_EOF
1233
+
1234
+ log_info "Deploy prompt: $deploy_prompt"
1235
+ log_info "Deploy log: $deploy_session_dir/logs/session.log"
1236
+
1237
+ case "$CLI_CMD" in
1238
+ *claude*)
1239
+ "$CLI_CMD" \
1240
+ -p "$(cat "$deploy_prompt")" \
1241
+ --dangerously-skip-permissions \
1242
+ > "$deploy_session_dir/logs/session.log" 2>&1
1243
+ ;;
1244
+ *)
1245
+ "$CLI_CMD" \
1246
+ --print \
1247
+ -y \
1248
+ < "$deploy_prompt" \
1249
+ > "$deploy_session_dir/logs/session.log" 2>&1
1250
+ ;;
1251
+ esac
1252
+ local deploy_exit=$?
1253
+
1254
+ if [[ $deploy_exit -eq 0 ]]; then
1255
+ log_success "Deploy session completed (exit 0)"
1256
+ else
1257
+ log_warn "Deploy session exited with code $deploy_exit"
1258
+ log_warn "Review log: $deploy_session_dir/logs/session.log"
1259
+ fi
1260
+ fi
1261
+ fi
1262
+
1189
1263
  break
1190
1264
  fi
1191
1265
 
@@ -52,6 +52,7 @@ DEV_BRANCH=${DEV_BRANCH:-""}
52
52
  AUTO_PUSH=${AUTO_PUSH:-0}
53
53
  STOP_ON_FAILURE=${STOP_ON_FAILURE:-0}
54
54
  STRICT_BEHAVIOR_CHECK=${STRICT_BEHAVIOR_CHECK:-1}
55
+ ENABLE_DEPLOY=${ENABLE_DEPLOY:-0}
55
56
 
56
57
  # Source shared common helpers (CLI/platform detection + logs + deps)
57
58
  source "$SCRIPT_DIR/lib/common.sh"
@@ -990,6 +991,79 @@ main() {
990
991
  log_success " Total subagent calls: $total_subagent_calls"
991
992
  log_success "════════════════════════════════════════════════════"
992
993
 
994
+ # ── Deploy session (only if ENABLE_DEPLOY=1 and all refactors completed) ──
995
+ if [[ "$ENABLE_DEPLOY" == "1" ]]; then
996
+ local incomplete_count
997
+ incomplete_count=$({ python3 -c "
998
+ import json, sys
999
+ with open(sys.argv[1]) as f:
1000
+ data = json.load(f)
1001
+ bad = [r for r in data.get('refactors', [])
1002
+ if r.get('status') not in ('completed', 'skipped')]
1003
+ for r in bad:
1004
+ print(f\" {r['id']}: {r.get('status', 'unknown')} — {r.get('title', '')}\")
1005
+ print(len(bad))
1006
+ " "$refactor_list" 2>/dev/null || echo "0"; } | tee /dev/stderr | tail -1)
1007
+
1008
+ if [[ "$incomplete_count" -gt 0 ]]; then
1009
+ echo ""
1010
+ log_warn "DEPLOY BLOCKED: $incomplete_count refactor(s) not completed successfully."
1011
+ log_warn "Fix failed refactors and re-run, or manually run /prizmkit-deploy."
1012
+ else
1013
+ echo ""
1014
+ log_info "All refactors completed — starting deploy session..."
1015
+ log_info "ENABLE_DEPLOY=1"
1016
+
1017
+ local deploy_session_id="deploy-$(date +%Y%m%d%H%M%S)"
1018
+ local deploy_session_dir="$STATE_DIR/deploy/$deploy_session_id"
1019
+ mkdir -p "$deploy_session_dir/logs"
1020
+
1021
+ local deploy_prompt="$deploy_session_dir/bootstrap-prompt.md"
1022
+ local _deploy_branch _deploy_commit
1023
+ _deploy_branch=$(git -C "$_proj_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
1024
+ _deploy_commit=$(git -C "$_proj_root" rev-parse --short HEAD 2>/dev/null || echo "unknown")
1025
+ cat > "$deploy_prompt" << DEPLOY_PROMPT_EOF
1026
+ ## Deploy
1027
+
1028
+ All refactor tasks have been completed successfully.
1029
+
1030
+ - Branch: $_deploy_branch
1031
+ - Commit: $_deploy_commit
1032
+
1033
+ Run /prizmkit-deploy to deploy the project. Read .prizmkit/deploy/deploy.config.json
1034
+ for deployment configuration. If no deploy config exists, guide the user through
1035
+ setting one up before deploying.
1036
+ DEPLOY_PROMPT_EOF
1037
+
1038
+ log_info "Deploy prompt: $deploy_prompt"
1039
+ log_info "Deploy log: $deploy_session_dir/logs/session.log"
1040
+
1041
+ case "$CLI_CMD" in
1042
+ *claude*)
1043
+ "$CLI_CMD" \
1044
+ -p "$(cat "$deploy_prompt")" \
1045
+ --dangerously-skip-permissions \
1046
+ > "$deploy_session_dir/logs/session.log" 2>&1
1047
+ ;;
1048
+ *)
1049
+ "$CLI_CMD" \
1050
+ --print \
1051
+ -y \
1052
+ < "$deploy_prompt" \
1053
+ > "$deploy_session_dir/logs/session.log" 2>&1
1054
+ ;;
1055
+ esac
1056
+ local deploy_exit=$?
1057
+
1058
+ if [[ $deploy_exit -eq 0 ]]; then
1059
+ log_success "Deploy session completed (exit 0)"
1060
+ else
1061
+ log_warn "Deploy session exited with code $deploy_exit"
1062
+ log_warn "Review log: $deploy_session_dir/logs/session.log"
1063
+ fi
1064
+ fi
1065
+ fi
1066
+
993
1067
  break
994
1068
  fi
995
1069
 
@@ -625,7 +625,6 @@ SECTION_TO_SKILL = {
625
625
  "phase-implement": ("prizmkit-implement", "Implement + Test", []),
626
626
  "phase-review": ("prizmkit-code-review", "Code Review", []),
627
627
  "phase-browser": ("browser-verification", "Browser Verification", []),
628
- "phase-deploy": ("deploy-verification", "Deploy Verification", []),
629
628
  "phase-commit": None, # special: split into retrospective + committer
630
629
  }
631
630
 
@@ -1072,11 +1071,6 @@ def assemble_sections(pipeline_mode, sections_dir, init_done, is_resume,
1072
1071
  load_section(sections_dir,
1073
1072
  browser_section_file)))
1074
1073
 
1075
- # --- Deploy Verification ---
1076
- sections.append(("phase-deploy",
1077
- load_section(sections_dir,
1078
- "phase-deploy-verification.md")))
1079
-
1080
1074
  # --- Commit (tier-dependent) ---
1081
1075
  if pipeline_mode == "full":
1082
1076
  sections.append(("phase-commit",
@@ -260,6 +260,39 @@ def build_replacements(args, bug, global_context, script_dir):
260
260
  # Determine verification type
261
261
  vtype = bug.get("verification_type", "automated")
262
262
 
263
+ # Browser interaction - extract from bug if present
264
+ browser_interaction = bug.get("browser_interaction")
265
+ browser_enabled = False
266
+ browser_verify_steps = ""
267
+ browser_tool = "auto"
268
+
269
+ # Environment override
270
+ browser_verify_env = os.environ.get("BROWSER_VERIFY", "").lower()
271
+ if browser_verify_env == "false":
272
+ browser_interaction = None
273
+
274
+ # Extract browser config (same logic as feature pipeline)
275
+ if browser_interaction and isinstance(browser_interaction, bool):
276
+ browser_enabled = True
277
+ browser_tool = "auto"
278
+ browser_verify_steps = " # (no specific verify goals — reproduce the bug and verify it's fixed)"
279
+ elif browser_interaction and isinstance(browser_interaction, dict):
280
+ browser_tool = browser_interaction.get("tool", "auto")
281
+ if browser_tool not in ("playwright-cli", "opencli", "auto"):
282
+ LOGGER.warning("Unknown browser_interaction.tool '%s', defaulting to 'auto'", browser_tool)
283
+ browser_tool = "auto"
284
+
285
+ steps = browser_interaction.get("verify_steps", [])
286
+ if steps:
287
+ browser_enabled = True
288
+ browser_verify_steps = "\n".join(
289
+ " # Step {}: {}".format(i + 1, step)
290
+ for i, step in enumerate(steps)
291
+ )
292
+ elif browser_interaction.get("url") or browser_interaction.get("enabled", True):
293
+ browser_enabled = True
294
+ browser_verify_steps = " # (reproduce bug and verify fix)"
295
+
263
296
  replacements = {
264
297
  "{{RUN_ID}}": args.run_id,
265
298
  "{{SESSION_ID}}": args.session_id,
@@ -284,13 +317,17 @@ def build_replacements(args, bug, global_context, script_dir):
284
317
  "{{PROJECT_ROOT}}": project_root,
285
318
  "{{FIX_SCOPE}}": fix_scope,
286
319
  "{{TIMESTAMP}}": "", # Placeholder, agent fills in the timestamp
320
+ "{{BROWSER_ENABLED}}": "true" if browser_enabled else "false",
321
+ "{{BROWSER_TOOL}}": browser_tool,
322
+ "{{BROWSER_VERIFY_STEPS}}": browser_verify_steps,
287
323
  }
288
324
 
289
325
  return replacements
290
326
 
291
327
 
292
328
  def process_conditional_blocks(content, bug):
293
- """Handle conditional blocks based on verification_type."""
329
+ """Handle conditional blocks based on verification_type and browser_interaction."""
330
+ # Handle verification type blocks
294
331
  vtype = bug.get("verification_type", "automated")
295
332
  is_manual_or_hybrid = vtype in ("manual", "hybrid")
296
333
 
@@ -306,6 +343,64 @@ def process_conditional_blocks(content, bug):
306
343
  "", content, flags=re.DOTALL,
307
344
  )
308
345
 
346
+ # Handle browser interaction blocks
347
+ browser_interaction = bug.get("browser_interaction")
348
+ browser_enabled = False
349
+ browser_tool = "auto"
350
+
351
+ # Check environment override
352
+ browser_verify_env = os.environ.get("BROWSER_VERIFY", "").lower()
353
+ if browser_verify_env == "false":
354
+ browser_interaction = None
355
+
356
+ # Determine if browser is enabled
357
+ if browser_interaction:
358
+ if isinstance(browser_interaction, bool):
359
+ browser_enabled = True
360
+ elif isinstance(browser_interaction, dict):
361
+ steps = browser_interaction.get("verify_steps", [])
362
+ if steps or browser_interaction.get("url") or browser_interaction.get("enabled", True):
363
+ browser_enabled = True
364
+ browser_tool = browser_interaction.get("tool", "auto")
365
+
366
+ # Process browser interaction blocks
367
+ browser_open = "{{IF_BROWSER_INTERACTION}}"
368
+ browser_close = "{{END_IF_BROWSER_INTERACTION}}"
369
+
370
+ if browser_enabled:
371
+ # Keep content, remove tags
372
+ content = content.replace(browser_open + "\n", "")
373
+ content = content.replace(browser_open, "")
374
+ content = content.replace(browser_close + "\n", "")
375
+ content = content.replace(browser_close, "")
376
+ else:
377
+ # Remove entire block
378
+ pattern = re.escape(browser_open) + r".*?" + re.escape(browser_close) + r"\n?"
379
+ content = re.sub(pattern, "", content, flags=re.DOTALL)
380
+
381
+ # Process browser tool selection blocks (nested inside browser interaction)
382
+ tool_variants = ["PLAYWRIGHT", "OPENCLI", "AUTO"]
383
+ active_variant = {
384
+ "playwright-cli": "PLAYWRIGHT",
385
+ "opencli": "OPENCLI",
386
+ "auto": "AUTO",
387
+ }.get(browser_tool, "AUTO")
388
+
389
+ for variant in tool_variants:
390
+ tool_open = "{{{{IF_BROWSER_TOOL_{}}}}}".format(variant)
391
+ tool_close = "{{{{END_IF_BROWSER_TOOL_{}}}}}".format(variant)
392
+
393
+ if variant == active_variant and browser_enabled:
394
+ # Keep content, remove tags
395
+ content = content.replace(tool_open + "\n", "")
396
+ content = content.replace(tool_open, "")
397
+ content = content.replace(tool_close + "\n", "")
398
+ content = content.replace(tool_close, "")
399
+ else:
400
+ # Remove entire block
401
+ pat = re.escape(tool_open) + r".*?" + re.escape(tool_close) + r"\n?"
402
+ content = re.sub(pat, "", content, flags=re.DOTALL)
403
+
309
404
  return content
310
405
 
311
406
 
@@ -529,6 +624,26 @@ def main():
529
624
 
530
625
  # Success
531
626
  bug_model = bug.get("model", "")
627
+ # Extract browser interaction state for output
628
+ browser_interaction = bug.get("browser_interaction")
629
+ browser_enabled = False
630
+ browser_tool = "auto"
631
+
632
+ browser_verify_env = os.environ.get("BROWSER_VERIFY", "").lower()
633
+ if browser_verify_env == "false":
634
+ browser_interaction = None
635
+
636
+ if browser_interaction:
637
+ if isinstance(browser_interaction, bool):
638
+ browser_enabled = True
639
+ elif isinstance(browser_interaction, dict):
640
+ steps = browser_interaction.get("verify_steps", [])
641
+ if steps or browser_interaction.get("url") or browser_interaction.get("enabled", True):
642
+ browser_enabled = True
643
+ browser_tool = browser_interaction.get("tool", "auto")
644
+ if browser_tool not in ("playwright-cli", "opencli", "auto"):
645
+ browser_tool = "auto"
646
+
532
647
  output = {
533
648
  "success": True,
534
649
  "output_path": os.path.abspath(args.output),
@@ -537,6 +652,8 @@ def main():
537
652
  "pipeline_mode": pipeline_mode,
538
653
  "agent_count": agent_count,
539
654
  "critic_enabled": critic_enabled,
655
+ "browser_enabled": browser_enabled,
656
+ "browser_tool": browser_tool
540
657
  }
541
658
  print(json.dumps(output, indent=2, ensure_ascii=False))
542
659
  sys.exit(0)
@@ -420,6 +420,39 @@ def build_replacements(args, refactor, refactors, global_context, script_dir):
420
420
  else:
421
421
  new_tests_str = "- (none specified)"
422
422
 
423
+ # Browser interaction - extract from refactor if present
424
+ browser_interaction = refactor.get("browser_interaction")
425
+ browser_enabled = False
426
+ browser_verify_steps = ""
427
+ browser_tool = "auto"
428
+
429
+ # Environment override
430
+ browser_verify_env = os.environ.get("BROWSER_VERIFY", "").lower()
431
+ if browser_verify_env == "false":
432
+ browser_interaction = None
433
+
434
+ # Extract browser config (same logic as feature and bugfix pipelines)
435
+ if browser_interaction and isinstance(browser_interaction, bool):
436
+ browser_enabled = True
437
+ browser_tool = "auto"
438
+ browser_verify_steps = " # (no specific verify goals — validate UI renders correctly and feature still works)"
439
+ elif browser_interaction and isinstance(browser_interaction, dict):
440
+ browser_tool = browser_interaction.get("tool", "auto")
441
+ if browser_tool not in ("playwright-cli", "opencli", "auto"):
442
+ LOGGER.warning("Unknown browser_interaction.tool '%s', defaulting to 'auto'", browser_tool)
443
+ browser_tool = "auto"
444
+
445
+ steps = browser_interaction.get("verify_steps", [])
446
+ if steps:
447
+ browser_enabled = True
448
+ browser_verify_steps = "\n".join(
449
+ " # Step {}: {}".format(i + 1, step)
450
+ for i, step in enumerate(steps)
451
+ )
452
+ elif browser_interaction.get("url") or browser_interaction.get("enabled", True):
453
+ browser_enabled = True
454
+ browser_verify_steps = " # (validate UI renders correctly and feature still works)"
455
+
423
456
  replacements = {
424
457
  "{{RUN_ID}}": args.run_id,
425
458
  "{{SESSION_ID}}": args.session_id,
@@ -452,16 +485,17 @@ def build_replacements(args, refactor, refactors, global_context, script_dir):
452
485
  ),
453
486
  "{{TIMESTAMP}}": "", # Placeholder — agent fills in timestamp
454
487
  "{{PLATFORM_CONVENTIONS}}": read_platform_conventions(project_root),
488
+ "{{BROWSER_ENABLED}}": "true" if browser_enabled else "false",
489
+ "{{BROWSER_TOOL}}": browser_tool,
490
+ "{{BROWSER_VERIFY_STEPS}}": browser_verify_steps,
455
491
  }
456
492
 
457
493
  return replacements
458
494
 
459
495
 
460
- def process_conditional_blocks(content, resume_phase):
461
- """Handle conditional blocks based on resume_phase.
462
-
463
- - {{IF_FRESH_START}}...{{END_IF_FRESH_START}} — include only on fresh start (resume_phase == "null")
464
- """
496
+ def process_conditional_blocks(content, resume_phase, refactor):
497
+ """Handle conditional blocks based on resume_phase and browser_interaction."""
498
+ # Handle fresh start blocks
465
499
  is_resume = resume_phase != "null"
466
500
 
467
501
  if is_resume:
@@ -475,13 +509,72 @@ def process_conditional_blocks(content, resume_phase):
475
509
  content = content.replace("{{END_IF_FRESH_START}}\n", "")
476
510
  content = content.replace("{{END_IF_FRESH_START}}", "")
477
511
 
512
+ # Handle browser interaction blocks
513
+ browser_interaction = refactor.get("browser_interaction")
514
+ browser_enabled = False
515
+ browser_tool = "auto"
516
+
517
+ # Check environment override
518
+ browser_verify_env = os.environ.get("BROWSER_VERIFY", "").lower()
519
+ if browser_verify_env == "false":
520
+ browser_interaction = None
521
+
522
+ # Determine if browser is enabled
523
+ if browser_interaction:
524
+ if isinstance(browser_interaction, bool):
525
+ browser_enabled = True
526
+ elif isinstance(browser_interaction, dict):
527
+ steps = browser_interaction.get("verify_steps", [])
528
+ if steps or browser_interaction.get("url") or browser_interaction.get("enabled", True):
529
+ browser_enabled = True
530
+ browser_tool = browser_interaction.get("tool", "auto")
531
+
532
+ # Process browser interaction blocks
533
+ browser_open = "{{IF_BROWSER_INTERACTION}}"
534
+ browser_close = "{{END_IF_BROWSER_INTERACTION}}"
535
+
536
+ if browser_enabled:
537
+ # Keep content, remove tags
538
+ content = content.replace(browser_open + "\n", "")
539
+ content = content.replace(browser_open, "")
540
+ content = content.replace(browser_close + "\n", "")
541
+ content = content.replace(browser_close, "")
542
+ else:
543
+ # Remove entire block
544
+ pattern = re.escape(browser_open) + r".*?" + re.escape(browser_close) + r"\n?"
545
+ content = re.sub(pattern, "", content, flags=re.DOTALL)
546
+
547
+ # Process browser tool selection blocks (nested inside browser interaction)
548
+ tool_variants = ["PLAYWRIGHT", "OPENCLI", "AUTO"]
549
+ active_variant = {
550
+ "playwright-cli": "PLAYWRIGHT",
551
+ "opencli": "OPENCLI",
552
+ "auto": "AUTO",
553
+ }.get(browser_tool, "AUTO")
554
+
555
+ for variant in tool_variants:
556
+ tool_open = "{{{{IF_BROWSER_TOOL_{}}}}}".format(variant)
557
+ tool_close = "{{{{END_IF_BROWSER_TOOL_{}}}}}".format(variant)
558
+
559
+ if variant == active_variant and browser_enabled:
560
+ # Keep content, remove tags
561
+ content = content.replace(tool_open + "\n", "")
562
+ content = content.replace(tool_open, "")
563
+ content = content.replace(tool_close + "\n", "")
564
+ content = content.replace(tool_close, "")
565
+ else:
566
+ # Remove entire block
567
+ pat = re.escape(tool_open) + r".*?" + re.escape(tool_close) + r"\n?"
568
+ content = re.sub(pat, "", content, flags=re.DOTALL)
569
+
478
570
  return content
479
571
 
480
572
 
481
- def render_template(template_content, replacements, resume_phase):
573
+
574
+ def render_template(template_content, replacements, resume_phase, refactor):
482
575
  """Render the template by processing conditionals and replacing placeholders."""
483
576
  # Step 1: Process conditional blocks
484
- content = process_conditional_blocks(template_content, resume_phase)
577
+ content = process_conditional_blocks(template_content, resume_phase, refactor)
485
578
 
486
579
  # Step 2: Replace all {{PLACEHOLDER}} variables
487
580
  for placeholder, value in replacements.items():
@@ -560,7 +653,7 @@ def main():
560
653
  replacements = build_replacements(args, refactor, refactors, global_context, script_dir)
561
654
 
562
655
  # Render the template
563
- rendered = render_template(template_content, replacements, args.resume_phase)
656
+ rendered = render_template(template_content, replacements, args.resume_phase, refactor)
564
657
 
565
658
  # Write the output
566
659
  err = write_output(args.output, rendered)
@@ -610,13 +703,35 @@ def main():
610
703
 
611
704
  # Success
612
705
  refactor_model = refactor.get("model", "")
706
+
707
+ # Extract browser state for JSON output
708
+ browser_interaction = refactor.get("browser_interaction")
709
+ browser_enabled = False
710
+ browser_tool = "auto"
711
+
712
+ browser_verify_env = os.environ.get("BROWSER_VERIFY", "").lower()
713
+ if browser_verify_env == "false":
714
+ browser_interaction = None
715
+
716
+ if browser_interaction:
717
+ if isinstance(browser_interaction, bool):
718
+ browser_enabled = True
719
+ elif isinstance(browser_interaction, dict):
720
+ steps = browser_interaction.get("verify_steps", [])
721
+ if steps or browser_interaction.get("url") or browser_interaction.get("enabled", True):
722
+ browser_enabled = True
723
+ browser_tool = browser_interaction.get("tool", "auto")
724
+
613
725
  output = {
614
726
  "success": True,
615
727
  "output_path": os.path.abspath(args.output),
728
+ "checkpoint_path": checkpoint_path,
616
729
  "model": refactor_model,
617
730
  "pipeline_mode": pipeline_mode,
618
731
  "agent_count": agent_count,
619
732
  "critic_enabled": critic_enabled,
733
+ "browser_enabled": browser_enabled,
734
+ "browser_tool": browser_tool,
620
735
  }
621
736
  print(json.dumps(output, indent=2, ensure_ascii=False))
622
737
  sys.exit(0)