prizmkit 1.0.45 → 1.0.58

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.
Files changed (64) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/adapters/claude/agent-adapter.js +2 -1
  3. package/bundled/adapters/claude/command-adapter.js +4 -3
  4. package/bundled/agents/prizm-dev-team-dev.md +1 -1
  5. package/bundled/dev-pipeline/README.md +3 -4
  6. package/bundled/dev-pipeline/assets/prizm-dev-team-integration.md +2 -3
  7. package/bundled/dev-pipeline/launch-bugfix-daemon.sh +2 -2
  8. package/bundled/dev-pipeline/launch-daemon.sh +2 -2
  9. package/bundled/dev-pipeline/lib/branch.sh +76 -0
  10. package/bundled/dev-pipeline/run-bugfix.sh +58 -149
  11. package/bundled/dev-pipeline/run.sh +60 -153
  12. package/bundled/dev-pipeline/scripts/parse-stream-progress.py +1 -1
  13. package/bundled/dev-pipeline/templates/bootstrap-tier1.md +8 -16
  14. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +10 -18
  15. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +20 -24
  16. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +6 -6
  17. package/bundled/dev-pipeline/tests/conftest.py +19 -131
  18. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +207 -0
  19. package/bundled/dev-pipeline/tests/test_utils.py +51 -110
  20. package/bundled/rules/prizm/prizm-commit-workflow.md +3 -3
  21. package/bundled/skills/_metadata.json +15 -16
  22. package/bundled/skills/app-planner/SKILL.md +8 -7
  23. package/bundled/skills/bug-fix-workflow/SKILL.md +174 -0
  24. package/bundled/skills/bug-planner/SKILL.md +20 -32
  25. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +3 -5
  26. package/bundled/skills/dev-pipeline-launcher/SKILL.md +4 -6
  27. package/bundled/skills/feature-workflow/SKILL.md +25 -42
  28. package/bundled/skills/prizm-kit/SKILL.md +57 -21
  29. package/bundled/skills/prizm-kit/assets/{claude-md-template.md → project-memory-template.md} +2 -2
  30. package/bundled/skills/prizmkit-analyze/SKILL.md +41 -29
  31. package/bundled/skills/prizmkit-clarify/SKILL.md +40 -30
  32. package/bundled/skills/prizmkit-code-review/SKILL.md +48 -43
  33. package/bundled/skills/prizmkit-committer/SKILL.md +30 -68
  34. package/bundled/skills/prizmkit-implement/SKILL.md +48 -26
  35. package/bundled/skills/prizmkit-init/SKILL.md +57 -66
  36. package/bundled/skills/prizmkit-plan/SKILL.md +46 -20
  37. package/bundled/skills/prizmkit-prizm-docs/SKILL.md +60 -19
  38. package/bundled/skills/prizmkit-prizm-docs/assets/PRIZM-SPEC.md +23 -23
  39. package/bundled/skills/prizmkit-retrospective/SKILL.md +142 -65
  40. package/bundled/skills/prizmkit-retrospective/assets/retrospective-template.md +13 -0
  41. package/bundled/skills/prizmkit-specify/SKILL.md +63 -13
  42. package/bundled/skills/refactor-workflow/SKILL.md +105 -49
  43. package/bundled/team/prizm-dev-team.json +2 -2
  44. package/package.json +1 -1
  45. package/src/scaffold.js +3 -3
  46. package/bundled/dev-pipeline/lib/worktree.sh +0 -164
  47. package/bundled/dev-pipeline/tests/__init__.py +0 -0
  48. package/bundled/dev-pipeline/tests/test_check_session.py +0 -131
  49. package/bundled/dev-pipeline/tests/test_cleanup_logs.py +0 -119
  50. package/bundled/dev-pipeline/tests/test_detect_stuck.py +0 -207
  51. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +0 -181
  52. package/bundled/dev-pipeline/tests/test_generate_prompt.py +0 -190
  53. package/bundled/dev-pipeline/tests/test_init_bugfix_pipeline.py +0 -153
  54. package/bundled/dev-pipeline/tests/test_init_pipeline.py +0 -241
  55. package/bundled/dev-pipeline/tests/test_update_bug_status.py +0 -142
  56. package/bundled/dev-pipeline/tests/test_update_feature_status.py +0 -338
  57. package/bundled/dev-pipeline/tests/test_worktree.py +0 -236
  58. package/bundled/dev-pipeline/tests/test_worktree_integration.py +0 -796
  59. package/bundled/skills/prizm-kit/assets/codebuddy-md-template.md +0 -35
  60. package/bundled/skills/prizm-kit/assets/hooks/prizm-commit-hook.json +0 -15
  61. package/bundled/skills/prizmkit-summarize/SKILL.md +0 -51
  62. package/bundled/skills/prizmkit-summarize/assets/registry-template.md +0 -18
  63. package/bundled/templates/hooks/commit-intent-claude.json +0 -26
  64. /package/bundled/templates/hooks/{commit-intent-codebuddy.json → commit-intent.json} +0 -0
