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
|
@@ -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"), {}
|