prizmkit 1.1.152 → 1.1.154
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/dev-pipeline/README.md +100 -87
- package/bundled/dev-pipeline/assets/skill-subagent-integration.md +1 -1
- package/bundled/dev-pipeline/prizmkit_runtime/checkpoint_state.py +14 -0
- package/bundled/dev-pipeline/prizmkit_runtime/cli.py +192 -110
- package/bundled/dev-pipeline/prizmkit_runtime/commands.py +146 -111
- package/bundled/dev-pipeline/prizmkit_runtime/daemon.py +3 -5
- package/bundled/dev-pipeline/prizmkit_runtime/interoperability.py +0 -1
- package/bundled/dev-pipeline/prizmkit_runtime/paths.py +0 -3
- package/bundled/dev-pipeline/prizmkit_runtime/reset.py +122 -15
- package/bundled/dev-pipeline/prizmkit_runtime/reset_preserve.py +1 -1
- package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +1 -1
- package/bundled/dev-pipeline/prizmkit_runtime/runners.py +19 -52
- package/bundled/dev-pipeline/prizmkit_runtime/status.py +106 -1
- package/bundled/dev-pipeline/scripts/init-bugfix-pipeline.py +2 -2
- package/bundled/dev-pipeline/scripts/parse-stream-progress.py +10 -12
- package/bundled/dev-pipeline/scripts/update-bug-status.py +196 -67
- package/bundled/dev-pipeline/scripts/update-checkpoint.py +16 -3
- package/bundled/dev-pipeline/scripts/update-feature-status.py +45 -117
- package/bundled/dev-pipeline/scripts/update-refactor-status.py +45 -119
- package/bundled/dev-pipeline/scripts/utils.py +119 -0
- package/bundled/dev-pipeline/templates/bug-fix-list-schema.json +3 -2
- package/bundled/dev-pipeline/tests/test_auto_skip.py +111 -32
- package/bundled/dev-pipeline/tests/test_checkpoint_state.py +148 -12
- package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +0 -19
- package/bundled/dev-pipeline/tests/test_python_runner_parity.py +113 -188
- package/bundled/dev-pipeline/tests/test_recovery_workflow.py +211 -0
- package/bundled/dev-pipeline/tests/test_reset_modes.py +935 -0
- package/bundled/dev-pipeline/tests/test_reset_preserve.py +5 -5
- package/bundled/dev-pipeline/tests/test_unified_cli.py +544 -181
- package/bundled/skills/_metadata.json +1 -1
- package/bundled/skills/bug-planner/references/schema-validation.md +1 -1
- package/bundled/skills/bug-planner/scripts/validate-bug-list.py +1 -1
- package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +21 -13
- package/bundled/skills/feature-pipeline-launcher/SKILL.md +22 -14
- package/bundled/skills/recovery-workflow/SKILL.md +7 -5
- package/bundled/skills/recovery-workflow/evals/evals.json +2 -2
- package/bundled/skills/recovery-workflow/references/detection.md +3 -3
- package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +1 -1
- package/bundled/skills/refactor-pipeline-launcher/SKILL.md +21 -13
- package/bundled/templates/project-memory-template.md +19 -11
- package/package.json +1 -1
- package/bundled/dev-pipeline/prizmkit_runtime/runner_recovery.py +0 -228
- package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +0 -767
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Behavior tests for the canonical detector-only Recovery Workflow."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib.util
|
|
6
|
+
import json
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
13
|
+
DETECTOR_PATH = (
|
|
14
|
+
REPO_ROOT
|
|
15
|
+
/ "core"
|
|
16
|
+
/ "skills"
|
|
17
|
+
/ "orchestration-skill"
|
|
18
|
+
/ "workflows"
|
|
19
|
+
/ "recovery-workflow"
|
|
20
|
+
/ "scripts"
|
|
21
|
+
/ "detect-recovery-state.py"
|
|
22
|
+
)
|
|
23
|
+
STATE_DIRS = {
|
|
24
|
+
"feature": "features",
|
|
25
|
+
"bugfix": "bugfix",
|
|
26
|
+
"refactor": "refactor",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _load_detector(name: str):
|
|
31
|
+
spec = importlib.util.spec_from_file_location(name, DETECTOR_PATH)
|
|
32
|
+
assert spec is not None and spec.loader is not None
|
|
33
|
+
module = importlib.util.module_from_spec(spec)
|
|
34
|
+
spec.loader.exec_module(module)
|
|
35
|
+
return module
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _git(project: Path, *args: str) -> str:
|
|
39
|
+
result = subprocess.run(
|
|
40
|
+
["git", *args],
|
|
41
|
+
cwd=project,
|
|
42
|
+
check=True,
|
|
43
|
+
text=True,
|
|
44
|
+
stdout=subprocess.PIPE,
|
|
45
|
+
stderr=subprocess.PIPE,
|
|
46
|
+
)
|
|
47
|
+
return result.stdout.strip()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _init_repository(project: Path) -> None:
|
|
51
|
+
project.mkdir()
|
|
52
|
+
_git(project, "init", "-b", "main")
|
|
53
|
+
_git(project, "config", "user.email", "detector@example.com")
|
|
54
|
+
_git(project, "config", "user.name", "Detector Test")
|
|
55
|
+
(project / "README.md").write_text("detector fixture\n", encoding="utf-8")
|
|
56
|
+
_git(project, "add", "README.md")
|
|
57
|
+
_git(project, "commit", "-m", "fixture")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _write_checkout(
|
|
61
|
+
project: Path,
|
|
62
|
+
family: str,
|
|
63
|
+
task_id: str,
|
|
64
|
+
branch: str,
|
|
65
|
+
*,
|
|
66
|
+
state: str = "active",
|
|
67
|
+
worktree_path: str | None = None,
|
|
68
|
+
) -> Path:
|
|
69
|
+
checkout = project / ".prizmkit" / "state" / STATE_DIRS[family] / task_id / "checkout.json"
|
|
70
|
+
checkout.parent.mkdir(parents=True, exist_ok=True)
|
|
71
|
+
checkout.write_text(
|
|
72
|
+
json.dumps(
|
|
73
|
+
{
|
|
74
|
+
"task_type": family,
|
|
75
|
+
"task_id": task_id,
|
|
76
|
+
"state": state,
|
|
77
|
+
"active_dev_branch": branch,
|
|
78
|
+
"base_branch": "main",
|
|
79
|
+
"checkout_mode": "worktree" if worktree_path else "branch",
|
|
80
|
+
"worktree_path": worktree_path,
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
+ "\n",
|
|
84
|
+
encoding="utf-8",
|
|
85
|
+
)
|
|
86
|
+
return checkout
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _repository_snapshot(project: Path) -> tuple[str, str, str]:
|
|
90
|
+
return (
|
|
91
|
+
_git(project, "branch", "--show-current"),
|
|
92
|
+
_git(project, "for-each-ref", "--format=%(refname):%(objectname)", "refs/heads"),
|
|
93
|
+
_git(project, "status", "--porcelain=v1", "--untracked-files=all"),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _historical_snapshot(historical: Path) -> dict[str, bytes]:
|
|
98
|
+
return {
|
|
99
|
+
path.relative_to(historical).as_posix(): path.read_bytes()
|
|
100
|
+
for path in historical.rglob("*")
|
|
101
|
+
if path.is_file()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_single_active_checkout_reports_operation_first_command_without_mutation(tmp_path):
|
|
106
|
+
project = tmp_path / "project"
|
|
107
|
+
_init_repository(project)
|
|
108
|
+
branch = "dev/F-061-resume"
|
|
109
|
+
_git(project, "branch", branch)
|
|
110
|
+
checkout = _write_checkout(project, "feature", "F-061", branch)
|
|
111
|
+
before = _repository_snapshot(project)
|
|
112
|
+
|
|
113
|
+
result = subprocess.run(
|
|
114
|
+
[sys.executable, str(DETECTOR_PATH), "--project-root", str(project)],
|
|
115
|
+
cwd=project,
|
|
116
|
+
check=False,
|
|
117
|
+
text=True,
|
|
118
|
+
stdout=subprocess.PIPE,
|
|
119
|
+
stderr=subprocess.PIPE,
|
|
120
|
+
)
|
|
121
|
+
report = json.loads(result.stdout)
|
|
122
|
+
|
|
123
|
+
assert result.returncode == 0
|
|
124
|
+
assert result.stderr == ""
|
|
125
|
+
assert report["detected"] is True
|
|
126
|
+
assert report["selection_required"] is False
|
|
127
|
+
assert report["current_branch"] == "main"
|
|
128
|
+
assert len(report["candidates"]) == 1
|
|
129
|
+
candidate = report["candidates"][0]
|
|
130
|
+
assert candidate["family"] == "feature"
|
|
131
|
+
assert candidate["task_id"] == "F-061"
|
|
132
|
+
assert candidate["branch"] == branch
|
|
133
|
+
assert candidate["checkout_path"] == checkout.relative_to(project).as_posix()
|
|
134
|
+
assert candidate["worktree_path"] is None
|
|
135
|
+
assert candidate["continue_command"] == (
|
|
136
|
+
"python3 ./.prizmkit/dev-pipeline/cli.py run feature F-061"
|
|
137
|
+
)
|
|
138
|
+
assert _repository_snapshot(project) == before
|
|
139
|
+
assert not (project / ".prizmkit" / "state" / "recovery").exists()
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_multiple_candidates_remain_skill_owned_and_ignore_historical_recovery_data(tmp_path):
|
|
143
|
+
project = tmp_path / "project"
|
|
144
|
+
_init_repository(project)
|
|
145
|
+
branches = {
|
|
146
|
+
"feature": ("F-002", "dev/F-002-resume"),
|
|
147
|
+
"bugfix": ("B-007", "fix/B-007-resume"),
|
|
148
|
+
}
|
|
149
|
+
for task_id, branch in branches.values():
|
|
150
|
+
_git(project, "branch", branch)
|
|
151
|
+
for family, (task_id, branch) in branches.items():
|
|
152
|
+
_write_checkout(project, family, task_id, branch)
|
|
153
|
+
|
|
154
|
+
historical = project / ".prizmkit" / "state" / "recovery" / "old-session"
|
|
155
|
+
historical.mkdir(parents=True)
|
|
156
|
+
(historical / "detection.json").write_text(
|
|
157
|
+
'{"detected": true, "legacy": true}\n', encoding="utf-8"
|
|
158
|
+
)
|
|
159
|
+
history_before = _historical_snapshot(historical)
|
|
160
|
+
repository_before = _repository_snapshot(project)
|
|
161
|
+
detector = _load_detector("recovery_workflow_multiple")
|
|
162
|
+
|
|
163
|
+
report = detector.build_report(project)
|
|
164
|
+
|
|
165
|
+
assert report["detected"] is True
|
|
166
|
+
assert report["selection_required"] is True
|
|
167
|
+
assert [candidate["task_id"] for candidate in report["candidates"]] == ["F-002", "B-007"]
|
|
168
|
+
assert [candidate["continue_command"] for candidate in report["candidates"]] == [
|
|
169
|
+
"python3 ./.prizmkit/dev-pipeline/cli.py run feature F-002",
|
|
170
|
+
"python3 ./.prizmkit/dev-pipeline/cli.py run bugfix B-007",
|
|
171
|
+
]
|
|
172
|
+
assert _repository_snapshot(project) == repository_before
|
|
173
|
+
assert _historical_snapshot(historical) == history_before
|
|
174
|
+
assert sorted(path.name for path in historical.parent.iterdir()) == ["old-session"]
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def test_zero_candidates_filters_invalid_records_without_history_fallback_or_session_creation(tmp_path):
|
|
178
|
+
project = tmp_path / "project"
|
|
179
|
+
_init_repository(project)
|
|
180
|
+
completed_branch = "fix/B-003-complete"
|
|
181
|
+
wrong_family_branch = "dev/B-999-wrong-family"
|
|
182
|
+
_git(project, "branch", completed_branch)
|
|
183
|
+
_git(project, "branch", wrong_family_branch)
|
|
184
|
+
_write_checkout(
|
|
185
|
+
project,
|
|
186
|
+
"bugfix",
|
|
187
|
+
"B-003",
|
|
188
|
+
completed_branch,
|
|
189
|
+
state="completed",
|
|
190
|
+
)
|
|
191
|
+
_write_checkout(project, "feature", "B-999", wrong_family_branch)
|
|
192
|
+
_write_checkout(project, "refactor", "R-008", "refactor/R-008-missing")
|
|
193
|
+
|
|
194
|
+
historical = project / ".prizmkit" / "state" / "recovery" / "historical-only"
|
|
195
|
+
historical.mkdir(parents=True)
|
|
196
|
+
(historical / "bootstrap-prompt.md").write_text("do not read or delete\n", encoding="utf-8")
|
|
197
|
+
history_before = _historical_snapshot(historical)
|
|
198
|
+
repository_before = _repository_snapshot(project)
|
|
199
|
+
detector = _load_detector("recovery_workflow_empty")
|
|
200
|
+
|
|
201
|
+
report = detector.build_report(project)
|
|
202
|
+
|
|
203
|
+
assert report == {
|
|
204
|
+
"detected": False,
|
|
205
|
+
"current_branch": "main",
|
|
206
|
+
"candidates": [],
|
|
207
|
+
"message": "No active interrupted pipeline task branch was found.",
|
|
208
|
+
}
|
|
209
|
+
assert _repository_snapshot(project) == repository_before
|
|
210
|
+
assert _historical_snapshot(historical) == history_before
|
|
211
|
+
assert sorted(path.name for path in historical.parent.iterdir()) == ["historical-only"]
|