@@ -1,133 +1,21 @@
1
- """Shared fixtures for dev-pipeline tests."""
1
+ """Shared fixtures and import helpers for dev-pipeline tests."""
2
2
 
3
- import json
3
+ import importlib
4
4
  import os
5
- import pytest
6
-
7
-
8
- @pytest.fixture
9
- def sample_feature_list():
10
- """A valid feature-list.json structure."""
11
- return {
12
- "$schema": "dev-pipeline-feature-list-v1",
13
- "app_name": "TestApp",
14
- "global_context": {
15
- "language": "Python",
16
- "framework": "FastAPI",
17
- },
18
- "features": [
19
- {
20
- "id": "F-001",
21
- "title": "Project Infrastructure Setup",
22
- "description": "Set up the project structure.",
23
- "priority": 1,
24
- "estimated_complexity": "low",
25
- "dependencies": [],
26
- "acceptance_criteria": ["Has package.json", "Has README.md"],
27
- "status": "pending",
28
- },
29
- {
30
- "id": "F-002",
31
- "title": "User Authentication",
32
- "description": "Implement user login and registration.",
33
- "priority": 2,
34
- "estimated_complexity": "high",
35
- "dependencies": ["F-001"],
36
- "acceptance_criteria": ["Login works", "Registration works"],
37
- "status": "pending",
38
- },
39
- {
40
- "id": "F-003",
41
- "title": "Dashboard View",
42
- "description": "Create main dashboard.",
43
- "priority": 3,
44
- "estimated_complexity": "medium",
45
- "dependencies": ["F-001", "F-002"],
46
- "acceptance_criteria": ["Dashboard renders"],
47
- "status": "pending",
48
- },
49
- ],
50
- }
51
-
52
-
53
- @pytest.fixture
54
- def sample_bug_list():
55
- """A valid bug-fix-list.json structure."""
56
- return {
57
- "$schema": "dev-pipeline-bug-fix-list-v1",
58
- "project_name": "TestProject",
59
- "global_context": {
60
- "language": "Python",
61
- },
62
- "bugs": [
63
- {
64
- "id": "B-001",
65
- "title": "Login crash on empty password",
66
- "description": "App crashes when password is empty.",
67
- "severity": "critical",
68
- "priority": 1,
69
- "error_source": {
70
- "type": "stack_trace",
71
- "stack_trace": "Traceback ... ValueError",
72
- "error_message": "Password cannot be empty",
73
- },
74
- "verification_type": "automated",
75
- "acceptance_criteria": ["No crash on empty password"],
76
- "status": "pending",
77
- "affected_feature": "F-002",
78
- "affected_modules": ["auth"],
79
- },
80
- {
81
- "id": "B-002",
82
- "title": "Slow dashboard loading",
83
- "description": "Dashboard takes 10s to load.",
84
- "severity": "medium",
85
- "priority": 2,
86
- "error_source": {
87
- "type": "user_report",
88
- "reproduction_steps": ["Open dashboard", "Wait 10 seconds"],
89
- },
90
- "verification_type": "manual",
91
- "acceptance_criteria": ["Dashboard loads in <2s"],
92
- "status": "pending",
93
- "affected_feature": "F-003",
94
- "environment": {"os": "macOS", "browser": "Chrome"},
95
- },
96
- ],
97
- }
98
-
99
-
100
- @pytest.fixture
101
- def feature_list_file(tmp_path, sample_feature_list):
102
- """Write sample feature list to a temp file and return the path."""
103
- path = tmp_path / "feature-list.json"
104
- path.write_text(json.dumps(sample_feature_list, indent=2), encoding="utf-8")
105
- return str(path)
106
-
107
-
108
- @pytest.fixture
109
- def bug_list_file(tmp_path, sample_bug_list):
110
- """Write sample bug list to a temp file and return the path."""
111
- path = tmp_path / "bug-fix-list.json"
112
- path.write_text(json.dumps(sample_bug_list, indent=2), encoding="utf-8")
113
- return str(path)
114
-
115
-
116
- @pytest.fixture
117
- def state_dir(tmp_path):
118
- """Create and return a basic state directory."""
119
- sd = tmp_path / "state"
120
- sd.mkdir()
121
- features_dir = sd / "features"
122
- features_dir.mkdir()
123
- return str(sd)
124
-
125
-
126
- @pytest.fixture
127
- def bugfix_state_dir(tmp_path):
128
- """Create and return a basic bugfix state directory."""
129
- sd = tmp_path / "bugfix-state"
130
- sd.mkdir()
131
- bugs_dir = sd / "bugs"
132
- bugs_dir.mkdir()
133
- return str(sd)
5
+ import sys
6
+
7
+ # Add dev-pipeline/scripts to the import path so tests can import modules
8
+ # without requiring package installation.
9
+ _scripts_dir = os.path.join(os.path.dirname(__file__), "..", "scripts")
10
+ sys.path.insert(0, _scripts_dir)
11
+
12
+ # generate-bootstrap-prompt.py has a hyphenated filename that Python can't
13
+ # import normally. Load it via importlib and register under an underscore name
14
+ # so test files can do: `from generate_bootstrap_prompt import ...`
15
+ _spec = importlib.util.spec_from_file_location(
16
+ "generate_bootstrap_prompt",
17
+ os.path.join(_scripts_dir, "generate-bootstrap-prompt.py"),
18
+ )
19
+ _mod = importlib.util.module_from_spec(_spec)
20
+ sys.modules["generate_bootstrap_prompt"] = _mod
21
+ _spec.loader.exec_module(_mod)
@@ -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,141 +1,82 @@
1
- """Tests for utils.py."""
1
+ """Tests for dev-pipeline/scripts/utils.py shared utilities."""
2
2
 
