prizmkit 1.1.111 → 1.1.113

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 (119) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/adapters/codex/skill-adapter.js +4 -0
  3. package/bundled/agents/prizm-dev-team-reviewer.md +12 -1
  4. package/bundled/dev-pipeline/SCHEMA_ANALYSIS.md +10 -10
  5. package/bundled/dev-pipeline/prizmkit_runtime/interoperability.py +1 -0
  6. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +20 -73
  7. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +290 -421
  8. package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +1 -1
  9. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +259 -481
  10. package/bundled/dev-pipeline/scripts/init-bugfix-pipeline.py +34 -0
  11. package/bundled/dev-pipeline/scripts/init-pipeline.py +12 -0
  12. package/bundled/dev-pipeline/scripts/init-refactor-pipeline.py +24 -1
  13. package/bundled/dev-pipeline/scripts/prompt_framework.py +312 -0
  14. package/bundled/dev-pipeline/scripts/update-bug-status.py +87 -20
  15. package/bundled/dev-pipeline/scripts/update-feature-status.py +82 -16
  16. package/bundled/dev-pipeline/scripts/update-refactor-status.py +68 -28
  17. package/bundled/dev-pipeline/scripts/utils.py +171 -0
  18. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +1 -1
  19. package/bundled/dev-pipeline/templates/bootstrap-tier1.md +7 -1
  20. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +17 -19
  21. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +21 -26
  22. package/bundled/dev-pipeline/templates/bug-fix-list-schema.json +9 -1
  23. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +11 -8
  24. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +122 -182
  25. package/bundled/dev-pipeline/templates/sections/bugfix-artifacts.md +16 -0
  26. package/bundled/dev-pipeline/templates/sections/bugfix-critical-paths.md +12 -0
  27. package/bundled/dev-pipeline/templates/sections/bugfix-mission.md +9 -0
  28. package/bundled/dev-pipeline/templates/sections/bugfix-phase-commit-report.md +31 -0
  29. package/bundled/dev-pipeline/templates/sections/bugfix-phase-critic.md +22 -0
  30. package/bundled/dev-pipeline/templates/sections/bugfix-phase-diagnose-plan.md +23 -0
  31. package/bundled/dev-pipeline/templates/sections/bugfix-phase-implement.md +20 -0
  32. package/bundled/dev-pipeline/templates/sections/bugfix-phase-init.md +9 -0
  33. package/bundled/dev-pipeline/templates/sections/bugfix-phase-manual-verification.md +15 -0
  34. package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +29 -0
  35. package/bundled/dev-pipeline/templates/sections/bugfix-reminders.md +11 -0
  36. package/bundled/dev-pipeline/templates/sections/bugfix-session-context.md +7 -0
  37. package/bundled/dev-pipeline/templates/sections/bugfix-session-status.md +31 -0
  38. package/bundled/dev-pipeline/templates/sections/bugfix-task-contract.md +40 -0
  39. package/bundled/dev-pipeline/templates/sections/failure-capture.md +2 -2
  40. package/bundled/dev-pipeline/templates/sections/phase-browser-verification-auto.md +8 -2
  41. package/bundled/dev-pipeline/templates/sections/phase-browser-verification-opencli.md +7 -1
  42. package/bundled/dev-pipeline/templates/sections/phase-browser-verification.md +7 -1
  43. package/bundled/dev-pipeline/templates/sections/refactor-artifacts.md +17 -0
  44. package/bundled/dev-pipeline/templates/sections/refactor-critical-paths.md +13 -0
  45. package/bundled/dev-pipeline/templates/sections/refactor-mission.md +9 -0
  46. package/bundled/dev-pipeline/templates/sections/refactor-phase-commit-report.md +37 -0
  47. package/bundled/dev-pipeline/templates/sections/refactor-phase-critic.md +22 -0
  48. package/bundled/dev-pipeline/templates/sections/refactor-phase-implement.md +16 -0
  49. package/bundled/dev-pipeline/templates/sections/refactor-phase-init.md +9 -0
  50. package/bundled/dev-pipeline/templates/sections/refactor-phase-plan.md +22 -0
  51. package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +19 -0
  52. package/bundled/dev-pipeline/templates/sections/refactor-reminders.md +11 -0
  53. package/bundled/dev-pipeline/templates/sections/refactor-session-context.md +7 -0
  54. package/bundled/dev-pipeline/templates/sections/refactor-session-status.md +28 -0
  55. package/bundled/dev-pipeline/templates/sections/refactor-task-contract.md +52 -0
  56. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +104 -26
  57. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +14 -0
  58. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +239 -0
  59. package/bundled/dev-pipeline/tests/test_unified_cli.py +6 -2
  60. package/bundled/dev-pipeline/tests/test_utils.py +66 -1
  61. package/bundled/skills/_metadata.json +1 -1
  62. package/bundled/skills/app-planner/SKILL.md +8 -7
  63. package/bundled/skills/app-planner/references/project-brief-guide.md +2 -2
  64. package/bundled/skills/app-planner/references/rules/backend/derivation-rules.md +1 -1
  65. package/bundled/skills/app-planner/references/rules/frontend/derivation-rules.md +1 -1
  66. package/bundled/skills/app-planner/references/rules-configuration.md +53 -26
  67. package/bundled/skills/bug-fix-workflow/SKILL.md +191 -336
  68. package/bundled/skills/bug-planner/SKILL.md +6 -5
  69. package/bundled/skills/bug-planner/references/critic-and-verification.md +3 -3
  70. package/bundled/skills/bug-planner/references/schema-validation.md +2 -1
  71. package/bundled/skills/bug-planner/scripts/validate-bug-list.py +70 -1
  72. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +21 -21
  73. package/bundled/skills/bugfix-pipeline-launcher/references/configuration.md +2 -2
  74. package/bundled/skills/feature-pipeline-launcher/SKILL.md +25 -26
  75. package/bundled/skills/feature-planner/SKILL.md +9 -11
  76. package/bundled/skills/feature-planner/assets/planning-guide.md +1 -1
  77. package/bundled/skills/feature-planner/references/decomposition-patterns.md +1 -1
  78. package/bundled/skills/feature-planner/references/error-recovery.md +2 -1
  79. package/bundled/skills/feature-planner/references/incremental-feature-planning.md +2 -2
  80. package/bundled/skills/feature-planner/references/new-project-planning.md +35 -39
  81. package/bundled/skills/feature-planner/scripts/validate-and-generate.py +1 -1
  82. package/bundled/skills/feature-workflow/SKILL.md +170 -298
  83. package/bundled/skills/prizmkit/SKILL.md +103 -57
  84. package/bundled/skills/prizmkit-code-review/SKILL.md +97 -116
  85. package/bundled/skills/prizmkit-code-review/references/review-report-template.md +1 -0
  86. package/bundled/skills/prizmkit-code-review/references/reviewer-agent-prompt.md +14 -3
  87. package/bundled/skills/prizmkit-committer/SKILL.md +59 -47
  88. package/bundled/skills/prizmkit-deploy/SKILL.md +162 -381
  89. package/bundled/skills/prizmkit-deploy/references/database-setup.md +12 -3
  90. package/bundled/skills/prizmkit-deploy/references/ssh-adapter-flow.md +220 -0
  91. package/bundled/skills/prizmkit-implement/SKILL.md +51 -40
  92. package/bundled/skills/prizmkit-init/SKILL.md +4 -3
  93. package/bundled/skills/prizmkit-plan/SKILL.md +85 -70
  94. package/bundled/skills/prizmkit-prizm-docs/SKILL.md +94 -73
  95. package/bundled/skills/prizmkit-prizm-docs/assets/prizm-docs-format.md +20 -18
  96. package/bundled/skills/prizmkit-prizm-docs/references/op-init.md +11 -13
  97. package/bundled/skills/prizmkit-prizm-docs/references/op-update.md +18 -14
  98. package/bundled/skills/prizmkit-retrospective/SKILL.md +56 -48
  99. package/bundled/skills/prizmkit-retrospective/references/structural-sync-steps.md +79 -27
  100. package/bundled/skills/prizmkit-test/SKILL.md +138 -141
  101. package/bundled/skills/prizmkit-test/references/boundary-coverage-protocol.md +1 -1
  102. package/bundled/skills/prizmkit-test/references/examples.md +11 -9
  103. package/bundled/skills/prizmkit-test/references/test-generation-steps.md +1 -1
  104. package/bundled/skills/prizmkit-test/references/test-report-template.md +2 -2
  105. package/bundled/skills/recovery-workflow/SKILL.md +195 -256
  106. package/bundled/skills/recovery-workflow/evals/evals.json +21 -13
  107. package/bundled/skills/recovery-workflow/references/detection.md +48 -39
  108. package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +258 -322
  109. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +32 -13
  110. package/bundled/skills/refactor-planner/SKILL.md +2 -2
  111. package/bundled/skills/refactor-planner/references/behavior-preservation.md +24 -19
  112. package/bundled/skills/refactor-planner/references/fast-path.md +2 -4
  113. package/bundled/skills/refactor-planner/references/planning-phases.md +2 -2
  114. package/bundled/skills/refactor-workflow/SKILL.md +174 -307
  115. package/package.json +1 -1
  116. package/src/scaffold.js +5 -0
  117. package/bundled/dev-pipeline/templates/agent-prompts/reviewer-review.md +0 -6
  118. package/bundled/skills/feature-workflow/references/brainstorm-guide.md +0 -137
  119. package/bundled/skills/refactor-workflow/references/brainstorm-guide.md +0 -116
