prizmkit 1.0.45 → 1.0.66
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundled/VERSION.json +3 -3
- package/bundled/adapters/claude/agent-adapter.js +2 -1
- package/bundled/adapters/claude/command-adapter.js +3 -3
- package/bundled/agents/prizm-dev-team-dev.md +1 -1
- package/bundled/dev-pipeline/README.md +6 -8
- package/bundled/dev-pipeline/assets/prizm-dev-team-integration.md +24 -19
- package/bundled/dev-pipeline/launch-bugfix-daemon.sh +2 -2
- package/bundled/dev-pipeline/launch-daemon.sh +2 -2
- package/bundled/dev-pipeline/lib/branch.sh +76 -0
- package/bundled/dev-pipeline/run-bugfix.sh +58 -149
- package/bundled/dev-pipeline/run.sh +60 -153
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +17 -4
- package/bundled/dev-pipeline/scripts/parse-stream-progress.py +2 -2
- package/bundled/dev-pipeline/templates/bootstrap-tier1.md +16 -27
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +20 -32
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +32 -53
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +29 -41
- package/bundled/dev-pipeline/templates/session-status-schema.json +1 -1
- package/bundled/dev-pipeline/tests/conftest.py +19 -126
- package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +207 -0
- package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +128 -141
- package/bundled/dev-pipeline/tests/test_utils.py +51 -110
- package/bundled/rules/prizm/prizm-commit-workflow.md +3 -3
- package/bundled/skills/_metadata.json +15 -16
- package/bundled/skills/app-planner/SKILL.md +8 -7
- package/bundled/skills/bug-fix-workflow/SKILL.md +171 -0
- package/bundled/skills/bug-planner/SKILL.md +25 -33
- package/bundled/skills/bug-planner/scripts/validate-bug-list.py +156 -0
- package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +5 -7
- package/bundled/skills/dev-pipeline-launcher/SKILL.md +4 -6
- package/bundled/skills/feature-workflow/SKILL.md +25 -42
- package/bundled/skills/prizm-kit/SKILL.md +61 -23
- package/bundled/skills/prizm-kit/assets/{claude-md-template.md → project-memory-template.md} +3 -3
- package/bundled/skills/prizmkit-analyze/SKILL.md +44 -33
- package/bundled/skills/prizmkit-clarify/SKILL.md +40 -30
- package/bundled/skills/prizmkit-code-review/SKILL.md +58 -45
- package/bundled/skills/prizmkit-committer/SKILL.md +30 -68
- package/bundled/skills/prizmkit-implement/SKILL.md +60 -28
- package/bundled/skills/prizmkit-init/SKILL.md +57 -66
- package/bundled/skills/prizmkit-plan/SKILL.md +60 -23
- package/bundled/skills/prizmkit-prizm-docs/SKILL.md +74 -19
- package/bundled/skills/prizmkit-prizm-docs/assets/PRIZM-SPEC.md +23 -23
- package/bundled/skills/prizmkit-retrospective/SKILL.md +142 -65
- package/bundled/skills/prizmkit-retrospective/assets/retrospective-template.md +13 -0
- package/bundled/skills/prizmkit-specify/SKILL.md +69 -15
- package/bundled/skills/refactor-workflow/SKILL.md +116 -52
- package/bundled/team/prizm-dev-team.json +2 -2
- package/package.json +1 -1
- package/src/scaffold.js +4 -4
- package/bundled/dev-pipeline/lib/worktree.sh +0 -164
- package/bundled/dev-pipeline/tests/__init__.py +0 -0
- package/bundled/dev-pipeline/tests/test_check_session.py +0 -131
- package/bundled/dev-pipeline/tests/test_cleanup_logs.py +0 -119
- package/bundled/dev-pipeline/tests/test_detect_stuck.py +0 -207
- package/bundled/dev-pipeline/tests/test_generate_prompt.py +0 -190
- package/bundled/dev-pipeline/tests/test_init_bugfix_pipeline.py +0 -153
- package/bundled/dev-pipeline/tests/test_init_pipeline.py +0 -241
- package/bundled/dev-pipeline/tests/test_update_bug_status.py +0 -142
- package/bundled/dev-pipeline/tests/test_update_feature_status.py +0 -338
- package/bundled/dev-pipeline/tests/test_worktree.py +0 -236
- package/bundled/dev-pipeline/tests/test_worktree_integration.py +0 -796
- package/bundled/skills/prizm-kit/assets/codebuddy-md-template.md +0 -35
- package/bundled/skills/prizm-kit/assets/hooks/prizm-commit-hook.json +0 -15
- package/bundled/skills/prizmkit-summarize/SKILL.md +0 -51
- package/bundled/skills/prizmkit-summarize/assets/registry-template.md +0 -18
- package/bundled/templates/hooks/commit-intent-claude.json +0 -26
- /package/bundled/templates/hooks/{commit-intent-codebuddy.json → commit-intent.json} +0 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""Tests for generate-bootstrap-prompt.py core functions."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from generate_bootstrap_prompt import (
|
|
6
|
+
compute_feature_slug,
|
|
7
|
+
find_feature,
|
|
8
|
+
format_acceptance_criteria,
|
|
9
|
+
format_global_context,
|
|
10
|
+
get_completed_dependencies,
|
|
11
|
+
determine_pipeline_mode,
|
|
12
|
+
process_conditional_blocks,
|
|
13
|
+
process_mode_blocks,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
# compute_feature_slug
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
class TestComputeFeatureSlug:
|
|
22
|
+
def test_basic(self):
|
|
23
|
+
assert compute_feature_slug("F-001", "Project Infrastructure Setup") == "001-project-infrastructure-setup"
|
|
24
|
+
|
|
25
|
+
def test_strips_special_chars(self):
|
|
26
|
+
assert compute_feature_slug("F-012", "Add (User) Auth!") == "012-add-user-auth"
|
|
27
|
+
|
|
28
|
+
def test_pads_numeric(self):
|
|
29
|
+
assert compute_feature_slug("F-1", "Init") == "001-init"
|
|
30
|
+
|
|
31
|
+
def test_collapses_hyphens(self):
|
|
32
|
+
assert compute_feature_slug("F-003", "foo - - bar") == "003-foo---bar" or \
|
|
33
|
+
compute_feature_slug("F-003", "foo bar") == "003-foo-bar"
|
|
34
|
+
|
|
35
|
+
def test_empty_title(self):
|
|
36
|
+
result = compute_feature_slug("F-099", "")
|
|
37
|
+
assert result == "099-"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
# find_feature
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
class TestFindFeature:
|
|
45
|
+
def test_found(self):
|
|
46
|
+
features = [{"id": "F-001", "title": "A"}, {"id": "F-002", "title": "B"}]
|
|
47
|
+
assert find_feature(features, "F-002") == {"id": "F-002", "title": "B"}
|
|
48
|
+
|
|
49
|
+
def test_not_found(self):
|
|
50
|
+
assert find_feature([{"id": "F-001"}], "F-999") is None
|
|
51
|
+
|
|
52
|
+
def test_empty_list(self):
|
|
53
|
+
assert find_feature([], "F-001") is None
|
|
54
|
+
|
|
55
|
+
def test_non_dict_items(self):
|
|
56
|
+
assert find_feature(["garbage", 42, None, {"id": "F-001"}], "F-001") == {"id": "F-001"}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
# format_acceptance_criteria
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
class TestFormatAcceptanceCriteria:
|
|
64
|
+
def test_none(self):
|
|
65
|
+
assert format_acceptance_criteria(None) == "- (none specified)"
|
|
66
|
+
|
|
67
|
+
def test_empty(self):
|
|
68
|
+
assert format_acceptance_criteria([]) == "- (none specified)"
|
|
69
|
+
|
|
70
|
+
def test_items(self):
|
|
71
|
+
result = format_acceptance_criteria(["Users can log in", "Password reset works"])
|
|
72
|
+
assert "- Users can log in" in result
|
|
73
|
+
assert "- Password reset works" in result
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# ---------------------------------------------------------------------------
|
|
77
|
+
# format_global_context
|
|
78
|
+
# ---------------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
class TestFormatGlobalContext:
|
|
81
|
+
def test_none(self):
|
|
82
|
+
assert format_global_context(None) == "- (none specified)"
|
|
83
|
+
|
|
84
|
+
def test_empty(self):
|
|
85
|
+
assert format_global_context({}) == "- (none specified)"
|
|
86
|
+
|
|
87
|
+
def test_dict(self):
|
|
88
|
+
result = format_global_context({"framework": "React", "lang": "TypeScript"})
|
|
89
|
+
assert "**framework**: React" in result
|
|
90
|
+
assert "**lang**: TypeScript" in result
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# ---------------------------------------------------------------------------
|
|
94
|
+
# get_completed_dependencies
|
|
95
|
+
# ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
class TestGetCompletedDependencies:
|
|
98
|
+
def test_no_deps(self):
|
|
99
|
+
feature = {"id": "F-002", "dependencies": []}
|
|
100
|
+
assert "no dependencies" in get_completed_dependencies([], feature)
|
|
101
|
+
|
|
102
|
+
def test_with_completed_dep(self):
|
|
103
|
+
features = [
|
|
104
|
+
{"id": "F-001", "title": "Setup", "status": "completed"},
|
|
105
|
+
{"id": "F-002", "title": "Auth", "dependencies": ["F-001"]},
|
|
106
|
+
]
|
|
107
|
+
result = get_completed_dependencies(features, features[1])
|
|
108
|
+
assert "F-001" in result
|
|
109
|
+
assert "completed" in result
|
|
110
|
+
|
|
111
|
+
def test_no_completed_dep(self):
|
|
112
|
+
features = [
|
|
113
|
+
{"id": "F-001", "title": "Setup", "status": "pending"},
|
|
114
|
+
{"id": "F-002", "title": "Auth", "dependencies": ["F-001"]},
|
|
115
|
+
]
|
|
116
|
+
result = get_completed_dependencies(features, features[1])
|
|
117
|
+
assert "no completed dependencies" in result
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# ---------------------------------------------------------------------------
|
|
121
|
+
# determine_pipeline_mode
|
|
122
|
+
# ---------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
class TestDeterminePipelineMode:
|
|
125
|
+
def test_low(self):
|
|
126
|
+
assert determine_pipeline_mode("low") == "lite"
|
|
127
|
+
|
|
128
|
+
def test_medium(self):
|
|
129
|
+
assert determine_pipeline_mode("medium") == "standard"
|
|
130
|
+
|
|
131
|
+
def test_high(self):
|
|
132
|
+
assert determine_pipeline_mode("high") == "full"
|
|
133
|
+
|
|
134
|
+
def test_critical(self):
|
|
135
|
+
assert determine_pipeline_mode("critical") == "full"
|
|
136
|
+
|
|
137
|
+
def test_unknown(self):
|
|
138
|
+
assert determine_pipeline_mode("banana") == "standard"
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# ---------------------------------------------------------------------------
|
|
142
|
+
# process_conditional_blocks
|
|
143
|
+
# ---------------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
class TestProcessConditionalBlocks:
|
|
146
|
+
def test_fresh_start_keeps_fresh_block(self):
|
|
147
|
+
tpl = "before\n{{IF_FRESH_START}}\nfresh content\n{{END_IF_FRESH_START}}\nafter"
|
|
148
|
+
result = process_conditional_blocks(tpl, "null")
|
|
149
|
+
assert "fresh content" in result
|
|
150
|
+
assert "IF_FRESH_START" not in result
|
|
151
|
+
|
|
152
|
+
def test_fresh_start_removes_resume_block(self):
|
|
153
|
+
tpl = "before\n{{IF_RESUME}}\nresume content\n{{END_IF_RESUME}}\nafter"
|
|
154
|
+
result = process_conditional_blocks(tpl, "null")
|
|
155
|
+
assert "resume content" not in result
|
|
156
|
+
|
|
157
|
+
def test_resume_keeps_resume_block(self):
|
|
158
|
+
tpl = "before\n{{IF_RESUME}}\nresume content\n{{END_IF_RESUME}}\nafter"
|
|
159
|
+
result = process_conditional_blocks(tpl, "3")
|
|
160
|
+
assert "resume content" in result
|
|
161
|
+
assert "IF_RESUME" not in result
|
|
162
|
+
|
|
163
|
+
def test_resume_removes_fresh_block(self):
|
|
164
|
+
tpl = "before\n{{IF_FRESH_START}}\nfresh content\n{{END_IF_FRESH_START}}\nafter"
|
|
165
|
+
result = process_conditional_blocks(tpl, "3")
|
|
166
|
+
assert "fresh content" not in result
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# ---------------------------------------------------------------------------
|
|
170
|
+
# process_mode_blocks
|
|
171
|
+
# ---------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
class TestProcessModeBlocks:
|
|
174
|
+
def test_lite_mode_keeps_lite(self):
|
|
175
|
+
tpl = "{{IF_MODE_LITE}}lite content{{END_IF_MODE_LITE}}"
|
|
176
|
+
result = process_mode_blocks(tpl, "lite", init_done=True)
|
|
177
|
+
assert "lite content" in result
|
|
178
|
+
assert "IF_MODE" not in result
|
|
179
|
+
|
|
180
|
+
def test_lite_mode_removes_full(self):
|
|
181
|
+
tpl = "{{IF_MODE_FULL}}full content{{END_IF_MODE_FULL}}"
|
|
182
|
+
result = process_mode_blocks(tpl, "lite", init_done=True)
|
|
183
|
+
assert "full content" not in result
|
|
184
|
+
|
|
185
|
+
def test_init_done_keeps_init_done_block(self):
|
|
186
|
+
tpl = "{{IF_INIT_DONE}}\ninit done\n{{END_IF_INIT_DONE}}"
|
|
187
|
+
result = process_mode_blocks(tpl, "standard", init_done=True)
|
|
188
|
+
assert "init done" in result
|
|
189
|
+
|
|
190
|
+
def test_init_needed_when_not_done(self):
|
|
191
|
+
tpl = "{{IF_INIT_NEEDED}}\nneed init\n{{END_IF_INIT_NEEDED}}"
|
|
192
|
+
result = process_mode_blocks(tpl, "standard", init_done=False)
|
|
193
|
+
assert "need init" in result
|
|
194
|
+
|
|
195
|
+
def test_self_evolve_keeps_self_evolve_and_full(self):
|
|
196
|
+
tpl = (
|
|
197
|
+
"{{IF_MODE_SELF_EVOLVE}}se content{{END_IF_MODE_SELF_EVOLVE}}"
|
|
198
|
+
"{{IF_MODE_FULL}}full content{{END_IF_MODE_FULL}}"
|
|
199
|
+
)
|
|
200
|
+
result = process_mode_blocks(tpl, "self-evolve", init_done=True)
|
|
201
|
+
assert "se content" in result
|
|
202
|
+
assert "full content" in result
|
|
203
|
+
|
|
204
|
+
def test_self_evolve_removes_lite(self):
|
|
205
|
+
tpl = "{{IF_MODE_LITE}}lite content{{END_IF_MODE_LITE}}"
|
|
206
|
+
result = process_mode_blocks(tpl, "self-evolve", init_done=True)
|
|
207
|
+
assert "lite content" not in result
|
|
@@ -1,181 +1,168 @@
|
|
|
1
|
-
"""Tests for generate-bugfix-prompt.py."""
|
|
1
|
+
"""Tests for generate-bugfix-prompt.py core functions."""
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
from generate_bugfix_prompt import (
|
|
4
|
+
find_bug,
|
|
5
|
+
format_acceptance_criteria,
|
|
6
|
+
format_global_context,
|
|
7
|
+
format_error_source_details,
|
|
8
|
+
format_environment,
|
|
9
|
+
process_conditional_blocks,
|
|
10
|
+
render_template,
|
|
11
|
+
)
|
|
6
12
|
|
|
7
13
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
os.path.dirname(__file__), "..", "scripts", "generate-bugfix-prompt.py"
|
|
12
|
-
)
|
|
13
|
-
spec = importlib.util.spec_from_file_location("generate_bugfix_prompt", path)
|
|
14
|
-
mod = importlib.util.module_from_spec(spec)
|
|
15
|
-
sys.modules["generate_bugfix_prompt"] = mod
|
|
16
|
-
spec.loader.exec_module(mod)
|
|
17
|
-
return mod
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
# find_bug
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
18
17
|
|
|
18
|
+
class TestFindBug:
|
|
19
|
+
def test_found(self):
|
|
20
|
+
bugs = [{"id": "B-001", "title": "A"}, {"id": "B-002", "title": "B"}]
|
|
21
|
+
assert find_bug(bugs, "B-002") == {"id": "B-002", "title": "B"}
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
format_global_context = gen_bugfix.format_global_context
|
|
23
|
-
format_error_source_details = gen_bugfix.format_error_source_details
|
|
24
|
-
format_environment = gen_bugfix.format_environment
|
|
25
|
-
process_conditional_blocks = gen_bugfix.process_conditional_blocks
|
|
23
|
+
def test_not_found(self):
|
|
24
|
+
assert find_bug([{"id": "B-001"}], "B-999") is None
|
|
26
25
|
|
|
26
|
+
def test_empty_list(self):
|
|
27
|
+
assert find_bug([], "B-001") is None
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
result = format_acceptance_criteria(["Fix A", "Fix B"])
|
|
31
|
-
assert result == "- Fix A\n- Fix B"
|
|
29
|
+
def test_non_dict_items(self):
|
|
30
|
+
assert find_bug(["garbage", 42, None, {"id": "B-001"}], "B-001") == {"id": "B-001"}
|
|
32
31
|
|
|
33
|
-
def test_empty(self):
|
|
34
|
-
result = format_acceptance_criteria([])
|
|
35
|
-
assert "none specified" in result
|
|
36
|
-
|
|
37
|
-
def test_none(self):
|
|
38
|
-
result = format_acceptance_criteria(None)
|
|
39
|
-
assert "none specified" in result
|
|
40
32
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class TestFormatGlobalContext:
|
|
47
|
-
def test_normal(self):
|
|
48
|
-
result = format_global_context({"lang": "Python"})
|
|
49
|
-
assert "**lang**" in result
|
|
50
|
-
assert "Python" in result
|
|
51
|
-
|
|
52
|
-
def test_empty(self):
|
|
53
|
-
assert "none specified" in format_global_context({})
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
# format_error_source_details
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
54
36
|
|
|
37
|
+
class TestFormatErrorSourceDetails:
|
|
55
38
|
def test_none(self):
|
|
56
|
-
|
|
39
|
+
result = format_error_source_details(None)
|
|
40
|
+
assert "no error source" in result
|
|
57
41
|
|
|
42
|
+
def test_empty_dict(self):
|
|
43
|
+
result = format_error_source_details({})
|
|
44
|
+
assert "no error source" in result
|
|
58
45
|
|
|
59
|
-
class TestFormatErrorSourceDetails:
|
|
60
46
|
def test_stack_trace(self):
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
"error_message": "Something broke",
|
|
65
|
-
}
|
|
66
|
-
result = format_error_source_details(error_source)
|
|
47
|
+
source = {"type": "stack_trace", "stack_trace": "Error at line 42"}
|
|
48
|
+
result = format_error_source_details(source)
|
|
49
|
+
assert "Error at line 42" in result
|
|
67
50
|
assert "Stack Trace" in result
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
51
|
+
|
|
52
|
+
def test_error_message(self):
|
|
53
|
+
source = {"type": "unknown", "error_message": "Something went wrong"}
|
|
54
|
+
result = format_error_source_details(source)
|
|
55
|
+
assert "Something went wrong" in result
|
|
71
56
|
|
|
72
57
|
def test_log_pattern(self):
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
result = format_error_source_details(error_source)
|
|
78
|
-
assert "Log Snippet" in result
|
|
58
|
+
source = {"type": "log_pattern", "log_snippet": "FATAL: connection refused"}
|
|
59
|
+
result = format_error_source_details(source)
|
|
60
|
+
assert "connection refused" in result
|
|
79
61
|
|
|
80
62
|
def test_failed_test(self):
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
result = format_error_source_details(error_source)
|
|
86
|
-
assert "Failed Test" in result
|
|
87
|
-
assert "test_auth.py" in result
|
|
63
|
+
source = {"type": "failed_test", "failed_test_path": "tests/auth.test.js"}
|
|
64
|
+
result = format_error_source_details(source)
|
|
65
|
+
assert "tests/auth.test.js" in result
|
|
88
66
|
|
|
89
67
|
def test_user_report(self):
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
result = format_error_source_details(error_source)
|
|
95
|
-
assert "Reproduction Steps" in result
|
|
96
|
-
assert "Step 1" in result
|
|
97
|
-
assert "Step 2" in result
|
|
98
|
-
|
|
99
|
-
def test_none_source(self):
|
|
100
|
-
result = format_error_source_details(None)
|
|
101
|
-
assert "no error source details" in result
|
|
68
|
+
source = {"type": "user_report", "reproduction_steps": ["Click login", "Enter bad password"]}
|
|
69
|
+
result = format_error_source_details(source)
|
|
70
|
+
assert "Click login" in result
|
|
71
|
+
assert "Enter bad password" in result
|
|
102
72
|
|
|
103
|
-
def test_empty_source(self):
|
|
104
|
-
result = format_error_source_details({})
|
|
105
|
-
assert "no error source details" in result
|
|
106
|
-
|
|
107
|
-
def test_non_dict_source(self):
|
|
108
|
-
result = format_error_source_details("not a dict")
|
|
109
|
-
assert "no error source details" in result
|
|
110
|
-
|
|
111
|
-
def test_unknown_type_with_error_message(self):
|
|
112
|
-
error_source = {
|
|
113
|
-
"type": "unknown",
|
|
114
|
-
"error_message": "Something happened",
|
|
115
|
-
}
|
|
116
|
-
result = format_error_source_details(error_source)
|
|
117
|
-
assert "Error Message" in result
|
|
118
73
|
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
# format_environment
|
|
76
|
+
# ---------------------------------------------------------------------------
|
|
119
77
|
|
|
120
78
|
class TestFormatEnvironment:
|
|
121
|
-
def
|
|
122
|
-
|
|
123
|
-
assert "**browser**" in result
|
|
124
|
-
assert "**os**" in result
|
|
79
|
+
def test_none(self):
|
|
80
|
+
assert "not specified" in format_environment(None)
|
|
125
81
|
|
|
126
82
|
def test_empty(self):
|
|
127
|
-
|
|
128
|
-
assert "not specified" in result
|
|
83
|
+
assert "not specified" in format_environment({})
|
|
129
84
|
|
|
130
|
-
def
|
|
131
|
-
result = format_environment(
|
|
132
|
-
assert "
|
|
133
|
-
|
|
134
|
-
def test_non_dict(self):
|
|
135
|
-
result = format_environment("not a dict")
|
|
136
|
-
assert "not specified" in result
|
|
85
|
+
def test_with_values(self):
|
|
86
|
+
result = format_environment({"os": "Linux", "node": "20.1"})
|
|
87
|
+
assert "**node**: 20.1" in result
|
|
88
|
+
assert "**os**: Linux" in result
|
|
137
89
|
|
|
138
|
-
def
|
|
139
|
-
result = format_environment({"os": "
|
|
140
|
-
assert "os" in result
|
|
141
|
-
|
|
142
|
-
assert "empty_key" not in result
|
|
90
|
+
def test_skips_empty_values(self):
|
|
91
|
+
result = format_environment({"os": "Linux", "browser": ""})
|
|
92
|
+
assert "**os**: Linux" in result
|
|
93
|
+
assert "browser" not in result
|
|
143
94
|
|
|
144
|
-
def test_all_empty_values(self):
|
|
145
|
-
result = format_environment({"a": "", "b": None})
|
|
146
|
-
assert "not specified" in result
|
|
147
95
|
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
# process_conditional_blocks
|
|
98
|
+
# ---------------------------------------------------------------------------
|
|
148
99
|
|
|
149
100
|
class TestProcessConditionalBlocks:
|
|
150
|
-
def
|
|
101
|
+
def test_automated_removes_manual_block(self):
|
|
102
|
+
tpl = "before\n{{IF_VERIFICATION_MANUAL_OR_HYBRID}}\nmanual content\n{{END_IF_VERIFICATION_MANUAL_OR_HYBRID}}\nafter"
|
|
103
|
+
bug = {"verification_type": "automated"}
|
|
104
|
+
result = process_conditional_blocks(tpl, bug)
|
|
105
|
+
assert "manual content" not in result
|
|
106
|
+
assert "before" in result
|
|
107
|
+
assert "after" in result
|
|
108
|
+
|
|
109
|
+
def test_manual_keeps_block(self):
|
|
110
|
+
tpl = "before\n{{IF_VERIFICATION_MANUAL_OR_HYBRID}}\nmanual content\n{{END_IF_VERIFICATION_MANUAL_OR_HYBRID}}\nafter"
|
|
151
111
|
bug = {"verification_type": "manual"}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
assert "
|
|
155
|
-
assert "{{IF_VERIFICATION" not in result
|
|
112
|
+
result = process_conditional_blocks(tpl, bug)
|
|
113
|
+
assert "manual content" in result
|
|
114
|
+
assert "IF_VERIFICATION" not in result
|
|
156
115
|
|
|
157
|
-
def
|
|
116
|
+
def test_hybrid_keeps_block(self):
|
|
117
|
+
tpl = "{{IF_VERIFICATION_MANUAL_OR_HYBRID}}hybrid{{END_IF_VERIFICATION_MANUAL_OR_HYBRID}}"
|
|
158
118
|
bug = {"verification_type": "hybrid"}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
assert "hybrid stuff" in result
|
|
119
|
+
result = process_conditional_blocks(tpl, bug)
|
|
120
|
+
assert "hybrid" in result
|
|
162
121
|
|
|
163
|
-
def
|
|
164
|
-
|
|
165
|
-
content = "before\n{{IF_VERIFICATION_MANUAL_OR_HYBRID}}\nmanual stuff\n{{END_IF_VERIFICATION_MANUAL_OR_HYBRID}}\nafter"
|
|
166
|
-
result = process_conditional_blocks(content, bug)
|
|
167
|
-
assert "manual stuff" not in result
|
|
168
|
-
assert "after" in result
|
|
169
|
-
|
|
170
|
-
def test_no_verification_type_defaults_automated(self):
|
|
122
|
+
def test_default_is_automated(self):
|
|
123
|
+
tpl = "{{IF_VERIFICATION_MANUAL_OR_HYBRID}}content{{END_IF_VERIFICATION_MANUAL_OR_HYBRID}}"
|
|
171
124
|
bug = {}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
assert "rest" in result
|
|
125
|
+
result = process_conditional_blocks(tpl, bug)
|
|
126
|
+
assert "content" not in result
|
|
127
|
+
|
|
176
128
|
|
|
177
|
-
|
|
129
|
+
# ---------------------------------------------------------------------------
|
|
130
|
+
# render_template (integration)
|
|
131
|
+
# ---------------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
class TestRenderTemplate:
|
|
134
|
+
def test_placeholders_replaced(self):
|
|
135
|
+
tpl = "Bug: {{BUG_ID}} — {{BUG_TITLE}}"
|
|
136
|
+
replacements = {"{{BUG_ID}}": "B-042", "{{BUG_TITLE}}": "Login crash"}
|
|
137
|
+
bug = {"verification_type": "automated"}
|
|
138
|
+
result = render_template(tpl, replacements, bug)
|
|
139
|
+
assert result == "Bug: B-042 — Login crash"
|
|
140
|
+
|
|
141
|
+
def test_conditional_and_replacement(self):
|
|
142
|
+
tpl = (
|
|
143
|
+
"{{IF_VERIFICATION_MANUAL_OR_HYBRID}}manual{{END_IF_VERIFICATION_MANUAL_OR_HYBRID}}"
|
|
144
|
+
"id={{BUG_ID}}"
|
|
145
|
+
)
|
|
146
|
+
replacements = {"{{BUG_ID}}": "B-001"}
|
|
178
147
|
bug = {"verification_type": "automated"}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
assert
|
|
148
|
+
result = render_template(tpl, replacements, bug)
|
|
149
|
+
assert "manual" not in result
|
|
150
|
+
assert "id=B-001" in result
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# ---------------------------------------------------------------------------
|
|
154
|
+
# Reuse shared helpers (same as bootstrap prompt — verify they are present)
|
|
155
|
+
# ---------------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
class TestSharedHelpers:
|
|
158
|
+
def test_format_acceptance_criteria_items(self):
|
|
159
|
+
result = format_acceptance_criteria(["Fix works", "No regression"])
|
|
160
|
+
assert "- Fix works" in result
|
|
161
|
+
assert "- No regression" in result
|
|
162
|
+
|
|
163
|
+
def test_format_acceptance_criteria_empty(self):
|
|
164
|
+
assert "none specified" in format_acceptance_criteria([])
|
|
165
|
+
|
|
166
|
+
def test_format_global_context_dict(self):
|
|
167
|
+
result = format_global_context({"lang": "Python"})
|
|
168
|
+
assert "**lang**: Python" in result
|