3
3
  import json
4
4
  import os
5
- import pytest
5
+ import tempfile
6
6
 
7
7
  from utils import load_json_file, write_json_file, pad_right, _build_progress_bar
8
8
 
9
9
 
10
- class TestLoadJsonFile:
11
- def test_valid_json(self, tmp_path):
12
- p = tmp_path / "valid.json"
13
- p.write_text('{"key": "value"}', encoding="utf-8")
14
- data, err = load_json_file(str(p))
15
- assert err is None
16
- assert data == {"key": "value"}
17
-
18
- def test_invalid_json(self, tmp_path):
19
- p = tmp_path / "invalid.json"
20
- p.write_text("{not valid json", encoding="utf-8")
21
- data, err = load_json_file(str(p))
22
- assert data is None
23
- assert "Invalid JSON" in err
24
-
25
- def test_missing_file(self, tmp_path):
26
- data, err = load_json_file(str(tmp_path / "missing.json"))
27
- assert data is None
28
- assert "File not found" in err
29
-
30
- def test_empty_file(self, tmp_path):
31
- p = tmp_path / "empty.json"
32
- p.write_text("", encoding="utf-8")
33
- data, err = load_json_file(str(p))
34
- assert data is None
35
- assert "Invalid JSON" in err
36
-
37
- def test_unicode_content(self, tmp_path):
38
- p = tmp_path / "unicode.json"
39
- p.write_text('{"name": "hello"}', encoding="utf-8")
40
- data, err = load_json_file(str(p))
41
- assert err is None
42
- assert data["name"] == "hello"
10
+ # ---------------------------------------------------------------------------
11
+ # load_json_file / write_json_file round-trip
12
+ # ---------------------------------------------------------------------------
43
13
 
