prizmkit 1.0.35 → 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.
- package/bundled/VERSION.json +3 -3
- package/bundled/adapters/claude/agent-adapter.js +2 -1
- package/bundled/adapters/claude/command-adapter.js +4 -3
- package/bundled/agents/prizm-dev-team-dev.md +12 -12
- package/bundled/agents/prizm-dev-team-reviewer.md +10 -10
- package/bundled/dev-pipeline/README.md +15 -19
- package/bundled/dev-pipeline/assets/prizm-dev-team-integration.md +16 -23
- package/bundled/dev-pipeline/launch-bugfix-daemon.sh +8 -0
- package/bundled/dev-pipeline/launch-daemon.sh +2 -0
- package/bundled/dev-pipeline/lib/branch.sh +76 -0
- package/bundled/dev-pipeline/retry-bug.sh +5 -2
- package/bundled/dev-pipeline/retry-feature.sh +5 -2
- package/bundled/dev-pipeline/run-bugfix.sh +74 -0
- package/bundled/dev-pipeline/run.sh +76 -2
- package/bundled/dev-pipeline/scripts/check-session-status.py +3 -1
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +0 -8
- package/bundled/dev-pipeline/scripts/parse-stream-progress.py +1 -1
- package/bundled/dev-pipeline/scripts/update-bug-status.py +24 -1
- package/bundled/dev-pipeline/scripts/update-feature-status.py +3 -2
- package/bundled/dev-pipeline/templates/bootstrap-tier1.md +11 -25
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +12 -26
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +54 -65
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +7 -7
- package/bundled/dev-pipeline/templates/session-status-schema.json +1 -1
- package/bundled/dev-pipeline/tests/conftest.py +19 -131
- package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +207 -0
- 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 +174 -0
- package/bundled/skills/bug-planner/SKILL.md +20 -32
- package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +3 -5
- 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 +57 -21
- package/bundled/skills/prizm-kit/assets/{claude-md-template.md → project-memory-template.md} +2 -2
- package/bundled/skills/prizmkit-analyze/SKILL.md +41 -29
- package/bundled/skills/prizmkit-clarify/SKILL.md +40 -30
- package/bundled/skills/prizmkit-code-review/SKILL.md +48 -43
- package/bundled/skills/prizmkit-committer/SKILL.md +30 -68
- package/bundled/skills/prizmkit-implement/SKILL.md +48 -24
- package/bundled/skills/prizmkit-init/SKILL.md +57 -66
- package/bundled/skills/prizmkit-plan/SKILL.md +46 -20
- package/bundled/skills/prizmkit-prizm-docs/SKILL.md +60 -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 +63 -13
- package/bundled/skills/refactor-workflow/SKILL.md +105 -49
- package/bundled/team/prizm-dev-team.json +5 -19
- package/package.json +1 -1
- package/src/clean.js +0 -2
- package/src/manifest.js +8 -4
- package/src/scaffold.js +72 -6
- package/src/upgrade.js +32 -5
- package/bundled/agents/prizm-dev-team-coordinator.md +0 -141
- package/bundled/agents/prizm-dev-team-pm.md +0 -126
- package/bundled/dev-pipeline/tests/__init__.py +0 -0
- package/bundled/dev-pipeline/tests/test_check_session.py +0 -127
- 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_bugfix_prompt.py +0 -181
- 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 -268
- 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
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
"""Tests for update-bug-status.py."""
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
import os
|
|
5
|
-
import re
|
|
6
|
-
import sys
|
|
7
|
-
import pytest
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def _import_update_bug_status():
|
|
11
|
-
import importlib.util
|
|
12
|
-
path = os.path.join(
|
|
13
|
-
os.path.dirname(__file__), "..", "scripts", "update-bug-status.py"
|
|
14
|
-
)
|
|
15
|
-
spec = importlib.util.spec_from_file_location("update_bug_status", path)
|
|
16
|
-
mod = importlib.util.module_from_spec(spec)
|
|
17
|
-
sys.modules["update_bug_status"] = mod
|
|
18
|
-
spec.loader.exec_module(mod)
|
|
19
|
-
return mod
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
ubs = _import_update_bug_status()
|
|
23
|
-
now_iso = ubs.now_iso
|
|
24
|
-
load_bug_status = ubs.load_bug_status
|
|
25
|
-
save_bug_status = ubs.save_bug_status
|
|
26
|
-
action_get_next = ubs.action_get_next
|
|
27
|
-
SEVERITY_PRIORITY = ubs.SEVERITY_PRIORITY
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class TestNowIso:
|
|
31
|
-
def test_valid_format(self):
|
|
32
|
-
result = now_iso()
|
|
33
|
-
pattern = r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$"
|
|
34
|
-
assert re.match(pattern, result) is not None
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class TestLoadAndSaveBugStatus:
|
|
38
|
-
def test_round_trip(self, bugfix_state_dir):
|
|
39
|
-
bid = "B-001"
|
|
40
|
-
bdir = os.path.join(bugfix_state_dir, "bugs", bid)
|
|
41
|
-
os.makedirs(bdir, exist_ok=True)
|
|
42
|
-
|
|
43
|
-
data = {
|
|
44
|
-
"bug_id": bid,
|
|
45
|
-
"status": "in_progress",
|
|
46
|
-
"retry_count": 2,
|
|
47
|
-
"max_retries": 3,
|
|
48
|
-
"sessions": ["s-001"],
|
|
49
|
-
"last_session_id": "s-001",
|
|
50
|
-
"resume_from_phase": None,
|
|
51
|
-
"created_at": "2024-01-01T00:00:00Z",
|
|
52
|
-
"updated_at": "2024-01-01T01:00:00Z",
|
|
53
|
-
}
|
|
54
|
-
err = save_bug_status(bugfix_state_dir, bid, data)
|
|
55
|
-
assert err is None
|
|
56
|
-
|
|
57
|
-
loaded = load_bug_status(bugfix_state_dir, bid)
|
|
58
|
-
assert loaded["bug_id"] == bid
|
|
59
|
-
assert loaded["status"] == "in_progress"
|
|
60
|
-
assert loaded["retry_count"] == 2
|
|
61
|
-
|
|
62
|
-
def test_load_missing_returns_default(self, bugfix_state_dir):
|
|
63
|
-
result = load_bug_status(bugfix_state_dir, "B-999")
|
|
64
|
-
assert result["bug_id"] == "B-999"
|
|
65
|
-
assert result["status"] == "pending"
|
|
66
|
-
assert result["retry_count"] == 0
|
|
67
|
-
|
|
68
|
-
def test_load_invalid_json_returns_default(self, bugfix_state_dir):
|
|
69
|
-
bid = "B-BAD"
|
|
70
|
-
bdir = os.path.join(bugfix_state_dir, "bugs", bid)
|
|
71
|
-
os.makedirs(bdir, exist_ok=True)
|
|
72
|
-
with open(os.path.join(bdir, "status.json"), "w") as f:
|
|
73
|
-
f.write("not json!")
|
|
74
|
-
result = load_bug_status(bugfix_state_dir, bid)
|
|
75
|
-
assert result["status"] == "pending"
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
class TestActionGetNext:
|
|
79
|
-
def test_selects_critical_before_medium(self, bugfix_state_dir, capsys):
|
|
80
|
-
bug_list = {
|
|
81
|
-
"bugs": [
|
|
82
|
-
{"id": "B-001", "title": "Low", "severity": "medium", "priority": 1},
|
|
83
|
-
{"id": "B-002", "title": "High", "severity": "critical", "priority": 2},
|
|
84
|
-
]
|
|
85
|
-
}
|
|
86
|
-
action_get_next(bug_list, bugfix_state_dir)
|
|
87
|
-
captured = capsys.readouterr()
|
|
88
|
-
result = json.loads(captured.out)
|
|
89
|
-
assert result["bug_id"] == "B-002"
|
|
90
|
-
assert result["severity"] == "critical"
|
|
91
|
-
|
|
92
|
-
def test_same_severity_sorted_by_priority(self, bugfix_state_dir, capsys):
|
|
93
|
-
bug_list = {
|
|
94
|
-
"bugs": [
|
|
95
|
-
{"id": "B-001", "title": "P3", "severity": "high", "priority": 3},
|
|
96
|
-
{"id": "B-002", "title": "P1", "severity": "high", "priority": 1},
|
|
97
|
-
]
|
|
98
|
-
}
|
|
99
|
-
action_get_next(bug_list, bugfix_state_dir)
|
|
100
|
-
captured = capsys.readouterr()
|
|
101
|
-
result = json.loads(captured.out)
|
|
102
|
-
assert result["bug_id"] == "B-002"
|
|
103
|
-
|
|
104
|
-
def test_all_completed_prints_pipeline_complete(self, bugfix_state_dir, capsys):
|
|
105
|
-
bug_list = {"bugs": [{"id": "B-001", "title": "Done", "severity": "low"}]}
|
|
106
|
-
# Mark as completed
|
|
107
|
-
bdir = os.path.join(bugfix_state_dir, "bugs", "B-001")
|
|
108
|
-
os.makedirs(bdir, exist_ok=True)
|
|
109
|
-
with open(os.path.join(bdir, "status.json"), "w") as f:
|
|
110
|
-
json.dump({"status": "completed"}, f)
|
|
111
|
-
action_get_next(bug_list, bugfix_state_dir)
|
|
112
|
-
captured = capsys.readouterr()
|
|
113
|
-
assert "PIPELINE_COMPLETE" in captured.out
|
|
114
|
-
|
|
115
|
-
def test_empty_bugs_prints_pipeline_complete(self, bugfix_state_dir, capsys):
|
|
116
|
-
action_get_next({"bugs": []}, bugfix_state_dir)
|
|
117
|
-
captured = capsys.readouterr()
|
|
118
|
-
assert "PIPELINE_COMPLETE" in captured.out
|
|
119
|
-
|
|
120
|
-
def test_prefers_in_progress_over_pending(self, bugfix_state_dir, capsys):
|
|
121
|
-
bug_list = {
|
|
122
|
-
"bugs": [
|
|
123
|
-
{"id": "B-001", "title": "Pending", "severity": "critical", "priority": 1},
|
|
124
|
-
{"id": "B-002", "title": "In Progress", "severity": "low", "priority": 2},
|
|
125
|
-
]
|
|
126
|
-
}
|
|
127
|
-
# Mark B-002 as in_progress
|
|
128
|
-
bdir = os.path.join(bugfix_state_dir, "bugs", "B-002")
|
|
129
|
-
os.makedirs(bdir, exist_ok=True)
|
|
130
|
-
with open(os.path.join(bdir, "status.json"), "w") as f:
|
|
131
|
-
json.dump({"status": "in_progress", "retry_count": 0}, f)
|
|
132
|
-
action_get_next(bug_list, bugfix_state_dir)
|
|
133
|
-
captured = capsys.readouterr()
|
|
134
|
-
result = json.loads(captured.out)
|
|
135
|
-
assert result["bug_id"] == "B-002"
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
class TestSeverityPriority:
|
|
139
|
-
def test_order(self):
|
|
140
|
-
assert SEVERITY_PRIORITY["critical"] < SEVERITY_PRIORITY["high"]
|
|
141
|
-
assert SEVERITY_PRIORITY["high"] < SEVERITY_PRIORITY["medium"]
|
|
142
|
-
assert SEVERITY_PRIORITY["medium"] < SEVERITY_PRIORITY["low"]
|
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
"""Tests for update-feature-status.py."""
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
import os
|
|
5
|
-
import re
|
|
6
|
-
import sys
|
|
7
|
-
import pytest
|
|
8
|
-
from types import SimpleNamespace
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def _import_update_feature_status():
|
|
12
|
-
import importlib.util
|
|
13
|
-
path = os.path.join(
|
|
14
|
-
os.path.dirname(__file__), "..", "scripts", "update-feature-status.py"
|
|
15
|
-
)
|
|
16
|
-
spec = importlib.util.spec_from_file_location("update_feature_status", path)
|
|
17
|
-
mod = importlib.util.module_from_spec(spec)
|
|
18
|
-
sys.modules["update_feature_status"] = mod
|
|
19
|
-
spec.loader.exec_module(mod)
|
|
20
|
-
return mod
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
ufs = _import_update_feature_status()
|
|
24
|
-
now_iso = ufs.now_iso
|
|
25
|
-
load_feature_status = ufs.load_feature_status
|
|
26
|
-
save_feature_status = ufs.save_feature_status
|
|
27
|
-
_build_feature_slug = ufs._build_feature_slug
|
|
28
|
-
_format_duration = ufs._format_duration
|
|
29
|
-
_calc_feature_duration = ufs._calc_feature_duration
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class TestSessionStatusValues:
|
|
33
|
-
def test_contains_commit_missing(self):
|
|
34
|
-
assert "commit_missing" in ufs.SESSION_STATUS_VALUES
|
|
35
|
-
|
|
36
|
-
def test_contains_docs_missing(self):
|
|
37
|
-
assert "docs_missing" in ufs.SESSION_STATUS_VALUES
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class TestNowIso:
|
|
41
|
-
def test_returns_valid_iso_format(self):
|
|
42
|
-
result = now_iso()
|
|
43
|
-
# Should match YYYY-MM-DDTHH:MM:SSZ
|
|
44
|
-
pattern = r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$"
|
|
45
|
-
assert re.match(pattern, result) is not None
|
|
46
|
-
|
|
47
|
-
def test_returns_string(self):
|
|
48
|
-
assert isinstance(now_iso(), str)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
class TestLoadAndSaveFeatureStatus:
|
|
52
|
-
def test_round_trip(self, state_dir):
|
|
53
|
-
fid = "F-001"
|
|
54
|
-
# Create the feature directory
|
|
55
|
-
fdir = os.path.join(state_dir, "features", fid)
|
|
56
|
-
os.makedirs(fdir, exist_ok=True)
|
|
57
|
-
|
|
58
|
-
status_data = {
|
|
59
|
-
"feature_id": fid,
|
|
60
|
-
"status": "in_progress",
|
|
61
|
-
"retry_count": 1,
|
|
62
|
-
"max_retries": 3,
|
|
63
|
-
"sessions": ["s-001"],
|
|
64
|
-
"last_session_id": "s-001",
|
|
65
|
-
"resume_from_phase": "3",
|
|
66
|
-
"created_at": "2024-01-01T00:00:00Z",
|
|
67
|
-
"updated_at": "2024-01-01T01:00:00Z",
|
|
68
|
-
}
|
|
69
|
-
err = save_feature_status(state_dir, fid, status_data)
|
|
70
|
-
assert err is None
|
|
71
|
-
|
|
72
|
-
loaded = load_feature_status(state_dir, fid)
|
|
73
|
-
assert loaded["feature_id"] == fid
|
|
74
|
-
assert loaded["status"] == "in_progress"
|
|
75
|
-
assert loaded["retry_count"] == 1
|
|
76
|
-
|
|
77
|
-
def test_load_missing_returns_default(self, state_dir):
|
|
78
|
-
result = load_feature_status(state_dir, "F-999")
|
|
79
|
-
assert result["feature_id"] == "F-999"
|
|
80
|
-
assert result["status"] == "pending"
|
|
81
|
-
assert result["retry_count"] == 0
|
|
82
|
-
|
|
83
|
-
def test_load_invalid_json_returns_default(self, state_dir):
|
|
84
|
-
fid = "F-BAD"
|
|
85
|
-
fdir = os.path.join(state_dir, "features", fid)
|
|
86
|
-
os.makedirs(fdir, exist_ok=True)
|
|
87
|
-
with open(os.path.join(fdir, "status.json"), "w") as f:
|
|
88
|
-
f.write("{invalid json")
|
|
89
|
-
result = load_feature_status(state_dir, fid)
|
|
90
|
-
assert result["status"] == "pending"
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
class TestBuildFeatureSlug:
|
|
94
|
-
def test_basic(self):
|
|
95
|
-
assert _build_feature_slug("F-001", "Project Setup") == "001-project-setup"
|
|
96
|
-
|
|
97
|
-
def test_special_chars(self):
|
|
98
|
-
result = _build_feature_slug("F-002", "Auth (OAuth2.0)")
|
|
99
|
-
assert result == "002-auth-oauth20"
|
|
100
|
-
|
|
101
|
-
def test_empty_title(self):
|
|
102
|
-
result = _build_feature_slug("F-003", "")
|
|
103
|
-
assert result == "003-feature"
|
|
104
|
-
|
|
105
|
-
def test_none_title(self):
|
|
106
|
-
result = _build_feature_slug("F-003", None)
|
|
107
|
-
assert result == "003-feature"
|
|
108
|
-
|
|
109
|
-
def test_lowercase_f(self):
|
|
110
|
-
result = _build_feature_slug("f-5", "Test")
|
|
111
|
-
assert result == "005-test"
|
|
112
|
-
|
|
113
|
-
def test_numeric_padding(self):
|
|
114
|
-
result = _build_feature_slug("F-1", "A")
|
|
115
|
-
assert result.startswith("001-")
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
class TestFormatDuration:
|
|
119
|
-
def test_none(self):
|
|
120
|
-
assert _format_duration(None) == "N/A"
|
|
121
|
-
|
|
122
|
-
def test_seconds(self):
|
|
123
|
-
assert _format_duration(45) == "45s"
|
|
124
|
-
|
|
125
|
-
def test_minutes(self):
|
|
126
|
-
assert _format_duration(125) == "2m5s"
|
|
127
|
-
|
|
128
|
-
def test_hours(self):
|
|
129
|
-
assert _format_duration(3661) == "1h1m"
|
|
130
|
-
|
|
131
|
-
def test_zero(self):
|
|
132
|
-
assert _format_duration(0) == "0s"
|
|
133
|
-
|
|
134
|
-
def test_exact_minute(self):
|
|
135
|
-
assert _format_duration(60) == "1m0s"
|
|
136
|
-
|
|
137
|
-
def test_exact_hour(self):
|
|
138
|
-
assert _format_duration(3600) == "1h0m"
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
class TestCalcFeatureDuration:
|
|
142
|
-
def test_valid_duration(self, state_dir):
|
|
143
|
-
fid = "F-001"
|
|
144
|
-
fdir = os.path.join(state_dir, "features", fid)
|
|
145
|
-
os.makedirs(fdir, exist_ok=True)
|
|
146
|
-
status = {
|
|
147
|
-
"created_at": "2024-01-01T00:00:00Z",
|
|
148
|
-
"updated_at": "2024-01-01T00:05:00Z", # 300 seconds
|
|
149
|
-
}
|
|
150
|
-
with open(os.path.join(fdir, "status.json"), "w") as f:
|
|
151
|
-
json.dump(status, f)
|
|
152
|
-
result = _calc_feature_duration(state_dir, fid)
|
|
153
|
-
assert result == 300.0
|
|
154
|
-
|
|
155
|
-
def test_too_short_duration(self, state_dir):
|
|
156
|
-
fid = "F-002"
|
|
157
|
-
fdir = os.path.join(state_dir, "features", fid)
|
|
158
|
-
os.makedirs(fdir, exist_ok=True)
|
|
159
|
-
status = {
|
|
160
|
-
"created_at": "2024-01-01T00:00:00Z",
|
|
161
|
-
"updated_at": "2024-01-01T00:00:05Z", # 5 seconds - below 10s threshold
|
|
162
|
-
}
|
|
163
|
-
with open(os.path.join(fdir, "status.json"), "w") as f:
|
|
164
|
-
json.dump(status, f)
|
|
165
|
-
result = _calc_feature_duration(state_dir, fid)
|
|
166
|
-
assert result is None
|
|
167
|
-
|
|
168
|
-
def test_missing_file(self, state_dir):
|
|
169
|
-
result = _calc_feature_duration(state_dir, "F-MISSING")
|
|
170
|
-
assert result is None
|
|
171
|
-
|
|
172
|
-
def test_missing_timestamps(self, state_dir):
|
|
173
|
-
fid = "F-003"
|
|
174
|
-
fdir = os.path.join(state_dir, "features", fid)
|
|
175
|
-
os.makedirs(fdir, exist_ok=True)
|
|
176
|
-
with open(os.path.join(fdir, "status.json"), "w") as f:
|
|
177
|
-
json.dump({"status": "pending"}, f)
|
|
178
|
-
result = _calc_feature_duration(state_dir, fid)
|
|
179
|
-
assert result is None
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
class TestActionUpdateDegradedStatuses:
|
|
183
|
-
def test_commit_missing_updates_feature_state_without_cleanup(
|
|
184
|
-
self, feature_list_file, state_dir, monkeypatch, capsys
|
|
185
|
-
):
|
|
186
|
-
called = {"cleanup": 0}
|
|
187
|
-
|
|
188
|
-
def _fake_cleanup(**_kwargs):
|
|
189
|
-
called["cleanup"] += 1
|
|
190
|
-
return []
|
|
191
|
-
|
|
192
|
-
monkeypatch.setattr(ufs, "cleanup_feature_artifacts", _fake_cleanup)
|
|
193
|
-
|
|
194
|
-
args = SimpleNamespace(
|
|
195
|
-
feature_id="F-001",
|
|
196
|
-
session_status="commit_missing",
|
|
197
|
-
session_id="s-commit-missing",
|
|
198
|
-
max_retries=3,
|
|
199
|
-
project_root=None,
|
|
200
|
-
)
|
|
201
|
-
|
|
202
|
-
ufs.action_update(args, feature_list_file, state_dir)
|
|
203
|
-
out = capsys.readouterr().out
|
|
204
|
-
summary = json.loads(out)
|
|
205
|
-
|
|
206
|
-
fs = ufs.load_feature_status(state_dir, "F-001")
|
|
207
|
-
assert fs["status"] == "commit_missing"
|
|
208
|
-
assert fs["retry_count"] == 1
|
|
209
|
-
assert summary["new_status"] == "commit_missing"
|
|
210
|
-
assert summary["degraded_reason"] == "commit_missing"
|
|
211
|
-
assert summary["restart_policy"] == "finalization_retry"
|
|
212
|
-
assert called["cleanup"] == 0
|
|
213
|
-
|
|
214
|
-
with open(feature_list_file, "r", encoding="utf-8") as f:
|
|
215
|
-
data = json.load(f)
|
|
216
|
-
f1 = next(x for x in data["features"] if x["id"] == "F-001")
|
|
217
|
-
assert f1["status"] == "commit_missing"
|
|
218
|
-
|
|
219
|
-
def test_docs_missing_reaches_failed_when_retry_exhausted(
|
|
220
|
-
self, feature_list_file, state_dir, monkeypatch, capsys
|
|
221
|
-
):
|
|
222
|
-
called = {"cleanup": 0}
|
|
223
|
-
|
|
224
|
-
def _fake_cleanup(**_kwargs):
|
|
225
|
-
called["cleanup"] += 1
|
|
226
|
-
return []
|
|
227
|
-
|
|
228
|
-
monkeypatch.setattr(ufs, "cleanup_feature_artifacts", _fake_cleanup)
|
|
229
|
-
|
|
230
|
-
args = SimpleNamespace(
|
|
231
|
-
feature_id="F-001",
|
|
232
|
-
session_status="docs_missing",
|
|
233
|
-
session_id="s-docs-missing",
|
|
234
|
-
max_retries=1,
|
|
235
|
-
project_root=None,
|
|
236
|
-
)
|
|
237
|
-
|
|
238
|
-
ufs.action_update(args, feature_list_file, state_dir)
|
|
239
|
-
out = capsys.readouterr().out
|
|
240
|
-
summary = json.loads(out)
|
|
241
|
-
|
|
242
|
-
fs = ufs.load_feature_status(state_dir, "F-001")
|
|
243
|
-
assert fs["status"] == "failed"
|
|
244
|
-
assert fs["retry_count"] == 1
|
|
245
|
-
assert summary["new_status"] == "failed"
|
|
246
|
-
assert summary["degraded_reason"] == "docs_missing"
|
|
247
|
-
assert summary["restart_policy"] == "finalization_retry"
|
|
248
|
-
assert called["cleanup"] == 0
|
|
249
|
-
|
|
250
|
-
with open(feature_list_file, "r", encoding="utf-8") as f:
|
|
251
|
-
data = json.load(f)
|
|
252
|
-
f1 = next(x for x in data["features"] if x["id"] == "F-001")
|
|
253
|
-
assert f1["status"] == "failed"
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
class TestActionStatusForDegradedStates:
|
|
257
|
-
def test_status_output_contains_degraded_counters(self, feature_list_file, state_dir, capsys):
|
|
258
|
-
# Update feature-list.json to set degraded statuses (single source of truth)
|
|
259
|
-
with open(feature_list_file, "r", encoding="utf-8") as f:
|
|
260
|
-
feature_list_data = json.load(f)
|
|
261
|
-
|
|
262
|
-
feature_list_data["features"][0]["status"] = "commit_missing"
|
|
263
|
-
feature_list_data["features"][1]["status"] = "docs_missing"
|
|
264
|
-
|
|
265
|
-
ufs.action_status(feature_list_data, state_dir)
|
|
266
|
-
out = capsys.readouterr().out
|
|
267
|
-
|
|
268
|
-
assert "Commit Missing: 1 | Docs Missing: 1" in out
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
## PrizmKit Documentation Framework
|
|
2
|
-
|
|
3
|
-
This project uses PrizmKit with the Prizm documentation system for AI-optimized progressive context loading.
|
|
4
|
-
|
|
5
|
-
### Progressive Loading Protocol
|
|
6
|
-
- ON SESSION START: Always read `.prizm-docs/root.prizm` first (L0 — project map)
|
|
7
|
-
- ON TASK: Read L1 (`.prizm-docs/<module>.prizm`) for relevant modules referenced in MODULE_INDEX
|
|
8
|
-
- ON FILE EDIT: Read L2 (`.prizm-docs/<module>/<submodule>.prizm`) before modifying files. Pay attention to TRAPS and DECISIONS.
|
|
9
|
-
- NEVER load all .prizm docs at once. Load only what is needed for the current task.
|
|
10
|
-
|
|
11
|
-
### Auto-Update Protocol
|
|
12
|
-
- BEFORE EVERY COMMIT: Update affected `.prizm-docs/` files
|
|
13
|
-
- The UserPromptSubmit hook will remind you automatically
|
|
14
|
-
- Use `prizmkit-committer` skill for the complete commit workflow
|
|
15
|
-
|
|
16
|
-
### Doc Format Rules
|
|
17
|
-
- All `.prizm` files use KEY: value format, not prose
|
|
18
|
-
- Size limits: L0 = 4KB, L1 = 3KB, L2 = 5KB
|
|
19
|
-
- Arrow notation (->) indicates load pointers to other .prizm docs
|
|
20
|
-
- DECISIONS and CHANGELOG are append-only (never delete entries)
|
|
21
|
-
|
|
22
|
-
### Creating New L2 Docs
|
|
23
|
-
- When you first modify files in a sub-module that has no L2 doc:
|
|
24
|
-
1. Read the source files in that sub-module
|
|
25
|
-
2. Generate a new L2 `.prizm` file following Prizm specification
|
|
26
|
-
3. Add a pointer in the parent L1 doc's SUBDIRS section
|
|
27
|
-
|
|
28
|
-
### Fast Path for Simple Changes
|
|
29
|
-
Not every change needs the full spec -> plan -> tasks workflow. Use fast path for:
|
|
30
|
-
- Bug fixes with clear root cause, config tweaks, typo fixes, simple refactors
|
|
31
|
-
- Documentation-only changes, test additions for existing code
|
|
32
|
-
- Directly use `prizmkit.implement` with inline task description, then `prizmkit.commit`
|
|
33
|
-
|
|
34
|
-
Use the full workflow (specify -> plan -> tasks -> analyze -> implement) for:
|
|
35
|
-
- New features, multi-file coordinated changes, architectural decisions, data model or API changes
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"hooks": {
|
|
3
|
-
"UserPromptSubmit": [
|
|
4
|
-
{
|
|
5
|
-
"matcher": "\\b(commit|Commit|push|Push|finish|Finish|ship|Ship|merge|Merge|pull request|Pull Request|pr|PR)\\b|提交|推送|收尾|完成任务|合并|发版|发布",
|
|
6
|
-
"hooks": [
|
|
7
|
-
{
|
|
8
|
-
"type": "command",
|
|
9
|
-
"command": "echo 'PRIZMKIT_DOC_UPDATE_REQUIRED: Before committing, you MUST update .prizm-docs/ per Prizm auto-update protocol. Steps: 1) Run git diff --cached --name-status. 2) Map changed files to modules via root.prizm MODULE_INDEX. 3) Read and update affected .prizm files (only changed sections). 4) Append to changelog.prizm. 5) Stage .prizm files with git add .prizm-docs/. 6) Then proceed with commit using prizmkit-committer workflow. RULES: Never rewrite entire .prizm files. Never add prose. Only update affected sections.'"
|
|
10
|
-
}
|
|
11
|
-
]
|
|
12
|
-
}
|
|
13
|
-
]
|
|
14
|
-
}
|
|
15
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: "prizmkit-summarize"
|
|
3
|
-
description: "Archive completed features to REGISTRY.md. Extracts metadata from specs, scans code, updates feature index. Invoke after code review passes or feature is done. (project)"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# PrizmKit Summarize
|
|
7
|
-
|
|
8
|
-
Archive completed features to the feature registry. Extracts metadata from specs and code, generates a registry entry, and appends a changelog record. Idempotent — re-running produces the same output.
|
|
9
|
-
|
|
10
|
-
## Commands
|
|
11
|
-
|
|
12
|
-
### prizmkit.summarize
|
|
13
|
-
|
|
14
|
-
Archive a completed feature to the registry.
|
|
15
|
-
|
|
16
|
-
**PRECONDITION:** `spec.md`, `plan.md`, `tasks.md` exist in `.prizmkit/specs/###-feature-name/`
|
|
17
|
-
|
|
18
|
-
**STEPS:**
|
|
19
|
-
|
|
20
|
-
1. Read `spec.md`, `plan.md`, `tasks.md`
|
|
21
|
-
2. Analyze `tasks.md` completion rate — warn if < 100%
|
|
22
|
-
3. Scan actual code directories for core file paths
|
|
23
|
-
4. Generate REGISTRY entry:
|
|
24
|
-
- Feature number and name
|
|
25
|
-
- Branch name
|
|
26
|
-
- Status (complete/partial)
|
|
27
|
-
- Key files and directories
|
|
28
|
-
- API endpoints added/modified
|
|
29
|
-
- Data model changes
|
|
30
|
-
- Completion date
|
|
31
|
-
5. Append to `.prizmkit/specs/REGISTRY.md` (create from template `${SKILL_DIR}/assets/registry-template.md` if not exists)
|
|
32
|
-
6. Append changelog entry
|
|
33
|
-
7. Output: registry entry summary
|
|
34
|
-
|
|
35
|
-
**KEY RULES:**
|
|
36
|
-
- Idempotent: same input MUST produce same output on re-run
|
|
37
|
-
- If `tasks.md` completion < 100%, status is "Partial" with warning
|
|
38
|
-
- REGISTRY.md is append-only — never modify existing entries
|
|
39
|
-
- Changelog entries use format: `YYYY-MM-DD | [###] [Feature Name] | [Status]`
|
|
40
|
-
- If REGISTRY.md does not exist, create it from template before appending
|
|
41
|
-
- **Bug fixes MUST NOT create new REGISTRY.md entries.** Bugs are refinements of incomplete existing features, not new functionality. Bug fix commits should only update the original feature's changelog, not generate a new registry entry. Do NOT invoke this skill for bug fix commits.
|
|
42
|
-
|
|
43
|
-
**HANDOFF:** `prizmkit.specify` (start next feature) or `prizmkit.retrospective` (extract lessons)
|
|
44
|
-
|
|
45
|
-
## Template
|
|
46
|
-
|
|
47
|
-
The registry template is located at `${SKILL_DIR}/assets/registry-template.md`.
|
|
48
|
-
|
|
49
|
-
## Output
|
|
50
|
-
|
|
51
|
-
- `.prizmkit/specs/REGISTRY.md` — Updated with new feature entry and changelog record
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# PrizmKit Feature Registry
|
|
2
|
-
|
|
3
|
-
> Auto-generated by prizmkit-summarize. Do not edit manually.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
### [###] [Feature Name]
|
|
8
|
-
- **Branch**: ###-feature-name
|
|
9
|
-
- **Status**: Complete | Partial
|
|
10
|
-
- **Date**: YYYY-MM-DD
|
|
11
|
-
- **Key Files**: [list]
|
|
12
|
-
- **API Changes**: [summary]
|
|
13
|
-
- **Data Changes**: [summary]
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## Changelog
|
|
18
|
-
- YYYY-MM-DD | [###] [Feature Name] | [Status]
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"hooks": {
|
|
3
|
-
"UserPromptSubmit": [
|
|
4
|
-
{
|
|
5
|
-
"matcher": "\\b(commit|Commit|push|Push|finish|Finish|ship|Ship|merge|Merge|pull request|Pull Request|pr|PR)\\b|提交|推送|收尾|完成任务|合并|发版|发布",
|
|
6
|
-
"hooks": [
|
|
7
|
-
{
|
|
8
|
-
"type": "command",
|
|
9
|
-
"command": "SRC=$(git diff --cached --name-only 2>/dev/null | grep -cE '\\.(ts|tsx|js|jsx|py|go|rs|java)$'); if [ \"$SRC\" -gt 0 ]; then PRIZM=$(git diff --cached --name-only 2>/dev/null | grep -c '^\\.prizm-docs/'); if [ \"$PRIZM\" -gt 0 ]; then echo \"PRIZMKIT_DOC_STATUS: $SRC source file(s) staged | .prizm-docs/ updated ($PRIZM file(s))\"; else echo \"PRIZMKIT_DOC_STATUS: $SRC source file(s) staged | .prizm-docs/ NOT UPDATED — update if this is a feature change\"; fi; fi"
|
|
10
|
-
}
|
|
11
|
-
]
|
|
12
|
-
}
|
|
13
|
-
],
|
|
14
|
-
"PostToolUse": [
|
|
15
|
-
{
|
|
16
|
-
"matcher": "Bash",
|
|
17
|
-
"hooks": [
|
|
18
|
-
{
|
|
19
|
-
"type": "command",
|
|
20
|
-
"command": "if echo \"$CLAUDE_TOOL_INPUT\" 2>/dev/null | grep -qE 'git (commit|merge|rebase)' 2>/dev/null; then DRIFT=$(sh .prizmkit/scripts/diff-prizm-docs.sh 2>/dev/null); if [ -n \"$DRIFT\" ]; then echo \"PRIZMKIT_DRIFT_DETECTED: prizm-docs structural differences found after git operation:\"; echo \"$DRIFT\"; echo \"Please update affected .prizm-docs/ files using /prizmkit-prizm-docs.\"; fi; fi"
|
|
21
|
-
}
|
|
22
|
-
]
|
|
23
|
-
}
|
|
24
|
-
]
|
|
25
|
-
}
|
|
26
|
-
}
|
|
File without changes
|