prizmkit 1.1.111 → 1.1.112
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/agents/prizm-dev-team-reviewer.md +12 -1
- package/bundled/dev-pipeline/prizmkit_runtime/interoperability.py +1 -0
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +19 -73
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +283 -421
- package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +1 -1
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +259 -481
- package/bundled/dev-pipeline/scripts/init-bugfix-pipeline.py +34 -0
- package/bundled/dev-pipeline/scripts/init-pipeline.py +12 -0
- package/bundled/dev-pipeline/scripts/init-refactor-pipeline.py +24 -1
- package/bundled/dev-pipeline/scripts/prompt_framework.py +312 -0
- package/bundled/dev-pipeline/scripts/update-bug-status.py +87 -20
- package/bundled/dev-pipeline/scripts/update-feature-status.py +82 -16
- package/bundled/dev-pipeline/scripts/update-refactor-status.py +68 -28
- package/bundled/dev-pipeline/scripts/utils.py +171 -0
- package/bundled/dev-pipeline/templates/bootstrap-prompt.md +1 -1
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +10 -18
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +14 -25
- package/bundled/dev-pipeline/templates/bug-fix-list-schema.json +9 -1
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +11 -8
- package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +122 -182
- package/bundled/dev-pipeline/templates/sections/bugfix-artifacts.md +16 -0
- package/bundled/dev-pipeline/templates/sections/bugfix-critical-paths.md +12 -0
- package/bundled/dev-pipeline/templates/sections/bugfix-mission.md +9 -0
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-commit-report.md +31 -0
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-critic.md +22 -0
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-diagnose-plan.md +23 -0
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-implement.md +20 -0
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-init.md +9 -0
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-manual-verification.md +15 -0
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +29 -0
- package/bundled/dev-pipeline/templates/sections/bugfix-reminders.md +11 -0
- package/bundled/dev-pipeline/templates/sections/bugfix-session-context.md +7 -0
- package/bundled/dev-pipeline/templates/sections/bugfix-session-status.md +31 -0
- package/bundled/dev-pipeline/templates/sections/bugfix-task-contract.md +40 -0
- package/bundled/dev-pipeline/templates/sections/failure-capture.md +2 -2
- package/bundled/dev-pipeline/templates/sections/phase-browser-verification-auto.md +1 -1
- package/bundled/dev-pipeline/templates/sections/refactor-artifacts.md +17 -0
- package/bundled/dev-pipeline/templates/sections/refactor-critical-paths.md +13 -0
- package/bundled/dev-pipeline/templates/sections/refactor-mission.md +9 -0
- package/bundled/dev-pipeline/templates/sections/refactor-phase-commit-report.md +37 -0
- package/bundled/dev-pipeline/templates/sections/refactor-phase-critic.md +22 -0
- package/bundled/dev-pipeline/templates/sections/refactor-phase-implement.md +16 -0
- package/bundled/dev-pipeline/templates/sections/refactor-phase-init.md +9 -0
- package/bundled/dev-pipeline/templates/sections/refactor-phase-plan.md +22 -0
- package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +19 -0
- package/bundled/dev-pipeline/templates/sections/refactor-reminders.md +11 -0
- package/bundled/dev-pipeline/templates/sections/refactor-session-context.md +7 -0
- package/bundled/dev-pipeline/templates/sections/refactor-session-status.md +28 -0
- package/bundled/dev-pipeline/templates/sections/refactor-task-contract.md +52 -0
- package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +17 -5
- package/bundled/dev-pipeline/tests/test_python_runner_parity.py +197 -0
- package/bundled/dev-pipeline/tests/test_unified_cli.py +6 -2
- package/bundled/dev-pipeline/tests/test_utils.py +66 -1
- package/bundled/skills/_metadata.json +1 -1
- package/bundled/skills/app-planner/SKILL.md +8 -7
- package/bundled/skills/app-planner/references/project-brief-guide.md +2 -2
- package/bundled/skills/app-planner/references/rules/backend/derivation-rules.md +1 -1
- package/bundled/skills/app-planner/references/rules/frontend/derivation-rules.md +1 -1
- package/bundled/skills/app-planner/references/rules-configuration.md +53 -26
- package/bundled/skills/bug-fix-workflow/SKILL.md +191 -336
- package/bundled/skills/bug-planner/SKILL.md +6 -5
- package/bundled/skills/bug-planner/references/critic-and-verification.md +3 -3
- package/bundled/skills/bug-planner/references/schema-validation.md +2 -1
- package/bundled/skills/bug-planner/scripts/validate-bug-list.py +30 -1
- package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +16 -16
- package/bundled/skills/bugfix-pipeline-launcher/references/configuration.md +2 -2
- package/bundled/skills/feature-pipeline-launcher/SKILL.md +19 -11
- package/bundled/skills/feature-planner/SKILL.md +9 -11
- package/bundled/skills/feature-planner/assets/planning-guide.md +1 -1
- package/bundled/skills/feature-planner/references/decomposition-patterns.md +1 -1
- package/bundled/skills/feature-planner/references/error-recovery.md +2 -1
- package/bundled/skills/feature-planner/references/incremental-feature-planning.md +2 -2
- package/bundled/skills/feature-planner/references/new-project-planning.md +35 -39
- package/bundled/skills/feature-planner/scripts/validate-and-generate.py +1 -1
- package/bundled/skills/feature-workflow/SKILL.md +169 -298
- package/bundled/skills/prizmkit/SKILL.md +103 -57
- package/bundled/skills/prizmkit-code-review/SKILL.md +97 -116
- package/bundled/skills/prizmkit-code-review/references/review-report-template.md +1 -0
- package/bundled/skills/prizmkit-code-review/references/reviewer-agent-prompt.md +12 -1
- package/bundled/skills/prizmkit-committer/SKILL.md +59 -47
- package/bundled/skills/prizmkit-deploy/SKILL.md +162 -381
- package/bundled/skills/prizmkit-deploy/references/ssh-adapter-flow.md +220 -0
- package/bundled/skills/prizmkit-implement/SKILL.md +51 -40
- package/bundled/skills/prizmkit-init/SKILL.md +4 -3
- package/bundled/skills/prizmkit-plan/SKILL.md +85 -70
- package/bundled/skills/prizmkit-prizm-docs/SKILL.md +94 -73
- package/bundled/skills/prizmkit-prizm-docs/assets/prizm-docs-format.md +20 -18
- package/bundled/skills/prizmkit-prizm-docs/references/op-update.md +18 -14
- package/bundled/skills/prizmkit-retrospective/SKILL.md +56 -48
- package/bundled/skills/prizmkit-retrospective/references/structural-sync-steps.md +79 -27
- package/bundled/skills/prizmkit-test/SKILL.md +138 -141
- package/bundled/skills/prizmkit-test/references/examples.md +10 -8
- package/bundled/skills/prizmkit-test/references/test-generation-steps.md +1 -1
- package/bundled/skills/prizmkit-test/references/test-report-template.md +2 -2
- package/bundled/skills/recovery-workflow/SKILL.md +195 -256
- package/bundled/skills/recovery-workflow/evals/evals.json +21 -13
- package/bundled/skills/recovery-workflow/references/detection.md +48 -39
- package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +258 -322
- package/bundled/skills/refactor-pipeline-launcher/SKILL.md +29 -11
- package/bundled/skills/refactor-planner/SKILL.md +2 -2
- package/bundled/skills/refactor-planner/references/behavior-preservation.md +24 -19
- package/bundled/skills/refactor-planner/references/fast-path.md +2 -4
- package/bundled/skills/refactor-planner/references/planning-phases.md +2 -2
- package/bundled/skills/refactor-workflow/SKILL.md +173 -307
- package/package.json +1 -1
- package/src/scaffold.js +5 -0
- package/bundled/dev-pipeline/templates/agent-prompts/reviewer-review.md +0 -6
- package/bundled/skills/feature-workflow/references/brainstorm-guide.md +0 -137
- 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
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
""
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
126
|
-
"
|
|
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
|
|
139
|
-
parser.add_argument("--mode", default=None, help="Pipeline execution mode override
|
|
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
|
-
|
|
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
|
|
190
|
-
|
|
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
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
|
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
|
|
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
|
|
313
|
-
if isinstance(
|
|
314
|
-
refactor_map[
|
|
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
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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,
|
|
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,
|
|
434
|
-
reviewer_subagent = os.path.join(agents_dir,
|
|
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
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
if
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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
|
-
|
|
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
|
-
"{{
|
|
538
|
-
|
|
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}}": "",
|
|
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("{{
|
|
565
|
-
|
|
566
|
-
|
|
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
|
-
|
|
594
|
-
content = content.replace(
|
|
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
|
-
|
|
600
|
-
|
|
601
|
-
|
|
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
|
-
|
|
617
|
-
content = content.replace(
|
|
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
|
-
|
|
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
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
#
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
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
|
|
644
|
-
|
|
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
|
-
|
|
712
|
-
|
|
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
|
-
|
|
722
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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":
|
|
569
|
+
"model": refactor.get("model", ""),
|
|
795
570
|
"pipeline_mode": pipeline_mode,
|
|
796
|
-
"agent_count":
|
|
571
|
+
"agent_count": mode_agent_count(pipeline_mode, critic_enabled, critic_count),
|
|
797
572
|
"critic_enabled": critic_enabled,
|
|
798
|
-
"
|
|
799
|
-
"
|
|
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)
|