44
- def test_nested_json(self, tmp_path):
45
- nested = {"a": {"b": [1, 2, 3]}}
46
- p = tmp_path / "nested.json"
47
- p.write_text(json.dumps(nested), encoding="utf-8")
48
- data, err = load_json_file(str(p))
14
+ class TestJsonIO:
15
+ def test_round_trip(self, tmp_path):
16
+ path = str(tmp_path / "data.json")
17
+ data = {"features": [{"id": "F-001"}]}
18
+ err = write_json_file(path, data)
49
19
  assert err is None
50
- assert data == nested
51
20
 
52
-
53
- class TestWriteJsonFile:
54
- def test_write_and_read_back(self, tmp_path):
55
- p = str(tmp_path / "output.json")
56
- data = {"hello": "world", "num": 42}
57
- err = write_json_file(p, data)
21
+ loaded, err = load_json_file(path)
58
22
  assert err is None
59
- read_data, read_err = load_json_file(p)
60
- assert read_err is None
61
- assert read_data == data
62
-
63
- def test_creates_parent_directories(self, tmp_path):
64
- p = str(tmp_path / "a" / "b" / "c" / "output.json")
65
- err = write_json_file(p, {"nested": True})
23
+ assert loaded == data
24
+
25
+ def test_load_missing_file(self):
26
+ loaded, err = load_json_file("/nonexistent/path.json")
27
+ assert loaded is None
28
+ assert "not found" in err.lower() or "File not found" in err
29
+
30
+ def test_load_invalid_json(self, tmp_path):
31
+ path = str(tmp_path / "bad.json")
32
+ with open(path, "w") as f:
33
+ f.write("{invalid json}")
34
+ loaded, err = load_json_file(path)
35
+ assert loaded is None
36
+ assert "invalid" in err.lower() or "Invalid" in err
37
+
38
+ def test_write_creates_parent_dirs(self, tmp_path):
39
+ path = str(tmp_path / "a" / "b" / "c" / "data.json")
40
+ err = write_json_file(path, {"ok": True})
66
41
  assert err is None
67
- assert os.path.isfile(p)
68
-
69
- def test_overwrites_existing(self, tmp_path):
70
- p = str(tmp_path / "overwrite.json")
71
- write_json_file(p, {"v": 1})
72
- write_json_file(p, {"v": 2})
73
- data, _ = load_json_file(p)
74
- assert data["v"] == 2
75
-
76
- def test_unicode_data(self, tmp_path):
77
- p = str(tmp_path / "unicode.json")
78
- err = write_json_file(p, {"name": "hello"})
79
- assert err is None
80
- data, _ = load_json_file(p)
81
- assert data["name"] == "hello"
42
+ assert os.path.isfile(path)
43
+
82
44
 
45
+ # ---------------------------------------------------------------------------
46
+ # pad_right
47
+ # ---------------------------------------------------------------------------
83
48
 
84
49
  class TestPadRight:
85
- def test_shorter_than_width(self):
50
+ def test_basic_padding(self):
86
51
  result = pad_right("abc", 10)
