prizmkit 1.1.153 → 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.
Files changed (43) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/README.md +100 -87
  3. package/bundled/dev-pipeline/assets/skill-subagent-integration.md +1 -1
  4. package/bundled/dev-pipeline/prizmkit_runtime/checkpoint_state.py +14 -0
  5. package/bundled/dev-pipeline/prizmkit_runtime/cli.py +192 -110
  6. package/bundled/dev-pipeline/prizmkit_runtime/commands.py +146 -111
  7. package/bundled/dev-pipeline/prizmkit_runtime/daemon.py +3 -5
  8. package/bundled/dev-pipeline/prizmkit_runtime/interoperability.py +0 -1
  9. package/bundled/dev-pipeline/prizmkit_runtime/paths.py +0 -3
  10. package/bundled/dev-pipeline/prizmkit_runtime/reset.py +122 -15
  11. package/bundled/dev-pipeline/prizmkit_runtime/reset_preserve.py +1 -1
  12. package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +1 -1
  13. package/bundled/dev-pipeline/prizmkit_runtime/runners.py +19 -52
  14. package/bundled/dev-pipeline/prizmkit_runtime/status.py +106 -1
  15. package/bundled/dev-pipeline/scripts/init-bugfix-pipeline.py +2 -2
  16. package/bundled/dev-pipeline/scripts/update-bug-status.py +196 -67
  17. package/bundled/dev-pipeline/scripts/update-checkpoint.py +16 -3
  18. package/bundled/dev-pipeline/scripts/update-feature-status.py +45 -117
  19. package/bundled/dev-pipeline/scripts/update-refactor-status.py +45 -119
  20. package/bundled/dev-pipeline/scripts/utils.py +119 -0
  21. package/bundled/dev-pipeline/templates/bug-fix-list-schema.json +3 -2
  22. package/bundled/dev-pipeline/tests/test_auto_skip.py +111 -32
  23. package/bundled/dev-pipeline/tests/test_checkpoint_state.py +148 -12
  24. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +0 -19
  25. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +113 -188
  26. package/bundled/dev-pipeline/tests/test_recovery_workflow.py +211 -0
  27. package/bundled/dev-pipeline/tests/test_reset_modes.py +935 -0
  28. package/bundled/dev-pipeline/tests/test_reset_preserve.py +5 -5
  29. package/bundled/dev-pipeline/tests/test_unified_cli.py +468 -181
  30. package/bundled/skills/_metadata.json +1 -1
  31. package/bundled/skills/bug-planner/references/schema-validation.md +1 -1
  32. package/bundled/skills/bug-planner/scripts/validate-bug-list.py +1 -1
  33. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +21 -13
  34. package/bundled/skills/feature-pipeline-launcher/SKILL.md +22 -14
  35. package/bundled/skills/recovery-workflow/SKILL.md +7 -5
  36. package/bundled/skills/recovery-workflow/evals/evals.json +2 -2
  37. package/bundled/skills/recovery-workflow/references/detection.md +3 -3
  38. package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +1 -1
  39. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +21 -13
  40. package/bundled/templates/project-memory-template.md +19 -11
  41. package/package.json +1 -1
  42. package/bundled/dev-pipeline/prizmkit_runtime/runner_recovery.py +0 -228
  43. 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"]