@@ -1,16 +1,8 @@
1
1
  #!/usr/bin/env python3
2
- """Generate a session-specific refactor bootstrap prompt from template and .prizmkit/plans/refactor-list.json.
3
-
4
- Reads the refactor-bootstrap-prompt.md template and a .prizmkit/plans/refactor-list.json, resolves all
5
- {{PLACEHOLDER}} variables, handles conditional blocks, and writes the rendered
6
- prompt to the specified output path.
7
-
8
- Usage:
9
- python3 generate-refactor-prompt.py \
10
- --refactor-list <path> --refactor-id <id> \
11
- --session-id <id> --run-id <id> \
12
- --retry-count <n> --resume-phase <n|null> \
13
- --state-dir <path> --output <path>
2
+ """Generate a session-specific refactor bootstrap prompt from refactor-list.json.
3
+
4
+ The default path uses modular prompt sections and orchestrator-direct execution.
5
+ The legacy monolithic template is kept only for explicit --template compatibility.
14
6
  """
15
7
 
16
8
  import argparse
@@ -19,111 +11,61 @@ import os
19
11
  import re
20
12
  import sys
21
13
 
22
- from utils import enrich_global_context, helper_replacements, load_json_file, read_checkpoint_system_section, resolve_prompt_platform, setup_logging
23
14
  from continuation import add_continuation_args, append_continuation_handoff, is_continuation
15
+ from prompt_framework import (
16
+ browser_enabled_from_item,
17
+ detect_dev_server_port,
18
+ generate_checkpoint_from_sections,
19
+ load_section,
20
+ merge_checkpoint_state,
21
+ mode_agent_count,
22
+ normalize_mode,
23
+ read_text_file,
24
+ render_from_sections,
25
+ replace_placeholders,
26
+ validate_rendered,
27
+ write_output,
28
+ )
29
+ from utils import enrich_global_context, helper_replacements, load_json_file, read_checkpoint_system_section, resolve_prompt_platform, setup_logging
24
30
 
25
31
 
26
- DEFAULT_MAX_RETRIES = 3
27
-
28
32
  LOGGER = setup_logging("generate-refactor-prompt")
29
33
 
30
34
 
31
- # Refactor pipeline checkpoint steps (skill_key, display_name, required_artifacts)
32
- # Artifacts use {slug} placeholder, replaced with refactor_id at runtime.
33
- REFACTOR_STEPS = [
34
- ("prizmkit-init", "Initialize",
35
- [".prizmkit/refactor/{slug}"]),
36
- ("prizmkit-plan", "Plan — Specification & Plan Generation",
37
- [".prizmkit/refactor/{slug}/spec.md",
38
- ".prizmkit/refactor/{slug}/plan.md"]),
39
- ("prizmkit-implement", "Implement — Behavior-Preserving Refactoring",
40
- [".prizmkit/refactor/{slug}/plan.md"]),
41
- ("prizmkit-code-review", "Review — Code Review & Behavior Verification",
42
- [".prizmkit/refactor/{slug}/review-report.md"]),
43
- ("prizmkit-committer", "Commit",
44
- ["--headless"]),
45
- ("refactor-report", "Generate Refactor Report",
46
- [".prizmkit/refactor/{slug}/refactor-report.md"]),
47
- ]
48
-
49
-
50
- def generate_refactor_checkpoint(refactor_id, session_id):
51
- """Generate a checkpoint definition for refactor pipeline.
52
-
53
- Returns a dict suitable for writing as workflow-checkpoint.json.
54
- """
55
- steps = []
56
- prev_id = None
57
- for i, (skill, name, artifacts) in enumerate(REFACTOR_STEPS, 1):
58
- step_id = "S{:02d}".format(i)
59
- steps.append({
60
- "id": step_id,
61
- "skill": skill,
62
- "name": name,
63
- "status": "pending",
64
- "required_artifacts": [a.replace("{slug}", refactor_id) for a in artifacts],
65
- "depends_on": prev_id,
66
- })
67
- prev_id = step_id
68
-
69
- return {
70
- "version": 1,
71
- "workflow_type": "refactor-pipeline",
72
- "pipeline_mode": "standard",
73
- "item_id": refactor_id,
74
- "item_slug": refactor_id,
75
- "session_id": session_id,
76
- "steps": steps,
77
- }
78
-
79
-
80
- def merge_refactor_checkpoint_state(existing, fresh, project_root):
81
- """Merge existing refactor checkpoint state into fresh definition.
82
-
83
- Same logic as feature/bugfix pipelines: validate artifacts, break chain
84
- on first invalid step.
85
- """
86
- existing_status = {}
87
- existing_artifacts = {}
88
- for step in existing.get("steps", []):
89
- existing_status[step["skill"]] = step["status"]
90
- existing_artifacts[step["skill"]] = step.get("required_artifacts", [])
91
-
92
- valid_completed = set()
93
- for skill_key, status in existing_status.items():
94
- if status == "completed":
95
- artifacts = existing_artifacts.get(skill_key, [])
96
- if all(os.path.exists(os.path.join(project_root, a))
97
- for a in artifacts):
98
- valid_completed.add(skill_key)
99
- else:
100
- LOGGER.warning(
101
- "Step '%s' was completed but artifacts missing — "
102
- "resetting to pending", skill_key
103
- )
104
- elif status == "skipped":
105
- valid_completed.add(skill_key)
106
-
107
- chain_broken = False
108
- for step in fresh["steps"]:
109
- if chain_broken:
110
- step["status"] = "pending"
111
- continue
112
- prev = existing_status.get(step["skill"])
113
- if step["skill"] in valid_completed:
114
- step["status"] = prev
115
- else:
116
- chain_broken = True
117
- step["status"] = "pending"
118
-
119
- return fresh
35
+ REFACTOR_SECTION_TO_SKILL = {
36
+ "refactor-phase-init": ("prizmkit-init", "Initialize", [".prizmkit/refactor/{slug}"]),
37
+ "refactor-phase-plan": (
38
+ "prizmkit-plan", "Plan — Specification & Plan Generation",
39
+ [".prizmkit/refactor/{slug}/spec.md", ".prizmkit/refactor/{slug}/plan.md"],
40
+ ),
41
+ "refactor-phase-critic": ("critic-plan-review", "Critic: Behavior Preservation Review", []),
42
+ "refactor-phase-implement": (
43
+ "prizmkit-implement", "Implement — Behavior-Preserving Refactoring",
44
+ [".prizmkit/refactor/{slug}/plan.md"],
45
+ ),
46
+ "refactor-phase-review": (
47
+ "prizmkit-code-review", "Review — Code Review & Behavior Verification",
48
+ [".prizmkit/refactor/{slug}/review-report.md"],
49
+ ),
50
+ "phase-browser": ("browser-verification", "Browser Verification", []),
51
+ "refactor-phase-commit-report": None,
52
+ }
53
+
54
+ REFACTOR_SPLIT_STEPS = {
55
+ "refactor-phase-commit-report": [
56
+ ("prizmkit-retrospective", "Retrospective", []),
57
+ ("prizmkit-committer", "Commit", []),
58
+ ("refactor-report", "Generate Refactor Report", [".prizmkit/refactor/{slug}/refactor-report.md"]),
59
+ ("completion-summary", "Completion Summary", [".prizmkit/refactor/{slug}/completion-summary.json"]),
60
+ ],
61
+ }
120
62
 
121
63
 
122
64
  def parse_args():
123
65
  parser = argparse.ArgumentParser(
124
66
  description=(
125
- "Generate a session-specific refactor bootstrap prompt from a template "
126
- "and .prizmkit/plans/refactor-list.json."
67
+ "Generate a session-specific refactor bootstrap prompt from "
68
+ ".prizmkit/plans/refactor-list.json."
127
69
  )
128
70
  )
129
71
  parser.add_argument("--refactor-list", required=True, help="Path to .prizmkit/plans/refactor-list.json")
@@ -135,27 +77,13 @@ def parse_args():
135
77
  parser.add_argument("--state-dir", default=None, help="State directory (default: .prizmkit/state/refactor)")
136
78
  parser.add_argument("--output", required=True, help="Output path for the rendered prompt")
137
79
  parser.add_argument("--project-root", default=None, help="AI-visible project root used for rendered prompt paths")
138
- parser.add_argument("--template", default=None, help="Custom template path. Defaults to {script_dir}/../templates/refactor-bootstrap-prompt.md")
139
- parser.add_argument("--mode", default=None, help="Pipeline execution mode override: lite, standard, full")
140
- parser.add_argument("--critic", default=None, help="Enable critic agent: true/false")
80
+ parser.add_argument("--template", default=None, help="Custom legacy template path. Defaults to section assembly.")
81
+ parser.add_argument("--mode", choices=["lite", "standard", "full"], default=None, help="Pipeline execution mode override")
82
+ parser.add_argument("--critic", choices=["true", "false"], default=None, help="Enable critic agent: true/false")
141
83
  add_continuation_args(parser, "refactor")
142
-
143
84
  return parser.parse_args()
144
85
 
145
86
 
146
- def read_text_file(path):
147
- """Read and return the text content of a file."""
148
- abs_path = os.path.abspath(path)
149
- if not os.path.isfile(abs_path):
150
- return None, "File not found: {}".format(abs_path)
151
- try:
152
- with open(abs_path, "r", encoding="utf-8") as f:
153
- return f.read(), None
154
- except IOError as e:
155
- return None, "Cannot read file: {}".format(str(e))
156
-
157
-
158
-
159
87
  def _parse_positive_int_env(name, default):
160
88
  """Parse a positive integer environment variable."""
161
89
  value = os.environ.get(name, str(default))
@@ -177,38 +105,29 @@ def find_refactor(refactors, refactor_id):
177
105
 
178
106
 
179
107
  def format_acceptance_criteria(criteria):
180
- """Format acceptance criteria as a markdown bullet list."""
181
108
  if not criteria:
182
109
  return "- (none specified)"
183
- lines = []
184
- for item in criteria:
185
- lines.append("- {}".format(item))
186
- return "\n".join(lines)
110
+ return "\n".join("- {}".format(item) for item in criteria)
187
111
 
188
112
 
189
- def format_global_context(global_context, project_root=None):
190
- """Format global_context dict as a key-value list.
113
+ def format_ac_checklist(criteria):
114
+ if not criteria:
115
+ return "- [ ] (no acceptance criteria specified)"
116
+ return "\n".join("- [ ] {}".format(item) for item in criteria)
117
+
191
118
 
