prizmkit 1.0.12 → 1.0.14
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/bin/create-prizmkit.js +4 -1
- package/bundled/VERSION.json +3 -3
- package/bundled/adapters/claude/command-adapter.js +35 -4
- package/bundled/adapters/claude/rules-adapter.js +6 -58
- package/bundled/adapters/claude/team-adapter.js +2 -2
- package/bundled/adapters/codebuddy/agent-adapter.js +0 -1
- package/bundled/adapters/codebuddy/rules-adapter.js +30 -0
- package/bundled/adapters/shared/frontmatter.js +3 -1
- package/bundled/dev-pipeline/README.md +13 -3
- package/bundled/dev-pipeline/launch-bugfix-daemon.sh +10 -0
- package/bundled/dev-pipeline/launch-daemon.sh +18 -4
- package/bundled/dev-pipeline/lib/common.sh +105 -0
- package/bundled/dev-pipeline/retry-bug.sh +12 -0
- package/bundled/dev-pipeline/retry-feature.sh +12 -0
- package/bundled/dev-pipeline/run-bugfix.sh +71 -57
- package/bundled/dev-pipeline/run.sh +87 -57
- package/bundled/dev-pipeline/scripts/check-session-status.py +47 -2
- package/bundled/dev-pipeline/scripts/cleanup-logs.py +192 -0
- package/bundled/dev-pipeline/scripts/detect-stuck.py +15 -3
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +32 -27
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +23 -23
- package/bundled/dev-pipeline/scripts/update-feature-status.py +73 -2
- package/bundled/dev-pipeline/scripts/utils.py +22 -0
- package/bundled/dev-pipeline/templates/bootstrap-tier1.md +38 -2
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +39 -2
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +39 -2
- package/bundled/dev-pipeline/templates/session-status-schema.json +7 -1
- package/bundled/dev-pipeline/tests/__init__.py +0 -0
- package/bundled/dev-pipeline/tests/conftest.py +133 -0
- package/bundled/dev-pipeline/tests/test_check_session.py +127 -0
- package/bundled/dev-pipeline/tests/test_cleanup_logs.py +119 -0
- package/bundled/dev-pipeline/tests/test_detect_stuck.py +207 -0
- package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +181 -0
- package/bundled/dev-pipeline/tests/test_generate_prompt.py +190 -0
- package/bundled/dev-pipeline/tests/test_init_bugfix_pipeline.py +153 -0
- package/bundled/dev-pipeline/tests/test_init_pipeline.py +241 -0
- package/bundled/dev-pipeline/tests/test_update_bug_status.py +142 -0
- package/bundled/dev-pipeline/tests/test_update_feature_status.py +277 -0
- package/bundled/dev-pipeline/tests/test_utils.py +141 -0
- package/bundled/rules/USAGE.md +153 -0
- package/bundled/rules/_rules-metadata.json +43 -0
- package/bundled/rules/general/prefer-linux-commands.md +9 -0
- package/bundled/rules/prizm/prizm-commit-workflow.md +10 -0
- package/bundled/rules/prizm/prizm-documentation.md +19 -0
- package/bundled/rules/prizm/prizm-progressive-loading.md +11 -0
- package/bundled/skills/_metadata.json +130 -67
- package/bundled/skills/app-planner/SKILL.md +252 -499
- package/bundled/skills/app-planner/assets/evaluation-guide.md +44 -0
- package/bundled/skills/app-planner/scripts/validate-and-generate.py +143 -4
- package/bundled/skills/bug-planner/SKILL.md +58 -13
- package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +5 -7
- package/bundled/skills/dev-pipeline-launcher/SKILL.md +16 -7
- package/bundled/skills/feature-workflow/SKILL.md +175 -234
- package/bundled/skills/prizm-kit/SKILL.md +17 -31
- package/bundled/skills/{prizmkit-adr-manager → prizmkit-tool-adr-manager}/SKILL.md +6 -7
- package/bundled/skills/{prizmkit-api-doc-generator → prizmkit-tool-api-doc-generator}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-bug-reproducer → prizmkit-tool-bug-reproducer}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-ci-cd-generator → prizmkit-tool-ci-cd-generator}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-db-migration → prizmkit-tool-db-migration}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-dependency-health → prizmkit-tool-dependency-health}/SKILL.md +3 -4
- package/bundled/skills/{prizmkit-deployment-strategy → prizmkit-tool-deployment-strategy}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-error-triage → prizmkit-tool-error-triage}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-log-analyzer → prizmkit-tool-log-analyzer}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-monitoring-setup → prizmkit-tool-monitoring-setup}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-onboarding-generator → prizmkit-tool-onboarding-generator}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-perf-profiler → prizmkit-tool-perf-profiler}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-security-audit → prizmkit-tool-security-audit}/SKILL.md +3 -4
- package/bundled/skills/{prizmkit-tech-debt-tracker → prizmkit-tool-tech-debt-tracker}/SKILL.md +3 -4
- package/bundled/skills/refactor-skill/SKILL.md +371 -0
- package/bundled/skills/refactor-workflow/SKILL.md +17 -119
- package/package.json +1 -1
- package/src/external-skills.js +71 -0
- package/src/index.js +62 -4
- package/src/metadata.js +36 -0
- package/src/scaffold.js +136 -32
- package/bundled/skills/prizmkit-bug-fix-workflow/SKILL.md +0 -356
- package/bundled/templates/claude-md-template.md +0 -38
- package/bundled/templates/codebuddy-md-template.md +0 -35
- /package/bundled/skills/{prizmkit-adr-manager → prizmkit-tool-adr-manager}/assets/adr-template.md +0 -0
|
@@ -130,6 +130,7 @@ Prompt:
|
|
|
130
130
|
> 2. Run prizmkit-code-review: verify all acceptance criteria, check code quality and correctness. Only read files mentioned in the Implementation Log.
|
|
131
131
|
> 3. Run the test suite and report results.
|
|
132
132
|
> 4. Append a 'Review Notes' section to `context-snapshot.md`: issues found (severity), test results, final verdict.
|
|
133
|
+
> 5. If review uncovers durable pitfalls or conventions, add corresponding TRAPS/RULES notes to relevant `.prizm-docs/` files.
|
|
133
134
|
> Report verdict: PASS, PASS_WITH_WARNINGS, or NEEDS_FIXES."
|
|
134
135
|
|
|
135
136
|
Wait for Reviewer to return.
|
|
@@ -137,6 +138,21 @@ Wait for Reviewer to return.
|
|
|
137
138
|
|
|
138
139
|
**CP-2**: Tests pass, verdict is not NEEDS_FIXES.
|
|
139
140
|
|
|
141
|
+
### Phase 4.5: Prizm Doc Update (mandatory for feature sessions)
|
|
142
|
+
|
|
143
|
+
Run `prizmkit.doc.update` and sync project docs before commit:
|
|
144
|
+
1. Use `git diff --cached --name-status` (fallback: `git diff --name-status`) to locate changed modules
|
|
145
|
+
2. Update affected `.prizm-docs/` files (L1/L2, changelog.prizm)
|
|
146
|
+
3. Stage documentation updates (`git add .prizm-docs/`) if changed
|
|
147
|
+
|
|
148
|
+
Doc maintenance pass condition (pipeline-enforced): `REGISTRY.md` **or** `.prizm-docs/` changed in the final commit.
|
|
149
|
+
|
|
150
|
+
### Phase 4.7: Retrospective (feature sessions only, before commit)
|
|
151
|
+
|
|
152
|
+
If this session is a feature (not a bug-fix-only commit), run `prizmkit.retrospective` now — **before committing**.
|
|
153
|
+
Retrospective must update relevant `.prizm-docs/` sections (TRAPS/RULES/DECISIONS) when applicable, so those changes are included in the feature commit.
|
|
154
|
+
Stage any `.prizm-docs/` changes produced: `git add .prizm-docs/`
|
|
155
|
+
|
|
140
156
|
### Phase 5: Commit
|
|
141
157
|
|
|
142
158
|
- Run `prizmkit.summarize` → archive to REGISTRY.md
|
|
@@ -148,6 +164,7 @@ Wait for Reviewer to return.
|
|
|
148
164
|
--feature-id "{{FEATURE_ID}}" --session-id "{{SESSION_ID}}" --action complete
|
|
149
165
|
```
|
|
150
166
|
- Run `prizmkit.committer` → `feat({{FEATURE_ID}}): {{FEATURE_TITLE}}`, do NOT push
|
|
167
|
+
- MANDATORY: commit must be done via `prizmkit.committer` skill. Do NOT run manual `git add`/`git commit` as a substitute.
|
|
151
168
|
|
|
152
169
|
---
|
|
153
170
|
|
|
@@ -161,7 +178,7 @@ Write to: `{{SESSION_STATUS_PATH}}`
|
|
|
161
178
|
"feature_id": "{{FEATURE_ID}}",
|
|
162
179
|
"feature_slug": "{{FEATURE_SLUG}}",
|
|
163
180
|
"exec_tier": 2,
|
|
164
|
-
"status": "<success|partial|failed>",
|
|
181
|
+
"status": "<success|partial|failed|commit_missing|docs_missing>",
|
|
165
182
|
"completed_phases": [0, 1, 2, 3, 4, 5],
|
|
166
183
|
"current_phase": 5,
|
|
167
184
|
"checkpoint_reached": "CP-2",
|
|
@@ -170,6 +187,8 @@ Write to: `{{SESSION_STATUS_PATH}}`
|
|
|
170
187
|
"errors": [],
|
|
171
188
|
"can_resume": false,
|
|
172
189
|
"resume_from_phase": null,
|
|
190
|
+
"docs_maintained": true,
|
|
191
|
+
"retrospective_done": true,
|
|
173
192
|
"artifacts": {
|
|
174
193
|
"context_snapshot_path": ".prizmkit/specs/{{FEATURE_SLUG}}/context-snapshot.md",
|
|
175
194
|
"plan_path": ".prizmkit/specs/{{FEATURE_SLUG}}/plan.md",
|
|
@@ -180,6 +199,23 @@ Write to: `{{SESSION_STATUS_PATH}}`
|
|
|
180
199
|
}
|
|
181
200
|
```
|
|
182
201
|
|
|
202
|
+
### Step 3.1: Final Clean Check (before exit)
|
|
203
|
+
|
|
204
|
+
After writing `session-status.json`, verify repository is clean:
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
git status --short
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
If any files remain, include them in the last commit:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
git add -A
|
|
214
|
+
git commit --amend --no-edit
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Re-check `git status --short` and ensure it is empty before exiting.
|
|
218
|
+
|
|
183
219
|
## Critical Paths
|
|
184
220
|
|
|
185
221
|
| Resource | Path |
|
|
@@ -197,5 +233,6 @@ Write to: `{{SESSION_STATUS_PATH}}`
|
|
|
197
233
|
- Build context-snapshot.md FIRST; all subagents read it instead of re-reading source files
|
|
198
234
|
- Do NOT use `run_in_background=true` when spawning subagents
|
|
199
235
|
- ALWAYS write session-status.json before exiting
|
|
200
|
-
- `prizmkit.committer` is mandatory
|
|
236
|
+
- `prizmkit.committer` is mandatory, and must not be replaced with manual git commit commands
|
|
237
|
+
- Before exiting, `git status --short` must be empty
|
|
201
238
|
- On timeout: check snapshot → model:lite → remaining steps only → max 2 retries → orchestrator fallback
|
|
@@ -234,7 +234,7 @@ Wait for Reviewer to return.
|
|
|
234
234
|
|
|
235
235
|
### Phase 7: Summarize & Commit — DO NOT SKIP
|
|
236
236
|
|
|
237
|
-
**For bug fixes**: skip `prizmkit.summarize`, use `fix(<scope>):` commit prefix.
|
|
237
|
+
**For bug fixes**: skip `prizmkit.summarize`, skip retrospective, and use `fix(<scope>):` commit prefix.
|
|
238
238
|
|
|
239
239
|
**7a.** Check if feature already committed:
|
|
240
240
|
```bash
|
|
@@ -245,6 +245,20 @@ git log --oneline | grep "{{FEATURE_ID}}" | head -3
|
|
|
245
245
|
|
|
246
246
|
**7b.** Run `prizmkit.summarize` → archive to REGISTRY.md
|
|
247
247
|
|
|
248
|
+
**7b.5.** Update prizm-docs (feature sessions):
|
|
249
|
+
- Read the 'Implementation Log' in `context-snapshot.md` to identify changed files
|
|
250
|
+
- For each changed file, check if any related `.prizm-docs/` L1/L2 doc needs updating (new APIs, changed behavior, new TRAPS)
|
|
251
|
+
- Update `.prizm-docs/changelog.prizm`: append one-line entry `- feat({{FEATURE_ID}}): {{FEATURE_TITLE}}`
|
|
252
|
+
- Stage all modified prizm-docs: `git add .prizm-docs/`
|
|
253
|
+
- Pipeline docs pass condition is `REGISTRY.md` OR `.prizm-docs/` changed in final commit
|
|
254
|
+
|
|
255
|
+
**7b.6.** Run `prizmkit.retrospective` (feature sessions only, **before commit**):
|
|
256
|
+
- Extract lessons from completed feature — compare spec/plan/tasks vs actual
|
|
257
|
+
- Update relevant `.prizm-docs/` sections: TRAPS, RULES, DECISIONS
|
|
258
|
+
- Write `.prizmkit/specs/{{FEATURE_SLUG}}/retrospective.md`
|
|
259
|
+
- Stage any `.prizm-docs/` changes: `git add .prizm-docs/`
|
|
260
|
+
- **Skip if this session is bug-fix-only**
|
|
261
|
+
|
|
248
262
|
**7c.** Mark feature complete:
|
|
249
263
|
```bash
|
|
250
264
|
python3 {{VALIDATOR_SCRIPTS_DIR}}/update-feature-status.py \
|
|
@@ -255,6 +269,8 @@ python3 {{VALIDATOR_SCRIPTS_DIR}}/update-feature-status.py \
|
|
|
255
269
|
|
|
256
270
|
**7d.** Run `prizmkit.committer` → `feat({{FEATURE_ID}}): {{FEATURE_TITLE}}`, do NOT push
|
|
257
271
|
|
|
272
|
+
**7e.** MANDATORY: commit must be done via `prizmkit.committer` skill. Do NOT run manual `git add`/`git commit` as a substitute.
|
|
273
|
+
|
|
258
274
|
---
|
|
259
275
|
|
|
260
276
|
## Step 3: Write Session Status
|
|
@@ -267,7 +283,7 @@ Write to: `{{SESSION_STATUS_PATH}}`
|
|
|
267
283
|
"feature_id": "{{FEATURE_ID}}",
|
|
268
284
|
"feature_slug": "{{FEATURE_SLUG}}",
|
|
269
285
|
"exec_tier": 3,
|
|
270
|
-
"status": "<success|partial|failed>",
|
|
286
|
+
"status": "<success|partial|failed|commit_missing|docs_missing>",
|
|
271
287
|
"completed_phases": [0, 1, 2, 3, 4, 5, 6, 7],
|
|
272
288
|
"current_phase": 7,
|
|
273
289
|
"checkpoint_reached": "CP-3",
|
|
@@ -276,6 +292,8 @@ Write to: `{{SESSION_STATUS_PATH}}`
|
|
|
276
292
|
"errors": [],
|
|
277
293
|
"can_resume": false,
|
|
278
294
|
"resume_from_phase": null,
|
|
295
|
+
"docs_maintained": true,
|
|
296
|
+
"retrospective_done": true,
|
|
279
297
|
"artifacts": {
|
|
280
298
|
"context_snapshot_path": ".prizmkit/specs/{{FEATURE_SLUG}}/context-snapshot.md",
|
|
281
299
|
"spec_path": ".prizmkit/specs/{{FEATURE_SLUG}}/spec.md",
|
|
@@ -287,6 +305,23 @@ Write to: `{{SESSION_STATUS_PATH}}`
|
|
|
287
305
|
}
|
|
288
306
|
```
|
|
289
307
|
|
|
308
|
+
### Step 3.1: Final Clean Check (before exit)
|
|
309
|
+
|
|
310
|
+
After writing `session-status.json`, verify repository is clean:
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
git status --short
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
If any files remain, include them in the last commit:
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
git add -A
|
|
320
|
+
git commit --amend --no-edit
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Re-check `git status --short` and ensure it is empty before exiting.
|
|
324
|
+
|
|
290
325
|
## Step 4: Team Cleanup
|
|
291
326
|
|
|
292
327
|
No team cleanup needed — agents were spawned directly without TeamCreate.
|
|
@@ -311,4 +346,6 @@ No team cleanup needed — agents were spawned directly without TeamCreate.
|
|
|
311
346
|
- context-snapshot.md is the team knowledge base: PM writes it once, all agents read it
|
|
312
347
|
- Do NOT use `run_in_background=true` when spawning agents
|
|
313
348
|
- ALWAYS write session-status.json before exiting
|
|
349
|
+
- Commit phase must use `prizmkit.committer`; do NOT replace with manual git commit commands
|
|
350
|
+
- Before exiting, `git status --short` must be empty
|
|
314
351
|
- On timeout: check snapshot → model:lite → remaining steps only → max 2 retries → orchestrator fallback
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
"status": {
|
|
17
17
|
"type": "string",
|
|
18
|
-
"enum": ["success", "partial", "failed"]
|
|
18
|
+
"enum": ["success", "partial", "failed", "commit_missing", "docs_missing"]
|
|
19
19
|
},
|
|
20
20
|
"completed_phases": {
|
|
21
21
|
"type": "array",
|
|
@@ -58,6 +58,12 @@
|
|
|
58
58
|
"can_resume": {
|
|
59
59
|
"type": "boolean"
|
|
60
60
|
},
|
|
61
|
+
"docs_maintained": {
|
|
62
|
+
"type": "boolean"
|
|
63
|
+
},
|
|
64
|
+
"retrospective_done": {
|
|
65
|
+
"type": "boolean"
|
|
66
|
+
},
|
|
61
67
|
"resume_from_phase": {
|
|
62
68
|
"type": ["integer", "null"]
|
|
63
69
|
},
|
|
File without changes
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Shared fixtures for dev-pipeline tests."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
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)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Tests for check-session-status.py."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _import_check_session():
|
|
10
|
+
import importlib.util
|
|
11
|
+
path = os.path.join(
|
|
12
|
+
os.path.dirname(__file__), "..", "scripts", "check-session-status.py"
|
|
13
|
+
)
|
|
14
|
+
spec = importlib.util.spec_from_file_location("check_session_status", path)
|
|
15
|
+
mod = importlib.util.module_from_spec(spec)
|
|
16
|
+
sys.modules["check_session_status"] = mod
|
|
17
|
+
spec.loader.exec_module(mod)
|
|
18
|
+
return mod
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
css = _import_check_session()
|
|
22
|
+
validate_required_fields = css.validate_required_fields
|
|
23
|
+
determine_status = css.determine_status
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TestValidateRequiredFields:
|
|
27
|
+
def test_all_present(self):
|
|
28
|
+
data = {
|
|
29
|
+
"session_id": "s-001",
|
|
30
|
+
"feature_id": "F-001",
|
|
31
|
+
"status": "success",
|
|
32
|
+
"timestamp": "2024-01-01T00:00:00Z",
|
|
33
|
+
}
|
|
34
|
+
missing = validate_required_fields(data)
|
|
35
|
+
assert missing == []
|
|
36
|
+
|
|
37
|
+
def test_missing_session_id(self):
|
|
38
|
+
data = {
|
|
39
|
+
"feature_id": "F-001",
|
|
40
|
+
"status": "success",
|
|
41
|
+
"timestamp": "2024-01-01T00:00:00Z",
|
|
42
|
+
}
|
|
43
|
+
missing = validate_required_fields(data)
|
|
44
|
+
assert "session_id" in missing
|
|
45
|
+
|
|
46
|
+
def test_missing_multiple(self):
|
|
47
|
+
data = {"feature_id": "F-001"}
|
|
48
|
+
missing = validate_required_fields(data)
|
|
49
|
+
assert "session_id" in missing
|
|
50
|
+
assert "status" in missing
|
|
51
|
+
assert "timestamp" in missing
|
|
52
|
+
|
|
53
|
+
def test_empty_string_field(self):
|
|
54
|
+
data = {
|
|
55
|
+
"session_id": "",
|
|
56
|
+
"feature_id": "F-001",
|
|
57
|
+
"status": "success",
|
|
58
|
+
"timestamp": "2024-01-01T00:00:00Z",
|
|
59
|
+
}
|
|
60
|
+
missing = validate_required_fields(data)
|
|
61
|
+
assert "session_id" in missing
|
|
62
|
+
|
|
63
|
+
def test_whitespace_only_field(self):
|
|
64
|
+
data = {
|
|
65
|
+
"session_id": " ",
|
|
66
|
+
"feature_id": "F-001",
|
|
67
|
+
"status": "success",
|
|
68
|
+
"timestamp": "2024-01-01T00:00:00Z",
|
|
69
|
+
}
|
|
70
|
+
missing = validate_required_fields(data)
|
|
71
|
+
assert "session_id" in missing
|
|
72
|
+
|
|
73
|
+
def test_non_string_field(self):
|
|
74
|
+
data = {
|
|
75
|
+
"session_id": 123,
|
|
76
|
+
"feature_id": "F-001",
|
|
77
|
+
"status": "success",
|
|
78
|
+
"timestamp": "2024-01-01T00:00:00Z",
|
|
79
|
+
}
|
|
80
|
+
missing = validate_required_fields(data)
|
|
81
|
+
assert "session_id" in missing
|
|
82
|
+
|
|
83
|
+
def test_empty_data(self):
|
|
84
|
+
missing = validate_required_fields({})
|
|
85
|
+
assert len(missing) == 4
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class TestDetermineStatus:
|
|
89
|
+
def test_success(self):
|
|
90
|
+
data = {"status": "success"}
|
|
91
|
+
assert determine_status(data) == "success"
|
|
92
|
+
|
|
93
|
+
def test_failed(self):
|
|
94
|
+
data = {"status": "failed"}
|
|
95
|
+
assert determine_status(data) == "failed"
|
|
96
|
+
|
|
97
|
+
def test_partial_resumable(self):
|
|
98
|
+
data = {"status": "partial", "can_resume": True}
|
|
99
|
+
assert determine_status(data) == "partial_resumable"
|
|
100
|
+
|
|
101
|
+
def test_partial_not_resumable(self):
|
|
102
|
+
data = {"status": "partial", "can_resume": False}
|
|
103
|
+
assert determine_status(data) == "partial_not_resumable"
|
|
104
|
+
|
|
105
|
+
def test_partial_no_resume_key(self):
|
|
106
|
+
data = {"status": "partial"}
|
|
107
|
+
assert determine_status(data) == "partial_not_resumable"
|
|
108
|
+
|
|
109
|
+
def test_commit_missing(self):
|
|
110
|
+
data = {"status": "commit_missing"}
|
|
111
|
+
assert determine_status(data) == "commit_missing"
|
|
112
|
+
|
|
113
|
+
def test_docs_missing(self):
|
|
114
|
+
data = {"status": "docs_missing"}
|
|
115
|
+
assert determine_status(data) == "docs_missing"
|
|
116
|
+
|
|
117
|
+
def test_unknown_status(self):
|
|
118
|
+
data = {"status": "something_weird"}
|
|
119
|
+
assert determine_status(data) == "crashed"
|
|
120
|
+
|
|
121
|
+
def test_empty_status(self):
|
|
122
|
+
data = {"status": ""}
|
|
123
|
+
assert determine_status(data) == "crashed"
|
|
124
|
+
|
|
125
|
+
def test_missing_status(self):
|
|
126
|
+
data = {}
|
|
127
|
+
assert determine_status(data) == "crashed"
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Tests for cleanup-logs.py."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
import time
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _script_path():
|
|
11
|
+
return os.path.join(
|
|
12
|
+
os.path.dirname(__file__), "..", "scripts", "cleanup-logs.py"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _write_log_file(path, content):
|
|
17
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
18
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
19
|
+
f.write(content)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_iter_log_files_only_sessions_logs(state_dir):
|
|
23
|
+
# Targeted log path under sessions/*/logs/
|
|
24
|
+
valid_log = os.path.join(
|
|
25
|
+
state_dir, "features", "F-001", "sessions", "S-001", "logs", "session.log"
|
|
26
|
+
)
|
|
27
|
+
_write_log_file(valid_log, "ok")
|
|
28
|
+
|
|
29
|
+
# logs/ not under sessions should be ignored
|
|
30
|
+
ignored_log = os.path.join(state_dir, "features", "F-001", "logs", "misc.log")
|
|
31
|
+
_write_log_file(ignored_log, "ignore me")
|
|
32
|
+
|
|
33
|
+
# Import script module for direct function test
|
|
34
|
+
import importlib.util
|
|
35
|
+
|
|
36
|
+
path = _script_path()
|
|
37
|
+
spec = importlib.util.spec_from_file_location("cleanup_logs", path)
|
|
38
|
+
mod = importlib.util.module_from_spec(spec)
|
|
39
|
+
sys.modules["cleanup_logs"] = mod
|
|
40
|
+
spec.loader.exec_module(mod)
|
|
41
|
+
|
|
42
|
+
files = list(mod.iter_log_files(state_dir))
|
|
43
|
+
assert valid_log in files
|
|
44
|
+
assert ignored_log not in files
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_cleanup_removes_old_logs_by_retention(state_dir):
|
|
48
|
+
old_log = os.path.join(
|
|
49
|
+
state_dir, "features", "F-001", "sessions", "S-001", "logs", "session.log"
|
|
50
|
+
)
|
|
51
|
+
new_log = os.path.join(
|
|
52
|
+
state_dir, "features", "F-001", "sessions", "S-002", "logs", "session.log"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
_write_log_file(old_log, "old")
|
|
56
|
+
_write_log_file(new_log, "new")
|
|
57
|
+
|
|
58
|
+
# Make old log 20 days old
|
|
59
|
+
old_ts = time.time() - (20 * 86400)
|
|
60
|
+
os.utime(old_log, (old_ts, old_ts))
|
|
61
|
+
|
|
62
|
+
result = subprocess.run(
|
|
63
|
+
[
|
|
64
|
+
sys.executable,
|
|
65
|
+
_script_path(),
|
|
66
|
+
"--state-dir",
|
|
67
|
+
state_dir,
|
|
68
|
+
"--retention-days",
|
|
69
|
+
"14",
|
|
70
|
+
"--max-total-mb",
|
|
71
|
+
"1024",
|
|
72
|
+
],
|
|
73
|
+
check=True,
|
|
74
|
+
capture_output=True,
|
|
75
|
+
text=True,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
report = json.loads(result.stdout)
|
|
79
|
+
assert report["deleted_by_reason"]["retention"] >= 1
|
|
80
|
+
assert not os.path.exists(old_log)
|
|
81
|
+
assert os.path.exists(new_log)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_cleanup_enforces_total_size_cap(state_dir):
|
|
85
|
+
log1 = os.path.join(
|
|
86
|
+
state_dir, "features", "F-001", "sessions", "S-001", "logs", "session.log"
|
|
87
|
+
)
|
|
88
|
+
log2 = os.path.join(
|
|
89
|
+
state_dir, "features", "F-001", "sessions", "S-002", "logs", "session.log"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
_write_log_file(log1, "a" * 1024)
|
|
93
|
+
_write_log_file(log2, "b" * 1024)
|
|
94
|
+
|
|
95
|
+
# Ensure deterministic ordering: log1 older than log2
|
|
96
|
+
old_ts = time.time() - 120
|
|
97
|
+
new_ts = time.time() - 60
|
|
98
|
+
os.utime(log1, (old_ts, old_ts))
|
|
99
|
+
os.utime(log2, (new_ts, new_ts))
|
|
100
|
+
|
|
101
|
+
result = subprocess.run(
|
|
102
|
+
[
|
|
103
|
+
sys.executable,
|
|
104
|
+
_script_path(),
|
|
105
|
+
"--state-dir",
|
|
106
|
+
state_dir,
|
|
107
|
+
"--retention-days",
|
|
108
|
+
"999",
|
|
109
|
+
"--max-total-mb",
|
|
110
|
+
"0",
|
|
111
|
+
],
|
|
112
|
+
check=True,
|
|
113
|
+
capture_output=True,
|
|
114
|
+
text=True,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
report = json.loads(result.stdout)
|
|
118
|
+
assert report["deleted_by_reason"]["size"] >= 1
|
|
119
|
+
assert report["final_total_bytes"] == 0
|