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.
Files changed (44) 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/parse-stream-progress.py +10 -12
  17. package/bundled/dev-pipeline/scripts/update-bug-status.py +196 -67
  18. package/bundled/dev-pipeline/scripts/update-checkpoint.py +16 -3
  19. package/bundled/dev-pipeline/scripts/update-feature-status.py +45 -117
  20. package/bundled/dev-pipeline/scripts/update-refactor-status.py +45 -119
  21. package/bundled/dev-pipeline/scripts/utils.py +119 -0
  22. package/bundled/dev-pipeline/templates/bug-fix-list-schema.json +3 -2
  23. package/bundled/dev-pipeline/tests/test_auto_skip.py +111 -32
  24. package/bundled/dev-pipeline/tests/test_checkpoint_state.py +148 -12
  25. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +0 -19
  26. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +113 -188
  27. package/bundled/dev-pipeline/tests/test_recovery_workflow.py +211 -0
  28. package/bundled/dev-pipeline/tests/test_reset_modes.py +935 -0
  29. package/bundled/dev-pipeline/tests/test_reset_preserve.py +5 -5
  30. package/bundled/dev-pipeline/tests/test_unified_cli.py +544 -181
  31. package/bundled/skills/_metadata.json +1 -1
  32. package/bundled/skills/bug-planner/references/schema-validation.md +1 -1
  33. package/bundled/skills/bug-planner/scripts/validate-bug-list.py +1 -1
  34. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +21 -13
  35. package/bundled/skills/feature-pipeline-launcher/SKILL.md +22 -14
  36. package/bundled/skills/recovery-workflow/SKILL.md +7 -5
  37. package/bundled/skills/recovery-workflow/evals/evals.json +2 -2
  38. package/bundled/skills/recovery-workflow/references/detection.md +3 -3
  39. package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +1 -1
  40. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +21 -13
  41. package/bundled/templates/project-memory-template.md +19 -11
  42. package/package.json +1 -1
  43. package/bundled/dev-pipeline/prizmkit_runtime/runner_recovery.py +0 -228
  44. package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +0 -767