192
- If global_context is empty/sparse and project_root is provided,
193
- auto-detect tech stack from project files to fill gaps.
194
- """
119
+ def format_global_context(global_context, project_root=None):
195
120
  if project_root:
196
121
  enrich_global_context(global_context, project_root)
197
-
198
122
  if not global_context:
199
123
  return "- (none specified)"
200
- lines = []
201
- for key, value in sorted(global_context.items()):
202
- lines.append("- **{}**: {}".format(key, value))
203
- return "\n".join(lines)
124
+ return "\n".join(
125
+ "- **{}**: {}".format(key, value)
126
+ for key, value in sorted(global_context.items())
127
+ )
204
128
 
205
129
 
206
130
  def format_user_context(user_context):
207
- """Format user_context array as a markdown section.
208
-
209
- Returns empty string if user_context is empty or absent,
210
- so the template placeholder resolves to nothing.
211
- """
212
131
  if not user_context or not isinstance(user_context, list):
213
132
  return ""
214
133
  items = [item for item in user_context if isinstance(item, str) and item.strip()]
@@ -217,40 +136,14 @@ def format_user_context(user_context):
217
136
  lines = [
218
137
  "### User-Provided Context (HIGHEST PRIORITY)",
219
138
  "",
220
- "> The following materials were provided by the user. "
221
- "They take precedence over AI inference.",
139
+ "> The following materials were provided by the user. They take precedence over AI inference.",
222
140
  "",
223
141
  ]
224
- for item in items:
225
- lines.append("- {}".format(item))
226
- return "\n".join(lines)
227
-
228
-
229
- def format_scope(scope):
230
- """Format scope object into markdown detail lines."""
231
- if not scope or not isinstance(scope, dict):
232
- return "- (no scope details)"
233
- lines = []
234
-
235
- files = scope.get("files", [])
236
- if files:
237
- lines.append("- **Files**:")
238
- for f in files:
239
- lines.append(" - `{}`".format(f))
240
-
241
- modules = scope.get("modules", [])
242
- if modules:
243
- lines.append("- **Modules**:")
244
- for m in modules:
245
- lines.append(" - `{}`".format(m))
246
-
247
- if not lines:
248
- lines.append("- (no scope details)")
142
+ lines.extend("- {}".format(item) for item in items)
249
143
  return "\n".join(lines)
250
144
 
251
145
 
252
146
  def _format_scope_list(scope, key):
253
- """Extract and format a list from scope by key (files or modules)."""
254
147
  if not scope or not isinstance(scope, dict):
255
148
  return "- (none specified)"
256
149
  items = scope.get(key, [])
@@ -260,74 +153,47 @@ def _format_scope_list(scope, key):
260
153
 
261
154
 
262
155
  def format_scope_files(scope):
263
- """Extract and format just the files list from scope."""
264
156
  return _format_scope_list(scope, "files")
265
157
 
266
158
 
267
159
  def format_scope_modules(scope):
268
- """Extract and format just the modules list from scope."""
269
160
  return _format_scope_list(scope, "modules")
270
161
 
271
162
 
272
163
  def format_behavior_preservation(bp):
273
- """Format behavior_preservation object into markdown detail lines."""
274
164
  if not bp or not isinstance(bp, dict):
275
165
  return "- (no behavior preservation details)"
276
- lines = []
277
-
278
- strategy = bp.get("strategy", "unknown")
279
- lines.append("- **Strategy**: {}".format(strategy))
280
-
166
+ lines = ["- **Strategy**: {}".format(bp.get("strategy", "unknown"))]
281
167
  existing_tests = bp.get("existing_tests", [])
282
168
  if existing_tests:
283
169
  lines.append("- **Existing Tests**:")
284
- for t in existing_tests:
285
- lines.append(" - `{}`".format(t))
286
-
170
+ lines.extend(" - `{}`".format(test) for test in existing_tests)
287
171
  new_tests_needed = bp.get("new_tests_needed", [])
288
172
  if new_tests_needed:
289
173
  lines.append("- **New Tests Needed**:")
290
- for t in new_tests_needed:
291
- lines.append(" - {}".format(t))
292
-
174
+ lines.extend(" - {}".format(test) for test in new_tests_needed)
293
175
  if len(lines) == 1:
294
176
  lines.append("- (no additional details)")
295
177
  return "\n".join(lines)
296
178
 
297
179
 
298
180
  def format_dependencies(dependencies, refactors=None):
299
- """Format dependencies list as a markdown bullet list with completion context.
300
-
301
- When refactors list is provided, look up completed dependencies and include
302
- their completion_notes for rich context propagation.
303
- """
304
181
  if not dependencies or not isinstance(dependencies, list):
305
182
  return "- (none)"
306
- if len(dependencies) == 0:
307
- return "- (none)"
308
-
309
- # Build lookup map if refactors list is provided
310
183
  refactor_map = {}
311
184
  if refactors:
312
- for r in refactors:
313
- if isinstance(r, dict) and "id" in r:
314
- refactor_map[r["id"]] = r
315
-
185
+ for item in refactors:
186
+ if isinstance(item, dict) and "id" in item:
187
+ refactor_map[item["id"]] = item
316
188
  lines = []
317
189
  for dep in dependencies:
318
190
  dep_info = refactor_map.get(dep)
319
191
  if dep_info and dep_info.get("status") == "completed":
320
- header = "- **{}** — {} (completed)".format(
321
- dep, dep_info.get("title", "Untitled")
322
- )
192
+ header = "- **{}** — {} (completed)".format(dep, dep_info.get("title", "Untitled"))
323
193
  notes = dep_info.get("completion_notes", [])
324
- if notes and isinstance(notes, list):
325
- note_lines = [
326
- " - {}".format(n) for n in notes
327
- if isinstance(n, str) and n.strip()
328
- ]
329
- if note_lines:
330
- header += "\n" + "\n".join(note_lines)
194
+ note_lines = [" - {}".format(n) for n in notes if isinstance(n, str) and n.strip()]
195
+ if note_lines:
196
+ header += "\n" + "\n".join(note_lines)
331
197
  lines.append(header)
332
198
  else:
333
199
  lines.append("- `{}`".format(dep))
@@ -335,48 +201,35 @@ def format_dependencies(dependencies, refactors=None):
335
201
 
336
202
 
337
203
  def get_prev_session_status(state_dir, refactor_id):
338
- """Read previous session status from state dir if available."""
339
204
  if not state_dir:
340
205
  return "N/A (first run)"
341
-
342
206
  refactor_status_path = os.path.join(state_dir, refactor_id, "status.json")
343
207
  try:
344
208
  with open(refactor_status_path, "r", encoding="utf-8") as f:
345
209
  refactor_status = json.load(f)
346
210
  except (json.JSONDecodeError, IOError, OSError):
347
211
  return "N/A (first run)"
348
-
349
212
  last_session_id = refactor_status.get("last_session_id")
350
213
  if not last_session_id:
351
214
  return "N/A (first run)"
352
-
353
- session_status_path = os.path.join(state_dir, refactor_id, "sessions",
354
- last_session_id, "session-status.json"
355
- )
215
+ session_status_path = os.path.join(state_dir, refactor_id, "sessions", last_session_id, "session-status.json")
356
216
  try:
357
217
  with open(session_status_path, "r", encoding="utf-8") as f:
358
218
  session_data = json.load(f)
359
219
  except (json.JSONDecodeError, IOError, OSError):
360
220
  return "N/A (previous session status file not found)"
361
-
362
- status = session_data.get("status", "unknown")
363
- checkpoint = session_data.get("checkpoint_reached", "none")
364
- current_phase = session_data.get("current_phase", "unknown")
365
- errors = session_data.get("errors", [])
366
-
367
221
  result = "{} (checkpoint: {}, last phase: {})".format(
368
- status, checkpoint, current_phase
222
+ session_data.get("status", "unknown"),
223
+ session_data.get("checkpoint_reached", "none"),
224
+ session_data.get("current_phase", "unknown"),
369
225
  )
226
+ errors = session_data.get("errors", [])
370
227
  if errors:
371
228
  result += " — errors: {}".format("; ".join(str(e) for e in errors))
372
229
  return result
373
230
 
374
231
 
375
232
  def resolve_project_root(script_dir):
376
- """Resolve project root. Layout-aware:
377
- <project>/.prizmkit/dev-pipeline/scripts/ → <project>
378
- <repo>/dev-pipeline/scripts/ → <repo>
379
- """
380
233
  pipeline_dir = os.path.dirname(script_dir)
381
234
  pipeline_parent = os.path.dirname(pipeline_dir)
382
235
  if os.path.basename(pipeline_parent) == ".prizmkit":
@@ -385,7 +238,6 @@ def resolve_project_root(script_dir):
385
238
 
386
239
 
387
240
  def _project_root_from_prizmkit_path(path):
388
- """Infer project root from a path nested under .prizmkit/."""
389
241
  if not path:
390
242
  return ""
391
243
  current = os.path.abspath(path)
@@ -399,7 +251,6 @@ def _project_root_from_prizmkit_path(path):
399
251
 
400
252
 
401
253
  def _args_project_root(args, script_dir):
402
- """Return explicit AI-visible project root, plan-path root, or script-layout default."""
403
254
  explicit = getattr(args, "project_root", None)
404
255
  if explicit:
405
256
  return os.path.abspath(explicit)
@@ -409,11 +260,20 @@ def _args_project_root(args, script_dir):
409
260
  return resolve_project_root(script_dir)
410
261
 
411
262
 
263
+ def determine_refactor_mode(args, refactor):
264
+ if args.mode:
265
+ return args.mode
266
+ priority = str(refactor.get("priority", "medium")).lower()
267
+ complexity = str(refactor.get("complexity", "medium")).lower()
268
+ if priority == "critical" and complexity in ("high", "critical"):
269
+ return "full"
270
+ if complexity in ("high", "critical"):
271
+ return "standard"
272
+ return normalize_mode({"low": "lite", "medium": "lite"}.get(complexity, "lite"))
273
+
274
+
412
275
  def build_replacements(args, refactor, refactors, global_context, script_dir):
413
- """Build the full dict of placeholder -> replacement value."""
414
276
  project_root = _args_project_root(args, script_dir)
415
-
416
- # Platform-aware agent/team path resolution
417
277
  platform = resolve_prompt_platform(project_root)
418
278
  home_dir = os.path.expanduser("~")
419
279
 
@@ -425,30 +285,21 @@ def build_replacements(args, refactor, refactors, global_context, script_dir):
425
285
  team_config_path = os.path.join(project_root, ".codex", "team-info.json")
426
286
  else:
427
287
  agents_dir = os.path.join(project_root, ".codebuddy", "agents")
428
- team_config_path = os.path.join(
429
- home_dir, ".codebuddy", "teams", "prizm-dev-team", "config.json"
430
- )
288
+ team_config_path = os.path.join(home_dir, ".codebuddy", "teams", "prizm-dev-team", "config.json")
431
289
 
432
290
  agent_ext = ".toml" if platform == "codex" else ".md"
433
- dev_subagent = os.path.join(agents_dir, f"prizm-dev-team-dev{agent_ext}")
434
- reviewer_subagent = os.path.join(agents_dir, f"prizm-dev-team-reviewer{agent_ext}")
291
+ dev_subagent = os.path.join(agents_dir, "prizm-dev-team-dev{}".format(agent_ext))
292
+ reviewer_subagent = os.path.join(agents_dir, "prizm-dev-team-reviewer{}".format(agent_ext))
293
+ critic_subagent = os.path.join(agents_dir, "prizm-dev-team-critic{}".format(agent_ext))
435
294
 
436
- # Session status is controlled by the main runner state dir, even when
437
- # rendered project paths are AI-visible worktree paths.
438
295
  state_dir = (
439
296
  os.path.abspath(args.state_dir)
440
297
  if args.state_dir
441
298
  else os.path.join(project_root, ".prizmkit", "state", "refactor")
442
299
  )
443
- session_status_path = os.path.join(
444
- state_dir, args.refactor_id, "sessions", args.session_id,
445
- "session-status.json",
446
- )
300
+ session_status_path = os.path.join(state_dir, args.refactor_id, "sessions", args.session_id, "session-status.json")
447
301
 
448
- # Scope
449
302
  scope = refactor.get("scope", {})
450
-
451
- # Behavior preservation
452
303
  bp = refactor.get("behavior_preservation", {})
453
304
  behavior_strategy = bp.get("strategy", "test-gate") if isinstance(bp, dict) else "test-gate"
454
305
  existing_tests = bp.get("existing_tests", []) if isinstance(bp, dict) else []
@@ -457,291 +308,246 @@ def build_replacements(args, refactor, refactors, global_context, script_dir):
457
308
  new_tests_needed = bp.get("new_tests_needed", []) if isinstance(bp, dict) else []
458
309
  if not isinstance(new_tests_needed, list):
459
310
  new_tests_needed = []
460
-
461
- # Format existing tests
462
- if existing_tests:
463
- existing_tests_str = "\n".join("- `{}`".format(t) for t in existing_tests)
464
- else:
465
- existing_tests_str = "- (none specified)"
466
-
467
- # Format new tests needed
468
- if new_tests_needed:
469
- new_tests_str = "\n".join("- {}".format(t) for t in new_tests_needed)
470
- else:
471
- new_tests_str = "- (none specified)"
472
-
473
- # Browser interaction - extract from refactor if present
474
- browser_interaction = refactor.get("browser_interaction")
475
- browser_enabled = False
476
- browser_verify_steps = ""
477
- browser_tool = "auto"
478
-
479
- # Environment override
480
- browser_verify_env = os.environ.get("BROWSER_VERIFY", "").lower()
481
- if browser_verify_env == "false":
482
- browser_interaction = None
483
-
484
- # Extract browser config (same logic as feature and bugfix pipelines)
485
- if browser_interaction and isinstance(browser_interaction, bool):
486
- browser_enabled = True
487
- browser_tool = "auto"
488
- browser_verify_steps = " # (no specific verify goals — validate UI renders correctly and feature still works)"
489
- elif browser_interaction and isinstance(browser_interaction, dict):
490
- browser_tool = browser_interaction.get("tool", "auto")
491
- if browser_tool not in ("playwright-cli", "opencli", "auto"):
492
- LOGGER.warning("Unknown browser_interaction.tool '%s', defaulting to 'auto'", browser_tool)
493
- browser_tool = "auto"
494
-
495
- steps = browser_interaction.get("verify_steps", [])
496
- if steps:
497
- browser_enabled = True
498
- browser_verify_steps = "\n".join(
499
- " # Step {}: {}".format(i + 1, step)
500
- for i, step in enumerate(steps)
501
- )
502
- elif browser_interaction.get("url") or browser_interaction.get("enabled", True):
503
- browser_enabled = True
504
- browser_verify_steps = " # (validate UI renders correctly and feature still works)"
311
+ existing_tests_str = "\n".join("- `{}`".format(test) for test in existing_tests) if existing_tests else "- (none specified)"
312
+ new_tests_str = "\n".join("- {}".format(test) for test in new_tests_needed) if new_tests_needed else "- (none specified)"
313
+
314
+ browser_enabled, browser_tool, browser_verify_steps, browser_blocking = browser_enabled_from_item(refactor)
315
+ dev_port = detect_dev_server_port(project_root)
316
+ dev_url = "http://localhost:{}".format(dev_port) if dev_port.isdigit() else "<UNKNOWN_DETECT_FROM_PROJECT_CONFIG>"
317
+
318
+ pipeline_mode = determine_refactor_mode(args, refactor)
319
+ critic_requested = args.critic == "true" if args.critic is not None else bool(refactor.get("critic", False))
320
+ critic_enabled = critic_requested and pipeline_mode != "lite"
321
+ if critic_requested and pipeline_mode == "lite":
322
+ LOGGER.warning("Critic requested for refactor %s but lite mode skips critic phases", args.refactor_id)
323
+ if critic_enabled and not os.path.isfile(critic_subagent):
324
+ LOGGER.warning("Critic enabled but agent file not found: %s. Critic phase will be skipped.", critic_subagent)
325
+ critic_enabled = False
505
326
 
506
327
  replacements = {
507
- "{{SESSION_LOG_PATH}}": os.path.join(
508
- ".prizmkit", "state", "refactor", args.refactor_id,
509
- "sessions", args.session_id, "logs", "session.log",
510
- ),
328
+ "{{SESSION_LOG_PATH}}": os.path.join(".prizmkit", "state", "refactor", args.refactor_id, "sessions", args.session_id, "logs", "session.log"),
511
329
  "{{RUN_ID}}": args.run_id,
512
330
  "{{SESSION_ID}}": args.session_id,
513
331
  "{{REFACTOR_ID}}": args.refactor_id,
332
+ "{{TASK_ID}}": args.refactor_id,
333
+ "{{TASK_TITLE}}": refactor.get("title", ""),
514
334
  "{{REFACTOR_TITLE}}": refactor.get("title", ""),
515
335
  "{{REFACTOR_TYPE}}": refactor.get("type", "restructure"),
516
336
  "{{SCOPE_FILES}}": format_scope_files(scope),
517
337
  "{{SCOPE_MODULES}}": format_scope_modules(scope),
518
338
  "{{BEHAVIOR_STRATEGY}}": behavior_strategy,
339
+ "{{BEHAVIOR_PRESERVATION}}": format_behavior_preservation(bp),
519
340
  "{{EXISTING_TESTS}}": existing_tests_str,
520
341
  "{{NEW_TESTS_NEEDED}}": new_tests_str,
521
342
  "{{PRIORITY}}": refactor.get("priority", "medium"),
522
343
  "{{COMPLEXITY}}": refactor.get("complexity", "medium"),
344
+ "{{PIPELINE_MODE}}": pipeline_mode,
345
+ "{{CRITIC_ENABLED}}": "true" if critic_enabled else "false",
346
+ "{{CRITIC_COUNT}}": str(refactor.get("critic_count", 1)),
523
347
  "{{REFACTOR_DESCRIPTION}}": refactor.get("description", ""),
524
348
  "{{USER_CONTEXT}}": format_user_context(refactor.get("user_context", [])),
525
- "{{ACCEPTANCE_CRITERIA}}": format_acceptance_criteria(
526
- refactor.get("acceptance_criteria", [])
527
- ),
528
- "{{DEPENDENCIES}}": format_dependencies(
529
- refactor.get("dependencies", []), refactors
530
- ),
349
+ "{{ACCEPTANCE_CRITERIA}}": format_acceptance_criteria(refactor.get("acceptance_criteria", [])),
350
+ "{{AC_CHECKLIST}}": format_ac_checklist(refactor.get("acceptance_criteria", [])),
351
+ "{{DEPENDENCIES}}": format_dependencies(refactor.get("dependencies", []), refactors),
531
352
  "{{GLOBAL_CONTEXT}}": format_global_context(global_context, project_root),
532
353
  "{{TEAM_CONFIG_PATH}}": team_config_path,
533
354
  "{{DEV_SUBAGENT_PATH}}": dev_subagent,
534
355
  "{{REVIEWER_SUBAGENT_PATH}}": reviewer_subagent,
356
+ "{{CRITIC_SUBAGENT_PATH}}": critic_subagent,
535
357
  "{{SESSION_STATUS_PATH}}": session_status_path,
536
358
  "{{PROJECT_ROOT}}": project_root,
537
- "{{CHECKPOINT_PATH}}": os.path.join(
538
- ".prizmkit", "refactor", args.refactor_id, "workflow-checkpoint.json",
539
- ),
359
+ "{{ARTIFACT_DIR}}": ".prizmkit/refactor/{}".format(args.refactor_id),
360
+ "{{CHECKPOINT_PATH}}": os.path.join(".prizmkit", "refactor", args.refactor_id, "workflow-checkpoint.json"),
540
361
  "{{CHECKPOINT_SYSTEM}}": read_checkpoint_system_section(script_dir),
541
- "{{TIMESTAMP}}": "", # Placeholder — agent fills in timestamp
362
+ "{{TIMESTAMP}}": "",
542
363
  "{{BROWSER_ENABLED}}": "true" if browser_enabled else "false",
543
364
  "{{BROWSER_TOOL}}": browser_tool,
544
365
  "{{BROWSER_VERIFY_STEPS}}": browser_verify_steps,
366
+ "{{BROWSER_BLOCKING}}": "true" if browser_blocking else "false",
367
+ "{{DEV_PORT}}": dev_port,
368
+ "{{DEV_URL}}": dev_url,
545
369
  }
546
-
547
370
  replacements.update(helper_replacements(script_dir))
548
-
549
371
  return replacements
550
372
 
551
373
 
552
374
  def process_conditional_blocks(content, resume_phase, refactor):
553
- """Handle conditional blocks based on resume_phase and browser_interaction."""
554
- # Handle fresh start blocks
375
+ """Handle legacy conditional blocks based on resume_phase and browser_interaction."""
555
376
  is_resume = resume_phase != "null"
556
-
557
377
  if is_resume:
558
- content = re.sub(
559
- r"\{\{IF_FRESH_START\}\}.*?\{\{END_IF_FRESH_START\}\}\n?",
560
- "", content, flags=re.DOTALL,
561
- )
378
+ content = re.sub(r"\{\{IF_FRESH_START\}\}.*?\{\{END_IF_FRESH_START\}\}\n?", "", content, flags=re.DOTALL)
562
379
  else:
563
- content = content.replace("{{IF_FRESH_START}}\n", "")
564
- content = content.replace("{{IF_FRESH_START}}", "")
565
- content = content.replace("{{END_IF_FRESH_START}}\n", "")
566
- content = content.replace("{{END_IF_FRESH_START}}", "")
567
-
568
- # Handle browser interaction blocks
569
- browser_interaction = refactor.get("browser_interaction")
570
- browser_enabled = False
571
- browser_tool = "auto"
572
-
573
- # Check environment override
574
- browser_verify_env = os.environ.get("BROWSER_VERIFY", "").lower()
575
- if browser_verify_env == "false":
576
- browser_interaction = None
577
-
578
- # Determine if browser is enabled
579
- if browser_interaction:
580
- if isinstance(browser_interaction, bool):
581
- browser_enabled = True
582
- elif isinstance(browser_interaction, dict):
583
- steps = browser_interaction.get("verify_steps", [])
584
- if steps or browser_interaction.get("url") or browser_interaction.get("enabled", True):
585
- browser_enabled = True
586
- browser_tool = browser_interaction.get("tool", "auto")
587
-
588
- # Process browser interaction blocks
380
+ content = content.replace("{{IF_FRESH_START}}\n", "").replace("{{IF_FRESH_START}}", "")
381
+ content = content.replace("{{END_IF_FRESH_START}}\n", "").replace("{{END_IF_FRESH_START}}", "")
382
+
383
+ browser_enabled, browser_tool, _steps, _blocking = browser_enabled_from_item(refactor)
589
384
  browser_open = "{{IF_BROWSER_INTERACTION}}"
590
385
  browser_close = "{{END_IF_BROWSER_INTERACTION}}"
591
-
592
386
  if browser_enabled:
593
- # Keep content, remove tags
594
- content = content.replace(browser_open + "\n", "")
595
- content = content.replace(browser_open, "")
596
- content = content.replace(browser_close + "\n", "")
597
- content = content.replace(browser_close, "")
387
+ content = content.replace(browser_open + "\n", "").replace(browser_open, "")
388
+ content = content.replace(browser_close + "\n", "").replace(browser_close, "")
598
389
  else:
599
- # Remove entire block
600
- pattern = re.escape(browser_open) + r".*?" + re.escape(browser_close) + r"\n?"
601
- content = re.sub(pattern, "", content, flags=re.DOTALL)
602
-
603
- # Process browser tool selection blocks (nested inside browser interaction)
604
- tool_variants = ["PLAYWRIGHT", "OPENCLI", "AUTO"]
605
- active_variant = {
606
- "playwright-cli": "PLAYWRIGHT",
607
- "opencli": "OPENCLI",
608
- "auto": "AUTO",
609
- }.get(browser_tool, "AUTO")
610
-
611
- for variant in tool_variants:
390
+ content = re.sub(re.escape(browser_open) + r".*?" + re.escape(browser_close) + r"\n?", "", content, flags=re.DOTALL)
391
+
392
+ active_variant = {"playwright-cli": "PLAYWRIGHT", "opencli": "OPENCLI", "auto": "AUTO"}.get(browser_tool, "AUTO")
393
+ for variant in ("PLAYWRIGHT", "OPENCLI", "AUTO"):
612
394
  tool_open = "{{{{IF_BROWSER_TOOL_{}}}}}".format(variant)
613
395
  tool_close = "{{{{END_IF_BROWSER_TOOL_{}}}}}".format(variant)
614
-
615
396
  if variant == active_variant and browser_enabled:
616
- # Keep content, remove tags
617
- content = content.replace(tool_open + "\n", "")
618
- content = content.replace(tool_open, "")
619
- content = content.replace(tool_close + "\n", "")
620
- content = content.replace(tool_close, "")
397
+ content = content.replace(tool_open + "\n", "").replace(tool_open, "")
398
+ content = content.replace(tool_close + "\n", "").replace(tool_close, "")
621
399
  else:
622
- # Remove entire block
623
- pat = re.escape(tool_open) + r".*?" + re.escape(tool_close) + r"\n?"
624
- content = re.sub(pat, "", content, flags=re.DOTALL)
625
-
400
+ content = re.sub(re.escape(tool_open) + r".*?" + re.escape(tool_close) + r"\n?", "", content, flags=re.DOTALL)
626
401
  return content
627
402
 
628
403
 
629
-
630
404
  def render_template(template_content, replacements, resume_phase, refactor):
631
- """Render the template by processing conditionals and replacing placeholders."""
632
- # Step 1: Process conditional blocks
633
- content = process_conditional_blocks(template_content, resume_phase, refactor)
634
-
635
- # Step 2: Replace all {{PLACEHOLDER}} variables (two passes for nested sections)
636
- for _pass in range(2):
637
- for placeholder, value in replacements.items():
638
- content = content.replace(placeholder, value)
639
-
640
- return content
405
+ return replace_placeholders(process_conditional_blocks(template_content, resume_phase, refactor), replacements)
406
+
407
+
408
+ def _refactor_header(pipeline_mode):
409
+ title = "# Dev-Pipeline Refactor Session Bootstrap {}\n".format(pipeline_mode.title())
410
+ desc = {
411
+ "lite": "**Tier 1 Single Agent**: direct behavior-preserving refactor by the main orchestrator.",
412
+ "standard": "**Tier 2 — Orchestrator + optional Critic**: direct refactor with review gates.",
413
+ "full": "**Tier 3 — Full Refactor Guardrails**: direct refactor with stronger behavior-preservation evidence.",
414
+ }.get(pipeline_mode, "**Refactor pipeline**")
415
+ return title + "\n" + desc + "\n"
416
+
417
+
418
+ def assemble_refactor_sections(pipeline_mode, sections_dir, critic_enabled, browser_enabled):
419
+ sections = [
420
+ ("header", _refactor_header(pipeline_mode)),
421
+ ("refactor-session-context", load_section(sections_dir, "refactor-session-context.md")),
422
+ ("mission", load_section(sections_dir, "refactor-mission.md")),
423
+ ("refactor-task-contract", load_section(sections_dir, "refactor-task-contract.md")),
424
+ ("context-budget-rules", load_section(sections_dir, "context-budget-rules.md")),
425
+ ("refactor-artifacts", load_section(sections_dir, "refactor-artifacts.md")),
426
+ ("checkpoint-system", load_section(sections_dir, "checkpoint-system.md")),
427
+ ("execution-header", "---\n\n## Execution\n"),
428
+ ("refactor-phase-init", load_section(sections_dir, "refactor-phase-init.md")),
429
+ ("refactor-phase-plan", load_section(sections_dir, "refactor-phase-plan.md")),
430
+ ]
431
+ if critic_enabled:
432
+ sections.append(("refactor-phase-critic", load_section(sections_dir, "refactor-phase-critic.md")))
433
+ sections.extend([
434
+ ("refactor-phase-implement", load_section(sections_dir, "refactor-phase-implement.md")),
435
+ ("refactor-phase-review", load_section(sections_dir, "refactor-phase-review.md")),
436
+ ])
437
+ if browser_enabled:
438
+ sections.append(("phase-browser", load_section(sections_dir, "phase-browser-verification-auto.md")))
439
+ sections.extend([
440
+ ("refactor-phase-commit-report", load_section(sections_dir, "refactor-phase-commit-report.md")),
441
+ ("refactor-session-status", load_section(sections_dir, "refactor-session-status.md")),
442
+ ("refactor-critical-paths", load_section(sections_dir, "refactor-critical-paths.md")),
443
+ ("failure-capture", load_section(sections_dir, "failure-capture.md")),
444
+ ("refactor-reminders", load_section(sections_dir, "refactor-reminders.md")),
445
+ ])
446
+ return sections
447
+
448
+
449
+ def generate_refactor_checkpoint(refactor_id, session_id, pipeline_mode="standard", sections=None):
450
+ if sections is None:
451
+ sections = [
452
+ ("refactor-phase-init", ""),
453
+ ("refactor-phase-plan", ""),
454
+ ("refactor-phase-implement", ""),
455
+ ("refactor-phase-review", ""),
456
+ ("refactor-phase-commit-report", ""),
457
+ ]
458
+ return generate_checkpoint_from_sections(
459
+ sections,
460
+ REFACTOR_SECTION_TO_SKILL,
461
+ pipeline_mode,
462
+ "refactor-pipeline",
463
+ refactor_id,
464
+ refactor_id,
465
+ session_id,
466
+ split_steps=REFACTOR_SPLIT_STEPS,
467
+ )
641
468
 
642
469
 
643
- def write_output(output_path, content):
644
- """Write the rendered content to the output file."""
645
- abs_path = os.path.abspath(output_path)
646
- output_dir = os.path.dirname(abs_path)
647
- if output_dir and not os.path.isdir(output_dir):
648
- try:
649
- os.makedirs(output_dir, exist_ok=True)
650
- except OSError as e:
651
- return "Cannot create output directory: {}".format(str(e))
652
- try:
653
- with open(abs_path, "w", encoding="utf-8") as f:
654
- f.write(content)
655
- except IOError as e:
656
- return "Cannot write output file: {}".format(str(e))
657
- return None
470
+ def merge_refactor_checkpoint_state(existing, fresh, project_root):
471
+ return merge_checkpoint_state(existing, fresh, project_root, logger=LOGGER)
658
472
 
659
473
 
660
474
  def emit_failure(message):
661
- """Emit standardized failure JSON and exit.
662
-
663
- Uses a different format than error_out() — includes 'success: false'
664
- for compatibility with the pipeline's JSON parsing expectations.
665
- """
666
475
  print(json.dumps({"success": False, "error": message}, indent=2, ensure_ascii=False))
667
476
  sys.exit(1)
668
477
 
669
478
 
670
479
  def main():
671
480
  args = parse_args()
672
-
673
- # Resolve script directory
674
481
  script_dir = os.path.dirname(os.path.abspath(__file__))
482
+ templates_dir = os.path.join(script_dir, "..", "templates")
483
+ sections_dir = os.path.join(templates_dir, "sections")
675
484
 
676
- # Resolve template path
677
- if args.template:
678
- template_path = args.template
679
- else:
680
- template_path = os.path.join(
681
- script_dir, "..", "templates", "refactor-bootstrap-prompt.md"
682
- )
683
-
684
- # Load template
685
- template_content, err = read_text_file(template_path)
686
- if err:
687
- emit_failure("Template error: {}".format(err))
688
-
689
- # Load refactor list
690
485
  refactor_list_data, err = load_json_file(args.refactor_list)
691
486
  if err:
692
487
  emit_failure("Refactor list error: {}".format(err))
693
-
694
- # Extract refactors array
695
488
  refactors = refactor_list_data.get("refactors")
696
489
  if not isinstance(refactors, list):
697
490
  emit_failure("Refactor list does not contain a 'refactors' array")
698
-
699
- # Find the target refactor
700
491
  refactor = find_refactor(refactors, args.refactor_id)
701
492
  if refactor is None:
702
493
  emit_failure("Refactor '{}' not found in refactor list".format(args.refactor_id))
703
-
704
- # Extract global context
705
494
  global_context = refactor_list_data.get("global_context", {})
706
495
  if not isinstance(global_context, dict):
707
496
  global_context = {}
708
497
 
709
- # Build replacements
710
498
  replacements = build_replacements(args, refactor, refactors, global_context, script_dir)
711
- # Render the template
712
- rendered = render_template(template_content, replacements, args.resume_phase, refactor)
499
+ pipeline_mode = replacements["{{PIPELINE_MODE}}"]
500
+ critic_enabled = replacements["{{CRITIC_ENABLED}}"] == "true"
501
+ browser_enabled = replacements["{{BROWSER_ENABLED}}"] == "true"
502
+
503
+ use_sections = os.path.isdir(sections_dir) and not args.template
504
+ sections = []
505
+ if use_sections:
506
+ try:
507
+ sections = assemble_refactor_sections(pipeline_mode, sections_dir, critic_enabled, browser_enabled)
508
+ rendered = render_from_sections(sections, replacements)
509
+ except FileNotFoundError as exc:
510
+ LOGGER.warning("Refactor section assembly failed (%s), falling back to legacy template", exc)
511
+ use_sections = False
512
+
513
+ if not use_sections:
514
+ template_path = args.template or os.path.join(templates_dir, "refactor-bootstrap-prompt.md")
515
+ template_content, err = read_text_file(template_path)
516
+ if err:
517
+ emit_failure("Template error: {}".format(err))
518
+ rendered = render_template(template_content, replacements, args.resume_phase, refactor)
519
+
520
+ is_valid, warnings, errors = validate_rendered(
521
+ rendered,
522
+ required_markers=[
523
+ ("## Your Mission", "Mission section"),
524
+ ("## Execution", "Execution section"),
525
+ ("## Failure Capture", "Failure Capture Protocol"),
526
+ ],
527
+ logger=LOGGER,
528
+ )
529
+ if not is_valid:
530
+ LOGGER.error("Rendered refactor prompt failed validation: %s", "; ".join(errors))
713
531
 
714
- # Write the output
715
532
  err = write_output(args.output, rendered)
716
533
  if err:
717
534
  emit_failure(err)
718
535
 
719
- # Generate checkpoint file
720
536
  project_root = _args_project_root(args, script_dir)
721
- checkpoint_rel = os.path.join(
722
- ".prizmkit", "refactor", args.refactor_id, "workflow-checkpoint.json",
537
+ checkpoint_path = os.path.join(project_root, replacements["{{CHECKPOINT_PATH}}"])
538
+ os.makedirs(os.path.dirname(checkpoint_path), exist_ok=True)
539
+ checkpoint = generate_refactor_checkpoint(
540
+ args.refactor_id, args.session_id, pipeline_mode, sections if use_sections else None,
723
541
  )
724
- checkpoint_path = os.path.join(project_root, checkpoint_rel)
725
- checkpoint_dir = os.path.dirname(checkpoint_path)
726
- os.makedirs(checkpoint_dir, exist_ok=True)
727
-
728
- checkpoint = generate_refactor_checkpoint(args.refactor_id, args.session_id)
729
542
 
730
- is_resume = args.resume_phase != "null"
731
- if (is_resume or is_continuation(args)) and os.path.exists(checkpoint_path):
543
+ if (args.resume_phase != "null" or is_continuation(args)) and os.path.exists(checkpoint_path):
732
544
  try:
733
545
  with open(checkpoint_path, "r", encoding="utf-8") as f:
734
546
  existing = json.load(f)
735
- checkpoint = merge_refactor_checkpoint_state(
736
- existing, checkpoint, project_root,
737
- )
738
- LOGGER.info("Merged existing refactor checkpoint from %s",
739
- checkpoint_path)
547
+ checkpoint = merge_refactor_checkpoint_state(existing, checkpoint, project_root)
548
+ LOGGER.info("Merged existing refactor checkpoint from %s", checkpoint_path)
740
549
  except (json.JSONDecodeError, KeyError) as exc:
741
- LOGGER.warning(
742
- "Existing refactor checkpoint corrupted (%s) — generating fresh",
743
- exc,
744
- )
550
+ LOGGER.warning("Existing refactor checkpoint corrupted (%s) — generating fresh", exc)
745
551
 
746
552
  with open(checkpoint_path, "w", encoding="utf-8") as f:
747
553
  json.dump(checkpoint, f, indent=2, ensure_ascii=False)
@@ -749,54 +555,26 @@ def main():
749
555
 
750
556
  if is_continuation(args):
751
557
  rendered = append_continuation_handoff(
752
- rendered, args, project_root, args.task_type, args.refactor_id,
753
- args.refactor_id, checkpoint,
558
+ rendered, args, project_root, args.task_type, args.refactor_id, args.refactor_id, checkpoint,
754
559
  )
755
560
  err = write_output(args.output, rendered)
756
561
  if err:
757
562
  emit_failure(err)
758
563
 
759
- # Resolve critic and mode
760
- refactor_critic = refactor.get("critic", False)
761
- if args.critic is not None:
762
- critic_enabled = str(args.critic).lower() == "true"
763
- else:
764
- critic_enabled = bool(refactor_critic)
765
-
766
- pipeline_mode = args.mode or "standard"
767
- agent_count = 5 if critic_enabled else 3
768
-
769
- # Success
770
- refactor_model = refactor.get("model", "")
771
-
772
- # Extract browser state for JSON output
773
- browser_interaction = refactor.get("browser_interaction")
774
- browser_enabled = False
775
- browser_tool = "auto"
776
-
777
- browser_verify_env = os.environ.get("BROWSER_VERIFY", "").lower()
778
- if browser_verify_env == "false":
779
- browser_interaction = None
780
-
781
- if browser_interaction:
782
- if isinstance(browser_interaction, bool):
783
- browser_enabled = True
784
- elif isinstance(browser_interaction, dict):
785
- steps = browser_interaction.get("verify_steps", [])
786
- if steps or browser_interaction.get("url") or browser_interaction.get("enabled", True):
787
- browser_enabled = True
788
- browser_tool = browser_interaction.get("tool", "auto")
789
-
564
+ critic_count = int(replacements.get("{{CRITIC_COUNT}}", "1"))
790
565
  output = {
791
566
  "success": True,
792
567
  "output_path": os.path.abspath(args.output),
793
568
  "checkpoint_path": checkpoint_path,
794
- "model": refactor_model,
569
+ "model": refactor.get("model", ""),
795
570
  "pipeline_mode": pipeline_mode,
796
- "agent_count": agent_count,
571
+ "agent_count": mode_agent_count(pipeline_mode, critic_enabled, critic_count),
797
572
  "critic_enabled": critic_enabled,
798
- "browser_enabled": browser_enabled,
799
- "browser_tool": browser_tool,
573
+ "render_mode": "sections" if use_sections else "legacy",
574
+ "validation_warnings": len(warnings),
575
+ "validation_errors": len(errors),
576
+ "browser_enabled": replacements["{{BROWSER_ENABLED}}"] == "true",
577
+ "browser_tool": replacements["{{BROWSER_TOOL}}"],
800
578
  }
801
579
  print(json.dumps(output, indent=2, ensure_ascii=False))
802
580
  sys.exit(0)