prizmkit 1.1.110 → 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 +15 -0
- package/bundled/dev-pipeline/prizmkit_runtime/interoperability.py +1 -0
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +55 -76
- 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/checkpoint-system.md +1 -1
- 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 +122 -5
- package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +122 -0
- package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +124 -0
- package/bundled/dev-pipeline/tests/test_python_runner_parity.py +197 -0
- package/bundled/dev-pipeline/tests/test_unified_cli.py +7 -3
- 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 -94
- package/bundled/skills/prizmkit-code-review/references/review-report-template.md +1 -0
- package/bundled/skills/prizmkit-code-review/references/reviewer-agent-prompt.md +13 -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 bug fix bootstrap prompt from
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
prompt to the specified output path.
|
|
7
|
-
|
|
8
|
-
Usage:
|
|
9
|
-
python3 generate-bugfix-prompt.py \
|
|
10
|
-
--bug-list <path> --bug-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 bug fix bootstrap prompt from bug-fix-list.json.
|
|
3
|
+
|
|
4
|
+
The default path uses modular prompt sections. The legacy monolithic template is
|
|
5
|
+
kept only for explicit --template compatibility.
|
|
14
6
|
"""
|
|
15
7
|
|
|
16
8
|
import argparse
|
|
@@ -19,20 +11,55 @@ 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-bugfix-prompt")
|
|
29
33
|
|
|
30
34
|
|
|
35
|
+
# Section name -> checkpoint contract.
|
|
36
|
+
BUGFIX_SECTION_TO_SKILL = {
|
|
37
|
+
"bugfix-phase-init": ("prizmkit-init", "Initialize", [".prizmkit/bugfix/{slug}"]),
|
|
38
|
+
"bugfix-phase-diagnose-plan": (
|
|
39
|
+
"bug-diagnosis-and-plan", "Diagnose & Plan",
|
|
40
|
+
[".prizmkit/bugfix/{slug}/spec.md", ".prizmkit/bugfix/{slug}/plan.md"],
|
|
41
|
+
),
|
|
42
|
+
"bugfix-phase-critic": ("critic-plan-review", "Critic: Root Cause & Fix Plan Review", []),
|
|
43
|
+
"bugfix-phase-implement": ("prizmkit-implement", "Implement Fix", []),
|
|
44
|
+
"bugfix-phase-review": ("prizmkit-code-review", "Code Review", []),
|
|
45
|
+
"phase-browser": ("browser-verification", "Browser Verification", []),
|
|
46
|
+
"bugfix-phase-commit-report": None,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
BUGFIX_SPLIT_STEPS = {
|
|
50
|
+
"bugfix-phase-commit-report": [
|
|
51
|
+
("prizmkit-retrospective", "Retrospective", []),
|
|
52
|
+
("prizmkit-committer", "Commit", []),
|
|
53
|
+
("bug-report", "Generate Fix Report", [".prizmkit/bugfix/{slug}/fix-report.md"]),
|
|
54
|
+
],
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
31
58
|
def parse_args():
|
|
32
59
|
parser = argparse.ArgumentParser(
|
|
33
60
|
description=(
|
|
34
|
-
"Generate a session-specific bug fix bootstrap prompt from
|
|
35
|
-
"
|
|
61
|
+
"Generate a session-specific bug fix bootstrap prompt from "
|
|
62
|
+
".prizmkit/plans/bug-fix-list.json."
|
|
36
63
|
)
|
|
37
64
|
)
|
|
38
65
|
parser.add_argument("--bug-list", required=True, help="Path to .prizmkit/plans/bug-fix-list.json")
|
|
@@ -44,27 +71,13 @@ def parse_args():
|
|
|
44
71
|
parser.add_argument("--state-dir", default=None, help="State directory (default: .prizmkit/state/bugfix)")
|
|
45
72
|
parser.add_argument("--output", required=True, help="Output path for the rendered prompt")
|
|
46
73
|
parser.add_argument("--project-root", default=None, help="AI-visible project root used for rendered prompt paths")
|
|
47
|
-
parser.add_argument("--template", default=None, help="Custom template path. Defaults to
|
|
48
|
-
parser.add_argument("--mode", default=None, help="Pipeline execution mode override
|
|
49
|
-
parser.add_argument("--critic", default=None, help="Enable critic agent: true/false")
|
|
74
|
+
parser.add_argument("--template", default=None, help="Custom legacy template path. Defaults to section assembly.")
|
|
75
|
+
parser.add_argument("--mode", choices=["lite", "standard", "full"], default=None, help="Pipeline execution mode override")
|
|
76
|
+
parser.add_argument("--critic", choices=["true", "false"], default=None, help="Enable critic agent: true/false")
|
|
50
77
|
add_continuation_args(parser, "bugfix")
|
|
51
|
-
|
|
52
78
|
return parser.parse_args()
|
|
53
79
|
|
|
54
80
|
|
|
55
|
-
def read_text_file(path):
|
|
56
|
-
"""Read and return the text content of a file."""
|
|
57
|
-
abs_path = os.path.abspath(path)
|
|
58
|
-
if not os.path.isfile(abs_path):
|
|
59
|
-
return None, "File not found: {}".format(abs_path)
|
|
60
|
-
try:
|
|
61
|
-
with open(abs_path, "r", encoding="utf-8") as f:
|
|
62
|
-
return f.read(), None
|
|
63
|
-
except IOError as e:
|
|
64
|
-
return None, "Cannot read file: {}".format(str(e))
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
81
|
def _parse_positive_int_env(name, default):
|
|
69
82
|
"""Parse a positive integer environment variable."""
|
|
70
83
|
value = os.environ.get(name, str(default))
|
|
@@ -89,27 +102,26 @@ def format_acceptance_criteria(criteria):
|
|
|
89
102
|
"""Format acceptance criteria as a markdown bullet list."""
|
|
90
103
|
if not criteria:
|
|
91
104
|
return "- (none specified)"
|
|
92
|
-
|
|
93
|
-
for item in criteria:
|
|
94
|
-
lines.append("- {}".format(item))
|
|
95
|
-
return "\n".join(lines)
|
|
105
|
+
return "\n".join("- {}".format(item) for item in criteria)
|
|
96
106
|
|
|
97
107
|
|
|
98
|
-
def
|
|
99
|
-
"""Format
|
|
108
|
+
def format_ac_checklist(criteria):
|
|
109
|
+
"""Format acceptance criteria as a checkbox list."""
|
|
110
|
+
if not criteria:
|
|
111
|
+
return "- [ ] (no acceptance criteria specified)"
|
|
112
|
+
return "\n".join("- [ ] {}".format(item) for item in criteria)
|
|
113
|
+
|
|
100
114
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
"""
|
|
115
|
+
def format_global_context(global_context, project_root=None):
|
|
116
|
+
"""Format global_context dict as a key-value list."""
|
|
104
117
|
if project_root:
|
|
105
118
|
enrich_global_context(global_context, project_root)
|
|
106
|
-
|
|
107
119
|
if not global_context:
|
|
108
120
|
return "- (none specified)"
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
121
|
+
return "\n".join(
|
|
122
|
+
"- **{}**: {}".format(key, value)
|
|
123
|
+
for key, value in sorted(global_context.items())
|
|
124
|
+
)
|
|
113
125
|
|
|
114
126
|
|
|
115
127
|
def format_error_source_details(error_source):
|
|
@@ -120,35 +132,23 @@ def format_error_source_details(error_source):
|
|
|
120
132
|
etype = error_source.get("type", "unknown")
|
|
121
133
|
|
|
122
134
|
if etype == "stack_trace" and error_source.get("stack_trace"):
|
|
123
|
-
lines.
|
|
124
|
-
lines.append("```")
|
|
125
|
-
lines.append(error_source["stack_trace"])
|
|
126
|
-
lines.append("```")
|
|
135
|
+
lines.extend(["- **Stack Trace**:", "```", error_source["stack_trace"], "```"])
|
|
127
136
|
if error_source.get("error_message"):
|
|
128
137
|
lines.append("- **Error Message**: {}".format(error_source["error_message"]))
|
|
129
138
|
if etype == "log_pattern" and error_source.get("log_snippet"):
|
|
130
|
-
lines.
|
|
131
|
-
lines.append("```")
|
|
132
|
-
lines.append(error_source["log_snippet"])
|
|
133
|
-
lines.append("```")
|
|
139
|
+
lines.extend(["- **Log Snippet**:", "```", error_source["log_snippet"], "```"])
|
|
134
140
|
if etype == "failed_test" and error_source.get("failed_test_path"):
|
|
135
141
|
lines.append("- **Failed Test**: `{}`".format(error_source["failed_test_path"]))
|
|
136
142
|
if etype == "user_report" and error_source.get("reproduction_steps"):
|
|
137
143
|
lines.append("- **Reproduction Steps**:")
|
|
138
|
-
for
|
|
139
|
-
lines.append(" {}. {}".format(
|
|
144
|
+
for index, step in enumerate(error_source["reproduction_steps"], 1):
|
|
145
|
+
lines.append(" {}. {}".format(index, step))
|
|
140
146
|
|
|
141
|
-
|
|
142
|
-
lines.append("- (no additional details)")
|
|
143
|
-
return "\n".join(lines)
|
|
147
|
+
return "\n".join(lines or ["- (no additional details)"])
|
|
144
148
|
|
|
145
149
|
|
|
146
150
|
def format_user_context(user_context):
|
|
147
|
-
"""Format user_context array as a markdown section.
|
|
148
|
-
|
|
149
|
-
Returns empty string if user_context is empty or absent,
|
|
150
|
-
so the template placeholder resolves to nothing.
|
|
151
|
-
"""
|
|
151
|
+
"""Format user_context array as a markdown section."""
|
|
152
152
|
if not user_context or not isinstance(user_context, list):
|
|
153
153
|
return ""
|
|
154
154
|
items = [item for item in user_context if isinstance(item, str) and item.strip()]
|
|
@@ -157,12 +157,10 @@ def format_user_context(user_context):
|
|
|
157
157
|
lines = [
|
|
158
158
|
"### User-Provided Context (HIGHEST PRIORITY)",
|
|
159
159
|
"",
|
|
160
|
-
"> The following materials were provided by the user. "
|
|
161
|
-
"They take precedence over AI inference.",
|
|
160
|
+
"> The following materials were provided by the user. They take precedence over AI inference.",
|
|
162
161
|
"",
|
|
163
162
|
]
|
|
164
|
-
for item in items
|
|
165
|
-
lines.append("- {}".format(item))
|
|
163
|
+
lines.extend("- {}".format(item) for item in items)
|
|
166
164
|
return "\n".join(lines)
|
|
167
165
|
|
|
168
166
|
|
|
@@ -170,64 +168,50 @@ def format_environment(env):
|
|
|
170
168
|
"""Format environment dict as a key-value list."""
|
|
171
169
|
if not env or not isinstance(env, dict):
|
|
172
170
|
return "- (not specified)"
|
|
173
|
-
lines = [
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
return "\n".join(lines)
|
|
171
|
+
lines = [
|
|
172
|
+
"- **{}**: {}".format(key, value)
|
|
173
|
+
for key, value in sorted(env.items())
|
|
174
|
+
if value
|
|
175
|
+
]
|
|
176
|
+
return "\n".join(lines) if lines else "- (not specified)"
|
|
180
177
|
|
|
181
178
|
|
|
182
179
|
def get_prev_session_status(state_dir, bug_id):
|
|
183
180
|
"""Read previous session status from state dir if available."""
|
|
184
181
|
if not state_dir:
|
|
185
182
|
return "N/A (first run)"
|
|
186
|
-
|
|
187
183
|
bug_status_path = os.path.join(state_dir, bug_id, "status.json")
|
|
188
184
|
if not os.path.isfile(bug_status_path):
|
|
189
185
|
return "N/A (first run)"
|
|
190
|
-
|
|
191
186
|
try:
|
|
192
187
|
with open(bug_status_path, "r", encoding="utf-8") as f:
|
|
193
188
|
bug_status = json.load(f)
|
|
194
189
|
except (json.JSONDecodeError, IOError):
|
|
195
190
|
return "N/A (could not read bug status)"
|
|
196
|
-
|
|
197
191
|
last_session_id = bug_status.get("last_session_id")
|
|
198
192
|
if not last_session_id:
|
|
199
193
|
return "N/A (first run)"
|
|
200
|
-
|
|
201
|
-
session_status_path = os.path.join(state_dir, bug_id, "sessions",
|
|
202
|
-
last_session_id, "session-status.json"
|
|
203
|
-
)
|
|
194
|
+
session_status_path = os.path.join(state_dir, bug_id, "sessions", last_session_id, "session-status.json")
|
|
204
195
|
if not os.path.isfile(session_status_path):
|
|
205
196
|
return "N/A (previous session status file not found)"
|
|
206
|
-
|
|
207
197
|
try:
|
|
208
198
|
with open(session_status_path, "r", encoding="utf-8") as f:
|
|
209
199
|
session_data = json.load(f)
|
|
210
200
|
except (json.JSONDecodeError, IOError):
|
|
211
201
|
return "N/A (could not read previous session status)"
|
|
212
|
-
|
|
213
|
-
status = session_data.get("status", "unknown")
|
|
214
|
-
checkpoint = session_data.get("checkpoint_reached", "none")
|
|
215
|
-
current_phase = session_data.get("current_phase", "unknown")
|
|
216
|
-
errors = session_data.get("errors", [])
|
|
217
|
-
|
|
218
202
|
result = "{} (checkpoint: {}, last phase: {})".format(
|
|
219
|
-
status,
|
|
203
|
+
session_data.get("status", "unknown"),
|
|
204
|
+
session_data.get("checkpoint_reached", "none"),
|
|
205
|
+
session_data.get("current_phase", "unknown"),
|
|
220
206
|
)
|
|
207
|
+
errors = session_data.get("errors", [])
|
|
221
208
|
if errors:
|
|
222
209
|
result += " — errors: {}".format("; ".join(str(e) for e in errors))
|
|
223
210
|
return result
|
|
224
211
|
|
|
225
212
|
|
|
226
213
|
def resolve_project_root(script_dir):
|
|
227
|
-
"""Resolve project root.
|
|
228
|
-
<project>/.prizmkit/dev-pipeline/scripts/ → <project>
|
|
229
|
-
<repo>/dev-pipeline/scripts/ → <repo>
|
|
230
|
-
"""
|
|
214
|
+
"""Resolve project root from script location."""
|
|
231
215
|
pipeline_dir = os.path.dirname(script_dir)
|
|
232
216
|
pipeline_parent = os.path.dirname(pipeline_dir)
|
|
233
217
|
if os.path.basename(pipeline_parent) == ".prizmkit":
|
|
@@ -236,18 +220,34 @@ def resolve_project_root(script_dir):
|
|
|
236
220
|
|
|
237
221
|
|
|
238
222
|
def _args_project_root(args, script_dir):
|
|
239
|
-
"""Return explicit AI-visible project root or script-layout default."""
|
|
240
223
|
explicit = getattr(args, "project_root", None)
|
|
241
224
|
if explicit:
|
|
242
225
|
return os.path.abspath(explicit)
|
|
243
226
|
return resolve_project_root(script_dir)
|
|
244
227
|
|
|
245
228
|
|
|
229
|
+
def determine_bugfix_mode(args, bug):
|
|
230
|
+
if args.mode:
|
|
231
|
+
return args.mode
|
|
232
|
+
return normalize_mode(
|
|
233
|
+
{"low": "lite", "medium": "lite", "high": "standard", "critical": "full"}.get(
|
|
234
|
+
str(bug.get("severity", "medium")).lower(), "lite"
|
|
235
|
+
)
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def is_recovery_like(args):
|
|
240
|
+
retry_count = 0
|
|
241
|
+
try:
|
|
242
|
+
retry_count = int(getattr(args, "retry_count", 0))
|
|
243
|
+
except (TypeError, ValueError):
|
|
244
|
+
retry_count = 0
|
|
245
|
+
return is_continuation(args) or getattr(args, "resume_phase", "null") != "null" or retry_count > 0
|
|
246
|
+
|
|
247
|
+
|
|
246
248
|
def build_replacements(args, bug, global_context, script_dir):
|
|
247
249
|
"""Build the full dict of placeholder -> replacement value."""
|
|
248
250
|
project_root = _args_project_root(args, script_dir)
|
|
249
|
-
|
|
250
|
-
# Platform-aware agent/team path resolution
|
|
251
251
|
platform = resolve_prompt_platform(project_root)
|
|
252
252
|
home_dir = os.path.expanduser("~")
|
|
253
253
|
|
|
@@ -259,221 +259,199 @@ def build_replacements(args, bug, global_context, script_dir):
|
|
|
259
259
|
team_config_path = os.path.join(project_root, ".codex", "team-info.json")
|
|
260
260
|
else:
|
|
261
261
|
agents_dir = os.path.join(project_root, ".codebuddy", "agents")
|
|
262
|
-
team_config_path = os.path.join(
|
|
263
|
-
home_dir, ".codebuddy", "teams", "prizm-dev-team", "config.json"
|
|
264
|
-
)
|
|
262
|
+
team_config_path = os.path.join(home_dir, ".codebuddy", "teams", "prizm-dev-team", "config.json")
|
|
265
263
|
|
|
266
264
|
agent_ext = ".toml" if platform == "codex" else ".md"
|
|
267
|
-
dev_subagent = os.path.join(agents_dir,
|
|
268
|
-
reviewer_subagent = os.path.join(agents_dir,
|
|
265
|
+
dev_subagent = os.path.join(agents_dir, "prizm-dev-team-dev{}".format(agent_ext))
|
|
266
|
+
reviewer_subagent = os.path.join(agents_dir, "prizm-dev-team-reviewer{}".format(agent_ext))
|
|
267
|
+
critic_subagent = os.path.join(agents_dir, "prizm-dev-team-critic{}".format(agent_ext))
|
|
269
268
|
|
|
270
|
-
# Session status is controlled by the main runner state dir, even when
|
|
271
|
-
# rendered project paths are AI-visible worktree paths.
|
|
272
269
|
state_dir = (
|
|
273
270
|
os.path.abspath(args.state_dir)
|
|
274
271
|
if args.state_dir
|
|
275
272
|
else os.path.join(project_root, ".prizmkit", "state", "bugfix")
|
|
276
273
|
)
|
|
277
|
-
session_status_path = os.path.join(
|
|
278
|
-
state_dir, args.bug_id, "sessions", args.session_id,
|
|
279
|
-
"session-status.json",
|
|
280
|
-
)
|
|
274
|
+
session_status_path = os.path.join(state_dir, args.bug_id, "sessions", args.session_id, "session-status.json")
|
|
281
275
|
|
|
282
|
-
# Error source
|
|
283
276
|
error_source = bug.get("error_source", {})
|
|
284
277
|
error_type = error_source.get("type", "unknown") if isinstance(error_source, dict) else "unknown"
|
|
285
|
-
|
|
286
|
-
# Determine fix scope from affected_modules or title
|
|
287
278
|
affected_modules = bug.get("affected_modules", [])
|
|
288
|
-
if affected_modules
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
browser_tool = browser_interaction.get("tool", "auto")
|
|
314
|
-
if browser_tool not in ("playwright-cli", "opencli", "auto"):
|
|
315
|
-
LOGGER.warning("Unknown browser_interaction.tool '%s', defaulting to 'auto'", browser_tool)
|
|
316
|
-
browser_tool = "auto"
|
|
317
|
-
|
|
318
|
-
steps = browser_interaction.get("verify_steps", [])
|
|
319
|
-
if steps:
|
|
320
|
-
browser_enabled = True
|
|
321
|
-
browser_verify_steps = "\n".join(
|
|
322
|
-
" # Step {}: {}".format(i + 1, step)
|
|
323
|
-
for i, step in enumerate(steps)
|
|
324
|
-
)
|
|
325
|
-
elif browser_interaction.get("url") or browser_interaction.get("enabled", True):
|
|
326
|
-
browser_enabled = True
|
|
327
|
-
browser_verify_steps = " # (reproduce bug and verify fix)"
|
|
279
|
+
fix_scope = affected_modules[0] if affected_modules else (bug.get("title", "unknown").split()[0].lower() if bug.get("title") else "unknown")
|
|
280
|
+
verification_type = bug.get("verification_type", "automated")
|
|
281
|
+
|
|
282
|
+
browser_enabled, browser_tool, browser_verify_steps, browser_blocking = browser_enabled_from_item(bug)
|
|
283
|
+
dev_port = detect_dev_server_port(project_root)
|
|
284
|
+
dev_url = "http://localhost:{}".format(dev_port) if dev_port.isdigit() else "<UNKNOWN_DETECT_FROM_PROJECT_CONFIG>"
|
|
285
|
+
|
|
286
|
+
pipeline_mode = determine_bugfix_mode(args, bug)
|
|
287
|
+
critic_requested = args.critic == "true" if args.critic is not None else bool(bug.get("critic", False))
|
|
288
|
+
critic_enabled = critic_requested and pipeline_mode != "lite"
|
|
289
|
+
if critic_requested and pipeline_mode == "lite":
|
|
290
|
+
LOGGER.warning("Critic requested for bug %s but lite mode skips critic phases", args.bug_id)
|
|
291
|
+
if critic_enabled and not os.path.isfile(critic_subagent):
|
|
292
|
+
LOGGER.warning("Critic enabled but agent file not found: %s. Critic phase will be skipped.", critic_subagent)
|
|
293
|
+
critic_enabled = False
|
|
294
|
+
|
|
295
|
+
recovery_like = is_recovery_like(args)
|
|
296
|
+
manual_or_hybrid = verification_type in ("manual", "hybrid")
|
|
297
|
+
manual_policy = (
|
|
298
|
+
"Recovery-class session: automated verification may substitute for manual/hybrid UAT when evidence is documented."
|
|
299
|
+
if manual_or_hybrid and recovery_like
|
|
300
|
+
else "Fresh manual/hybrid bugfix: stop as partial before commit after automated checks; manual UAT is required."
|
|
301
|
+
if manual_or_hybrid
|
|
302
|
+
else "Automated verification: complete tests/review and proceed to commit when gates pass."
|
|
303
|
+
)
|
|
328
304
|
|
|
329
305
|
replacements = {
|
|
330
|
-
"{{SESSION_LOG_PATH}}": os.path.join(
|
|
331
|
-
".prizmkit", "state", "bugfix", args.bug_id,
|
|
332
|
-
"sessions", args.session_id, "logs", "session.log",
|
|
333
|
-
),
|
|
306
|
+
"{{SESSION_LOG_PATH}}": os.path.join(".prizmkit", "state", "bugfix", args.bug_id, "sessions", args.session_id, "logs", "session.log"),
|
|
334
307
|
"{{RUN_ID}}": args.run_id,
|
|
335
308
|
"{{SESSION_ID}}": args.session_id,
|
|
336
309
|
"{{BUG_ID}}": args.bug_id,
|
|
310
|
+
"{{TASK_ID}}": args.bug_id,
|
|
311
|
+
"{{TASK_TITLE}}": bug.get("title", ""),
|
|
337
312
|
"{{BUG_TITLE}}": bug.get("title", ""),
|
|
338
313
|
"{{SEVERITY}}": bug.get("severity", "medium"),
|
|
339
|
-
"{{
|
|
314
|
+
"{{PIPELINE_MODE}}": pipeline_mode,
|
|
315
|
+
"{{CRITIC_ENABLED}}": "true" if critic_enabled else "false",
|
|
316
|
+
"{{CRITIC_COUNT}}": str(bug.get("critic_count", 1)),
|
|
317
|
+
"{{VERIFICATION_TYPE}}": verification_type,
|
|
318
|
+
"{{MANUAL_VERIFICATION_POLICY}}": manual_policy,
|
|
340
319
|
"{{BUG_DESCRIPTION}}": bug.get("description", ""),
|
|
341
320
|
"{{USER_CONTEXT}}": format_user_context(bug.get("user_context", [])),
|
|
342
321
|
"{{ERROR_SOURCE_TYPE}}": error_type,
|
|
343
322
|
"{{ERROR_SOURCE_DETAILS}}": format_error_source_details(error_source),
|
|
344
|
-
"{{ACCEPTANCE_CRITERIA}}": format_acceptance_criteria(
|
|
345
|
-
|
|
346
|
-
),
|
|
323
|
+
"{{ACCEPTANCE_CRITERIA}}": format_acceptance_criteria(bug.get("acceptance_criteria", [])),
|
|
324
|
+
"{{AC_CHECKLIST}}": format_ac_checklist(bug.get("acceptance_criteria", [])),
|
|
347
325
|
"{{ENVIRONMENT}}": format_environment(bug.get("environment")),
|
|
348
326
|
"{{GLOBAL_CONTEXT}}": format_global_context(global_context, project_root),
|
|
349
327
|
"{{TEAM_CONFIG_PATH}}": team_config_path,
|
|
350
328
|
"{{DEV_SUBAGENT_PATH}}": dev_subagent,
|
|
351
329
|
"{{REVIEWER_SUBAGENT_PATH}}": reviewer_subagent,
|
|
330
|
+
"{{CRITIC_SUBAGENT_PATH}}": critic_subagent,
|
|
352
331
|
"{{SESSION_STATUS_PATH}}": session_status_path,
|
|
353
332
|
"{{PROJECT_ROOT}}": project_root,
|
|
354
|
-
"{{
|
|
355
|
-
|
|
356
|
-
),
|
|
333
|
+
"{{ARTIFACT_DIR}}": ".prizmkit/bugfix/{}".format(args.bug_id),
|
|
334
|
+
"{{CHECKPOINT_PATH}}": os.path.join(".prizmkit", "bugfix", args.bug_id, "workflow-checkpoint.json"),
|
|
357
335
|
"{{CHECKPOINT_SYSTEM}}": read_checkpoint_system_section(script_dir),
|
|
358
336
|
"{{FIX_SCOPE}}": fix_scope,
|
|
359
|
-
"{{TIMESTAMP}}": "",
|
|
337
|
+
"{{TIMESTAMP}}": "",
|
|
360
338
|
"{{BROWSER_ENABLED}}": "true" if browser_enabled else "false",
|
|
361
339
|
"{{BROWSER_TOOL}}": browser_tool,
|
|
362
340
|
"{{BROWSER_VERIFY_STEPS}}": browser_verify_steps,
|
|
341
|
+
"{{BROWSER_BLOCKING}}": "true" if browser_blocking else "false",
|
|
342
|
+
"{{DEV_PORT}}": dev_port,
|
|
343
|
+
"{{DEV_URL}}": dev_url,
|
|
363
344
|
}
|
|
364
|
-
|
|
365
345
|
replacements.update(helper_replacements(script_dir))
|
|
366
|
-
|
|
367
346
|
return replacements
|
|
368
347
|
|
|
369
348
|
|
|
370
349
|
def process_conditional_blocks(content, bug):
|
|
371
|
-
"""Handle conditional blocks based on verification_type and browser_interaction."""
|
|
372
|
-
# Handle verification type blocks
|
|
350
|
+
"""Handle legacy conditional blocks based on verification_type and browser_interaction."""
|
|
373
351
|
vtype = bug.get("verification_type", "automated")
|
|
374
352
|
is_manual_or_hybrid = vtype in ("manual", "hybrid")
|
|
375
|
-
|
|
376
353
|
if is_manual_or_hybrid:
|
|
377
354
|
content = content.replace("{{IF_VERIFICATION_MANUAL_OR_HYBRID}}\n", "")
|
|
378
355
|
content = content.replace("{{IF_VERIFICATION_MANUAL_OR_HYBRID}}", "")
|
|
379
356
|
content = content.replace("{{END_IF_VERIFICATION_MANUAL_OR_HYBRID}}\n", "")
|
|
380
357
|
content = content.replace("{{END_IF_VERIFICATION_MANUAL_OR_HYBRID}}", "")
|
|
381
358
|
else:
|
|
382
|
-
|
|
383
|
-
content = re.sub(
|
|
384
|
-
r"\{\{IF_VERIFICATION_MANUAL_OR_HYBRID\}\}.*?\{\{END_IF_VERIFICATION_MANUAL_OR_HYBRID\}\}\n?",
|
|
385
|
-
"", content, flags=re.DOTALL,
|
|
386
|
-
)
|
|
359
|
+
content = re.sub(r"\{\{IF_VERIFICATION_MANUAL_OR_HYBRID\}\}.*?\{\{END_IF_VERIFICATION_MANUAL_OR_HYBRID\}\}\n?", "", content, flags=re.DOTALL)
|
|
387
360
|
|
|
388
|
-
|
|
389
|
-
browser_interaction = bug.get("browser_interaction")
|
|
390
|
-
browser_enabled = False
|
|
391
|
-
browser_tool = "auto"
|
|
392
|
-
|
|
393
|
-
# Check environment override
|
|
394
|
-
browser_verify_env = os.environ.get("BROWSER_VERIFY", "").lower()
|
|
395
|
-
if browser_verify_env == "false":
|
|
396
|
-
browser_interaction = None
|
|
397
|
-
|
|
398
|
-
# Determine if browser is enabled
|
|
399
|
-
if browser_interaction:
|
|
400
|
-
if isinstance(browser_interaction, bool):
|
|
401
|
-
browser_enabled = True
|
|
402
|
-
elif isinstance(browser_interaction, dict):
|
|
403
|
-
steps = browser_interaction.get("verify_steps", [])
|
|
404
|
-
if steps or browser_interaction.get("url") or browser_interaction.get("enabled", True):
|
|
405
|
-
browser_enabled = True
|
|
406
|
-
browser_tool = browser_interaction.get("tool", "auto")
|
|
407
|
-
|
|
408
|
-
# Process browser interaction blocks
|
|
361
|
+
browser_enabled, browser_tool, _steps, _blocking = browser_enabled_from_item(bug)
|
|
409
362
|
browser_open = "{{IF_BROWSER_INTERACTION}}"
|
|
410
363
|
browser_close = "{{END_IF_BROWSER_INTERACTION}}"
|
|
411
|
-
|
|
412
364
|
if browser_enabled:
|
|
413
|
-
|
|
414
|
-
content = content.replace(
|
|
415
|
-
content = content.replace(browser_open, "")
|
|
416
|
-
content = content.replace(browser_close + "\n", "")
|
|
417
|
-
content = content.replace(browser_close, "")
|
|
365
|
+
content = content.replace(browser_open + "\n", "").replace(browser_open, "")
|
|
366
|
+
content = content.replace(browser_close + "\n", "").replace(browser_close, "")
|
|
418
367
|
else:
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
# Process browser tool selection blocks (nested inside browser interaction)
|
|
424
|
-
tool_variants = ["PLAYWRIGHT", "OPENCLI", "AUTO"]
|
|
425
|
-
active_variant = {
|
|
426
|
-
"playwright-cli": "PLAYWRIGHT",
|
|
427
|
-
"opencli": "OPENCLI",
|
|
428
|
-
"auto": "AUTO",
|
|
429
|
-
}.get(browser_tool, "AUTO")
|
|
430
|
-
|
|
431
|
-
for variant in tool_variants:
|
|
368
|
+
content = re.sub(re.escape(browser_open) + r".*?" + re.escape(browser_close) + r"\n?", "", content, flags=re.DOTALL)
|
|
369
|
+
|
|
370
|
+
active_variant = {"playwright-cli": "PLAYWRIGHT", "opencli": "OPENCLI", "auto": "AUTO"}.get(browser_tool, "AUTO")
|
|
371
|
+
for variant in ("PLAYWRIGHT", "OPENCLI", "AUTO"):
|
|
432
372
|
tool_open = "{{{{IF_BROWSER_TOOL_{}}}}}".format(variant)
|
|
433
373
|
tool_close = "{{{{END_IF_BROWSER_TOOL_{}}}}}".format(variant)
|
|
434
|
-
|
|
435
374
|
if variant == active_variant and browser_enabled:
|
|
436
|
-
|
|
437
|
-
content = content.replace(
|
|
438
|
-
content = content.replace(tool_open, "")
|
|
439
|
-
content = content.replace(tool_close + "\n", "")
|
|
440
|
-
content = content.replace(tool_close, "")
|
|
375
|
+
content = content.replace(tool_open + "\n", "").replace(tool_open, "")
|
|
376
|
+
content = content.replace(tool_close + "\n", "").replace(tool_close, "")
|
|
441
377
|
else:
|
|
442
|
-
|
|
443
|
-
pat = re.escape(tool_open) + r".*?" + re.escape(tool_close) + r"\n?"
|
|
444
|
-
content = re.sub(pat, "", content, flags=re.DOTALL)
|
|
445
|
-
|
|
378
|
+
content = re.sub(re.escape(tool_open) + r".*?" + re.escape(tool_close) + r"\n?", "", content, flags=re.DOTALL)
|
|
446
379
|
return content
|
|
447
380
|
|
|
448
381
|
|
|
449
382
|
def render_template(template_content, replacements, bug):
|
|
450
|
-
"""Render the template by processing conditionals and
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
383
|
+
"""Render the legacy template by processing conditionals and placeholders."""
|
|
384
|
+
return replace_placeholders(process_conditional_blocks(template_content, bug), replacements)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def _bugfix_header(pipeline_mode):
|
|
388
|
+
title = "# Dev-Pipeline Bug Fix Session Bootstrap — {}\n".format(pipeline_mode.title())
|
|
389
|
+
desc = {
|
|
390
|
+
"lite": "**Tier 1 — Single Agent**: direct root-cause fix by the main orchestrator.",
|
|
391
|
+
"standard": "**Tier 2 — Orchestrator + optional Critic**: direct implementation with review gates.",
|
|
392
|
+
"full": "**Tier 3 — Full Bugfix Guardrails**: direct implementation with stronger diagnosis and critic checks.",
|
|
393
|
+
}.get(pipeline_mode, "**Bugfix pipeline**")
|
|
394
|
+
return title + "\n" + desc + "\n"
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def assemble_bugfix_sections(pipeline_mode, sections_dir, critic_enabled, browser_enabled, manual_gate):
|
|
398
|
+
sections = [
|
|
399
|
+
("header", _bugfix_header(pipeline_mode)),
|
|
400
|
+
("bugfix-session-context", load_section(sections_dir, "bugfix-session-context.md")),
|
|
401
|
+
("mission", load_section(sections_dir, "bugfix-mission.md")),
|
|
402
|
+
("bugfix-task-contract", load_section(sections_dir, "bugfix-task-contract.md")),
|
|
403
|
+
("context-budget-rules", load_section(sections_dir, "context-budget-rules.md")),
|
|
404
|
+
("bugfix-artifacts", load_section(sections_dir, "bugfix-artifacts.md")),
|
|
405
|
+
("checkpoint-system", load_section(sections_dir, "checkpoint-system.md")),
|
|
406
|
+
("execution-header", "---\n\n## Execution\n"),
|
|
407
|
+
("bugfix-phase-init", load_section(sections_dir, "bugfix-phase-init.md")),
|
|
408
|
+
("bugfix-phase-diagnose-plan", load_section(sections_dir, "bugfix-phase-diagnose-plan.md")),
|
|
409
|
+
]
|
|
410
|
+
if critic_enabled:
|
|
411
|
+
sections.append(("bugfix-phase-critic", load_section(sections_dir, "bugfix-phase-critic.md")))
|
|
412
|
+
sections.extend([
|
|
413
|
+
("bugfix-phase-implement", load_section(sections_dir, "bugfix-phase-implement.md")),
|
|
414
|
+
("bugfix-phase-review", load_section(sections_dir, "bugfix-phase-review.md")),
|
|
415
|
+
])
|
|
416
|
+
if browser_enabled:
|
|
417
|
+
sections.append(("phase-browser", load_section(sections_dir, "phase-browser-verification-auto.md")))
|
|
418
|
+
if manual_gate:
|
|
419
|
+
sections.append(("bugfix-manual-gate", load_section(sections_dir, "bugfix-phase-manual-verification.md")))
|
|
420
|
+
sections.extend([
|
|
421
|
+
("bugfix-phase-commit-report", load_section(sections_dir, "bugfix-phase-commit-report.md")),
|
|
422
|
+
("bugfix-session-status", load_section(sections_dir, "bugfix-session-status.md")),
|
|
423
|
+
("bugfix-critical-paths", load_section(sections_dir, "bugfix-critical-paths.md")),
|
|
424
|
+
("failure-capture", load_section(sections_dir, "failure-capture.md")),
|
|
425
|
+
("bugfix-reminders", load_section(sections_dir, "bugfix-reminders.md")),
|
|
426
|
+
])
|
|
427
|
+
return sections
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def generate_bugfix_checkpoint(bug_id, session_id, pipeline_mode="standard", sections=None):
|
|
431
|
+
"""Generate a checkpoint definition for bugfix pipeline."""
|
|
432
|
+
if sections is None:
|
|
433
|
+
sections = [
|
|
434
|
+
("bugfix-phase-init", ""),
|
|
435
|
+
("bugfix-phase-diagnose-plan", ""),
|
|
436
|
+
("bugfix-phase-implement", ""),
|
|
437
|
+
("bugfix-phase-review", ""),
|
|
438
|
+
("bugfix-phase-commit-report", ""),
|
|
439
|
+
]
|
|
440
|
+
return generate_checkpoint_from_sections(
|
|
441
|
+
sections,
|
|
442
|
+
BUGFIX_SECTION_TO_SKILL,
|
|
443
|
+
pipeline_mode,
|
|
444
|
+
"bugfix-pipeline",
|
|
445
|
+
bug_id,
|
|
446
|
+
bug_id,
|
|
447
|
+
session_id,
|
|
448
|
+
split_steps=BUGFIX_SPLIT_STEPS,
|
|
449
|
+
)
|
|
460
450
|
|
|
461
451
|
|
|
462
|
-
def
|
|
463
|
-
"""
|
|
464
|
-
|
|
465
|
-
output_dir = os.path.dirname(abs_path)
|
|
466
|
-
if output_dir and not os.path.isdir(output_dir):
|
|
467
|
-
try:
|
|
468
|
-
os.makedirs(output_dir, exist_ok=True)
|
|
469
|
-
except OSError as e:
|
|
470
|
-
return "Cannot create output directory: {}".format(str(e))
|
|
471
|
-
try:
|
|
472
|
-
with open(abs_path, "w", encoding="utf-8") as f:
|
|
473
|
-
f.write(content)
|
|
474
|
-
except IOError as e:
|
|
475
|
-
return "Cannot write output file: {}".format(str(e))
|
|
476
|
-
return None
|
|
452
|
+
def merge_bugfix_checkpoint_state(existing, fresh, project_root):
|
|
453
|
+
"""Merge existing bugfix checkpoint state into fresh definition."""
|
|
454
|
+
return merge_checkpoint_state(existing, fresh, project_root, logger=LOGGER)
|
|
477
455
|
|
|
478
456
|
|
|
479
457
|
def emit_failure(message):
|
|
@@ -482,168 +460,81 @@ def emit_failure(message):
|
|
|
482
460
|
sys.exit(1)
|
|
483
461
|
|
|
484
462
|
|
|
485
|
-
# ============================================================
|
|
486
|
-
# Checkpoint generation for bugfix pipeline
|
|
487
|
-
# ============================================================
|
|
488
|
-
|
|
489
|
-
BUGFIX_STEPS = [
|
|
490
|
-
("prizmkit-init", "Initialize", []),
|
|
491
|
-
("bug-diagnosis-and-plan", "Diagnose & Plan",
|
|
492
|
-
[".prizmkit/bugfix/{slug}/spec.md",
|
|
493
|
-
".prizmkit/bugfix/{slug}/plan.md"]),
|
|
494
|
-
("prizmkit-implement", "Implement Fix", []),
|
|
495
|
-
("prizmkit-code-review", "Code Review", []),
|
|
496
|
-
("prizmkit-committer", "Commit", ["--headless"]),
|
|
497
|
-
("bug-report", "Generate Fix Report",
|
|
498
|
-
[".prizmkit/bugfix/{slug}/fix-report.md"]),
|
|
499
|
-
]
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
def generate_bugfix_checkpoint(bug_id, session_id):
|
|
503
|
-
"""Generate a checkpoint definition for bugfix pipeline.
|
|
504
|
-
|
|
505
|
-
Returns a dict suitable for writing as workflow-checkpoint.json.
|
|
506
|
-
"""
|
|
507
|
-
steps = []
|
|
508
|
-
prev_id = None
|
|
509
|
-
for i, (skill, name, artifacts) in enumerate(BUGFIX_STEPS, 1):
|
|
510
|
-
step_id = "S{:02d}".format(i)
|
|
511
|
-
steps.append({
|
|
512
|
-
"id": step_id,
|
|
513
|
-
"skill": skill,
|
|
514
|
-
"name": name,
|
|
515
|
-
"status": "pending",
|
|
516
|
-
"required_artifacts": [a.replace("{slug}", bug_id) for a in artifacts],
|
|
517
|
-
"depends_on": prev_id,
|
|
518
|
-
})
|
|
519
|
-
prev_id = step_id
|
|
520
|
-
|
|
521
|
-
return {
|
|
522
|
-
"version": 1,
|
|
523
|
-
"workflow_type": "bugfix-pipeline",
|
|
524
|
-
"pipeline_mode": "single",
|
|
525
|
-
"item_id": bug_id,
|
|
526
|
-
"item_slug": bug_id,
|
|
527
|
-
"session_id": session_id,
|
|
528
|
-
"steps": steps,
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
def merge_bugfix_checkpoint_state(existing, fresh, project_root):
|
|
533
|
-
"""Merge existing bugfix checkpoint state into fresh definition.
|
|
534
|
-
|
|
535
|
-
Same logic as feature pipeline: validate artifacts, break chain on
|
|
536
|
-
first invalid step.
|
|
537
|
-
"""
|
|
538
|
-
existing_status = {}
|
|
539
|
-
existing_artifacts = {}
|
|
540
|
-
for step in existing.get("steps", []):
|
|
541
|
-
existing_status[step["skill"]] = step["status"]
|
|
542
|
-
existing_artifacts[step["skill"]] = step.get("required_artifacts", [])
|
|
543
|
-
|
|
544
|
-
valid_completed = set()
|
|
545
|
-
for skill_key, status in existing_status.items():
|
|
546
|
-
if status == "completed":
|
|
547
|
-
artifacts = existing_artifacts.get(skill_key, [])
|
|
548
|
-
if all(os.path.exists(os.path.join(project_root, a))
|
|
549
|
-
for a in artifacts):
|
|
550
|
-
valid_completed.add(skill_key)
|
|
551
|
-
else:
|
|
552
|
-
LOGGER.warning(
|
|
553
|
-
"Step '%s' was completed but artifacts missing — "
|
|
554
|
-
"resetting to pending", skill_key
|
|
555
|
-
)
|
|
556
|
-
elif status == "skipped":
|
|
557
|
-
valid_completed.add(skill_key)
|
|
558
|
-
|
|
559
|
-
chain_broken = False
|
|
560
|
-
for step in fresh["steps"]:
|
|
561
|
-
if chain_broken:
|
|
562
|
-
step["status"] = "pending"
|
|
563
|
-
continue
|
|
564
|
-
prev = existing_status.get(step["skill"])
|
|
565
|
-
if step["skill"] in valid_completed:
|
|
566
|
-
step["status"] = prev
|
|
567
|
-
else:
|
|
568
|
-
chain_broken = True
|
|
569
|
-
step["status"] = "pending"
|
|
570
|
-
|
|
571
|
-
return fresh
|
|
572
|
-
|
|
573
|
-
|
|
574
463
|
def main():
|
|
575
464
|
args = parse_args()
|
|
576
|
-
|
|
577
|
-
# Resolve script directory
|
|
578
465
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
466
|
+
templates_dir = os.path.join(script_dir, "..", "templates")
|
|
467
|
+
sections_dir = os.path.join(templates_dir, "sections")
|
|
579
468
|
|
|
580
|
-
# Resolve template path
|
|
581
|
-
if args.template:
|
|
582
|
-
template_path = args.template
|
|
583
|
-
else:
|
|
584
|
-
template_path = os.path.join(
|
|
585
|
-
script_dir, "..", "templates", "bugfix-bootstrap-prompt.md"
|
|
586
|
-
)
|
|
587
|
-
|
|
588
|
-
# Load template
|
|
589
|
-
template_content, err = read_text_file(template_path)
|
|
590
|
-
if err:
|
|
591
|
-
emit_failure("Template error: {}".format(err))
|
|
592
|
-
|
|
593
|
-
# Load bug fix list
|
|
594
469
|
bug_list_data, err = load_json_file(args.bug_list)
|
|
595
470
|
if err:
|
|
596
471
|
emit_failure("Bug list error: {}".format(err))
|
|
597
|
-
|
|
598
|
-
# Extract bugs array
|
|
599
472
|
bugs = bug_list_data.get("bugs")
|
|
600
473
|
if not isinstance(bugs, list):
|
|
601
474
|
emit_failure("Bug fix list does not contain a 'bugs' array")
|
|
602
|
-
|
|
603
|
-
# Find the target bug
|
|
604
475
|
bug = find_bug(bugs, args.bug_id)
|
|
605
476
|
if bug is None:
|
|
606
477
|
emit_failure("Bug '{}' not found in bug fix list".format(args.bug_id))
|
|
607
|
-
|
|
608
|
-
# Extract global context
|
|
609
478
|
global_context = bug_list_data.get("global_context", {})
|
|
610
479
|
if not isinstance(global_context, dict):
|
|
611
480
|
global_context = {}
|
|
612
481
|
|
|
613
|
-
# Build replacements
|
|
614
482
|
replacements = build_replacements(args, bug, global_context, script_dir)
|
|
615
|
-
|
|
616
|
-
|
|
483
|
+
pipeline_mode = replacements["{{PIPELINE_MODE}}"]
|
|
484
|
+
critic_enabled = replacements["{{CRITIC_ENABLED}}"] == "true"
|
|
485
|
+
browser_enabled = replacements["{{BROWSER_ENABLED}}"] == "true"
|
|
486
|
+
manual_gate = bug.get("verification_type", "automated") in ("manual", "hybrid") and not is_recovery_like(args)
|
|
487
|
+
|
|
488
|
+
use_sections = os.path.isdir(sections_dir) and not args.template
|
|
489
|
+
sections = []
|
|
490
|
+
if use_sections:
|
|
491
|
+
try:
|
|
492
|
+
sections = assemble_bugfix_sections(
|
|
493
|
+
pipeline_mode, sections_dir, critic_enabled, browser_enabled, manual_gate,
|
|
494
|
+
)
|
|
495
|
+
rendered = render_from_sections(sections, replacements)
|
|
496
|
+
except FileNotFoundError as exc:
|
|
497
|
+
LOGGER.warning("Bugfix section assembly failed (%s), falling back to legacy template", exc)
|
|
498
|
+
use_sections = False
|
|
499
|
+
|
|
500
|
+
if not use_sections:
|
|
501
|
+
template_path = args.template or os.path.join(templates_dir, "bugfix-bootstrap-prompt.md")
|
|
502
|
+
template_content, err = read_text_file(template_path)
|
|
503
|
+
if err:
|
|
504
|
+
emit_failure("Template error: {}".format(err))
|
|
505
|
+
rendered = render_template(template_content, replacements, bug)
|
|
506
|
+
|
|
507
|
+
is_valid, warnings, errors = validate_rendered(
|
|
508
|
+
rendered,
|
|
509
|
+
required_markers=[
|
|
510
|
+
("## Your Mission", "Mission section"),
|
|
511
|
+
("## Execution", "Execution section"),
|
|
512
|
+
("## Failure Capture", "Failure Capture Protocol"),
|
|
513
|
+
],
|
|
514
|
+
logger=LOGGER,
|
|
515
|
+
)
|
|
516
|
+
if not is_valid:
|
|
517
|
+
LOGGER.error("Rendered bugfix prompt failed validation: %s", "; ".join(errors))
|
|
617
518
|
|
|
618
|
-
# Write the output
|
|
619
519
|
err = write_output(args.output, rendered)
|
|
620
520
|
if err:
|
|
621
521
|
emit_failure(err)
|
|
622
522
|
|
|
623
|
-
# Generate checkpoint file
|
|
624
523
|
project_root = _args_project_root(args, script_dir)
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
checkpoint = generate_bugfix_checkpoint(args.bug_id, args.session_id)
|
|
524
|
+
checkpoint_path = os.path.join(project_root, replacements["{{CHECKPOINT_PATH}}"])
|
|
525
|
+
os.makedirs(os.path.dirname(checkpoint_path), exist_ok=True)
|
|
526
|
+
checkpoint = generate_bugfix_checkpoint(
|
|
527
|
+
args.bug_id, args.session_id, pipeline_mode, sections if use_sections else None,
|
|
528
|
+
)
|
|
631
529
|
|
|
632
|
-
|
|
633
|
-
if (is_resume or is_continuation(args)) and os.path.exists(checkpoint_path):
|
|
530
|
+
if (args.resume_phase != "null" or is_continuation(args)) and os.path.exists(checkpoint_path):
|
|
634
531
|
try:
|
|
635
532
|
with open(checkpoint_path, "r", encoding="utf-8") as f:
|
|
636
533
|
existing = json.load(f)
|
|
637
|
-
checkpoint = merge_bugfix_checkpoint_state(
|
|
638
|
-
|
|
639
|
-
)
|
|
640
|
-
LOGGER.info("Merged existing bugfix checkpoint from %s",
|
|
641
|
-
checkpoint_path)
|
|
534
|
+
checkpoint = merge_bugfix_checkpoint_state(existing, checkpoint, project_root)
|
|
535
|
+
LOGGER.info("Merged existing bugfix checkpoint from %s", checkpoint_path)
|
|
642
536
|
except (json.JSONDecodeError, KeyError) as exc:
|
|
643
|
-
LOGGER.warning(
|
|
644
|
-
"Existing bugfix checkpoint corrupted (%s) — generating fresh",
|
|
645
|
-
exc,
|
|
646
|
-
)
|
|
537
|
+
LOGGER.warning("Existing bugfix checkpoint corrupted (%s) — generating fresh", exc)
|
|
647
538
|
|
|
648
539
|
with open(checkpoint_path, "w", encoding="utf-8") as f:
|
|
649
540
|
json.dump(checkpoint, f, indent=2, ensure_ascii=False)
|
|
@@ -651,55 +542,26 @@ def main():
|
|
|
651
542
|
|
|
652
543
|
if is_continuation(args):
|
|
653
544
|
rendered = append_continuation_handoff(
|
|
654
|
-
rendered, args, project_root, args.task_type, args.bug_id,
|
|
655
|
-
args.bug_id, checkpoint,
|
|
545
|
+
rendered, args, project_root, args.task_type, args.bug_id, args.bug_id, checkpoint,
|
|
656
546
|
)
|
|
657
547
|
err = write_output(args.output, rendered)
|
|
658
548
|
if err:
|
|
659
549
|
emit_failure(err)
|
|
660
550
|
|
|
661
|
-
|
|
662
|
-
bug_critic = bug.get("critic", False)
|
|
663
|
-
if args.critic is not None:
|
|
664
|
-
critic_enabled = str(args.critic).lower() == "true"
|
|
665
|
-
else:
|
|
666
|
-
critic_enabled = bool(bug_critic)
|
|
667
|
-
|
|
668
|
-
pipeline_mode = args.mode or "standard"
|
|
669
|
-
agent_count = 5 if critic_enabled else 3
|
|
670
|
-
|
|
671
|
-
# Success
|
|
672
|
-
bug_model = bug.get("model", "")
|
|
673
|
-
# Extract browser interaction state for output
|
|
674
|
-
browser_interaction = bug.get("browser_interaction")
|
|
675
|
-
browser_enabled = False
|
|
676
|
-
browser_tool = "auto"
|
|
677
|
-
|
|
678
|
-
browser_verify_env = os.environ.get("BROWSER_VERIFY", "").lower()
|
|
679
|
-
if browser_verify_env == "false":
|
|
680
|
-
browser_interaction = None
|
|
681
|
-
|
|
682
|
-
if browser_interaction:
|
|
683
|
-
if isinstance(browser_interaction, bool):
|
|
684
|
-
browser_enabled = True
|
|
685
|
-
elif isinstance(browser_interaction, dict):
|
|
686
|
-
steps = browser_interaction.get("verify_steps", [])
|
|
687
|
-
if steps or browser_interaction.get("url") or browser_interaction.get("enabled", True):
|
|
688
|
-
browser_enabled = True
|
|
689
|
-
browser_tool = browser_interaction.get("tool", "auto")
|
|
690
|
-
if browser_tool not in ("playwright-cli", "opencli", "auto"):
|
|
691
|
-
browser_tool = "auto"
|
|
692
|
-
|
|
551
|
+
critic_count = int(replacements.get("{{CRITIC_COUNT}}", "1"))
|
|
693
552
|
output = {
|
|
694
553
|
"success": True,
|
|
695
554
|
"output_path": os.path.abspath(args.output),
|
|
696
555
|
"checkpoint_path": checkpoint_path,
|
|
697
|
-
"model":
|
|
556
|
+
"model": bug.get("model", ""),
|
|
698
557
|
"pipeline_mode": pipeline_mode,
|
|
699
|
-
"agent_count":
|
|
558
|
+
"agent_count": mode_agent_count(pipeline_mode, critic_enabled, critic_count),
|
|
700
559
|
"critic_enabled": critic_enabled,
|
|
701
|
-
"
|
|
702
|
-
"
|
|
560
|
+
"render_mode": "sections" if use_sections else "legacy",
|
|
561
|
+
"validation_warnings": len(warnings),
|
|
562
|
+
"validation_errors": len(errors),
|
|
563
|
+
"browser_enabled": replacements["{{BROWSER_ENABLED}}"] == "true",
|
|
564
|
+
"browser_tool": replacements["{{BROWSER_TOOL}}"],
|
|
703
565
|
}
|
|
704
566
|
print(json.dumps(output, indent=2, ensure_ascii=False))
|
|
705
567
|
sys.exit(0)
|