prizmkit 1.1.111 → 1.1.113
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundled/VERSION.json +3 -3
- package/bundled/adapters/codex/skill-adapter.js +4 -0
- package/bundled/agents/prizm-dev-team-reviewer.md +12 -1
- package/bundled/dev-pipeline/SCHEMA_ANALYSIS.md +10 -10
- package/bundled/dev-pipeline/prizmkit_runtime/interoperability.py +1 -0
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +20 -73
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +290 -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-tier1.md +7 -1
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +17 -19
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +21 -26
- 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 +8 -2
- package/bundled/dev-pipeline/templates/sections/phase-browser-verification-opencli.md +7 -1
- package/bundled/dev-pipeline/templates/sections/phase-browser-verification.md +7 -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 +104 -26
- package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +14 -0
- package/bundled/dev-pipeline/tests/test_python_runner_parity.py +239 -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 +70 -1
- package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +21 -21
- package/bundled/skills/bugfix-pipeline-launcher/references/configuration.md +2 -2
- package/bundled/skills/feature-pipeline-launcher/SKILL.md +25 -26
- 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 +170 -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 +14 -3
- package/bundled/skills/prizmkit-committer/SKILL.md +59 -47
- package/bundled/skills/prizmkit-deploy/SKILL.md +162 -381
- package/bundled/skills/prizmkit-deploy/references/database-setup.md +12 -3
- 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-init.md +11 -13
- 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/boundary-coverage-protocol.md +1 -1
- package/bundled/skills/prizmkit-test/references/examples.md +11 -9
- 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 +32 -13
- 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 +174 -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,206 @@ 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-session-status", load_section(sections_dir, "bugfix-session-status.md")),
|
|
422
|
+
("bugfix-critical-paths", load_section(sections_dir, "bugfix-critical-paths.md")),
|
|
423
|
+
("failure-capture", load_section(sections_dir, "failure-capture.md")),
|
|
424
|
+
("bugfix-reminders", load_section(sections_dir, "bugfix-reminders.md")),
|
|
425
|
+
])
|
|
426
|
+
return sections
|
|
427
|
+
sections.extend([
|
|
428
|
+
("bugfix-phase-commit-report", load_section(sections_dir, "bugfix-phase-commit-report.md")),
|
|
429
|
+
("bugfix-session-status", load_section(sections_dir, "bugfix-session-status.md")),
|
|
430
|
+
("bugfix-critical-paths", load_section(sections_dir, "bugfix-critical-paths.md")),
|
|
431
|
+
("failure-capture", load_section(sections_dir, "failure-capture.md")),
|
|
432
|
+
("bugfix-reminders", load_section(sections_dir, "bugfix-reminders.md")),
|
|
433
|
+
])
|
|
434
|
+
return sections
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def generate_bugfix_checkpoint(bug_id, session_id, pipeline_mode="standard", sections=None):
|
|
438
|
+
"""Generate a checkpoint definition for bugfix pipeline."""
|
|
439
|
+
if sections is None:
|
|
440
|
+
sections = [
|
|
441
|
+
("bugfix-phase-init", ""),
|
|
442
|
+
("bugfix-phase-diagnose-plan", ""),
|
|
443
|
+
("bugfix-phase-implement", ""),
|
|
444
|
+
("bugfix-phase-review", ""),
|
|
445
|
+
("bugfix-phase-commit-report", ""),
|
|
446
|
+
]
|
|
447
|
+
return generate_checkpoint_from_sections(
|
|
448
|
+
sections,
|
|
449
|
+
BUGFIX_SECTION_TO_SKILL,
|
|
450
|
+
pipeline_mode,
|
|
451
|
+
"bugfix-pipeline",
|
|
452
|
+
bug_id,
|
|
453
|
+
bug_id,
|
|
454
|
+
session_id,
|
|
455
|
+
split_steps=BUGFIX_SPLIT_STEPS,
|
|
456
|
+
)
|
|
460
457
|
|
|
461
458
|
|
|
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
|
|
459
|
+
def merge_bugfix_checkpoint_state(existing, fresh, project_root):
|
|
460
|
+
"""Merge existing bugfix checkpoint state into fresh definition."""
|
|
461
|
+
return merge_checkpoint_state(existing, fresh, project_root, logger=LOGGER)
|
|
477
462
|
|
|
478
463
|
|
|
479
464
|
def emit_failure(message):
|
|
@@ -482,168 +467,81 @@ def emit_failure(message):
|
|
|
482
467
|
sys.exit(1)
|
|
483
468
|
|
|
484
469
|
|
|
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
470
|
def main():
|
|
575
471
|
args = parse_args()
|
|
576
|
-
|
|
577
|
-
# Resolve script directory
|
|
578
472
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
473
|
+
templates_dir = os.path.join(script_dir, "..", "templates")
|
|
474
|
+
sections_dir = os.path.join(templates_dir, "sections")
|
|
579
475
|
|
|
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
476
|
bug_list_data, err = load_json_file(args.bug_list)
|
|
595
477
|
if err:
|
|
596
478
|
emit_failure("Bug list error: {}".format(err))
|
|
597
|
-
|
|
598
|
-
# Extract bugs array
|
|
599
479
|
bugs = bug_list_data.get("bugs")
|
|
600
480
|
if not isinstance(bugs, list):
|
|
601
481
|
emit_failure("Bug fix list does not contain a 'bugs' array")
|
|
602
|
-
|
|
603
|
-
# Find the target bug
|
|
604
482
|
bug = find_bug(bugs, args.bug_id)
|
|
605
483
|
if bug is None:
|
|
606
484
|
emit_failure("Bug '{}' not found in bug fix list".format(args.bug_id))
|
|
607
|
-
|
|
608
|
-
# Extract global context
|
|
609
485
|
global_context = bug_list_data.get("global_context", {})
|
|
610
486
|
if not isinstance(global_context, dict):
|
|
611
487
|
global_context = {}
|
|
612
488
|
|
|
613
|
-
# Build replacements
|
|
614
489
|
replacements = build_replacements(args, bug, global_context, script_dir)
|
|
615
|
-
|
|
616
|
-
|
|
490
|
+
pipeline_mode = replacements["{{PIPELINE_MODE}}"]
|
|
491
|
+
critic_enabled = replacements["{{CRITIC_ENABLED}}"] == "true"
|
|
492
|
+
browser_enabled = replacements["{{BROWSER_ENABLED}}"] == "true"
|
|
493
|
+
manual_gate = bug.get("verification_type", "automated") in ("manual", "hybrid") and not is_recovery_like(args)
|
|
494
|
+
|
|
495
|
+
use_sections = os.path.isdir(sections_dir) and not args.template
|
|
496
|
+
sections = []
|
|
497
|
+
if use_sections:
|
|
498
|
+
try:
|
|
499
|
+
sections = assemble_bugfix_sections(
|
|
500
|
+
pipeline_mode, sections_dir, critic_enabled, browser_enabled, manual_gate,
|
|
501
|
+
)
|
|
502
|
+
rendered = render_from_sections(sections, replacements)
|
|
503
|
+
except FileNotFoundError as exc:
|
|
504
|
+
LOGGER.warning("Bugfix section assembly failed (%s), falling back to legacy template", exc)
|
|
505
|
+
use_sections = False
|
|
506
|
+
|
|
507
|
+
if not use_sections:
|
|
508
|
+
template_path = args.template or os.path.join(templates_dir, "bugfix-bootstrap-prompt.md")
|
|
509
|
+
template_content, err = read_text_file(template_path)
|
|
510
|
+
if err:
|
|
511
|
+
emit_failure("Template error: {}".format(err))
|
|
512
|
+
rendered = render_template(template_content, replacements, bug)
|
|
513
|
+
|
|
514
|
+
is_valid, warnings, errors = validate_rendered(
|
|
515
|
+
rendered,
|
|
516
|
+
required_markers=[
|
|
517
|
+
("## Your Mission", "Mission section"),
|
|
518
|
+
("## Execution", "Execution section"),
|
|
519
|
+
("## Failure Capture", "Failure Capture Protocol"),
|
|
520
|
+
],
|
|
521
|
+
logger=LOGGER,
|
|
522
|
+
)
|
|
523
|
+
if not is_valid:
|
|
524
|
+
LOGGER.error("Rendered bugfix prompt failed validation: %s", "; ".join(errors))
|
|
617
525
|
|
|
618
|
-
# Write the output
|
|
619
526
|
err = write_output(args.output, rendered)
|
|
620
527
|
if err:
|
|
621
528
|
emit_failure(err)
|
|
622
529
|
|
|
623
|
-
# Generate checkpoint file
|
|
624
530
|
project_root = _args_project_root(args, script_dir)
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
checkpoint = generate_bugfix_checkpoint(args.bug_id, args.session_id)
|
|
531
|
+
checkpoint_path = os.path.join(project_root, replacements["{{CHECKPOINT_PATH}}"])
|
|
532
|
+
os.makedirs(os.path.dirname(checkpoint_path), exist_ok=True)
|
|
533
|
+
checkpoint = generate_bugfix_checkpoint(
|
|
534
|
+
args.bug_id, args.session_id, pipeline_mode, sections if use_sections else None,
|
|
535
|
+
)
|
|
631
536
|
|
|
632
|
-
|
|
633
|
-
if (is_resume or is_continuation(args)) and os.path.exists(checkpoint_path):
|
|
537
|
+
if (args.resume_phase != "null" or is_continuation(args)) and os.path.exists(checkpoint_path):
|
|
634
538
|
try:
|
|
635
539
|
with open(checkpoint_path, "r", encoding="utf-8") as f:
|
|
636
540
|
existing = json.load(f)
|
|
637
|
-
checkpoint = merge_bugfix_checkpoint_state(
|
|
638
|
-
|
|
639
|
-
)
|
|
640
|
-
LOGGER.info("Merged existing bugfix checkpoint from %s",
|
|
641
|
-
checkpoint_path)
|
|
541
|
+
checkpoint = merge_bugfix_checkpoint_state(existing, checkpoint, project_root)
|
|
542
|
+
LOGGER.info("Merged existing bugfix checkpoint from %s", checkpoint_path)
|
|
642
543
|
except (json.JSONDecodeError, KeyError) as exc:
|
|
643
|
-
LOGGER.warning(
|
|
644
|
-
"Existing bugfix checkpoint corrupted (%s) — generating fresh",
|
|
645
|
-
exc,
|
|
646
|
-
)
|
|
544
|
+
LOGGER.warning("Existing bugfix checkpoint corrupted (%s) — generating fresh", exc)
|
|
647
545
|
|
|
648
546
|
with open(checkpoint_path, "w", encoding="utf-8") as f:
|
|
649
547
|
json.dump(checkpoint, f, indent=2, ensure_ascii=False)
|
|
@@ -651,55 +549,26 @@ def main():
|
|
|
651
549
|
|
|
652
550
|
if is_continuation(args):
|
|
653
551
|
rendered = append_continuation_handoff(
|
|
654
|
-
rendered, args, project_root, args.task_type, args.bug_id,
|
|
655
|
-
args.bug_id, checkpoint,
|
|
552
|
+
rendered, args, project_root, args.task_type, args.bug_id, args.bug_id, checkpoint,
|
|
656
553
|
)
|
|
657
554
|
err = write_output(args.output, rendered)
|
|
658
555
|
if err:
|
|
659
556
|
emit_failure(err)
|
|
660
557
|
|
|
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
|
-
|
|
558
|
+
critic_count = int(replacements.get("{{CRITIC_COUNT}}", "1"))
|
|
693
559
|
output = {
|
|
694
560
|
"success": True,
|
|
695
561
|
"output_path": os.path.abspath(args.output),
|
|
696
562
|
"checkpoint_path": checkpoint_path,
|
|
697
|
-
"model":
|
|
563
|
+
"model": bug.get("model", ""),
|
|
698
564
|
"pipeline_mode": pipeline_mode,
|
|
699
|
-
"agent_count":
|
|
565
|
+
"agent_count": mode_agent_count(pipeline_mode, critic_enabled, critic_count),
|
|
700
566
|
"critic_enabled": critic_enabled,
|
|
701
|
-
"
|
|
702
|
-
"
|
|
567
|
+
"render_mode": "sections" if use_sections else "legacy",
|
|
568
|
+
"validation_warnings": len(warnings),
|
|
569
|
+
"validation_errors": len(errors),
|
|
570
|
+
"browser_enabled": replacements["{{BROWSER_ENABLED}}"] == "true",
|
|
571
|
+
"browser_tool": replacements["{{BROWSER_TOOL}}"],
|
|
703
572
|
}
|
|
704
573
|
print(json.dumps(output, indent=2, ensure_ascii=False))
|
|
705
574
|
sys.exit(0)
|