prizmkit 1.1.1 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundled/VERSION.json +3 -3
- package/bundled/adapters/claude/agent-adapter.js +18 -0
- package/bundled/adapters/claude/command-adapter.js +1 -27
- package/bundled/agents/prizm-dev-team-critic.md +2 -0
- package/bundled/agents/prizm-dev-team-dev.md +2 -0
- package/bundled/agents/prizm-dev-team-reviewer.md +2 -0
- package/bundled/dev-pipeline/README.md +63 -63
- package/bundled/dev-pipeline/assets/feature-list-example.json +1 -1
- package/bundled/dev-pipeline/assets/prizm-dev-team-integration.md +1 -1
- package/bundled/dev-pipeline/{launch-daemon.sh → launch-feature-daemon.sh} +33 -33
- package/bundled/dev-pipeline/launch-refactor-daemon.sh +454 -0
- package/bundled/dev-pipeline/lib/branch.sh +1 -1
- package/bundled/dev-pipeline/reset-feature.sh +3 -3
- package/bundled/dev-pipeline/reset-refactor.sh +312 -0
- package/bundled/dev-pipeline/{retry-bug.sh → retry-bugfix.sh} +47 -59
- package/bundled/dev-pipeline/retry-feature.sh +41 -54
- package/bundled/dev-pipeline/retry-refactor.sh +358 -0
- package/bundled/dev-pipeline/run-bugfix.sh +41 -0
- package/bundled/dev-pipeline/{run.sh → run-feature.sh} +64 -31
- package/bundled/dev-pipeline/run-refactor.sh +787 -0
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +398 -10
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +124 -0
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +419 -0
- package/bundled/dev-pipeline/scripts/init-refactor-pipeline.py +393 -0
- package/bundled/dev-pipeline/scripts/update-refactor-status.py +726 -0
- package/bundled/dev-pipeline/templates/agent-prompts/critic-code-challenge.md +13 -0
- package/bundled/dev-pipeline/templates/agent-prompts/critic-plan-challenge.md +7 -0
- package/bundled/dev-pipeline/templates/agent-prompts/dev-fix.md +7 -0
- package/bundled/dev-pipeline/templates/agent-prompts/dev-implement.md +27 -0
- package/bundled/dev-pipeline/templates/agent-prompts/dev-resume.md +5 -0
- package/bundled/dev-pipeline/templates/agent-prompts/reviewer-analyze.md +5 -0
- package/bundled/dev-pipeline/templates/agent-prompts/reviewer-review.md +12 -0
- package/bundled/dev-pipeline/templates/bootstrap-tier1.md +33 -2
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +13 -9
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +16 -12
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +22 -4
- package/bundled/dev-pipeline/templates/feature-list-schema.json +1 -1
- package/bundled/dev-pipeline/templates/refactor-list-schema.json +159 -0
- package/bundled/dev-pipeline/templates/sections/ac-verification-checklist.md +13 -0
- package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +36 -0
- package/bundled/dev-pipeline/templates/sections/failure-log-check.md +2 -1
- package/bundled/dev-pipeline/templates/sections/feature-context.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-analyze-agent.md +11 -7
- package/bundled/dev-pipeline/templates/sections/phase-analyze-full.md +11 -7
- package/bundled/dev-pipeline/templates/sections/phase-browser-verification.md +5 -1
- package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +3 -0
- package/bundled/dev-pipeline/templates/sections/phase-commit.md +3 -0
- package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-agent-suffix.md +3 -0
- package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-lite-suffix.md +3 -0
- package/bundled/dev-pipeline/templates/sections/phase-critic-code.md +11 -10
- package/bundled/dev-pipeline/templates/sections/phase-critic-plan-full.md +12 -10
- package/bundled/dev-pipeline/templates/sections/phase-critic-plan.md +11 -9
- package/bundled/dev-pipeline/templates/sections/phase-deploy-verification.md +3 -0
- package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +10 -10
- package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +12 -16
- package/bundled/dev-pipeline/templates/sections/phase-implement-lite.md +3 -0
- package/bundled/dev-pipeline/templates/sections/phase-plan-agent.md +3 -0
- package/bundled/dev-pipeline/templates/sections/phase-plan-lite.md +3 -0
- package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +11 -13
- package/bundled/dev-pipeline/templates/sections/phase-review-full.md +12 -20
- package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +3 -0
- package/bundled/dev-pipeline/templates/sections/phase0-init.md +3 -0
- package/bundled/dev-pipeline/templates/sections/phase0-test-baseline.md +3 -0
- package/bundled/dev-pipeline/templates/sections/resume-header.md +4 -1
- package/bundled/dev-pipeline/templates/sections/test-failure-recovery.md +75 -0
- package/bundled/rules/prizm/prizm-commit-workflow.md +1 -0
- package/bundled/rules/prizm/prizm-documentation.md +15 -15
- package/bundled/rules/prizm/prizm-progressive-loading.md +2 -1
- package/bundled/skills/_metadata.json +33 -6
- package/bundled/skills/app-planner/SKILL.md +105 -320
- package/bundled/skills/app-planner/assets/app-design-guide.md +101 -0
- package/bundled/skills/app-planner/references/frontend-design-guide.md +1 -1
- package/bundled/skills/app-planner/references/project-brief-guide.md +49 -80
- package/bundled/skills/bug-fix-workflow/SKILL.md +2 -2
- package/bundled/skills/bug-planner/SKILL.md +68 -5
- package/bundled/skills/bug-planner/scripts/validate-bug-list.py +3 -2
- package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +19 -5
- package/bundled/skills/{dev-pipeline-launcher → feature-pipeline-launcher}/SKILL.md +32 -32
- package/bundled/skills/feature-planner/SKILL.md +337 -0
- package/bundled/skills/{app-planner → feature-planner}/assets/evaluation-guide.md +4 -4
- package/bundled/skills/{app-planner → feature-planner}/assets/planning-guide.md +3 -171
- package/bundled/skills/{app-planner → feature-planner}/references/browser-interaction.md +6 -5
- package/bundled/skills/feature-planner/references/decomposition-patterns.md +75 -0
- package/bundled/skills/{app-planner → feature-planner}/references/error-recovery.md +8 -8
- package/bundled/skills/{app-planner → feature-planner}/references/incremental-feature-planning.md +1 -1
- package/bundled/skills/{app-planner/references/new-app-planning.md → feature-planner/references/new-project-planning.md} +1 -1
- package/bundled/skills/{app-planner → feature-planner}/scripts/validate-and-generate.py +4 -4
- package/bundled/skills/feature-workflow/SKILL.md +23 -23
- package/bundled/skills/prizm-kit/SKILL.md +1 -3
- package/bundled/skills/prizm-kit/assets/project-memory-template.md +4 -2
- package/bundled/skills/prizmkit-analyze/SKILL.md +2 -5
- package/bundled/skills/prizmkit-code-review/SKILL.md +2 -2
- package/bundled/skills/prizmkit-committer/SKILL.md +32 -8
- package/bundled/skills/prizmkit-deploy/SKILL.md +1 -5
- package/bundled/skills/prizmkit-implement/SKILL.md +5 -51
- package/bundled/skills/prizmkit-init/SKILL.md +7 -78
- package/bundled/skills/prizmkit-plan/SKILL.md +1 -12
- package/bundled/skills/prizmkit-prizm-docs/SKILL.md +13 -28
- package/bundled/skills/prizmkit-prizm-docs/assets/PRIZM-SPEC.md +52 -1
- package/bundled/skills/prizmkit-retrospective/SKILL.md +12 -117
- package/bundled/skills/recovery-workflow/SKILL.md +168 -316
- package/bundled/skills/recovery-workflow/evals/evals.json +29 -13
- package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +232 -274
- package/bundled/skills/refactor-pipeline-launcher/SKILL.md +352 -0
- package/bundled/skills/refactor-planner/SKILL.md +436 -0
- package/bundled/skills/refactor-planner/assets/planning-guide.md +292 -0
- package/bundled/skills/refactor-planner/references/behavior-preservation.md +301 -0
- package/bundled/skills/refactor-planner/references/refactor-scoping-guide.md +221 -0
- package/bundled/skills/refactor-planner/scripts/validate-and-generate-refactor.py +786 -0
- package/bundled/skills/refactor-workflow/SKILL.md +299 -319
- package/bundled/team/prizm-dev-team.json +1 -1
- package/package.json +1 -1
- package/src/clean.js +3 -3
- package/src/scaffold.js +6 -6
- package/bundled/skills/prizmkit-plan/assets/spec-template.md +0 -56
- package/bundled/skills/prizmkit-plan/references/clarify-guide.md +0 -67
- package/src/config.js +0 -504
- package/src/prompts.js +0 -210
- /package/bundled/skills/{dev-pipeline-launcher → feature-pipeline-launcher}/scripts/preflight-check.py +0 -0
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Generate a session-specific refactor bootstrap prompt from template and refactor-list.json.
|
|
3
|
+
|
|
4
|
+
Reads the refactor-bootstrap-prompt.md template and a 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 sys
|
|
20
|
+
|
|
21
|
+
from utils import enrich_global_context, load_json_file, setup_logging
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
DEFAULT_MAX_RETRIES = 3
|
|
25
|
+
|
|
26
|
+
LOGGER = setup_logging("generate-refactor-prompt")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def parse_args():
|
|
30
|
+
parser = argparse.ArgumentParser(
|
|
31
|
+
description=(
|
|
32
|
+
"Generate a session-specific refactor bootstrap prompt from a template "
|
|
33
|
+
"and refactor-list.json."
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
parser.add_argument("--refactor-list", required=True, help="Path to refactor-list.json")
|
|
37
|
+
parser.add_argument("--refactor-id", required=True, help="Refactor ID to generate prompt for (e.g. R-001)")
|
|
38
|
+
parser.add_argument("--session-id", required=True, help="Session ID for this pipeline session")
|
|
39
|
+
parser.add_argument("--run-id", required=True, help="Pipeline run ID")
|
|
40
|
+
parser.add_argument("--retry-count", required=True, help="Current retry count")
|
|
41
|
+
parser.add_argument("--resume-phase", required=True, help='Phase to resume from, or "null" for fresh start')
|
|
42
|
+
parser.add_argument("--state-dir", default=None, help="State directory path for reading previous session info")
|
|
43
|
+
parser.add_argument("--output", required=True, help="Output path for the rendered prompt")
|
|
44
|
+
parser.add_argument("--template", default=None, help="Custom template path. Defaults to {script_dir}/../templates/refactor-bootstrap-prompt.md")
|
|
45
|
+
return parser.parse_args()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def read_text_file(path):
|
|
49
|
+
"""Read and return the text content of a file."""
|
|
50
|
+
abs_path = os.path.abspath(path)
|
|
51
|
+
if not os.path.isfile(abs_path):
|
|
52
|
+
return None, "File not found: {}".format(abs_path)
|
|
53
|
+
try:
|
|
54
|
+
with open(abs_path, "r", encoding="utf-8") as f:
|
|
55
|
+
return f.read(), None
|
|
56
|
+
except IOError as e:
|
|
57
|
+
return None, "Cannot read file: {}".format(str(e))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def find_refactor(refactors, refactor_id):
|
|
61
|
+
"""Find and return the refactor dict matching the given ID."""
|
|
62
|
+
for refactor in refactors:
|
|
63
|
+
if isinstance(refactor, dict) and refactor.get("id") == refactor_id:
|
|
64
|
+
return refactor
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def format_acceptance_criteria(criteria):
|
|
69
|
+
"""Format acceptance criteria as a markdown bullet list."""
|
|
70
|
+
if not criteria:
|
|
71
|
+
return "- (none specified)"
|
|
72
|
+
lines = []
|
|
73
|
+
for item in criteria:
|
|
74
|
+
lines.append("- {}".format(item))
|
|
75
|
+
return "\n".join(lines)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def format_global_context(global_context, project_root=None):
|
|
79
|
+
"""Format global_context dict as a key-value list.
|
|
80
|
+
|
|
81
|
+
If global_context is empty/sparse and project_root is provided,
|
|
82
|
+
auto-detect tech stack from project files to fill gaps.
|
|
83
|
+
"""
|
|
84
|
+
if project_root:
|
|
85
|
+
enrich_global_context(global_context, project_root)
|
|
86
|
+
|
|
87
|
+
if not global_context:
|
|
88
|
+
return "- (none specified)"
|
|
89
|
+
lines = []
|
|
90
|
+
for key, value in sorted(global_context.items()):
|
|
91
|
+
lines.append("- **{}**: {}".format(key, value))
|
|
92
|
+
return "\n".join(lines)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def format_scope(scope):
|
|
96
|
+
"""Format scope object into markdown detail lines."""
|
|
97
|
+
if not scope or not isinstance(scope, dict):
|
|
98
|
+
return "- (no scope details)"
|
|
99
|
+
lines = []
|
|
100
|
+
|
|
101
|
+
files = scope.get("files", [])
|
|
102
|
+
if files:
|
|
103
|
+
lines.append("- **Files**:")
|
|
104
|
+
for f in files:
|
|
105
|
+
lines.append(" - `{}`".format(f))
|
|
106
|
+
|
|
107
|
+
modules = scope.get("modules", [])
|
|
108
|
+
if modules:
|
|
109
|
+
lines.append("- **Modules**:")
|
|
110
|
+
for m in modules:
|
|
111
|
+
lines.append(" - `{}`".format(m))
|
|
112
|
+
|
|
113
|
+
if not lines:
|
|
114
|
+
lines.append("- (no scope details)")
|
|
115
|
+
return "\n".join(lines)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _format_scope_list(scope, key):
|
|
119
|
+
"""Extract and format a list from scope by key (files or modules)."""
|
|
120
|
+
if not scope or not isinstance(scope, dict):
|
|
121
|
+
return "- (none specified)"
|
|
122
|
+
items = scope.get(key, [])
|
|
123
|
+
if not items:
|
|
124
|
+
return "- (none specified)"
|
|
125
|
+
return "\n".join("- `{}`".format(item) for item in items)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def format_scope_files(scope):
|
|
129
|
+
"""Extract and format just the files list from scope."""
|
|
130
|
+
return _format_scope_list(scope, "files")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def format_scope_modules(scope):
|
|
134
|
+
"""Extract and format just the modules list from scope."""
|
|
135
|
+
return _format_scope_list(scope, "modules")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def format_behavior_preservation(bp):
|
|
139
|
+
"""Format behavior_preservation object into markdown detail lines."""
|
|
140
|
+
if not bp or not isinstance(bp, dict):
|
|
141
|
+
return "- (no behavior preservation details)"
|
|
142
|
+
lines = []
|
|
143
|
+
|
|
144
|
+
strategy = bp.get("strategy", "unknown")
|
|
145
|
+
lines.append("- **Strategy**: {}".format(strategy))
|
|
146
|
+
|
|
147
|
+
existing_tests = bp.get("existing_tests", [])
|
|
148
|
+
if existing_tests:
|
|
149
|
+
lines.append("- **Existing Tests**:")
|
|
150
|
+
for t in existing_tests:
|
|
151
|
+
lines.append(" - `{}`".format(t))
|
|
152
|
+
|
|
153
|
+
new_tests_needed = bp.get("new_tests_needed", [])
|
|
154
|
+
if new_tests_needed:
|
|
155
|
+
lines.append("- **New Tests Needed**:")
|
|
156
|
+
for t in new_tests_needed:
|
|
157
|
+
lines.append(" - {}".format(t))
|
|
158
|
+
|
|
159
|
+
if len(lines) == 1:
|
|
160
|
+
lines.append("- (no additional details)")
|
|
161
|
+
return "\n".join(lines)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def format_dependencies(dependencies):
|
|
165
|
+
"""Format dependencies list as a markdown bullet list."""
|
|
166
|
+
if not dependencies or not isinstance(dependencies, list):
|
|
167
|
+
return "- (none)"
|
|
168
|
+
if len(dependencies) == 0:
|
|
169
|
+
return "- (none)"
|
|
170
|
+
lines = []
|
|
171
|
+
for dep in dependencies:
|
|
172
|
+
lines.append("- `{}`".format(dep))
|
|
173
|
+
return "\n".join(lines)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def get_prev_session_status(state_dir, refactor_id):
|
|
177
|
+
"""Read previous session status from state dir if available."""
|
|
178
|
+
if not state_dir:
|
|
179
|
+
return "N/A (first run)"
|
|
180
|
+
|
|
181
|
+
refactor_status_path = os.path.join(state_dir, "refactors", refactor_id, "status.json")
|
|
182
|
+
try:
|
|
183
|
+
with open(refactor_status_path, "r", encoding="utf-8") as f:
|
|
184
|
+
refactor_status = json.load(f)
|
|
185
|
+
except (json.JSONDecodeError, IOError, OSError):
|
|
186
|
+
return "N/A (first run)"
|
|
187
|
+
|
|
188
|
+
last_session_id = refactor_status.get("last_session_id")
|
|
189
|
+
if not last_session_id:
|
|
190
|
+
return "N/A (first run)"
|
|
191
|
+
|
|
192
|
+
session_status_path = os.path.join(
|
|
193
|
+
state_dir, "refactors", refactor_id, "sessions",
|
|
194
|
+
last_session_id, "session-status.json"
|
|
195
|
+
)
|
|
196
|
+
try:
|
|
197
|
+
with open(session_status_path, "r", encoding="utf-8") as f:
|
|
198
|
+
session_data = json.load(f)
|
|
199
|
+
except (json.JSONDecodeError, IOError, OSError):
|
|
200
|
+
return "N/A (previous session status file not found)"
|
|
201
|
+
|
|
202
|
+
status = session_data.get("status", "unknown")
|
|
203
|
+
checkpoint = session_data.get("checkpoint_reached", "none")
|
|
204
|
+
current_phase = session_data.get("current_phase", "unknown")
|
|
205
|
+
errors = session_data.get("errors", [])
|
|
206
|
+
|
|
207
|
+
result = "{} (checkpoint: {}, last phase: {})".format(
|
|
208
|
+
status, checkpoint, current_phase
|
|
209
|
+
)
|
|
210
|
+
if errors:
|
|
211
|
+
result += " — errors: {}".format("; ".join(str(e) for e in errors))
|
|
212
|
+
return result
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def resolve_project_root(script_dir):
|
|
216
|
+
"""Resolve project root as the parent of dev-pipeline/."""
|
|
217
|
+
dev_pipeline_dir = os.path.dirname(script_dir)
|
|
218
|
+
project_root = os.path.dirname(dev_pipeline_dir)
|
|
219
|
+
return os.path.abspath(project_root)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def build_replacements(args, refactor, global_context, script_dir):
|
|
223
|
+
"""Build the full dict of placeholder -> replacement value."""
|
|
224
|
+
project_root = resolve_project_root(script_dir)
|
|
225
|
+
|
|
226
|
+
# Platform-aware agent/team path resolution
|
|
227
|
+
platform = os.environ.get("PRIZMKIT_PLATFORM", "")
|
|
228
|
+
home_dir = os.path.expanduser("~")
|
|
229
|
+
|
|
230
|
+
if not platform:
|
|
231
|
+
if os.path.isdir(os.path.join(project_root, ".claude", "agents")):
|
|
232
|
+
platform = "claude"
|
|
233
|
+
else:
|
|
234
|
+
platform = "codebuddy"
|
|
235
|
+
|
|
236
|
+
if platform == "claude":
|
|
237
|
+
agents_dir = os.path.join(project_root, ".claude", "agents")
|
|
238
|
+
team_config_path = os.path.join(project_root, ".claude", "team-info.json")
|
|
239
|
+
else:
|
|
240
|
+
agents_dir = os.path.join(project_root, ".codebuddy", "agents")
|
|
241
|
+
team_config_path = os.path.join(
|
|
242
|
+
home_dir, ".codebuddy", "teams", "prizm-dev-team", "config.json"
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
dev_subagent = os.path.join(agents_dir, "prizm-dev-team-dev.md")
|
|
246
|
+
reviewer_subagent = os.path.join(agents_dir, "prizm-dev-team-reviewer.md")
|
|
247
|
+
|
|
248
|
+
# Session status path
|
|
249
|
+
session_status_path = os.path.join(
|
|
250
|
+
project_root, "dev-pipeline", "refactor-state", "refactors", args.refactor_id,
|
|
251
|
+
"sessions", args.session_id, "session-status.json"
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
prev_status = get_prev_session_status(args.state_dir, args.refactor_id)
|
|
255
|
+
|
|
256
|
+
# Scope
|
|
257
|
+
scope = refactor.get("scope", {})
|
|
258
|
+
|
|
259
|
+
# Behavior preservation
|
|
260
|
+
bp = refactor.get("behavior_preservation", {})
|
|
261
|
+
behavior_strategy = bp.get("strategy", "test-gate") if isinstance(bp, dict) else "test-gate"
|
|
262
|
+
existing_tests = bp.get("existing_tests", []) if isinstance(bp, dict) else []
|
|
263
|
+
new_tests_needed = bp.get("new_tests_needed", []) if isinstance(bp, dict) else []
|
|
264
|
+
|
|
265
|
+
# Format existing tests
|
|
266
|
+
if existing_tests:
|
|
267
|
+
existing_tests_str = "\n".join("- `{}`".format(t) for t in existing_tests)
|
|
268
|
+
else:
|
|
269
|
+
existing_tests_str = "- (none specified)"
|
|
270
|
+
|
|
271
|
+
# Format new tests needed
|
|
272
|
+
if new_tests_needed:
|
|
273
|
+
new_tests_str = "\n".join("- {}".format(t) for t in new_tests_needed)
|
|
274
|
+
else:
|
|
275
|
+
new_tests_str = "- (none specified)"
|
|
276
|
+
|
|
277
|
+
replacements = {
|
|
278
|
+
"{{RUN_ID}}": args.run_id,
|
|
279
|
+
"{{SESSION_ID}}": args.session_id,
|
|
280
|
+
"{{REFACTOR_ID}}": args.refactor_id,
|
|
281
|
+
"{{REFACTOR_TITLE}}": refactor.get("title", ""),
|
|
282
|
+
"{{REFACTOR_TYPE}}": refactor.get("type", "restructure"),
|
|
283
|
+
"{{SCOPE_FILES}}": format_scope_files(scope),
|
|
284
|
+
"{{SCOPE_MODULES}}": format_scope_modules(scope),
|
|
285
|
+
"{{BEHAVIOR_STRATEGY}}": behavior_strategy,
|
|
286
|
+
"{{EXISTING_TESTS}}": existing_tests_str,
|
|
287
|
+
"{{NEW_TESTS_NEEDED}}": new_tests_str,
|
|
288
|
+
"{{PRIORITY}}": refactor.get("priority", "medium"),
|
|
289
|
+
"{{COMPLEXITY}}": refactor.get("complexity", "medium"),
|
|
290
|
+
"{{RETRY_COUNT}}": str(args.retry_count),
|
|
291
|
+
"{{MAX_RETRIES}}": str(DEFAULT_MAX_RETRIES),
|
|
292
|
+
"{{PREV_SESSION_STATUS}}": prev_status,
|
|
293
|
+
"{{RESUME_PHASE}}": args.resume_phase,
|
|
294
|
+
"{{REFACTOR_DESCRIPTION}}": refactor.get("description", ""),
|
|
295
|
+
"{{ACCEPTANCE_CRITERIA}}": format_acceptance_criteria(
|
|
296
|
+
refactor.get("acceptance_criteria", [])
|
|
297
|
+
),
|
|
298
|
+
"{{DEPENDENCIES}}": format_dependencies(
|
|
299
|
+
refactor.get("dependencies", [])
|
|
300
|
+
),
|
|
301
|
+
"{{GLOBAL_CONTEXT}}": format_global_context(global_context, project_root),
|
|
302
|
+
"{{TEAM_CONFIG_PATH}}": team_config_path,
|
|
303
|
+
"{{DEV_SUBAGENT_PATH}}": dev_subagent,
|
|
304
|
+
"{{REVIEWER_SUBAGENT_PATH}}": reviewer_subagent,
|
|
305
|
+
"{{SESSION_STATUS_PATH}}": session_status_path,
|
|
306
|
+
"{{PROJECT_ROOT}}": project_root,
|
|
307
|
+
"{{TIMESTAMP}}": "", # Placeholder — agent fills in timestamp
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return replacements
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def render_template(template_content, replacements):
|
|
314
|
+
"""Render the template by replacing all {{PLACEHOLDER}} variables."""
|
|
315
|
+
content = template_content
|
|
316
|
+
|
|
317
|
+
# Replace all {{PLACEHOLDER}} variables
|
|
318
|
+
for placeholder, value in replacements.items():
|
|
319
|
+
content = content.replace(placeholder, value)
|
|
320
|
+
|
|
321
|
+
return content
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def write_output(output_path, content):
|
|
325
|
+
"""Write the rendered content to the output file."""
|
|
326
|
+
abs_path = os.path.abspath(output_path)
|
|
327
|
+
output_dir = os.path.dirname(abs_path)
|
|
328
|
+
if output_dir and not os.path.isdir(output_dir):
|
|
329
|
+
try:
|
|
330
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
331
|
+
except OSError as e:
|
|
332
|
+
return "Cannot create output directory: {}".format(str(e))
|
|
333
|
+
try:
|
|
334
|
+
with open(abs_path, "w", encoding="utf-8") as f:
|
|
335
|
+
f.write(content)
|
|
336
|
+
except IOError as e:
|
|
337
|
+
return "Cannot write output file: {}".format(str(e))
|
|
338
|
+
return None
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def emit_failure(message):
|
|
342
|
+
"""Emit standardized failure JSON and exit.
|
|
343
|
+
|
|
344
|
+
Uses a different format than error_out() — includes 'success: false'
|
|
345
|
+
for compatibility with the pipeline's JSON parsing expectations.
|
|
346
|
+
"""
|
|
347
|
+
print(json.dumps({"success": False, "error": message}, indent=2, ensure_ascii=False))
|
|
348
|
+
sys.exit(1)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def main():
|
|
352
|
+
args = parse_args()
|
|
353
|
+
|
|
354
|
+
# Resolve script directory
|
|
355
|
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
356
|
+
|
|
357
|
+
# Resolve template path
|
|
358
|
+
if args.template:
|
|
359
|
+
template_path = args.template
|
|
360
|
+
else:
|
|
361
|
+
template_path = os.path.join(
|
|
362
|
+
script_dir, "..", "templates", "refactor-bootstrap-prompt.md"
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
# Load template
|
|
366
|
+
template_content, err = read_text_file(template_path)
|
|
367
|
+
if err:
|
|
368
|
+
emit_failure("Template error: {}".format(err))
|
|
369
|
+
|
|
370
|
+
# Load refactor list
|
|
371
|
+
refactor_list_data, err = load_json_file(args.refactor_list)
|
|
372
|
+
if err:
|
|
373
|
+
emit_failure("Refactor list error: {}".format(err))
|
|
374
|
+
|
|
375
|
+
# Extract refactors array
|
|
376
|
+
refactors = refactor_list_data.get("refactors")
|
|
377
|
+
if not isinstance(refactors, list):
|
|
378
|
+
emit_failure("Refactor list does not contain a 'refactors' array")
|
|
379
|
+
|
|
380
|
+
# Find the target refactor
|
|
381
|
+
refactor = find_refactor(refactors, args.refactor_id)
|
|
382
|
+
if refactor is None:
|
|
383
|
+
emit_failure("Refactor '{}' not found in refactor list".format(args.refactor_id))
|
|
384
|
+
|
|
385
|
+
# Extract global context
|
|
386
|
+
global_context = refactor_list_data.get("global_context", {})
|
|
387
|
+
if not isinstance(global_context, dict):
|
|
388
|
+
global_context = {}
|
|
389
|
+
|
|
390
|
+
# Build replacements
|
|
391
|
+
replacements = build_replacements(args, refactor, global_context, script_dir)
|
|
392
|
+
|
|
393
|
+
# Render the template
|
|
394
|
+
rendered = render_template(template_content, replacements)
|
|
395
|
+
|
|
396
|
+
# Write the output
|
|
397
|
+
err = write_output(args.output, rendered)
|
|
398
|
+
if err:
|
|
399
|
+
emit_failure(err)
|
|
400
|
+
|
|
401
|
+
# Success
|
|
402
|
+
output = {
|
|
403
|
+
"success": True,
|
|
404
|
+
"output_path": os.path.abspath(args.output),
|
|
405
|
+
}
|
|
406
|
+
print(json.dumps(output, indent=2, ensure_ascii=False))
|
|
407
|
+
sys.exit(0)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
if __name__ == "__main__":
|
|
411
|
+
try:
|
|
412
|
+
main()
|
|
413
|
+
except KeyboardInterrupt:
|
|
414
|
+
emit_failure("generate-refactor-prompt interrupted")
|
|
415
|
+
except SystemExit:
|
|
416
|
+
raise
|
|
417
|
+
except Exception as exc:
|
|
418
|
+
LOGGER.exception("Unhandled exception in generate-refactor-prompt")
|
|
419
|
+
emit_failure("Unexpected error: {}".format(str(exc)))
|