prizmkit 1.1.130 → 1.1.134
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/adapters/claude/command-adapter.js +0 -2
- package/bundled/dev-pipeline/.env.example +1 -1
- package/bundled/dev-pipeline/assets/skill-subagent-integration.md +11 -6
- package/bundled/dev-pipeline/prizmkit_runtime/checkpoint_state.py +445 -8
- package/bundled/dev-pipeline/prizmkit_runtime/gitops.py +2 -9
- package/bundled/dev-pipeline/prizmkit_runtime/processes.py +21 -1
- package/bundled/dev-pipeline/prizmkit_runtime/runner_classification.py +87 -66
- package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +53 -3
- package/bundled/dev-pipeline/prizmkit_runtime/runner_recovery.py +55 -25
- package/bundled/dev-pipeline/prizmkit_runtime/runner_status.py +4 -0
- package/bundled/dev-pipeline/prizmkit_runtime/runners.py +119 -18
- package/bundled/dev-pipeline/prizmkit_runtime/sessions.py +3 -2
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +411 -17
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +57 -14
- package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +5 -4
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +55 -2
- package/bundled/dev-pipeline/scripts/prompt_framework.py +152 -1
- package/bundled/dev-pipeline/scripts/update-bug-status.py +53 -0
- package/bundled/dev-pipeline/scripts/update-checkpoint.py +294 -69
- package/bundled/dev-pipeline/scripts/update-feature-status.py +57 -0
- package/bundled/dev-pipeline/scripts/update-refactor-status.py +53 -0
- package/bundled/dev-pipeline/templates/bootstrap-prompt.md +5 -5
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +28 -22
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +24 -10
- package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +20 -2
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-commit-report.md +2 -2
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-diagnose-plan.md +1 -1
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-manual-verification.md +5 -10
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +9 -19
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-test.md +21 -0
- package/bundled/dev-pipeline/templates/sections/bugfix-session-status.md +1 -1
- package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +10 -10
- package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +1 -1
- package/bundled/dev-pipeline/templates/sections/headless-commit-authorization.md +15 -0
- package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +2 -2
- package/bundled/dev-pipeline/templates/sections/phase-commit.md +2 -2
- package/bundled/dev-pipeline/templates/sections/phase-prizmkit-test.md +26 -23
- package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +5 -5
- package/bundled/dev-pipeline/templates/sections/phase-review-full.md +15 -11
- package/bundled/dev-pipeline/templates/sections/refactor-phase-commit-report.md +1 -1
- package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +6 -6
- package/bundled/dev-pipeline/templates/sections/refactor-phase-test.md +21 -0
- package/bundled/dev-pipeline/templates/sections/refactor-reminders.md +1 -1
- package/bundled/dev-pipeline/templates/sections/subagent-timeout-recovery.md +1 -1
- package/bundled/dev-pipeline/templates/sections/task-contract.md +4 -0
- package/bundled/dev-pipeline/tests/test_checkpoint_state.py +362 -3
- package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +349 -48
- package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +129 -0
- package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +91 -0
- package/bundled/dev-pipeline/tests/test_python_runner_parity.py +567 -23
- package/bundled/dev-pipeline/tests/test_unified_cli.py +37 -10
- package/bundled/skills/_metadata.json +63 -26
- package/bundled/skills/app-planner/SKILL.md +4 -3
- package/bundled/skills/bug-fix-workflow/SKILL.md +89 -180
- package/bundled/skills/bug-planner/SKILL.md +12 -27
- package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +131 -220
- package/bundled/skills/bugfix-pipeline-launcher/references/configuration.md +36 -90
- package/bundled/skills/feature-pipeline-launcher/SKILL.md +129 -271
- package/bundled/skills/feature-pipeline-launcher/references/configuration.md +35 -76
- package/bundled/skills/feature-planner/SKILL.md +27 -22
- package/bundled/skills/feature-planner/assets/planning-guide.md +4 -4
- package/bundled/skills/feature-workflow/SKILL.md +94 -121
- package/bundled/skills/prizmkit/SKILL.md +130 -94
- package/bundled/skills/prizmkit/references/workflow-state-protocol.md +181 -0
- package/bundled/skills/prizmkit-code-review/SKILL.md +132 -86
- package/bundled/skills/prizmkit-code-review/references/independent-code-review.md +243 -0
- package/bundled/skills/prizmkit-code-review/references/review-report-template.md +42 -1
- package/bundled/skills/prizmkit-code-review/references/workflow-state-protocol.md +181 -0
- package/bundled/skills/prizmkit-code-review/scripts/render_review_report.py +93 -1
- package/bundled/skills/prizmkit-committer/SKILL.md +125 -48
- package/bundled/skills/prizmkit-committer/references/workflow-state-protocol.md +181 -0
- package/bundled/skills/prizmkit-implement/SKILL.md +99 -73
- package/bundled/skills/prizmkit-implement/references/implementation-subagent-procedure.md +1 -1
- package/bundled/skills/prizmkit-implement/references/workflow-state-protocol.md +181 -0
- package/bundled/skills/prizmkit-init/SKILL.md +5 -5
- package/bundled/skills/prizmkit-plan/SKILL.md +146 -105
- package/bundled/skills/prizmkit-plan/assets/plan-template.md +18 -0
- package/bundled/skills/prizmkit-plan/references/independent-plan-review.md +242 -0
- package/bundled/skills/prizmkit-plan/references/review-plan-spec-loop.md +4 -4
- package/bundled/skills/prizmkit-plan/references/workflow-state-protocol.md +181 -0
- package/bundled/skills/prizmkit-retrospective/SKILL.md +111 -53
- package/bundled/skills/prizmkit-retrospective/references/knowledge-injection-steps.md +7 -6
- package/bundled/skills/prizmkit-retrospective/references/structural-sync-steps.md +5 -4
- package/bundled/skills/prizmkit-retrospective/references/workflow-state-protocol.md +181 -0
- package/bundled/skills/prizmkit-test/SKILL.md +52 -6
- package/bundled/skills/prizmkit-test/assets/authoritative-records.schema.json +63 -12
- package/bundled/skills/prizmkit-test/assets/evidence-manifest.schema.json +1 -1
- package/bundled/skills/prizmkit-test/assets/evidence-package-template.json +13 -0
- package/bundled/skills/prizmkit-test/references/evidence-protocol.md +6 -1
- package/bundled/skills/prizmkit-test/references/evidence-request-protocol.md +8 -1
- package/bundled/skills/prizmkit-test/references/examples.md +1 -1
- package/bundled/skills/prizmkit-test/references/test-generation-steps.md +4 -1
- package/bundled/skills/prizmkit-test/references/test-report-template.md +1 -1
- package/bundled/skills/prizmkit-test/references/trusted-evidence-execution.md +15 -0
- package/bundled/skills/prizmkit-test/references/workflow-state-protocol.md +181 -0
- package/bundled/skills/prizmkit-test/scripts/build_test_evidence.py +744 -23
- package/bundled/skills/prizmkit-test/scripts/validate_test_evidence.py +194 -15
- package/bundled/skills/prizmkit-workflow/SKILL.md +266 -0
- package/bundled/skills/prizmkit-workflow/references/workflow-state-protocol.md +181 -0
- package/bundled/skills/recovery-workflow/SKILL.md +62 -241
- package/bundled/skills/recovery-workflow/evals/evals.json +17 -33
- package/bundled/skills/recovery-workflow/references/detection.md +28 -52
- package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +105 -439
- package/bundled/skills/refactor-pipeline-launcher/SKILL.md +136 -275
- package/bundled/skills/refactor-pipeline-launcher/references/configuration.md +53 -88
- package/bundled/skills/refactor-planner/SKILL.md +12 -57
- package/bundled/skills/refactor-planner/references/behavior-preservation.md +111 -230
- package/bundled/skills/refactor-planner/references/error-recovery.md +37 -0
- package/bundled/skills/refactor-planner/references/planning-phases.md +11 -17
- package/bundled/skills/refactor-planner/scripts/validate-and-generate-refactor.py +1 -1
- package/bundled/skills/refactor-workflow/SKILL.md +98 -139
- package/bundled/templates/project-memory-template.md +4 -4
- package/package.json +1 -1
- package/src/index.js +4 -0
- package/src/prompts.js +10 -3
- package/src/scaffold.js +1 -1
- package/bundled/dev-pipeline/tests/test-deploy-safety.sh +0 -223
- package/bundled/skills/feature-pipeline-launcher/scripts/preflight-check.py +0 -462
|
@@ -1,480 +1,146 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
detect-recovery-state.py — Universal workflow recovery detector.
|
|
2
|
+
"""Detect resumable pipeline task branches and print exact continuation commands."""
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
by inspecting git branch names, durable planning artifacts, pipeline state,
|
|
7
|
-
current-workspace plans, review artifacts, and code changes.
|
|
8
|
-
|
|
9
|
-
Does NOT run tests — the skill controls test execution so the user can see
|
|
10
|
-
real-time output.
|
|
11
|
-
|
|
12
|
-
Usage:
|
|
13
|
-
python3 detect-recovery-state.py [--project-root .]
|
|
14
|
-
"""
|
|
4
|
+
from __future__ import annotations
|
|
15
5
|
|
|
16
6
|
import argparse
|
|
17
7
|
import json
|
|
18
|
-
import os
|
|
19
|
-
import re
|
|
20
8
|
import subprocess
|
|
21
|
-
import sys
|
|
22
9
|
from pathlib import Path
|
|
23
10
|
|
|
24
11
|
|
|
25
|
-
|
|
26
|
-
|
|
12
|
+
FAMILIES = (
|
|
13
|
+
{
|
|
14
|
+
"name": "feature",
|
|
15
|
+
"state_dir": "features",
|
|
16
|
+
"plan_path": ".prizmkit/plans/feature-list.json",
|
|
17
|
+
"id_prefix": "F-",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "bugfix",
|
|
21
|
+
"state_dir": "bugfix",
|
|
22
|
+
"plan_path": ".prizmkit/plans/bug-fix-list.json",
|
|
23
|
+
"id_prefix": "B-",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "refactor",
|
|
27
|
+
"state_dir": "refactor",
|
|
28
|
+
"plan_path": ".prizmkit/plans/refactor-list.json",
|
|
29
|
+
"id_prefix": "R-",
|
|
30
|
+
},
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def run_git(project_root: Path, *args: str) -> str:
|
|
35
|
+
"""Return stripped Git stdout, or an empty string when Git cannot answer."""
|
|
27
36
|
try:
|
|
28
37
|
result = subprocess.run(
|
|
29
|
-
["git"
|
|
38
|
+
["git", *args],
|
|
39
|
+
cwd=project_root,
|
|
30
40
|
capture_output=True,
|
|
31
41
|
text=True,
|
|
32
|
-
cwd=cwd,
|
|
33
42
|
timeout=10,
|
|
43
|
+
check=False,
|
|
34
44
|
)
|
|
35
|
-
|
|
36
|
-
except (subprocess.SubprocessError, FileNotFoundError):
|
|
45
|
+
except (OSError, subprocess.SubprocessError):
|
|
37
46
|
return ""
|
|
47
|
+
return result.stdout.strip() if result.returncode == 0 else ""
|
|
38
48
|
|
|
39
49
|
|
|
40
|
-
def
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if check:
|
|
45
|
-
return candidate
|
|
46
|
-
return "main"
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def relpath(path, project_root):
|
|
50
|
-
return os.path.relpath(path, project_root)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def file_exists(project_root, *parts):
|
|
54
|
-
path = os.path.join(project_root, *parts)
|
|
55
|
-
return path if os.path.isfile(path) else None
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def dir_has_content(project_root, *parts):
|
|
59
|
-
path = os.path.join(project_root, *parts)
|
|
60
|
-
return os.path.isdir(path) and bool(os.listdir(path))
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def extract_bug_id_from_branch(branch):
|
|
64
|
-
"""Extract bug ID from branch name like fix/B-001-login-crash -> B-001."""
|
|
65
|
-
match = re.match(r"fix/(B-\d+)", branch or "")
|
|
66
|
-
return match.group(1) if match else None
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def latest_bugfix_artifact_id(project_root):
|
|
70
|
-
bugfix_dir = os.path.join(project_root, ".prizmkit", "bugfix")
|
|
71
|
-
if not os.path.isdir(bugfix_dir):
|
|
50
|
+
def load_json(path: Path) -> dict[str, object] | None:
|
|
51
|
+
try:
|
|
52
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
53
|
+
except (OSError, json.JSONDecodeError):
|
|
72
54
|
return None
|
|
73
|
-
|
|
74
|
-
d for d in os.listdir(bugfix_dir)
|
|
75
|
-
if os.path.isdir(os.path.join(bugfix_dir, d))
|
|
76
|
-
)
|
|
77
|
-
return bug_ids[-1] if bug_ids else None
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def detect_workflow_type(project_root):
|
|
81
|
-
"""Priority-ordered signature matching.
|
|
82
|
-
|
|
83
|
-
Returns (workflow_type, context_dict) or (None, None).
|
|
84
|
-
"""
|
|
85
|
-
branch = run_git(["branch", "--show-current"], cwd=project_root)
|
|
86
|
-
|
|
87
|
-
if branch.startswith("fix/"):
|
|
88
|
-
return ("bug-fix-workflow", {
|
|
89
|
-
"bug_id": extract_bug_id_from_branch(branch),
|
|
90
|
-
"branch": branch,
|
|
91
|
-
"signal": "fix branch",
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
if dir_has_content(project_root, ".prizmkit", "state", "bugfix"):
|
|
95
|
-
return ("bug-fix-workflow", {
|
|
96
|
-
"bug_id": extract_bug_id_from_branch(branch) or latest_bugfix_artifact_id(project_root),
|
|
97
|
-
"branch": branch,
|
|
98
|
-
"signal": "bugfix pipeline state",
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
if file_exists(project_root, ".prizmkit", "plans", "bug-fix-list.json"):
|
|
102
|
-
return ("bug-fix-workflow", {
|
|
103
|
-
"bug_id": extract_bug_id_from_branch(branch) or latest_bugfix_artifact_id(project_root),
|
|
104
|
-
"branch": branch,
|
|
105
|
-
"signal": "bug-fix-list.json",
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
bug_id = latest_bugfix_artifact_id(project_root)
|
|
109
|
-
if bug_id:
|
|
110
|
-
return ("bug-fix-workflow", {
|
|
111
|
-
"bug_id": bug_id,
|
|
112
|
-
"branch": branch,
|
|
113
|
-
"signal": "optional bugfix artifacts",
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
if branch.startswith("refactor/"):
|
|
117
|
-
return ("refactor-workflow", {"branch": branch, "signal": "refactor branch"})
|
|
118
|
-
|
|
119
|
-
if file_exists(project_root, ".prizmkit", "plans", "refactor-list.json"):
|
|
120
|
-
return ("refactor-workflow", {"branch": branch, "signal": "refactor-list.json"})
|
|
121
|
-
|
|
122
|
-
if dir_has_content(project_root, ".prizmkit", "state", "refactor"):
|
|
123
|
-
return ("refactor-workflow", {"branch": branch, "signal": "refactor pipeline state"})
|
|
55
|
+
return data if isinstance(data, dict) else None
|
|
124
56
|
|
|
125
|
-
if branch.startswith("feat/"):
|
|
126
|
-
return ("feature-workflow", {"branch": branch, "signal": "feature branch"})
|
|
127
57
|
|
|
128
|
-
|
|
129
|
-
|
|
58
|
+
def branch_exists(project_root: Path, branch: str) -> bool:
|
|
59
|
+
if not branch:
|
|
60
|
+
return False
|
|
61
|
+
return bool(run_git(project_root, "rev-parse", "--verify", f"refs/heads/{branch}"))
|
|
130
62
|
|
|
131
|
-
if dir_has_content(project_root, ".prizmkit", "state", "features"):
|
|
132
|
-
return ("feature-workflow", {"branch": branch, "signal": "feature pipeline state"})
|
|
133
63
|
|
|
134
|
-
|
|
135
|
-
|
|
64
|
+
def current_branch(project_root: Path) -> str:
|
|
65
|
+
return run_git(project_root, "branch", "--show-current")
|
|
136
66
|
|
|
137
|
-
return (None, None)
|
|
138
67
|
|
|
68
|
+
def command_for(family: dict[str, str], task_id: str) -> str:
|
|
69
|
+
return f"python3 ./.prizmkit/dev-pipeline/cli.py {family['name']} run {task_id}"
|
|
139
70
|
|
|
140
|
-
def detect_other_workflows(project_root, primary_type):
|
|
141
|
-
"""Scan for secondary workflow signals beyond the primary match."""
|
|
142
|
-
others = []
|
|
143
|
-
branch = run_git(["branch", "--show-current"], cwd=project_root)
|
|
144
71
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
latest_bugfix_artifact_id(project_root),
|
|
151
|
-
]),
|
|
152
|
-
("refactor-workflow", [
|
|
153
|
-
branch.startswith("refactor/"),
|
|
154
|
-
file_exists(project_root, ".prizmkit", "plans", "refactor-list.json"),
|
|
155
|
-
dir_has_content(project_root, ".prizmkit", "state", "refactor"),
|
|
156
|
-
]),
|
|
157
|
-
("feature-workflow", [
|
|
158
|
-
branch.startswith("feat/"),
|
|
159
|
-
file_exists(project_root, ".prizmkit", "plans", "feature-list.json"),
|
|
160
|
-
dir_has_content(project_root, ".prizmkit", "state", "features"),
|
|
161
|
-
]),
|
|
162
|
-
]
|
|
72
|
+
def detect_candidates(project_root: Path) -> list[dict[str, object]]:
|
|
73
|
+
"""Return active durable task checkouts whose recorded branch still exists."""
|
|
74
|
+
candidates: list[dict[str, object]] = []
|
|
75
|
+
state_root = project_root / ".prizmkit" / "state"
|
|
76
|
+
active = current_branch(project_root)
|
|
163
77
|
|
|
164
|
-
for
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
return others
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
def find_first_existing(project_root, candidates):
|
|
171
|
-
for parts in candidates:
|
|
172
|
-
path = os.path.join(project_root, *parts)
|
|
173
|
-
if os.path.isfile(path) or os.path.isdir(path):
|
|
174
|
-
return relpath(path, project_root)
|
|
175
|
-
return None
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
def find_review_artifact(project_root):
|
|
179
|
-
candidates = [
|
|
180
|
-
("review-report.md",),
|
|
181
|
-
("code-review-report.md",),
|
|
182
|
-
(".prizmkit", "review-report.md"),
|
|
183
|
-
(".prizmkit", "code-review", "review-report.md"),
|
|
184
|
-
(".prizmkit", "reviews"),
|
|
185
|
-
]
|
|
186
|
-
return find_first_existing(project_root, candidates)
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
def find_session_artifact(project_root):
|
|
190
|
-
candidates = [
|
|
191
|
-
("context-snapshot.md",),
|
|
192
|
-
("session-summary.md",),
|
|
193
|
-
(".prizmkit", "context-snapshot.md"),
|
|
194
|
-
(".prizmkit", "session-summary.md"),
|
|
195
|
-
]
|
|
196
|
-
return find_first_existing(project_root, candidates)
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
def detect_fast_path_artifacts(project_root):
|
|
200
|
-
artifacts = {}
|
|
201
|
-
for name in ["spec.md", "plan.md"]:
|
|
202
|
-
path = file_exists(project_root, name)
|
|
203
|
-
artifacts[f"{name.replace('.', '_')}_exists"] = bool(path)
|
|
204
|
-
if path:
|
|
205
|
-
artifacts[f"{name.replace('.', '_')}_path"] = relpath(path, project_root)
|
|
206
|
-
|
|
207
|
-
review = find_review_artifact(project_root)
|
|
208
|
-
artifacts["review_artifact_exists"] = bool(review)
|
|
209
|
-
if review:
|
|
210
|
-
artifacts["review_artifact_path"] = review
|
|
211
|
-
|
|
212
|
-
session = find_session_artifact(project_root)
|
|
213
|
-
artifacts["session_artifact_exists"] = bool(session)
|
|
214
|
-
if session:
|
|
215
|
-
artifacts["session_artifact_path"] = session
|
|
216
|
-
|
|
217
|
-
return artifacts
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
def infer_bugfix_phase(project_root, context, code_changes, commits_ahead):
|
|
221
|
-
"""Infer bug-fix recovery phase from optional signals and git state."""
|
|
222
|
-
bug_id = context.get("bug_id") if context else None
|
|
223
|
-
bugfix_dir = os.path.join(project_root, ".prizmkit", "bugfix", bug_id) if bug_id else ""
|
|
224
|
-
has_fix_plan = bool(bugfix_dir and os.path.isfile(os.path.join(bugfix_dir, "fix-plan.md")))
|
|
225
|
-
has_fix_report = bool(bugfix_dir and os.path.isfile(os.path.join(bugfix_dir, "fix-report.md")))
|
|
226
|
-
|
|
227
|
-
artifacts = detect_fast_path_artifacts(project_root)
|
|
228
|
-
artifacts.update({
|
|
229
|
-
"fix_plan_exists": has_fix_plan,
|
|
230
|
-
"fix_report_exists": has_fix_report,
|
|
231
|
-
})
|
|
232
|
-
if has_fix_plan:
|
|
233
|
-
artifacts["fix_plan_path"] = relpath(os.path.join(bugfix_dir, "fix-plan.md"), project_root)
|
|
234
|
-
if has_fix_report:
|
|
235
|
-
artifacts["fix_report_path"] = relpath(os.path.join(bugfix_dir, "fix-report.md"), project_root)
|
|
236
|
-
|
|
237
|
-
has_plan_artifact = artifacts.get("spec_md_exists") or artifacts.get("plan_md_exists") or has_fix_plan
|
|
238
|
-
has_review_artifact = artifacts.get("review_artifact_exists") or has_fix_report
|
|
239
|
-
|
|
240
|
-
if commits_ahead > 0:
|
|
241
|
-
return 7, "Merge Decision", artifacts, \
|
|
242
|
-
f"{commits_ahead} commit(s) ahead — fix may already be committed", \
|
|
243
|
-
"merge decision or branch handoff"
|
|
244
|
-
|
|
245
|
-
if has_review_artifact and code_changes["has_changes"]:
|
|
246
|
-
return 6, "User Verification", artifacts, \
|
|
247
|
-
"review artifact found with code changes", \
|
|
248
|
-
"user verification -> commit -> merge decision"
|
|
249
|
-
|
|
250
|
-
if code_changes["has_changes"]:
|
|
251
|
-
return 5, "Review", artifacts, \
|
|
252
|
-
"code/test changes present without review artifact", \
|
|
253
|
-
"run tests if safe -> code review -> verification -> commit"
|
|
254
|
-
|
|
255
|
-
if has_plan_artifact:
|
|
256
|
-
return 4, "Fix", artifacts, \
|
|
257
|
-
"plan artifact found but no implementation changes", \
|
|
258
|
-
"implement fix -> review -> verification -> commit"
|
|
259
|
-
|
|
260
|
-
return 1, "Diagnosis / Triage", artifacts, \
|
|
261
|
-
"bug-fix signal found but no durable implementation artifacts", \
|
|
262
|
-
"restore bug context -> diagnose -> choose recovery action"
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
def infer_pipeline_workflow_phase(project_root, list_filename, state_subdir, workflow_label):
|
|
266
|
-
list_path = file_exists(project_root, ".prizmkit", "plans", list_filename)
|
|
267
|
-
has_state = dir_has_content(project_root, ".prizmkit", "state", state_subdir)
|
|
268
|
-
fast_path = detect_fast_path_artifacts(project_root)
|
|
269
|
-
|
|
270
|
-
artifacts = {
|
|
271
|
-
f"{workflow_label}_list_exists": bool(list_path),
|
|
272
|
-
"pipeline_state_exists": bool(has_state),
|
|
273
|
-
**fast_path,
|
|
274
|
-
}
|
|
275
|
-
if list_path:
|
|
276
|
-
artifacts[f"{workflow_label}_list_path"] = relpath(list_path, project_root)
|
|
277
|
-
|
|
278
|
-
if list_path and has_state:
|
|
279
|
-
return 4, "Monitor / Recover Pipeline", artifacts, \
|
|
280
|
-
f"{list_filename} and pipeline state both exist", \
|
|
281
|
-
"check launcher status or run recovery script"
|
|
282
|
-
|
|
283
|
-
if list_path:
|
|
284
|
-
return 3, "Launch", artifacts, \
|
|
285
|
-
f"{list_filename} exists without pipeline state", \
|
|
286
|
-
"launch pipeline or inspect/update list"
|
|
287
|
-
|
|
288
|
-
if fast_path.get("spec_md_exists") or fast_path.get("plan_md_exists"):
|
|
289
|
-
return 2, "Fast Path Continuation", artifacts, \
|
|
290
|
-
"current-workspace spec/plan artifact found", \
|
|
291
|
-
"continue implementation/review/commit based on diff"
|
|
292
|
-
|
|
293
|
-
return 1, "Route Selection", artifacts, \
|
|
294
|
-
f"no {list_filename} or pipeline state found", \
|
|
295
|
-
f"clarify {workflow_label} intent and choose path"
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
def infer_feature_phase(project_root):
|
|
299
|
-
return infer_pipeline_workflow_phase(project_root, "feature-list.json", "features", "feature")
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
def infer_refactor_phase(project_root):
|
|
303
|
-
return infer_pipeline_workflow_phase(project_root, "refactor-list.json", "refactor", "refactor")
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
def infer_unknown_fast_path_phase(project_root, code_changes):
|
|
307
|
-
artifacts = detect_fast_path_artifacts(project_root)
|
|
308
|
-
if code_changes["has_changes"]:
|
|
309
|
-
return 5, "Review", artifacts, \
|
|
310
|
-
"spec/plan plus code changes found, but workflow family is unknown", \
|
|
311
|
-
"ask workflow family -> run tests/review -> continue"
|
|
312
|
-
return 2, "Fast Path Continuation", artifacts, \
|
|
313
|
-
"spec/plan found, but workflow family is unknown", \
|
|
314
|
-
"ask whether this is feature, bug fix, or refactor work"
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
def detect_commits_ahead(project_root, main_branch="main"):
|
|
318
|
-
log_output = run_git(["log", f"{main_branch}..HEAD", "--oneline"], cwd=project_root)
|
|
319
|
-
if log_output:
|
|
320
|
-
return len(log_output.strip().split("\n"))
|
|
321
|
-
return 0
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
def detect_git_state(project_root, main_branch="main", cached_branch=None):
|
|
325
|
-
current = cached_branch or run_git(["branch", "--show-current"], cwd=project_root)
|
|
326
|
-
|
|
327
|
-
uncommitted = run_git(["status", "--porcelain"], cwd=project_root)
|
|
328
|
-
commits_ahead = detect_commits_ahead(project_root, main_branch)
|
|
329
|
-
|
|
330
|
-
return {
|
|
331
|
-
"current_branch": current,
|
|
332
|
-
"changed_paths": len([line for line in uncommitted.splitlines() if line.strip()]),
|
|
333
|
-
"commits_ahead_of_main": commits_ahead,
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
def detect_code_changes(project_root, main_branch="main"):
|
|
338
|
-
"""Analyze implementation changes relative to main and working tree."""
|
|
339
|
-
ignored_files = {
|
|
340
|
-
"feature-list.json",
|
|
341
|
-
"bug-fix-list.json",
|
|
342
|
-
"refactor-list.json",
|
|
343
|
-
"spec.md",
|
|
344
|
-
"plan.md",
|
|
345
|
-
"review-report.md",
|
|
346
|
-
"code-review-report.md",
|
|
347
|
-
"session-summary.md",
|
|
348
|
-
"context-snapshot.md",
|
|
349
|
-
"package-lock.json",
|
|
350
|
-
"yarn.lock",
|
|
351
|
-
"pnpm-lock.yaml",
|
|
352
|
-
}
|
|
353
|
-
ignored_prefixes = (
|
|
354
|
-
".prizmkit/",
|
|
355
|
-
".agents/",
|
|
356
|
-
".codex/",
|
|
357
|
-
".claude/",
|
|
358
|
-
".codebuddy/",
|
|
359
|
-
)
|
|
360
|
-
|
|
361
|
-
def is_implementation_file(filepath):
|
|
362
|
-
basename = os.path.basename(filepath)
|
|
363
|
-
if basename in ignored_files:
|
|
364
|
-
return False
|
|
365
|
-
if any(filepath.startswith(prefix) for prefix in ignored_prefixes):
|
|
366
|
-
return False
|
|
367
|
-
return True
|
|
368
|
-
|
|
369
|
-
file_statuses = {}
|
|
370
|
-
|
|
371
|
-
for command in (["diff", main_branch, "--name-status"], ["diff", "--name-status"]):
|
|
372
|
-
output = run_git(command, cwd=project_root)
|
|
373
|
-
if not output:
|
|
78
|
+
for family in FAMILIES:
|
|
79
|
+
family_dir = state_root / family["state_dir"]
|
|
80
|
+
if not family_dir.is_dir():
|
|
374
81
|
continue
|
|
375
|
-
for
|
|
376
|
-
|
|
377
|
-
if
|
|
82
|
+
for checkout_path in sorted(family_dir.glob("*/checkout.json")):
|
|
83
|
+
data = load_json(checkout_path)
|
|
84
|
+
if not data or str(data.get("state") or "") != "active":
|
|
378
85
|
continue
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
86
|
+
task_id = str(data.get("task_id") or checkout_path.parent.name).strip()
|
|
87
|
+
branch = str(data.get("active_dev_branch") or "").strip()
|
|
88
|
+
if not task_id.startswith(family["id_prefix"]) or not branch_exists(project_root, branch):
|
|
89
|
+
continue
|
|
90
|
+
candidates.append(
|
|
91
|
+
{
|
|
92
|
+
"family": family["name"],
|
|
93
|
+
"task_id": task_id,
|
|
94
|
+
"branch": branch,
|
|
95
|
+
"base_branch": str(data.get("base_branch") or "").strip(),
|
|
96
|
+
"checkout_mode": str(data.get("checkout_mode") or "branch"),
|
|
97
|
+
"worktree_path": str(data.get("worktree_path") or "").strip() or None,
|
|
98
|
+
"checkout_path": checkout_path.relative_to(project_root).as_posix(),
|
|
99
|
+
"plan_path": family["plan_path"],
|
|
100
|
+
"branch_is_current": branch == active,
|
|
101
|
+
"continue_command": command_for(family, task_id),
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
return candidates
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def build_report(project_root: Path) -> dict[str, object]:
|
|
108
|
+
candidates = detect_candidates(project_root)
|
|
109
|
+
if not candidates:
|
|
110
|
+
return {
|
|
111
|
+
"detected": False,
|
|
112
|
+
"current_branch": current_branch(project_root),
|
|
113
|
+
"candidates": [],
|
|
114
|
+
"message": "No active interrupted pipeline task branch was found.",
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
"detected": True,
|
|
118
|
+
"current_branch": current_branch(project_root),
|
|
119
|
+
"selection_required": len(candidates) > 1,
|
|
120
|
+
"candidates": candidates,
|
|
399
121
|
}
|
|
400
122
|
|
|
401
|
-
for filepath, status in file_statuses.items():
|
|
402
|
-
if status == "M":
|
|
403
|
-
result["files_modified"] += 1
|
|
404
|
-
elif status == "A":
|
|
405
|
-
result["files_added"] += 1
|
|
406
|
-
elif status == "D":
|
|
407
|
-
result["files_deleted"] += 1
|
|
408
|
-
if test_pattern.search(filepath):
|
|
409
|
-
result["test_files_touched"] += 1
|
|
410
|
-
parent = os.path.dirname(filepath)
|
|
411
|
-
if parent:
|
|
412
|
-
parts = parent.split(os.sep)
|
|
413
|
-
dirs.add(os.sep.join(parts[:2]) + "/")
|
|
414
|
-
|
|
415
|
-
result["directories_touched"] = sorted(dirs)
|
|
416
|
-
return result
|
|
417
123
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
124
|
+
def main() -> int:
|
|
125
|
+
parser = argparse.ArgumentParser(
|
|
126
|
+
description="Detect active interrupted pipeline task branches and return continuation commands."
|
|
127
|
+
)
|
|
128
|
+
parser.add_argument(
|
|
129
|
+
"--project-root",
|
|
130
|
+
default=None,
|
|
131
|
+
help="Project root; defaults to the current Git root or current directory.",
|
|
132
|
+
)
|
|
422
133
|
args = parser.parse_args()
|
|
423
134
|
|
|
424
135
|
if args.project_root:
|
|
425
|
-
project_root =
|
|
136
|
+
project_root = Path(args.project_root).expanduser().resolve()
|
|
426
137
|
else:
|
|
427
|
-
git_root = run_git(
|
|
428
|
-
project_root = git_root if git_root else
|
|
138
|
+
git_root = run_git(Path.cwd(), "rev-parse", "--show-toplevel")
|
|
139
|
+
project_root = Path(git_root).resolve() if git_root else Path.cwd().resolve()
|
|
429
140
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
if workflow_type is None:
|
|
434
|
-
print(json.dumps({
|
|
435
|
-
"detected": False,
|
|
436
|
-
"message": "No interrupted workflow detected. Use /feature-workflow, /bug-fix-workflow, or /refactor-workflow to start.",
|
|
437
|
-
}, indent=2))
|
|
438
|
-
sys.exit(0)
|
|
439
|
-
|
|
440
|
-
cached_branch = context.get("branch") if context else None
|
|
441
|
-
git_state = detect_git_state(project_root, main_branch, cached_branch=cached_branch)
|
|
442
|
-
code_changes = detect_code_changes(project_root, main_branch)
|
|
443
|
-
other_workflows = detect_other_workflows(project_root, workflow_type)
|
|
444
|
-
|
|
445
|
-
if workflow_type == "bug-fix-workflow":
|
|
446
|
-
phase, phase_name, artifacts, reason, remaining = infer_bugfix_phase(
|
|
447
|
-
project_root, context, code_changes, git_state["commits_ahead_of_main"]
|
|
448
|
-
)
|
|
449
|
-
elif workflow_type == "feature-workflow":
|
|
450
|
-
phase, phase_name, artifacts, reason, remaining = infer_feature_phase(project_root)
|
|
451
|
-
elif workflow_type == "refactor-workflow":
|
|
452
|
-
phase, phase_name, artifacts, reason, remaining = infer_refactor_phase(project_root)
|
|
453
|
-
elif workflow_type == "unknown-fast-path":
|
|
454
|
-
phase, phase_name, artifacts, reason, remaining = infer_unknown_fast_path_phase(project_root, code_changes)
|
|
455
|
-
else:
|
|
456
|
-
print(json.dumps({"detected": False, "message": "Unknown workflow type"}), file=sys.stderr)
|
|
457
|
-
sys.exit(1)
|
|
458
|
-
|
|
459
|
-
report = {
|
|
460
|
-
"detected": True,
|
|
461
|
-
"workflow_type": workflow_type,
|
|
462
|
-
"phase": phase,
|
|
463
|
-
"phase_name": phase_name,
|
|
464
|
-
"context": context,
|
|
465
|
-
"artifacts": artifacts,
|
|
466
|
-
"git": git_state,
|
|
467
|
-
"code": code_changes,
|
|
468
|
-
"recovery": {
|
|
469
|
-
"reason": reason,
|
|
470
|
-
"remaining_work": remaining,
|
|
471
|
-
},
|
|
472
|
-
}
|
|
473
|
-
if other_workflows:
|
|
474
|
-
report["other_interrupted_workflows"] = other_workflows
|
|
475
|
-
|
|
476
|
-
print(json.dumps(report, indent=2))
|
|
141
|
+
print(json.dumps(build_report(project_root), indent=2, ensure_ascii=False))
|
|
142
|
+
return 0
|
|
477
143
|
|
|
478
144
|
|
|
479
145
|
if __name__ == "__main__":
|
|
480
|
-
main()
|
|
146
|
+
raise SystemExit(main())
|