87
52
  assert len(result) == 10
88
53
  assert result == "abc "
89
54
 
90
- def test_exact_width(self):
91
- result = pad_right("abcde", 5)
92
- assert result == "abcde"
93
-
94
- def test_longer_than_width(self):
95
- result = pad_right("abcdefgh", 5)
96
- assert result == "abcdefgh"
55
+ def test_no_padding_needed(self):
56
+ result = pad_right("abcdef", 3)
57
+ assert result == "abcdef"
97
58
 
98
- def test_empty_string(self):
99
- result = pad_right("", 5)
100
- assert result == " "
59
+ def test_ansi_escape_ignored(self):
60
+ # ANSI color code should not count toward visible length
61
+ text = "\033[31mred\033[0m" # "red" with ANSI color (3 visible chars)
62
+ result = pad_right(text, 10)
63
+ # Visible length is 3, so 7 spaces of padding
64
+ assert result.endswith(" ")
101
65
 
102
- def test_ansi_codes_ignored(self):
103
- # ANSI escape should not count toward visible length
104
- text = "\033[92mHi\033[0m" # visible "Hi" = 2 chars
105
- result = pad_right(text, 5)
106
- # visible width is 2, so 3 spaces of padding
107
- assert result.endswith(" ")
108
-
109
- def test_zero_width(self):
110
- result = pad_right("abc", 0)
111
- assert result == "abc"
112
66
 
67
+ # ---------------------------------------------------------------------------
68
+ # _build_progress_bar
69
+ # ---------------------------------------------------------------------------
113
70
 
114
71
  class TestBuildProgressBar:
115
72
  def test_zero_percent(self):
116
- bar = _build_progress_bar(0)
73
+ bar = _build_progress_bar(0, width=10)
117
74
  assert "0%" in bar
118
- assert "\u2591" in bar # empty blocks
119
- assert "\u2588" not in bar # no filled blocks
120
-
121
- def test_fifty_percent(self):
122
- bar = _build_progress_bar(50, width=20)
123
- assert "50%" in bar
124
- assert bar.count("\u2588") == 10
125
- assert bar.count("\u2591") == 10
126
75
 
127
76
  def test_hundred_percent(self):
128
- bar = _build_progress_bar(100, width=20)
77
+ bar = _build_progress_bar(100, width=10)
129
78
  assert "100%" in bar
130
- assert bar.count("\u2588") == 20
131
- assert bar.count("\u2591") == 0
132
79
 
133
- def test_custom_width(self):
80
+ def test_fifty_percent(self):
134
81
  bar = _build_progress_bar(50, width=10)
135
- assert bar.count("\u2588") == 5
136
- assert bar.count("\u2591") == 5
137
-
138
- def test_partial_percent(self):
139
- bar = _build_progress_bar(33, width=10)
140
- assert bar.count("\u2588") == 3
141
- assert bar.count("\u2591") == 7
82
+ assert "50%" in bar
@@ -3,8 +3,8 @@ description: "PrizmKit commit workflow rules"
3
3
  ---
4
4
 
5
5
  Before any git commit in this project:
6
- 1. Update `.prizm-docs/` for affected modules
6
+ 1. Run `/prizmkit-retrospective` to sync `.prizm-docs/` (memory maintenance)
7
7
  2. Use Conventional Commits format: type(scope): description
8
8
  3. Bug fixes use `fix()` prefix, not `feat()`
