prizmkit 1.1.57 → 1.1.60

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 (188) hide show
  1. package/bin/create-prizmkit.js +8 -6
  2. package/bundled/VERSION.json +3 -3
  3. package/bundled/adapters/codex/agent-adapter.js +38 -0
  4. package/bundled/adapters/codex/paths.js +27 -0
  5. package/bundled/adapters/codex/rules-adapter.js +30 -0
  6. package/bundled/adapters/codex/settings-adapter.js +27 -0
  7. package/bundled/adapters/codex/skill-adapter.js +65 -0
  8. package/bundled/adapters/codex/team-adapter.js +37 -0
  9. package/bundled/dev-pipeline/.env.example +2 -1
  10. package/bundled/dev-pipeline/README.md +10 -7
  11. package/bundled/dev-pipeline/lib/common.sh +278 -37
  12. package/bundled/dev-pipeline/run-bugfix.sh +10 -61
  13. package/bundled/dev-pipeline/run-feature.sh +10 -78
  14. package/bundled/dev-pipeline/run-recovery.sh +10 -46
  15. package/bundled/dev-pipeline/run-refactor.sh +10 -61
  16. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +17 -7
  17. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +9 -3
  18. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +9 -3
  19. package/bundled/dev-pipeline/scripts/utils.py +6 -4
  20. package/bundled/dev-pipeline-windows/.env.example +28 -0
  21. package/bundled/dev-pipeline-windows/README.md +30 -0
  22. package/bundled/dev-pipeline-windows/SCHEMA_ANALYSIS.md +525 -0
  23. package/bundled/dev-pipeline-windows/assets/feature-list-example.json +146 -0
  24. package/bundled/dev-pipeline-windows/assets/prizm-dev-team-integration.md +138 -0
  25. package/bundled/dev-pipeline-windows/launch-bugfix-daemon.ps1 +9 -0
  26. package/bundled/dev-pipeline-windows/launch-feature-daemon.ps1 +9 -0
  27. package/bundled/dev-pipeline-windows/launch-refactor-daemon.ps1 +9 -0
  28. package/bundled/dev-pipeline-windows/lib/common.ps1 +432 -0
  29. package/bundled/dev-pipeline-windows/lib/daemon.ps1 +140 -0
  30. package/bundled/dev-pipeline-windows/lib/pipeline.ps1 +446 -0
  31. package/bundled/dev-pipeline-windows/lib/reset.ps1 +87 -0
  32. package/bundled/dev-pipeline-windows/reset-bug.ps1 +9 -0
  33. package/bundled/dev-pipeline-windows/reset-feature.ps1 +9 -0
  34. package/bundled/dev-pipeline-windows/reset-refactor.ps1 +9 -0
  35. package/bundled/dev-pipeline-windows/run-bugfix.ps1 +9 -0
  36. package/bundled/dev-pipeline-windows/run-feature.ps1 +9 -0
  37. package/bundled/dev-pipeline-windows/run-recovery.ps1 +76 -0
  38. package/bundled/dev-pipeline-windows/run-refactor.ps1 +9 -0
  39. package/bundled/dev-pipeline-windows/scripts/check-session-status.py +228 -0
  40. package/bundled/dev-pipeline-windows/scripts/cleanup-logs.py +192 -0
  41. package/bundled/dev-pipeline-windows/scripts/detect-stuck.py +530 -0
  42. package/bundled/dev-pipeline-windows/scripts/generate-bootstrap-prompt.py +1737 -0
  43. package/bundled/dev-pipeline-windows/scripts/generate-bugfix-prompt.py +685 -0
  44. package/bundled/dev-pipeline-windows/scripts/generate-recovery-prompt.py +805 -0
  45. package/bundled/dev-pipeline-windows/scripts/generate-refactor-prompt.py +763 -0
  46. package/bundled/dev-pipeline-windows/scripts/init-bugfix-pipeline.py +316 -0
  47. package/bundled/dev-pipeline-windows/scripts/init-dev-team.py +134 -0
  48. package/bundled/dev-pipeline-windows/scripts/init-pipeline.py +380 -0
  49. package/bundled/dev-pipeline-windows/scripts/init-refactor-pipeline.py +399 -0
  50. package/bundled/dev-pipeline-windows/scripts/parse-stream-progress.py +388 -0
  51. package/bundled/dev-pipeline-windows/scripts/patch-completion-notes.py +191 -0
  52. package/bundled/dev-pipeline-windows/scripts/update-bug-status.py +864 -0
  53. package/bundled/dev-pipeline-windows/scripts/update-checkpoint.py +173 -0
  54. package/bundled/dev-pipeline-windows/scripts/update-feature-status.py +1501 -0
  55. package/bundled/dev-pipeline-windows/scripts/update-refactor-status.py +1073 -0
  56. package/bundled/dev-pipeline-windows/scripts/utils.py +542 -0
  57. package/bundled/dev-pipeline-windows/templates/agent-prompts/critic-plan-challenge.md +7 -0
  58. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-fix.md +7 -0
  59. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-implement.md +30 -0
  60. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-resume.md +5 -0
  61. package/bundled/dev-pipeline-windows/templates/agent-prompts/reviewer-review.md +7 -0
  62. package/bundled/dev-pipeline-windows/templates/bootstrap-prompt.md +46 -0
  63. package/bundled/dev-pipeline-windows/templates/bootstrap-tier1.md +43 -0
  64. package/bundled/dev-pipeline-windows/templates/bootstrap-tier2.md +43 -0
  65. package/bundled/dev-pipeline-windows/templates/bootstrap-tier3.md +43 -0
  66. package/bundled/dev-pipeline-windows/templates/bug-fix-list-schema.json +263 -0
  67. package/bundled/dev-pipeline-windows/templates/bugfix-bootstrap-prompt.md +320 -0
  68. package/bundled/dev-pipeline-windows/templates/feature-list-schema.json +237 -0
  69. package/bundled/dev-pipeline-windows/templates/refactor-bootstrap-prompt.md +331 -0
  70. package/bundled/dev-pipeline-windows/templates/refactor-list-schema.json +270 -0
  71. package/bundled/dev-pipeline-windows/templates/sections/ac-verification-checklist.md +13 -0
  72. package/bundled/dev-pipeline-windows/templates/sections/checkpoint-system.md +91 -0
  73. package/bundled/dev-pipeline-windows/templates/sections/context-budget-rules.md +33 -0
  74. package/bundled/dev-pipeline-windows/templates/sections/critical-paths-agent.md +10 -0
  75. package/bundled/dev-pipeline-windows/templates/sections/critical-paths-full.md +12 -0
  76. package/bundled/dev-pipeline-windows/templates/sections/critical-paths-lite.md +7 -0
  77. package/bundled/dev-pipeline-windows/templates/sections/directory-convention-agent.md +8 -0
  78. package/bundled/dev-pipeline-windows/templates/sections/directory-convention-full.md +9 -0
  79. package/bundled/dev-pipeline-windows/templates/sections/directory-convention-lite.md +6 -0
  80. package/bundled/dev-pipeline-windows/templates/sections/failure-capture.md +21 -0
  81. package/bundled/dev-pipeline-windows/templates/sections/feature-context.md +31 -0
  82. package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification-auto.md +72 -0
  83. package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification-opencli.md +63 -0
  84. package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification.md +62 -0
  85. package/bundled/dev-pipeline-windows/templates/sections/phase-commit-full.md +71 -0
  86. package/bundled/dev-pipeline-windows/templates/sections/phase-commit.md +64 -0
  87. package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-agent-suffix.md +23 -0
  88. package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-base.md +24 -0
  89. package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-lite-suffix.md +12 -0
  90. package/bundled/dev-pipeline-windows/templates/sections/phase-critic-plan-full.md +53 -0
  91. package/bundled/dev-pipeline-windows/templates/sections/phase-critic-plan.md +32 -0
  92. package/bundled/dev-pipeline-windows/templates/sections/phase-implement-agent.md +37 -0
  93. package/bundled/dev-pipeline-windows/templates/sections/phase-implement-full.md +50 -0
  94. package/bundled/dev-pipeline-windows/templates/sections/phase-implement-lite.md +52 -0
  95. package/bundled/dev-pipeline-windows/templates/sections/phase-plan-agent.md +27 -0
  96. package/bundled/dev-pipeline-windows/templates/sections/phase-plan-lite.md +27 -0
  97. package/bundled/dev-pipeline-windows/templates/sections/phase-review-agent.md +27 -0
  98. package/bundled/dev-pipeline-windows/templates/sections/phase-review-full.md +29 -0
  99. package/bundled/dev-pipeline-windows/templates/sections/phase-specify-plan-full.md +77 -0
  100. package/bundled/dev-pipeline-windows/templates/sections/phase0-init.md +13 -0
  101. package/bundled/dev-pipeline-windows/templates/sections/phase0-test-baseline.md +23 -0
  102. package/bundled/dev-pipeline-windows/templates/sections/session-context.md +5 -0
  103. package/bundled/dev-pipeline-windows/templates/sections/subagent-timeout-recovery.md +6 -0
  104. package/bundled/dev-pipeline-windows/templates/sections/test-failure-recovery-agent.md +67 -0
  105. package/bundled/dev-pipeline-windows/templates/sections/test-failure-recovery-lite.md +58 -0
  106. package/bundled/dev-pipeline-windows/templates/session-status-schema.json +83 -0
  107. package/bundled/skills/_metadata.json +1 -1
  108. package/bundled/skills/app-planner/SKILL.md +26 -18
  109. package/bundled/skills/app-planner/references/architecture-decisions.md +9 -5
  110. package/bundled/skills/app-planner/references/frontend-design-guide.md +1 -1
  111. package/bundled/skills/feature-planner/SKILL.md +9 -2
  112. package/bundled/skills/prizmkit-init/SKILL.md +7 -6
  113. package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +2 -0
  114. package/bundled/skills-windows/app-planner/SKILL.md +639 -0
  115. package/bundled/skills-windows/app-planner/assets/app-design-guide.md +101 -0
  116. package/bundled/skills-windows/app-planner/references/architecture-decisions.md +52 -0
  117. package/bundled/skills-windows/app-planner/references/brainstorm-guide.md +101 -0
  118. package/bundled/skills-windows/app-planner/references/frontend-design-guide.md +71 -0
  119. package/bundled/skills-windows/app-planner/references/project-brief-guide.md +82 -0
  120. package/bundled/skills-windows/app-planner/references/red-team-checklist.md +40 -0
  121. package/bundled/skills-windows/app-planner/references/rules/backend/derivation-rules.md +609 -0
  122. package/bundled/skills-windows/app-planner/references/rules/backend/fixed-rules.md +285 -0
  123. package/bundled/skills-windows/app-planner/references/rules/backend/question-bank.md +249 -0
  124. package/bundled/skills-windows/app-planner/references/rules/backend/template.md +173 -0
  125. package/bundled/skills-windows/app-planner/references/rules/database/derivation-rules.md +373 -0
  126. package/bundled/skills-windows/app-planner/references/rules/database/fixed-rules.md +211 -0
  127. package/bundled/skills-windows/app-planner/references/rules/database/question-bank.md +184 -0
  128. package/bundled/skills-windows/app-planner/references/rules/database/template.md +158 -0
  129. package/bundled/skills-windows/app-planner/references/rules/frontend/derivation-rules.md +810 -0
  130. package/bundled/skills-windows/app-planner/references/rules/frontend/fixed-rules.md +188 -0
  131. package/bundled/skills-windows/app-planner/references/rules/frontend/question-bank.md +302 -0
  132. package/bundled/skills-windows/app-planner/references/rules/frontend/template.md +320 -0
  133. package/bundled/skills-windows/app-planner/references/rules/mobile/derivation-rules.md +639 -0
  134. package/bundled/skills-windows/app-planner/references/rules/mobile/fixed-rules.md +290 -0
  135. package/bundled/skills-windows/app-planner/references/rules/mobile/question-bank.md +232 -0
  136. package/bundled/skills-windows/app-planner/references/rules/mobile/template.md +175 -0
  137. package/bundled/skills-windows/bug-fix-workflow/SKILL.md +415 -0
  138. package/bundled/skills-windows/bug-planner/SKILL.md +395 -0
  139. package/bundled/skills-windows/bug-planner/assets/bug-confirmation-template.md +43 -0
  140. package/bundled/skills-windows/bug-planner/references/critic-and-verification.md +44 -0
  141. package/bundled/skills-windows/bug-planner/references/error-recovery.md +73 -0
  142. package/bundled/skills-windows/bug-planner/references/input-formats.md +53 -0
  143. package/bundled/skills-windows/bug-planner/references/schema-validation.md +25 -0
  144. package/bundled/skills-windows/bug-planner/references/severity-rules.md +16 -0
  145. package/bundled/skills-windows/bug-planner/scripts/validate-bug-list.py +322 -0
  146. package/bundled/skills-windows/bugfix-pipeline-launcher/SKILL.md +380 -0
  147. package/bundled/skills-windows/feature-pipeline-launcher/SKILL.md +441 -0
  148. package/bundled/skills-windows/feature-pipeline-launcher/scripts/preflight-check.py +462 -0
  149. package/bundled/skills-windows/feature-planner/SKILL.md +401 -0
  150. package/bundled/skills-windows/feature-planner/assets/evaluation-guide.md +64 -0
  151. package/bundled/skills-windows/feature-planner/assets/planning-guide.md +214 -0
  152. package/bundled/skills-windows/feature-planner/references/browser-interaction.md +59 -0
  153. package/bundled/skills-windows/feature-planner/references/completeness-review.md +57 -0
  154. package/bundled/skills-windows/feature-planner/references/decomposition-patterns.md +75 -0
  155. package/bundled/skills-windows/feature-planner/references/error-recovery.md +90 -0
  156. package/bundled/skills-windows/feature-planner/references/incremental-feature-planning.md +112 -0
  157. package/bundled/skills-windows/feature-planner/references/new-project-planning.md +85 -0
  158. package/bundled/skills-windows/feature-planner/scripts/validate-and-generate.py +1029 -0
  159. package/bundled/skills-windows/feature-workflow/SKILL.md +531 -0
  160. package/bundled/skills-windows/prizmkit-init/SKILL.md +356 -0
  161. package/bundled/skills-windows/prizmkit-init/assets/project-brief-template.md +82 -0
  162. package/bundled/skills-windows/prizmkit-init/references/config-schema.md +68 -0
  163. package/bundled/skills-windows/prizmkit-init/references/rules/layer-detection.md +41 -0
  164. package/bundled/skills-windows/prizmkit-init/references/tech-stack-catalog.md +13 -0
  165. package/bundled/skills-windows/prizmkit-init/references/update-supplement.md +9 -0
  166. package/bundled/skills-windows/recovery-workflow/SKILL.md +456 -0
  167. package/bundled/skills-windows/recovery-workflow/evals/evals.json +46 -0
  168. package/bundled/skills-windows/recovery-workflow/scripts/detect-recovery-state.py +544 -0
  169. package/bundled/skills-windows/refactor-pipeline-launcher/SKILL.md +406 -0
  170. package/bundled/skills-windows/refactor-planner/SKILL.md +540 -0
  171. package/bundled/skills-windows/refactor-planner/assets/planning-guide.md +292 -0
  172. package/bundled/skills-windows/refactor-planner/references/behavior-preservation.md +301 -0
  173. package/bundled/skills-windows/refactor-planner/references/refactor-scoping-guide.md +221 -0
  174. package/bundled/skills-windows/refactor-planner/scripts/validate-and-generate-refactor.py +858 -0
  175. package/bundled/skills-windows/refactor-workflow/SKILL.md +503 -0
  176. package/package.json +3 -2
  177. package/src/clean.js +73 -2
  178. package/src/config.js +159 -50
  179. package/src/detect-platform.js +16 -8
  180. package/src/external-skills.js +26 -19
  181. package/src/index.js +31 -9
  182. package/src/manifest.js +6 -2
  183. package/src/metadata.js +43 -5
  184. package/src/platforms.js +36 -0
  185. package/src/prompts.js +31 -6
  186. package/src/runtimes.js +20 -0
  187. package/src/scaffold.js +314 -110
  188. package/src/upgrade.js +81 -41