@@ -1,228 +0,0 @@
1
- """Recovery detection and execution for Python foreground runner commands."""
2
-
3
- from __future__ import annotations
4
-
5
- import json
6
- import subprocess
7
- import sys
8
- import time
9
- from dataclasses import dataclass
10
- from pathlib import Path
11
-
12
- from .checkpoint_state import load_checkpoint_state
13
- from .config import load_runtime_config
14
- from .paths import RuntimePaths
15
- from .runner_models import RunnerEnvironment, SessionPaths
16
- from .sessions import AISessionConfig, AISessionLauncher, detect_stream_json_support
17
-
18
-
19
- @dataclass(frozen=True)
20
- class RecoveryDetectorResolver:
21
- """Resolve recovery detector sources without coupling to one platform copy."""
22
-
23
- paths: RuntimePaths
24
-
25
- def candidates(self) -> tuple[Path, ...]:
26
- project = self.paths.project_root
27
- return (
28
- project / ".agents" / "skills" / "recovery-workflow" / "scripts" / "detect-recovery-state.py",
29
- project / ".claude" / "command-assets" / "recovery-workflow" / "scripts" / "detect-recovery-state.py",
30
- project / ".codebuddy" / "skills" / "recovery-workflow" / "scripts" / "detect-recovery-state.py",
31
- project / ".pi" / "skills" / "recovery-workflow" / "scripts" / "detect-recovery-state.py",
32
- project
33
- / "core"
34
- / "skills"
35
- / "orchestration-skill"
36
- / "workflows"
37
- / "recovery-workflow"
38
- / "scripts"
39
- / "detect-recovery-state.py",
40
- )
41
-
42
- def resolve(self) -> Path:
43
- for candidate in self.candidates():
44
- if candidate.is_file():
45
- return candidate
46
- raise FileNotFoundError("recovery detector script not found")
47
-
48
-
49
- def run_recovery_detect(paths: RuntimePaths) -> tuple[int, str, str, dict[str, object] | None]:
50
- """Run recovery state detection and parse the JSON report."""
51
- detector = RecoveryDetectorResolver(paths).resolve()
52
- completed = subprocess.run(
53
- [sys.executable, str(detector), "--project-root", str(paths.project_root)],
54
- cwd=str(paths.project_root),
55
- stdout=subprocess.PIPE,
56
- stderr=subprocess.PIPE,
57
- text=True,
58
- check=False,
59
- )
60
- data = None
61
- if completed.stdout.strip():
62
- try:
63
- data = json.loads(completed.stdout)
64
- except json.JSONDecodeError:
65
- data = None
66
- return completed.returncode, completed.stdout, completed.stderr, data
67
-
68
-
69
- def run_recovery(paths: RuntimePaths, legacy_args: tuple[str, ...] = ()) -> tuple[int, str, str]:
70
- """Detect recovery state, render a prompt, launch an AI session, and classify."""
71
- dry_run = "--dry-run" in legacy_args
72
- model_override = _option_value(legacy_args, "--model")
73
- code, stdout, stderr, detection = run_recovery_detect(paths)
74
- if code != 0 or not detection:
75
- return code or 1, stdout, stderr
76
- if not detection.get("detected"):
77
- return 0, stdout, stderr
78
-
79
- session_id = "recovery-{}-{}".format(
80
- str(detection.get("workflow_type") or "workflow"),
81
- time.strftime("%Y%m%d-%H%M%S"),
82
- )
83
- session_dir = paths.recovery_state_dir / session_id
84
- session_paths = SessionPaths(
85
- session_dir=session_dir,
86
- prompt_path=session_dir / "bootstrap-prompt.md",
87
- logs_dir=session_dir / "logs",
88
- session_log=session_dir / "logs" / "session.log",
89
- progress_json=session_dir / "logs" / "progress.json",
90
- heartbeat_json=session_dir / "logs" / "heartbeat.json",
91
- session_status_json=session_dir / "session-status.json",
92
- )
93
- detection_json = session_paths.session_dir / "detection.json"
94
- session_paths.session_dir.mkdir(parents=True, exist_ok=True)
95
- detection_json.write_text(json.dumps(detection, indent=2, ensure_ascii=False), encoding="utf-8")
96
- prompt_output = session_paths.prompt_path
97
- generator = paths.pipeline_root / "scripts" / "generate-recovery-prompt.py"
98
- prompt_cmd = [
99
- sys.executable,
100
- str(generator),
101
- "--detection-json",
102
- str(detection_json),
103
- "--output",
104
- str(prompt_output),
105
- "--project-root",
106
- str(paths.project_root),
107
- "--session-id",
108
- session_id,
109
- ]
110
- prompt_result = subprocess.run(
111
- prompt_cmd,
112
- cwd=str(paths.project_root),
113
- stdout=subprocess.PIPE,
114
- stderr=subprocess.PIPE,
115
- text=True,
116
- check=False,
117
- )
118
- if prompt_result.returncode != 0:
119
- return prompt_result.returncode, prompt_result.stdout, prompt_result.stderr
120
- if dry_run:
121
- return 0, prompt_result.stdout, prompt_result.stderr
122
-
123
- config = load_runtime_config(paths)
124
- if not config.ai_client.command:
125
- return 1, "", "No supported AI CLI command was found for recovery\n"
126
- env = RunnerEnvironment.from_env()
127
- stream_json = False
128
- if env.live_output and getattr(config.ai_client, "launch_profile", None) is None:
129
- stream_json = detect_stream_json_support(config.ai_client.command, config.ai_client.platform).enabled
130
- launcher = AISessionLauncher(
131
- AISessionConfig(
132
- cli_command=config.ai_client.command,
133
- platform=config.ai_client.platform or "codebuddy",
134
- model=model_override or config.model,
135
- prompt_path=prompt_output,
136
- cwd=paths.project_root,
137
- log_path=session_paths.session_log,
138
- progress_path=session_paths.progress_json,
139
- heartbeat_path=session_paths.heartbeat_json,
140
- effort=config.effort,
141
- verbose=env.verbose,
142
- live_output=env.live_output,
143
- use_stream_json=stream_json,
144
- launch_profile=getattr(config.ai_client, "launch_profile", None),
145
- stale_kill_threshold_seconds=env.stale_kill_threshold_seconds,
146
- heartbeat_stale_threshold_seconds=env.heartbeat_stale_threshold_seconds,
147
- live_progress_interval_seconds=env.heartbeat_interval_seconds,
148
- live_banner=False,
149
- suppress_stream_output=True,
150
- )
151
- )
152
- result = launcher.run()
153
- checkpoint_path = _recovery_checkpoint_path(paths.project_root, detection)
154
- outcome, semantic = _recovery_outcome(
155
- paths.project_root,
156
- result.exit_code,
157
- checkpoint_path=checkpoint_path,
158
- )
159
- session_paths.session_status_json.write_text(
160
- json.dumps(
161
- {
162
- "session_id": session_id,
163
- "outcome": outcome,
164
- "checkpoint_path": str(checkpoint_path) if checkpoint_path else None,
165
- "checkpoint_state": semantic,
166
- },
167
- indent=2,
168
- )
169
- + "\n",
170
- encoding="utf-8",
171
- )
172
- return 0 if outcome in {"success", "no_changes"} else 1, outcome + "\n", ""
173
-
174
-
175
- def _option_value(args: tuple[str, ...], flag: str) -> str | None:
176
- for index, arg in enumerate(args):
177
- if arg == flag and index + 1 < len(args):
178
- return args[index + 1]
179
- if arg.startswith(flag + "="):
180
- return arg.split("=", 1)[1]
181
- return None
182
-
183
-
184
- def _recovery_checkpoint_path(project_root: Path, detection: dict[str, object]) -> Path | None:
185
- """Select the L4 checkpoint identified by recovery inspection."""
186
- semantic = detection.get("semantic_checkpoint")
187
- if isinstance(semantic, dict) and semantic.get("path"):
188
- candidate = project_root / str(semantic["path"])
189
- if candidate.is_file():
190
- return candidate
191
- execution_owner = detection.get("execution_owner")
192
- checkpoints = execution_owner.get("l4_checkpoints") if isinstance(execution_owner, dict) else []
193
- if isinstance(checkpoints, list) and len(checkpoints) == 1:
194
- candidate = project_root / str(checkpoints[0])
195
- if candidate.is_file():
196
- return candidate
197
- return None
198
-
199
-
200
- def _recovery_outcome(
201
- project_root: Path,
202
- exit_code: int | None,
203
- *,
204
- checkpoint_path: Path | None = None,
205
- ) -> tuple[str, dict[str, object]]:
206
- """Classify recovery from semantic L4 state without mutating partial work."""
207
- if checkpoint_path is not None:
208
- state = load_checkpoint_state(checkpoint_path, project_root=project_root)
209
- semantic = state.semantic_snapshot
210
- if state.valid and state.semantic_complete:
211
- return "success", semantic
212
- if state.valid and state.semantic_blocked:
213
- return "workflow_blocked", semantic
214
- if state.exists and not state.valid:
215
- return "blocked_invalid_checkpoint", semantic
216
- return "partial" if exit_code == 0 else "failed", semantic
217
-
218
- status = subprocess.run(
219
- ["git", "-C", str(project_root), "status", "--porcelain"],
220
- stdout=subprocess.PIPE,
221
- stderr=subprocess.DEVNULL,
222
- text=True,
223
- check=False,
224
- )
225
- has_changes = bool(status.stdout.strip())
226
- if exit_code == 0 and not has_changes:
227
- return "no_changes", {}
228
- return ("partial" if has_changes else "failed"), {}