9
- 4. Do NOT create REGISTRY.md entries for bug fixes
10
- 5. Use `/prizmkit-committer` command for the complete commit workflow
9
+ 4. Bug fixes run retrospective with structural sync only (Job 1)
10
+ 5. Use `/prizmkit-committer` command for the pure commit workflow
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.0.45",
2
+ "version": "1.0.58",
3
3
  "skills": {
4
4
  "prizm-kit": {
5
5
  "description": "Full-lifecycle dev toolkit. Covers spec-driven development, Prizm context docs, code quality, debugging, deployment, and knowledge management.",
@@ -23,7 +23,7 @@
23
23
  "hasScripts": false
24
24
  },
25
25
  "prizmkit-clarify": {
26
- "description": "Interactive requirement clarification. Resolves ambiguities in feature specs.",
26
+ "description": "Interactive requirement clarification. Resolves ambiguities in feature specs. Use when spec has unclear parts or you're unsure about requirements before planning.",
27
27
  "tier": "1",
28
28
  "category": "prizmkit-skill",
29
29
  "hasAssets": false,
@@ -37,7 +37,7 @@
37
37
  "hasScripts": false
38
38
  },
39
39
  "prizmkit-analyze": {
40
- "description": "Cross-document consistency analysis for spec.md and plan.md (including Tasks section).",
40
+ "description": "Cross-document consistency analysis for spec.md and plan.md. Check if spec and plan are aligned, validate documents before coding.",
41
41
  "tier": "1",
42
42
  "category": "prizmkit-skill",
43
43
  "hasAssets": false,
@@ -58,24 +58,17 @@
58
58
  "hasScripts": false
59
59
  },
60
60
  "prizmkit-committer": {
61
- "description": "Commit workflow with automatic Prizm doc updates and changelog management.",
61
+ "description": "Pure git commit workflow with safety checks. Does NOT modify .prizm-docs/.",
62
62
  "tier": "1",
63
63
  "category": "prizmkit-skill",
64
64
  "hasAssets": false,
65
65
  "hasScripts": false
66
66
  },
67
- "prizmkit-summarize": {
68
- "description": "Archive completed features to REGISTRY.md.",
69
- "tier": "1",
70
- "category": "prizmkit-skill",
71
- "hasAssets": true,
72
- "hasScripts": false
73
- },
74
67
  "prizmkit-retrospective": {
75
- "description": "Post-feature retrospective. Extracts lessons from completed features.",
68
+ "description": "Sole .prizm-docs/ maintainer. Update project documentation after code changes. Structural sync + knowledge injection (TRAPS/RULES/DECISIONS). Run after code review passes, before committing.",
76
69
  "tier": "1",
77
70
  "category": "prizmkit-skill",
78
- "hasAssets": false,
71
+ "hasAssets": true,
79
72
  "hasScripts": false
80
73
  },
81
74
  "prizmkit-prizm-docs": {
@@ -224,6 +217,13 @@
224
217
  "category": "Custom-skill",
225
218
  "hasAssets": false,
226
219
  "hasScripts": false
220
+ },
221
+ "bug-fix-workflow": {
222
+ "description": "Interactive single-bug fix in current session. Guides triage → reproduce → fix → review → commit without background pipeline.",
223
+ "tier": "companion",
224
+ "category": "Custom-skill",
225
+ "hasAssets": false,
226
+ "hasScripts": false
227
227
  }
228
228
  },
229
229
  "suites": {
@@ -244,14 +244,14 @@
244
244
  "prizmkit-implement",
245
245
  "prizmkit-code-review",
246
246
  "prizmkit-committer",
247
- "prizmkit-summarize",
248
247
  "prizmkit-retrospective",
249
248
  "feature-workflow",
250
249
  "refactor-workflow",
251
250
  "app-planner",
252
251
  "bug-planner",
253
252
  "dev-pipeline-launcher",
254
- "bugfix-pipeline-launcher"
253
+ "bugfix-pipeline-launcher",
254
+ "bug-fix-workflow"
255
255
  ]
256
256
  },
257
257
  "minimal": {
@@ -267,7 +267,6 @@
267
267
  "prizmkit-implement",
268
268
  "prizmkit-code-review",
269
269
  "prizmkit-committer",
270
- "prizmkit-summarize",
271
270
  "prizmkit-retrospective"
272
271
  ]
273
272
  }