@@ -0,0 +1,763 @@
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>
14
+ """
15
+
16
+ import argparse
17
+ import json
18
+ import os
19
+ import re
20
+ import sys
21
+
22
+ from utils import enrich_global_context, load_json_file, read_platform_conventions, setup_logging
23
+
24
+
25
+ DEFAULT_MAX_RETRIES = 3
26
+
27
+ LOGGER = setup_logging("generate-refactor-prompt")
28
+
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
+ ["--headless"]),
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
+
121
+ def parse_args():
122
+ parser = argparse.ArgumentParser(
123
+ description=(
124
+ "Generate a session-specific refactor bootstrap prompt from a template "
125
+ "and .prizmkit/plans/refactor-list.json."
126
+ )
127
+ )
128
+ parser.add_argument("--refactor-list", required=True, help="Path to .prizmkit/plans/refactor-list.json")
129
+ parser.add_argument("--refactor-id", required=True, help="Refactor ID to generate prompt for (e.g. R-001)")
130
+ parser.add_argument("--session-id", required=True, help="Session ID for this pipeline session")
131
+ parser.add_argument("--run-id", required=True, help="Pipeline run ID")
132
+ parser.add_argument("--retry-count", required=True, help="Current retry count")
133
+ parser.add_argument("--resume-phase", required=True, help='Phase to resume from, or "null" for fresh start')
134
+ parser.add_argument("--state-dir", default=None, help="State directory (default: .prizmkit/state/refactor)")
135
+ parser.add_argument("--output", required=True, help="Output path for the rendered prompt")
136
+ parser.add_argument("--template", default=None, help="Custom template path. Defaults to {script_dir}/../templates/refactor-bootstrap-prompt.md")
137
+ parser.add_argument("--mode", default=None, help="Pipeline execution mode override: lite, standard, full")
138
+ parser.add_argument("--critic", default=None, help="Enable critic agent: true/false")
139
+ parser.add_argument("--no-checkpoint", action="store_true", help="Do not write workflow-checkpoint.json (used by pipeline dry-run)")
140
+ return parser.parse_args()
141
+
142
+
143
+ def read_text_file(path):
144
+ """Read and return the text content of a file."""
145
+ abs_path = os.path.abspath(path)
146
+ if not os.path.isfile(abs_path):
147
+ return None, "File not found: {}".format(abs_path)
148
+ try:
149
+ with open(abs_path, "r", encoding="utf-8") as f:
150
+ return f.read(), None
151
+ except IOError as e:
152
+ return None, "Cannot read file: {}".format(str(e))
153
+
154
+
155
+ def find_refactor(refactors, refactor_id):
156
+ """Find and return the refactor dict matching the given ID."""
157
+ for refactor in refactors:
158
+ if isinstance(refactor, dict) and refactor.get("id") == refactor_id:
159
+ return refactor
160
+ return None
161
+
162
+
163
+ def format_acceptance_criteria(criteria):
164
+ """Format acceptance criteria as a markdown bullet list."""
165
+ if not criteria:
166
+ return "- (none specified)"
167
+ lines = []
168
+ for item in criteria:
169
+ lines.append("- {}".format(item))
170
+ return "\n".join(lines)
171
+
172
+
173
+ def format_global_context(global_context, project_root=None):
174
+ """Format global_context dict as a key-value list.
175
+
176
+ If global_context is empty/sparse and project_root is provided,
177
+ auto-detect tech stack from project files to fill gaps.
178
+ """
179
+ if project_root:
180
+ enrich_global_context(global_context, project_root)
181
+
182
+ if not global_context:
183
+ return "- (none specified)"
184
+ lines = []
185
+ for key, value in sorted(global_context.items()):
186
+ lines.append("- **{}**: {}".format(key, value))
187
+ return "\n".join(lines)
188
+
189
+
190
+ def format_user_context(user_context):
191
+ """Format user_context array as a markdown section.
192
+
193
+ Returns empty string if user_context is empty or absent,
194
+ so the template placeholder resolves to nothing.
195
+ """
196
+ if not user_context or not isinstance(user_context, list):
197
+ return ""
198
+ items = [item for item in user_context if isinstance(item, str) and item.strip()]
199
+ if not items:
200
+ return ""
201
+ lines = [
202
+ "### User-Provided Context (HIGHEST PRIORITY)",
203
+ "",
204
+ "> The following materials were provided by the user. "
205
+ "They take precedence over AI inference.",
206
+ "",
207
+ ]
208
+ for item in items:
209
+ lines.append("- {}".format(item))
210
+ return "\n".join(lines)
211
+
212
+
213
+ def format_scope(scope):
214
+ """Format scope object into markdown detail lines."""
215
+ if not scope or not isinstance(scope, dict):
216
+ return "- (no scope details)"
217
+ lines = []
218
+
219
+ files = scope.get("files", [])
220
+ if files:
221
+ lines.append("- **Files**:")
222
+ for f in files:
223
+ lines.append(" - `{}`".format(f))
224
+
225
+ modules = scope.get("modules", [])
226
+ if modules:
227
+ lines.append("- **Modules**:")
228
+ for m in modules:
229
+ lines.append(" - `{}`".format(m))
230
+
231
+ if not lines:
232
+ lines.append("- (no scope details)")
233
+ return "\n".join(lines)
234
+
235
+
236
+ def _format_scope_list(scope, key):
237
+ """Extract and format a list from scope by key (files or modules)."""
238
+ if not scope or not isinstance(scope, dict):
239
+ return "- (none specified)"
240
+ items = scope.get(key, [])
241
+ if not items:
242
+ return "- (none specified)"
243
+ return "\n".join("- `{}`".format(item) for item in items)
244
+
245
+
246
+ def format_scope_files(scope):
247
+ """Extract and format just the files list from scope."""
248
+ return _format_scope_list(scope, "files")
249
+
250
+
251
+ def format_scope_modules(scope):
252
+ """Extract and format just the modules list from scope."""
253
+ return _format_scope_list(scope, "modules")
254
+
255
+
256
+ def format_behavior_preservation(bp):
257
+ """Format behavior_preservation object into markdown detail lines."""
258
+ if not bp or not isinstance(bp, dict):
259
+ return "- (no behavior preservation details)"
260
+ lines = []
261
+
262
+ strategy = bp.get("strategy", "unknown")
263
+ lines.append("- **Strategy**: {}".format(strategy))
264
+
265
+ existing_tests = bp.get("existing_tests", [])
266
+ if existing_tests:
267
+ lines.append("- **Existing Tests**:")
268
+ for t in existing_tests:
269
+ lines.append(" - `{}`".format(t))
270
+
271
+ new_tests_needed = bp.get("new_tests_needed", [])
272
+ if new_tests_needed:
273
+ lines.append("- **New Tests Needed**:")
274
+ for t in new_tests_needed:
275
+ lines.append(" - {}".format(t))
276
+
277
+ if len(lines) == 1:
278
+ lines.append("- (no additional details)")
279
+ return "\n".join(lines)
280
+
281
+
282
+ def format_dependencies(dependencies, refactors=None):
283
+ """Format dependencies list as a markdown bullet list with completion context.
284
+
285
+ When refactors list is provided, look up completed dependencies and include
286
+ their completion_notes for rich context propagation.
287
+ """
288
+ if not dependencies or not isinstance(dependencies, list):
289
+ return "- (none)"
290
+ if len(dependencies) == 0:
291
+ return "- (none)"
292
+
293
+ # Build lookup map if refactors list is provided
294
+ refactor_map = {}
295
+ if refactors:
296
+ for r in refactors:
297
+ if isinstance(r, dict) and "id" in r:
298
+ refactor_map[r["id"]] = r
299
+
300
+ lines = []
301
+ for dep in dependencies:
302
+ dep_info = refactor_map.get(dep)
303
+ if dep_info and dep_info.get("status") == "completed":
304
+ header = "- **{}** — {} (completed)".format(
305
+ dep, dep_info.get("title", "Untitled")
306
+ )
307
+ notes = dep_info.get("completion_notes", [])
308
+ if notes and isinstance(notes, list):
309
+ note_lines = [
310
+ " - {}".format(n) for n in notes
311
+ if isinstance(n, str) and n.strip()
312
+ ]
313
+ if note_lines:
314
+ header += "\n" + "\n".join(note_lines)
315
+ lines.append(header)
316
+ else:
317
+ lines.append("- `{}`".format(dep))
318
+ return "\n".join(lines)
319
+
320
+
321
+ def get_prev_session_status(state_dir, refactor_id):
322
+ """Read previous session status from state dir if available."""
323
+ if not state_dir:
324
+ return "N/A (first run)"
325
+
326
+ refactor_status_path = os.path.join(state_dir, "refactors", refactor_id, "status.json")
327
+ try:
328
+ with open(refactor_status_path, "r", encoding="utf-8") as f:
329
+ refactor_status = json.load(f)
330
+ except (json.JSONDecodeError, IOError, OSError):
331
+ return "N/A (first run)"
332
+
333
+ last_session_id = refactor_status.get("last_session_id")
334
+ if not last_session_id:
335
+ return "N/A (first run)"
336
+
337
+ session_status_path = os.path.join(
338
+ state_dir, "refactors", refactor_id, "sessions",
339
+ last_session_id, "session-status.json"
340
+ )
341
+ try:
342
+ with open(session_status_path, "r", encoding="utf-8") as f:
343
+ session_data = json.load(f)
344
+ except (json.JSONDecodeError, IOError, OSError):
345
+ return "N/A (previous session status file not found)"
346
+
347
+ status = session_data.get("status", "unknown")
348
+ checkpoint = session_data.get("checkpoint_reached", "none")
349
+ current_phase = session_data.get("current_phase", "unknown")
350
+ errors = session_data.get("errors", [])
351
+
352
+ result = "{} (checkpoint: {}, last phase: {})".format(
353
+ status, checkpoint, current_phase
354
+ )
355
+ if errors:
356
+ result += " — errors: {}".format("; ".join(str(e) for e in errors))
357
+ return result
358
+
359
+
360
+ def resolve_project_root(script_dir):
361
+ """Resolve project root. Layout-aware:
362
+ <project>/.prizmkit/dev-pipeline/scripts/ → <project>
363
+ <repo>/dev-pipeline/scripts/ → <repo>
364
+ """
365
+ pipeline_dir = os.path.dirname(script_dir)
366
+ pipeline_parent = os.path.dirname(pipeline_dir)
367
+ if os.path.basename(pipeline_parent) == ".prizmkit":
368
+ return os.path.abspath(os.path.dirname(pipeline_parent))
369
+ return os.path.abspath(pipeline_parent)
370
+
371
+
372
+ def build_replacements(args, refactor, refactors, global_context, script_dir):
373
+ """Build the full dict of placeholder -> replacement value."""
374
+ project_root = resolve_project_root(script_dir)
375
+
376
+ # Platform-aware agent/team path resolution
377
+ platform = os.environ.get("PRIZMKIT_PLATFORM", "")
378
+ home_dir = os.path.expanduser("~")
379
+
380
+ if not platform:
381
+ if os.path.isdir(os.path.join(project_root, ".codex", "agents")):
382
+ platform = "codex"
383
+ elif os.path.isdir(os.path.join(project_root, ".claude", "agents")):
384
+ platform = "claude"
385
+ else:
386
+ platform = "codebuddy"
387
+
388
+ if platform == "claude":
389
+ agents_dir = os.path.join(project_root, ".claude", "agents")
390
+ team_config_path = os.path.join(project_root, ".claude", "team-info.json")
391
+ elif platform == "codex":
392
+ agents_dir = os.path.join(project_root, ".codex", "agents")
393
+ team_config_path = os.path.join(project_root, ".codex", "team-info.json")
394
+ else:
395
+ agents_dir = os.path.join(project_root, ".codebuddy", "agents")
396
+ team_config_path = os.path.join(
397
+ home_dir, ".codebuddy", "teams", "prizm-dev-team", "config.json"
398
+ )
399
+
400
+ agent_ext = ".toml" if platform == "codex" else ".md"
401
+ dev_subagent = os.path.join(agents_dir, f"prizm-dev-team-dev{agent_ext}")
402
+ reviewer_subagent = os.path.join(agents_dir, f"prizm-dev-team-reviewer{agent_ext}")
403
+
404
+ # Session status path
405
+ session_status_path = os.path.join(
406
+ project_root, ".prizmkit", "state", "refactor", "refactors", args.refactor_id,
407
+ "sessions", args.session_id, "session-status.json"
408
+ )
409
+
410
+ # Scope
411
+ scope = refactor.get("scope", {})
412
+
413
+ # Behavior preservation
414
+ bp = refactor.get("behavior_preservation", {})
415
+ behavior_strategy = bp.get("strategy", "test-gate") if isinstance(bp, dict) else "test-gate"
416
+ existing_tests = bp.get("existing_tests", []) if isinstance(bp, dict) else []
417
+ if not isinstance(existing_tests, list):
418
+ existing_tests = []
419
+ new_tests_needed = bp.get("new_tests_needed", []) if isinstance(bp, dict) else []
420
+ if not isinstance(new_tests_needed, list):
421
+ new_tests_needed = []
422
+
423
+ # Format existing tests
424
+ if existing_tests:
425
+ existing_tests_str = "\n".join("- `{}`".format(t) for t in existing_tests)
426
+ else:
427
+ existing_tests_str = "- (none specified)"
428
+
429
+ # Format new tests needed
430
+ if new_tests_needed:
431
+ new_tests_str = "\n".join("- {}".format(t) for t in new_tests_needed)
432
+ else:
433
+ new_tests_str = "- (none specified)"
434
+
435
+ # Browser interaction - extract from refactor if present
436
+ browser_interaction = refactor.get("browser_interaction")
437
+ browser_enabled = False
438
+ browser_verify_steps = ""
439
+ browser_tool = "auto"
440
+
441
+ # Environment override
442
+ browser_verify_env = os.environ.get("BROWSER_VERIFY", "").lower()
443
+ if browser_verify_env == "false":
444
+ browser_interaction = None
445
+
446
+ # Extract browser config (same logic as feature and bugfix pipelines)
447
+ if browser_interaction and isinstance(browser_interaction, bool):
448
+ browser_enabled = True
449
+ browser_tool = "auto"
450
+ browser_verify_steps = " # (no specific verify goals — validate UI renders correctly and feature still works)"
451
+ elif browser_interaction and isinstance(browser_interaction, dict):
452
+ browser_tool = browser_interaction.get("tool", "auto")
453
+ if browser_tool not in ("playwright-cli", "opencli", "auto"):
454
+ LOGGER.warning("Unknown browser_interaction.tool '%s', defaulting to 'auto'", browser_tool)
455
+ browser_tool = "auto"
456
+
457
+ steps = browser_interaction.get("verify_steps", [])
458
+ if steps:
459
+ browser_enabled = True
460
+ browser_verify_steps = "\n".join(
461
+ " # Step {}: {}".format(i + 1, step)
462
+ for i, step in enumerate(steps)
463
+ )
464
+ elif browser_interaction.get("url") or browser_interaction.get("enabled", True):
465
+ browser_enabled = True
466
+ browser_verify_steps = " # (validate UI renders correctly and feature still works)"
467
+
468
+ replacements = {
469
+ "{{RUN_ID}}": args.run_id,
470
+ "{{SESSION_ID}}": args.session_id,
471
+ "{{REFACTOR_ID}}": args.refactor_id,
472
+ "{{REFACTOR_TITLE}}": refactor.get("title", ""),
473
+ "{{REFACTOR_TYPE}}": refactor.get("type", "restructure"),
474
+ "{{SCOPE_FILES}}": format_scope_files(scope),
475
+ "{{SCOPE_MODULES}}": format_scope_modules(scope),
476
+ "{{BEHAVIOR_STRATEGY}}": behavior_strategy,
477
+ "{{EXISTING_TESTS}}": existing_tests_str,
478
+ "{{NEW_TESTS_NEEDED}}": new_tests_str,
479
+ "{{PRIORITY}}": refactor.get("priority", "medium"),
480
+ "{{COMPLEXITY}}": refactor.get("complexity", "medium"),
481
+ "{{REFACTOR_DESCRIPTION}}": refactor.get("description", ""),
482
+ "{{USER_CONTEXT}}": format_user_context(refactor.get("user_context", [])),
483
+ "{{ACCEPTANCE_CRITERIA}}": format_acceptance_criteria(
484
+ refactor.get("acceptance_criteria", [])
485
+ ),
486
+ "{{DEPENDENCIES}}": format_dependencies(
487
+ refactor.get("dependencies", []), refactors
488
+ ),
489
+ "{{GLOBAL_CONTEXT}}": format_global_context(global_context, project_root),
490
+ "{{TEAM_CONFIG_PATH}}": team_config_path,
491
+ "{{DEV_SUBAGENT_PATH}}": dev_subagent,
492
+ "{{REVIEWER_SUBAGENT_PATH}}": reviewer_subagent,
493
+ "{{SESSION_STATUS_PATH}}": session_status_path,
494
+ "{{PROJECT_ROOT}}": project_root,
495
+ "{{PIPELINE_DIR}}": ".prizmkit\\dev-pipeline",
496
+ "{{CHECKPOINT_PATH}}": os.path.join(
497
+ ".prizmkit", "refactor", args.refactor_id, "workflow-checkpoint.json",
498
+ ),
499
+ "{{TIMESTAMP}}": "", # Placeholder — agent fills in timestamp
500
+ "{{PLATFORM_CONVENTIONS}}": read_platform_conventions(project_root),
501
+ "{{BROWSER_ENABLED}}": "true" if browser_enabled else "false",
502
+ "{{BROWSER_TOOL}}": browser_tool,
503
+ "{{BROWSER_VERIFY_STEPS}}": browser_verify_steps,
504
+ }
505
+
506
+ return replacements
507
+
508
+
509
+ def process_conditional_blocks(content, resume_phase, refactor):
510
+ """Handle conditional blocks based on resume_phase and browser_interaction."""
511
+ # Handle fresh start blocks
512
+ is_resume = resume_phase != "null"
513
+
514
+ if is_resume:
515
+ content = re.sub(
516
+ r"\{\{IF_FRESH_START\}\}.*?\{\{END_IF_FRESH_START\}\}\n?",
517
+ "", content, flags=re.DOTALL,
518
+ )
519
+ else:
520
+ content = content.replace("{{IF_FRESH_START}}\n", "")
521
+ content = content.replace("{{IF_FRESH_START}}", "")
522
+ content = content.replace("{{END_IF_FRESH_START}}\n", "")
523
+ content = content.replace("{{END_IF_FRESH_START}}", "")
524
+
525
+ # Handle browser interaction blocks
526
+ browser_interaction = refactor.get("browser_interaction")
527
+ browser_enabled = False
528
+ browser_tool = "auto"
529
+
530
+ # Check environment override
531
+ browser_verify_env = os.environ.get("BROWSER_VERIFY", "").lower()
532
+ if browser_verify_env == "false":
533
+ browser_interaction = None
534
+
535
+ # Determine if browser is enabled
536
+ if browser_interaction:
537
+ if isinstance(browser_interaction, bool):
538
+ browser_enabled = True
539
+ elif isinstance(browser_interaction, dict):
540
+ steps = browser_interaction.get("verify_steps", [])
541
+ if steps or browser_interaction.get("url") or browser_interaction.get("enabled", True):
542
+ browser_enabled = True
543
+ browser_tool = browser_interaction.get("tool", "auto")
544
+
545
+ # Process browser interaction blocks
546
+ browser_open = "{{IF_BROWSER_INTERACTION}}"
547
+ browser_close = "{{END_IF_BROWSER_INTERACTION}}"
548
+
549
+ if browser_enabled:
550
+ # Keep content, remove tags
551
+ content = content.replace(browser_open + "\n", "")
552
+ content = content.replace(browser_open, "")
553
+ content = content.replace(browser_close + "\n", "")
554
+ content = content.replace(browser_close, "")
555
+ else:
556
+ # Remove entire block
557
+ pattern = re.escape(browser_open) + r".*?" + re.escape(browser_close) + r"\n?"
558
+ content = re.sub(pattern, "", content, flags=re.DOTALL)
559
+
560
+ # Process browser tool selection blocks (nested inside browser interaction)
561
+ tool_variants = ["PLAYWRIGHT", "OPENCLI", "AUTO"]
562
+ active_variant = {
563
+ "playwright-cli": "PLAYWRIGHT",
564
+ "opencli": "OPENCLI",
565
+ "auto": "AUTO",
566
+ }.get(browser_tool, "AUTO")
567
+
568
+ for variant in tool_variants:
569
+ tool_open = "{{{{IF_BROWSER_TOOL_{}}}}}".format(variant)
570
+ tool_close = "{{{{END_IF_BROWSER_TOOL_{}}}}}".format(variant)
571
+
572
+ if variant == active_variant and browser_enabled:
573
+ # Keep content, remove tags
574
+ content = content.replace(tool_open + "\n", "")
575
+ content = content.replace(tool_open, "")
576
+ content = content.replace(tool_close + "\n", "")
577
+ content = content.replace(tool_close, "")
578
+ else:
579
+ # Remove entire block
580
+ pat = re.escape(tool_open) + r".*?" + re.escape(tool_close) + r"\n?"
581
+ content = re.sub(pat, "", content, flags=re.DOTALL)
582
+
583
+ return content
584
+
585
+
586
+
587
+ def render_template(template_content, replacements, resume_phase, refactor):
588
+ """Render the template by processing conditionals and replacing placeholders."""
589
+ # Step 1: Process conditional blocks
590
+ content = process_conditional_blocks(template_content, resume_phase, refactor)
591
+
592
+ # Step 2: Replace all {{PLACEHOLDER}} variables
593
+ for placeholder, value in replacements.items():
594
+ content = content.replace(placeholder, value)
595
+
596
+ return content
597
+
598
+
599
+ def write_output(output_path, content):
600
+ """Write the rendered content to the output file."""
601
+ abs_path = os.path.abspath(output_path)
602
+ output_dir = os.path.dirname(abs_path)
603
+ if output_dir and not os.path.isdir(output_dir):
604
+ try:
605
+ os.makedirs(output_dir, exist_ok=True)
606
+ except OSError as e:
607
+ return "Cannot create output directory: {}".format(str(e))
608
+ try:
609
+ with open(abs_path, "w", encoding="utf-8") as f:
610
+ f.write(content)
611
+ except IOError as e:
612
+ return "Cannot write output file: {}".format(str(e))
613
+ return None
614
+
615
+
616
+ def emit_failure(message):
617
+ """Emit standardized failure JSON and exit.
618
+
619
+ Uses a different format than error_out() — includes 'success: false'
620
+ for compatibility with the pipeline's JSON parsing expectations.
621
+ """
622
+ print(json.dumps({"success": False, "error": message}, indent=2, ensure_ascii=False))
623
+ sys.exit(1)
624
+
625
+
626
+ def main():
627
+ args = parse_args()
628
+
629
+ # Resolve script directory
630
+ script_dir = os.path.dirname(os.path.abspath(__file__))
631
+
632
+ # Resolve template path
633
+ if args.template:
634
+ template_path = args.template
635
+ else:
636
+ template_path = os.path.join(
637
+ script_dir, "..", "templates", "refactor-bootstrap-prompt.md"
638
+ )
639
+
640
+ # Load template
641
+ template_content, err = read_text_file(template_path)
642
+ if err:
643
+ emit_failure("Template error: {}".format(err))
644
+
645
+ # Load refactor list
646
+ refactor_list_data, err = load_json_file(args.refactor_list)
647
+ if err:
648
+ emit_failure("Refactor list error: {}".format(err))
649
+
650
+ # Extract refactors array
651
+ refactors = refactor_list_data.get("refactors")
652
+ if not isinstance(refactors, list):
653
+ emit_failure("Refactor list does not contain a 'refactors' array")
654
+
655
+ # Find the target refactor
656
+ refactor = find_refactor(refactors, args.refactor_id)
657
+ if refactor is None:
658
+ emit_failure("Refactor '{}' not found in refactor list".format(args.refactor_id))
659
+
660
+ # Extract global context
661
+ global_context = refactor_list_data.get("global_context", {})
662
+ if not isinstance(global_context, dict):
663
+ global_context = {}
664
+
665
+ # Build replacements
666
+ replacements = build_replacements(args, refactor, refactors, global_context, script_dir)
667
+
668
+ # Render the template
669
+ rendered = render_template(template_content, replacements, args.resume_phase, refactor)
670
+
671
+ # Write the output
672
+ err = write_output(args.output, rendered)
673
+ if err:
674
+ emit_failure(err)
675
+
676
+ # Generate checkpoint file
677
+ project_root = resolve_project_root(script_dir)
678
+ checkpoint_rel = os.path.join(
679
+ ".prizmkit", "refactor", args.refactor_id, "workflow-checkpoint.json",
680
+ )
681
+ checkpoint_path = os.path.join(project_root, checkpoint_rel)
682
+ if not args.no_checkpoint:
683
+ checkpoint_dir = os.path.dirname(checkpoint_path)
684
+ os.makedirs(checkpoint_dir, exist_ok=True)
685
+
686
+ checkpoint = generate_refactor_checkpoint(args.refactor_id, args.session_id)
687
+
688
+ is_resume = args.resume_phase != "null"
689
+ if is_resume and os.path.exists(checkpoint_path):
690
+ try:
691
+ with open(checkpoint_path, "r", encoding="utf-8") as f:
692
+ existing = json.load(f)
693
+ checkpoint = merge_refactor_checkpoint_state(
694
+ existing, checkpoint, project_root,
695
+ )
696
+ LOGGER.info("Merged existing refactor checkpoint from %s",
697
+ checkpoint_path)
698
+ except (json.JSONDecodeError, KeyError) as exc:
699
+ LOGGER.warning(
700
+ "Existing refactor checkpoint corrupted (%s) — generating fresh",
701
+ exc,
702
+ )
703
+
704
+ with open(checkpoint_path, "w", encoding="utf-8") as f:
705
+ json.dump(checkpoint, f, indent=2, ensure_ascii=False)
706
+ LOGGER.info("Wrote refactor checkpoint to %s", checkpoint_path)
707
+
708
+ # Resolve critic and mode
709
+ refactor_critic = refactor.get("critic", False)
710
+ if args.critic is not None:
711
+ critic_enabled = str(args.critic).lower() == "true"
712
+ else:
713
+ critic_enabled = bool(refactor_critic)
714
+
715
+ pipeline_mode = args.mode or "standard"
716
+ agent_count = 5 if critic_enabled else 3
717
+
718
+ # Success
719
+ refactor_model = refactor.get("model", "")
720
+
721
+ # Extract browser state for JSON output
722
+ browser_interaction = refactor.get("browser_interaction")
723
+ browser_enabled = False
724
+ browser_tool = "auto"
725
+
726
+ browser_verify_env = os.environ.get("BROWSER_VERIFY", "").lower()
727
+ if browser_verify_env == "false":
728
+ browser_interaction = None
729
+
730
+ if browser_interaction:
731
+ if isinstance(browser_interaction, bool):
732
+ browser_enabled = True
733
+ elif isinstance(browser_interaction, dict):
734
+ steps = browser_interaction.get("verify_steps", [])
735
+ if steps or browser_interaction.get("url") or browser_interaction.get("enabled", True):
736
+ browser_enabled = True
737
+ browser_tool = browser_interaction.get("tool", "auto")
738
+
739
+ output = {
740
+ "success": True,
741
+ "output_path": os.path.abspath(args.output),
742
+ "checkpoint_path": checkpoint_path,
743
+ "model": refactor_model,
744
+ "pipeline_mode": pipeline_mode,
745
+ "agent_count": agent_count,
746
+ "critic_enabled": critic_enabled,
747
+ "browser_enabled": browser_enabled,
748
+ "browser_tool": browser_tool,
749
+ }
750
+ print(json.dumps(output, indent=2, ensure_ascii=False))
751
+ sys.exit(0)
752
+
753
+
754
+ if __name__ == "__main__":
755
+ try:
756
+ main()
757
+ except KeyboardInterrupt:
758
+ emit_failure("generate-refactor-prompt interrupted")
759
+ except SystemExit:
760
+ raise
761
+ except Exception as exc:
762
+ LOGGER.exception("Unhandled exception in generate-refactor-prompt")
763
+ emit_failure("Unexpected error: {}".format(str(exc)))