prizmkit 1.1.1 → 1.1.3
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/agent-adapter.js +18 -0
- package/bundled/adapters/claude/command-adapter.js +1 -27
- package/bundled/agents/prizm-dev-team-critic.md +2 -0
- package/bundled/agents/prizm-dev-team-dev.md +2 -0
- package/bundled/agents/prizm-dev-team-reviewer.md +2 -0
- package/bundled/dev-pipeline/README.md +63 -63
- package/bundled/dev-pipeline/assets/feature-list-example.json +1 -1
- package/bundled/dev-pipeline/assets/prizm-dev-team-integration.md +1 -1
- package/bundled/dev-pipeline/{launch-daemon.sh → launch-feature-daemon.sh} +33 -33
- package/bundled/dev-pipeline/launch-refactor-daemon.sh +454 -0
- package/bundled/dev-pipeline/lib/branch.sh +1 -1
- package/bundled/dev-pipeline/reset-feature.sh +3 -3
- package/bundled/dev-pipeline/reset-refactor.sh +312 -0
- package/bundled/dev-pipeline/{retry-bug.sh → retry-bugfix.sh} +47 -59
- package/bundled/dev-pipeline/retry-feature.sh +41 -54
- package/bundled/dev-pipeline/retry-refactor.sh +358 -0
- package/bundled/dev-pipeline/run-bugfix.sh +6 -0
- package/bundled/dev-pipeline/{run.sh → run-feature.sh} +31 -31
- package/bundled/dev-pipeline/run-refactor.sh +787 -0
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +177 -10
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +419 -0
- package/bundled/dev-pipeline/scripts/init-refactor-pipeline.py +393 -0
- package/bundled/dev-pipeline/scripts/update-refactor-status.py +726 -0
- package/bundled/dev-pipeline/templates/agent-prompts/critic-code-challenge.md +13 -0
- package/bundled/dev-pipeline/templates/agent-prompts/critic-plan-challenge.md +7 -0
- package/bundled/dev-pipeline/templates/agent-prompts/dev-fix.md +7 -0
- package/bundled/dev-pipeline/templates/agent-prompts/dev-implement.md +26 -0
- package/bundled/dev-pipeline/templates/agent-prompts/dev-resume.md +5 -0
- package/bundled/dev-pipeline/templates/agent-prompts/reviewer-analyze.md +5 -0
- package/bundled/dev-pipeline/templates/agent-prompts/reviewer-review.md +12 -0
- package/bundled/dev-pipeline/templates/bootstrap-tier1.md +29 -2
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +8 -7
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +11 -10
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +2 -3
- package/bundled/dev-pipeline/templates/feature-list-schema.json +1 -1
- package/bundled/dev-pipeline/templates/refactor-list-schema.json +159 -0
- package/bundled/dev-pipeline/templates/sections/ac-verification-checklist.md +13 -0
- package/bundled/dev-pipeline/templates/sections/feature-context.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-analyze-agent.md +9 -8
- package/bundled/dev-pipeline/templates/sections/phase-analyze-full.md +9 -8
- package/bundled/dev-pipeline/templates/sections/phase-browser-verification.md +2 -1
- package/bundled/dev-pipeline/templates/sections/phase-critic-code.md +8 -10
- package/bundled/dev-pipeline/templates/sections/phase-critic-plan-full.md +9 -10
- package/bundled/dev-pipeline/templates/sections/phase-critic-plan.md +8 -9
- package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +7 -10
- package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +8 -15
- package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +7 -12
- package/bundled/dev-pipeline/templates/sections/phase-review-full.md +8 -19
- package/bundled/dev-pipeline/templates/sections/test-failure-recovery.md +75 -0
- package/bundled/skills/_metadata.json +33 -6
- package/bundled/skills/app-planner/SKILL.md +105 -320
- package/bundled/skills/app-planner/assets/app-design-guide.md +101 -0
- package/bundled/skills/app-planner/references/frontend-design-guide.md +1 -1
- package/bundled/skills/app-planner/references/project-brief-guide.md +49 -80
- package/bundled/skills/bug-fix-workflow/SKILL.md +2 -2
- package/bundled/skills/bug-planner/SKILL.md +68 -5
- package/bundled/skills/bug-planner/scripts/validate-bug-list.py +3 -2
- package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +19 -5
- package/bundled/skills/{dev-pipeline-launcher → feature-pipeline-launcher}/SKILL.md +32 -32
- package/bundled/skills/feature-planner/SKILL.md +337 -0
- package/bundled/skills/{app-planner → feature-planner}/assets/evaluation-guide.md +4 -4
- package/bundled/skills/{app-planner → feature-planner}/assets/planning-guide.md +3 -171
- package/bundled/skills/{app-planner → feature-planner}/references/browser-interaction.md +6 -5
- package/bundled/skills/feature-planner/references/decomposition-patterns.md +75 -0
- package/bundled/skills/{app-planner → feature-planner}/references/error-recovery.md +8 -8
- package/bundled/skills/{app-planner → feature-planner}/references/incremental-feature-planning.md +1 -1
- package/bundled/skills/{app-planner/references/new-app-planning.md → feature-planner/references/new-project-planning.md} +1 -1
- package/bundled/skills/{app-planner → feature-planner}/scripts/validate-and-generate.py +4 -4
- package/bundled/skills/feature-workflow/SKILL.md +23 -23
- package/bundled/skills/prizm-kit/SKILL.md +1 -3
- package/bundled/skills/prizmkit-analyze/SKILL.md +2 -5
- package/bundled/skills/prizmkit-code-review/SKILL.md +2 -2
- package/bundled/skills/prizmkit-committer/SKILL.md +4 -8
- package/bundled/skills/prizmkit-deploy/SKILL.md +1 -5
- package/bundled/skills/prizmkit-implement/SKILL.md +3 -50
- package/bundled/skills/prizmkit-init/SKILL.md +5 -77
- package/bundled/skills/prizmkit-plan/SKILL.md +1 -12
- package/bundled/skills/prizmkit-prizm-docs/SKILL.md +6 -24
- package/bundled/skills/prizmkit-prizm-docs/assets/PRIZM-SPEC.md +21 -0
- package/bundled/skills/prizmkit-retrospective/SKILL.md +12 -117
- package/bundled/skills/recovery-workflow/SKILL.md +166 -316
- package/bundled/skills/recovery-workflow/evals/evals.json +29 -13
- package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +232 -274
- package/bundled/skills/refactor-pipeline-launcher/SKILL.md +352 -0
- package/bundled/skills/refactor-planner/SKILL.md +436 -0
- package/bundled/skills/refactor-planner/assets/planning-guide.md +292 -0
- package/bundled/skills/refactor-planner/references/behavior-preservation.md +301 -0
- package/bundled/skills/refactor-planner/references/refactor-scoping-guide.md +221 -0
- package/bundled/skills/refactor-planner/scripts/validate-and-generate-refactor.py +786 -0
- package/bundled/skills/refactor-workflow/SKILL.md +299 -319
- package/package.json +1 -1
- package/src/clean.js +3 -3
- package/src/scaffold.js +6 -6
- package/bundled/skills/prizmkit-plan/assets/spec-template.md +0 -56
- package/bundled/skills/prizmkit-plan/references/clarify-guide.md +0 -67
- package/src/config.js +0 -504
- package/src/prompts.js +0 -210
- /package/bundled/skills/{dev-pipeline-launcher → feature-pipeline-launcher}/scripts/preflight-check.py +0 -0
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
|
-
detect-recovery-state.py —
|
|
4
|
-
feature pipeline session and output a structured recovery report.
|
|
3
|
+
detect-recovery-state.py — Universal workflow recovery detector.
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
3. Git state (branches, uncommitted changes, commits ahead)
|
|
10
|
-
4. Code changes (file counts, directories touched)
|
|
5
|
+
Auto-detects which interactive workflow (feature-workflow, bug-fix-workflow,
|
|
6
|
+
refactor-workflow) was interrupted and what phase it reached, by inspecting
|
|
7
|
+
the workspace: git branch names, characteristic artifacts, and pipeline state.
|
|
11
8
|
|
|
12
9
|
Does NOT run tests — that's left to the skill so the user sees output in real time.
|
|
13
10
|
|
|
14
11
|
Usage:
|
|
15
|
-
python3 detect-recovery-state.py --
|
|
12
|
+
python3 detect-recovery-state.py [--project-root .]
|
|
16
13
|
"""
|
|
17
14
|
|
|
18
15
|
import argparse
|
|
@@ -23,6 +20,10 @@ import subprocess
|
|
|
23
20
|
import sys
|
|
24
21
|
|
|
25
22
|
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
# Git helper
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
|
|
26
27
|
def run_git(args, cwd=None):
|
|
27
28
|
"""Run a git command and return stdout, or empty string on failure."""
|
|
28
29
|
try:
|
|
@@ -38,175 +39,215 @@ def run_git(args, cwd=None):
|
|
|
38
39
|
return ""
|
|
39
40
|
|
|
40
41
|
|
|
41
|
-
def
|
|
42
|
-
"""
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
return f"{numeric}-{slug}"
|
|
42
|
+
def detect_main_branch(project_root):
|
|
43
|
+
"""Detect the default branch name (main or master)."""
|
|
44
|
+
for candidate in ["main", "master"]:
|
|
45
|
+
check = run_git(["rev-parse", "--verify", candidate], cwd=project_root)
|
|
46
|
+
if check:
|
|
47
|
+
return candidate
|
|
48
|
+
return "main"
|
|
49
49
|
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
data = json.load(f)
|
|
55
|
-
for feat in data.get("features", []):
|
|
56
|
-
if feat.get("id") == feature_id:
|
|
57
|
-
return feat
|
|
58
|
-
return None
|
|
51
|
+
# ---------------------------------------------------------------------------
|
|
52
|
+
# Workflow signature detection (priority-ordered)
|
|
53
|
+
# ---------------------------------------------------------------------------
|
|
59
54
|
|
|
55
|
+
def extract_bug_id_from_branch(branch):
|
|
56
|
+
"""Extract bug ID from branch name like fix/B-001-login-crash → B-001."""
|
|
57
|
+
match = re.match(r"fix/(B-\d+)", branch)
|
|
58
|
+
return match.group(1) if match else None
|
|
60
59
|
|
|
61
|
-
def detect_pipeline_state(state_dir, feature_id):
|
|
62
|
-
"""Check dev-pipeline/state/ for feature status."""
|
|
63
|
-
result = {
|
|
64
|
-
"status": "unknown",
|
|
65
|
-
"retry_count": 0,
|
|
66
|
-
"last_session_id": None,
|
|
67
|
-
"last_session_dir": None,
|
|
68
|
-
"has_state": False,
|
|
69
|
-
}
|
|
70
60
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return result
|
|
61
|
+
def detect_workflow_type(project_root):
|
|
62
|
+
"""Priority-ordered signature matching.
|
|
74
63
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
|
|
64
|
+
Returns (workflow_type, context_dict) or (None, None).
|
|
65
|
+
"""
|
|
66
|
+
branch = run_git(["branch", "--show-current"], cwd=project_root)
|
|
67
|
+
|
|
68
|
+
# Priority 1: fix/* branch → bug-fix-workflow
|
|
69
|
+
if branch.startswith("fix/"):
|
|
70
|
+
bug_id = extract_bug_id_from_branch(branch)
|
|
71
|
+
return ("bug-fix-workflow", {"bug_id": bug_id, "branch": branch})
|
|
72
|
+
|
|
73
|
+
# Priority 2: .prizmkit/bugfix/ has content → bug-fix-workflow
|
|
74
|
+
bugfix_dir = os.path.join(project_root, ".prizmkit", "bugfix")
|
|
75
|
+
if os.path.isdir(bugfix_dir):
|
|
76
|
+
bug_ids = sorted(
|
|
77
|
+
d for d in os.listdir(bugfix_dir)
|
|
78
|
+
if os.path.isdir(os.path.join(bugfix_dir, d))
|
|
79
|
+
)
|
|
80
|
+
if bug_ids:
|
|
81
|
+
return ("bug-fix-workflow", {"bug_id": bug_ids[-1], "branch": branch})
|
|
92
82
|
|
|
93
|
-
|
|
83
|
+
# Priority 3: refactor/* branch → refactor-workflow
|
|
84
|
+
if branch.startswith("refactor/"):
|
|
85
|
+
return ("refactor-workflow", {"branch": branch})
|
|
94
86
|
|
|
87
|
+
# Priority 4: refactor-list.json exists → refactor-workflow
|
|
88
|
+
if os.path.isfile(os.path.join(project_root, "refactor-list.json")):
|
|
89
|
+
return ("refactor-workflow", {"branch": branch})
|
|
95
90
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
"
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
91
|
+
# Priority 5: feat/* branch → feature-workflow
|
|
92
|
+
if branch.startswith("feat/"):
|
|
93
|
+
return ("feature-workflow", {"branch": branch})
|
|
94
|
+
|
|
95
|
+
# Priority 6: feature-list.json exists → feature-workflow
|
|
96
|
+
if os.path.isfile(os.path.join(project_root, "feature-list.json")):
|
|
97
|
+
return ("feature-workflow", {"branch": branch})
|
|
98
|
+
|
|
99
|
+
# No match
|
|
100
|
+
return (None, None)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# ---------------------------------------------------------------------------
|
|
104
|
+
# Phase inference — one function per workflow
|
|
105
|
+
# ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
def infer_bugfix_phase(project_root, bug_id, code_changes, commits_ahead):
|
|
108
|
+
"""Infer bug-fix-workflow phase from artifacts and git state.
|
|
109
|
+
|
|
110
|
+
Detection table (from bug-fix-workflow SKILL.md):
|
|
111
|
+
(nothing) → Phase 1: Deep Bug Diagnosis
|
|
112
|
+
fix-plan.md only → Phase 4: Fix
|
|
113
|
+
fix-plan.md + code changes → Phase 5: Review
|
|
114
|
+
all docs + review passed → Phase 6: User Verification
|
|
115
|
+
all docs + committed → Phase 7: Merge Decision
|
|
116
|
+
"""
|
|
117
|
+
bugfix_dir = os.path.join(project_root, ".prizmkit", "bugfix", bug_id) if bug_id else ""
|
|
118
|
+
has_fix_plan = bugfix_dir and os.path.isfile(os.path.join(bugfix_dir, "fix-plan.md"))
|
|
119
|
+
has_fix_report = bugfix_dir and os.path.isfile(os.path.join(bugfix_dir, "fix-report.md"))
|
|
120
|
+
|
|
121
|
+
artifacts = {
|
|
122
|
+
"fix_plan_exists": has_fix_plan,
|
|
123
|
+
"fix_report_exists": has_fix_report,
|
|
107
124
|
}
|
|
125
|
+
if has_fix_plan:
|
|
126
|
+
artifacts["fix_plan_path"] = os.path.relpath(
|
|
127
|
+
os.path.join(bugfix_dir, "fix-plan.md"), project_root
|
|
128
|
+
)
|
|
129
|
+
if has_fix_report:
|
|
130
|
+
artifacts["fix_report_path"] = os.path.relpath(
|
|
131
|
+
os.path.join(bugfix_dir, "fix-report.md"), project_root
|
|
132
|
+
)
|
|
108
133
|
|
|
109
|
-
if
|
|
110
|
-
return
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
plan_path = os.path.join(specs_dir, "plan.md")
|
|
114
|
-
|
|
115
|
-
if os.path.isfile(spec_path):
|
|
116
|
-
result["spec_exists"] = True
|
|
117
|
-
result["spec_path"] = os.path.relpath(spec_path, project_root)
|
|
118
|
-
|
|
119
|
-
if os.path.isfile(plan_path):
|
|
120
|
-
result["plan_exists"] = True
|
|
121
|
-
result["plan_path"] = os.path.relpath(plan_path, project_root)
|
|
122
|
-
|
|
123
|
-
# Count tasks in plan.md (look for checkbox pattern)
|
|
124
|
-
try:
|
|
125
|
-
with open(plan_path) as f:
|
|
126
|
-
content = f.read()
|
|
127
|
-
# Match both [x] and [ ] patterns (task checkboxes)
|
|
128
|
-
completed = len(re.findall(r"^\s*-\s*\[x\]", content, re.MULTILINE | re.IGNORECASE))
|
|
129
|
-
pending = len(re.findall(r"^\s*-\s*\[ \]", content, re.MULTILINE))
|
|
130
|
-
result["plan_tasks_total"] = completed + pending
|
|
131
|
-
result["plan_tasks_completed"] = completed
|
|
132
|
-
except IOError:
|
|
133
|
-
pass
|
|
134
|
-
|
|
135
|
-
# Check for other artifacts
|
|
136
|
-
try:
|
|
137
|
-
for name in os.listdir(specs_dir):
|
|
138
|
-
if name not in ("spec.md", "plan.md") and os.path.isfile(
|
|
139
|
-
os.path.join(specs_dir, name)
|
|
140
|
-
):
|
|
141
|
-
result["other_artifacts"].append(name)
|
|
142
|
-
except IOError:
|
|
143
|
-
pass
|
|
134
|
+
if commits_ahead > 0:
|
|
135
|
+
return 7, "Merge Decision", artifacts, \
|
|
136
|
+
f"{commits_ahead} commit(s) ahead — fix likely committed", \
|
|
137
|
+
"merge decision only"
|
|
144
138
|
|
|
145
|
-
|
|
139
|
+
if has_fix_report:
|
|
140
|
+
return 6, "User Verification", artifacts, \
|
|
141
|
+
"fix-report.md exists — review completed", \
|
|
142
|
+
"user verification → commit & merge"
|
|
146
143
|
|
|
144
|
+
if has_fix_plan and code_changes["has_changes"]:
|
|
145
|
+
return 5, "Review", artifacts, \
|
|
146
|
+
"fix-plan.md exists + code changes present", \
|
|
147
|
+
"code review → user verification → commit & merge"
|
|
147
148
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
"
|
|
156
|
-
"
|
|
157
|
-
|
|
149
|
+
if has_fix_plan:
|
|
150
|
+
return 4, "Fix", artifacts, \
|
|
151
|
+
"fix-plan.md exists, no code changes yet", \
|
|
152
|
+
"implement fix → review → user verification → commit & merge"
|
|
153
|
+
|
|
154
|
+
# On fix branch but no artifacts
|
|
155
|
+
return 1, "Deep Bug Diagnosis", artifacts, \
|
|
156
|
+
"on fix branch but no artifacts found", \
|
|
157
|
+
"diagnosis → triage → reproduce → fix → review → commit & merge"
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _infer_pipeline_workflow_phase(project_root, list_filename, state_subdir, workflow_label):
|
|
161
|
+
"""Infer phase for pipeline-driven workflows (feature-workflow, refactor-workflow).
|
|
162
|
+
|
|
163
|
+
Both follow the same structure:
|
|
164
|
+
No list file → Phase 1: Brainstorm
|
|
165
|
+
List file, no pipeline state → Phase 3: Launch
|
|
166
|
+
List file + pipeline state → Phase 4: Monitor
|
|
167
|
+
"""
|
|
168
|
+
has_list = os.path.isfile(os.path.join(project_root, list_filename))
|
|
169
|
+
state_dir = os.path.join(project_root, "dev-pipeline", "state", state_subdir)
|
|
170
|
+
has_pipeline_state = os.path.isdir(state_dir) and bool(os.listdir(state_dir))
|
|
171
|
+
|
|
172
|
+
artifacts = {
|
|
173
|
+
f"{workflow_label}_list_exists": has_list,
|
|
174
|
+
"pipeline_state_exists": has_pipeline_state,
|
|
158
175
|
}
|
|
159
176
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
177
|
+
if has_list and has_pipeline_state:
|
|
178
|
+
return 4, "Monitor", artifacts, \
|
|
179
|
+
f"{list_filename} + pipeline state exist", \
|
|
180
|
+
"check pipeline status and report results"
|
|
181
|
+
|
|
182
|
+
if has_list:
|
|
183
|
+
return 3, "Launch", artifacts, \
|
|
184
|
+
f"{list_filename} exists, no pipeline state", \
|
|
185
|
+
"launch pipeline → monitor progress"
|
|
186
|
+
|
|
187
|
+
return 1, "Brainstorm", artifacts, \
|
|
188
|
+
f"no {list_filename} found", \
|
|
189
|
+
f"{workflow_label} goal clarification → plan → launch → monitor"
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def infer_feature_phase(project_root):
|
|
193
|
+
"""Infer feature-workflow phase from artifacts and pipeline state."""
|
|
194
|
+
return _infer_pipeline_workflow_phase(
|
|
195
|
+
project_root, "feature-list.json", "features", "feature"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def infer_refactor_phase(project_root):
|
|
200
|
+
"""Infer refactor-workflow phase from artifacts and pipeline state."""
|
|
201
|
+
return _infer_pipeline_workflow_phase(
|
|
202
|
+
project_root, "refactor-list.json", "refactors", "refactor"
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
# ---------------------------------------------------------------------------
|
|
207
|
+
# Git state helpers
|
|
208
|
+
# ---------------------------------------------------------------------------
|
|
163
209
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
210
|
+
def detect_commits_ahead(project_root, main_branch="main"):
|
|
211
|
+
"""Count commits ahead of main branch."""
|
|
212
|
+
log_output = run_git(
|
|
213
|
+
["log", f"{main_branch}..HEAD", "--oneline"], cwd=project_root
|
|
214
|
+
)
|
|
215
|
+
if log_output:
|
|
216
|
+
return len(log_output.strip().split("\n"))
|
|
217
|
+
return 0
|
|
169
218
|
|
|
170
|
-
# Also check without feat/ prefix (some pipelines use different naming)
|
|
171
|
-
if not result["branch_exists"]:
|
|
172
|
-
alt_branch = f"feature/{feature_slug}"
|
|
173
|
-
alt_output = run_git(["branch", "--list", alt_branch], cwd=project_root)
|
|
174
|
-
if alt_output.strip():
|
|
175
|
-
result["branch_exists"] = True
|
|
176
|
-
result["feature_branch"] = alt_branch
|
|
177
219
|
|
|
178
|
-
|
|
220
|
+
def detect_git_state(project_root, main_branch="main", cached_branch=None):
|
|
221
|
+
"""Detect git branch and change state."""
|
|
222
|
+
current = cached_branch or run_git(["branch", "--show-current"], cwd=project_root)
|
|
179
223
|
|
|
180
224
|
# Uncommitted changes (working tree)
|
|
225
|
+
uncommitted = 0
|
|
181
226
|
diff_stat = run_git(["diff", "--stat"], cwd=project_root)
|
|
182
227
|
if diff_stat:
|
|
183
|
-
# Count "N files changed" from the summary line
|
|
184
228
|
lines = diff_stat.strip().split("\n")
|
|
185
|
-
|
|
229
|
+
uncommitted = max(0, len(lines) - 1)
|
|
186
230
|
|
|
187
231
|
# Staged changes
|
|
232
|
+
staged = 0
|
|
188
233
|
staged_stat = run_git(["diff", "--cached", "--stat"], cwd=project_root)
|
|
189
234
|
if staged_stat:
|
|
190
235
|
lines = staged_stat.strip().split("\n")
|
|
191
|
-
|
|
236
|
+
staged = max(0, len(lines) - 1)
|
|
192
237
|
|
|
193
|
-
|
|
194
|
-
main_branch = "main"
|
|
195
|
-
# Try to detect default branch
|
|
196
|
-
for candidate in ["main", "master"]:
|
|
197
|
-
check = run_git(["rev-parse", "--verify", candidate], cwd=project_root)
|
|
198
|
-
if check:
|
|
199
|
-
main_branch = candidate
|
|
200
|
-
break
|
|
238
|
+
commits_ahead = detect_commits_ahead(project_root, main_branch)
|
|
201
239
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
240
|
+
return {
|
|
241
|
+
"current_branch": current,
|
|
242
|
+
"uncommitted_files": uncommitted,
|
|
243
|
+
"staged_files": staged,
|
|
244
|
+
"commits_ahead_of_main": commits_ahead,
|
|
245
|
+
}
|
|
207
246
|
|
|
208
|
-
return result
|
|
209
247
|
|
|
248
|
+
# ---------------------------------------------------------------------------
|
|
249
|
+
# Code change detection (reused from original, workflow-agnostic)
|
|
250
|
+
# ---------------------------------------------------------------------------
|
|
210
251
|
|
|
211
252
|
def detect_code_changes(project_root, main_branch="main"):
|
|
212
253
|
"""Analyze code changes relative to main branch.
|
|
@@ -214,10 +255,10 @@ def detect_code_changes(project_root, main_branch="main"):
|
|
|
214
255
|
Filters out pipeline/config files that aren't source code — only counts
|
|
215
256
|
files that represent actual implementation work.
|
|
216
257
|
"""
|
|
217
|
-
# Files/patterns that are pipeline artifacts, not implementation code
|
|
218
258
|
IGNORED_FILES = {
|
|
219
259
|
"feature-list.json",
|
|
220
260
|
"bug-fix-list.json",
|
|
261
|
+
"refactor-list.json",
|
|
221
262
|
"package-lock.json",
|
|
222
263
|
"yarn.lock",
|
|
223
264
|
"pnpm-lock.yaml",
|
|
@@ -249,7 +290,7 @@ def detect_code_changes(project_root, main_branch="main"):
|
|
|
249
290
|
"has_changes": False,
|
|
250
291
|
}
|
|
251
292
|
|
|
252
|
-
#
|
|
293
|
+
# Diff relative to main
|
|
253
294
|
diff_output = run_git(
|
|
254
295
|
["diff", main_branch, "--name-status"], cwd=project_root
|
|
255
296
|
)
|
|
@@ -308,7 +349,6 @@ def detect_code_changes(project_root, main_branch="main"):
|
|
|
308
349
|
result["test_files_touched"] += 1
|
|
309
350
|
parent = os.path.dirname(filepath)
|
|
310
351
|
if parent:
|
|
311
|
-
# Keep first two levels for readability
|
|
312
352
|
parts = parent.split(os.sep)
|
|
313
353
|
dirs.add(os.sep.join(parts[:2]) + "/")
|
|
314
354
|
|
|
@@ -318,98 +358,13 @@ def detect_code_changes(project_root, main_branch="main"):
|
|
|
318
358
|
return result
|
|
319
359
|
|
|
320
360
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
has_plan = artifacts["plan_exists"]
|
|
325
|
-
has_code = code_changes["has_changes"]
|
|
326
|
-
has_commits = git_state["commits_ahead_of_main"] > 0
|
|
327
|
-
tasks_total = artifacts["plan_tasks_total"]
|
|
328
|
-
tasks_done = artifacts["plan_tasks_completed"]
|
|
329
|
-
|
|
330
|
-
# Scenario D: Already committed
|
|
331
|
-
if has_commits:
|
|
332
|
-
return {
|
|
333
|
-
"recommended_action": "complete_post_commit",
|
|
334
|
-
"recommended_phase": "review",
|
|
335
|
-
"scenario": "D",
|
|
336
|
-
"reason": f"{git_state['commits_ahead_of_main']} commit(s) ahead of main — implementation may be complete",
|
|
337
|
-
"remaining_work": "code review + retrospective + merge",
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
# Scenario A: Implementation in progress
|
|
341
|
-
if has_plan and has_code:
|
|
342
|
-
if tasks_total > 0 and tasks_done == tasks_total:
|
|
343
|
-
return {
|
|
344
|
-
"recommended_action": "review_and_commit",
|
|
345
|
-
"recommended_phase": "review",
|
|
346
|
-
"scenario": "A",
|
|
347
|
-
"reason": f"all {tasks_total} plan tasks completed, code changes present",
|
|
348
|
-
"remaining_work": "code review + commit",
|
|
349
|
-
}
|
|
350
|
-
else:
|
|
351
|
-
tasks_remaining = tasks_total - tasks_done if tasks_total > 0 else "unknown"
|
|
352
|
-
return {
|
|
353
|
-
"recommended_action": "continue_implementation",
|
|
354
|
-
"recommended_phase": "implement",
|
|
355
|
-
"scenario": "A",
|
|
356
|
-
"reason": f"spec and plan exist, {tasks_done}/{tasks_total} tasks completed, code changes present",
|
|
357
|
-
"remaining_work": f"{tasks_remaining} tasks + review + commit",
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
# Scenario B: Only planning artifacts
|
|
361
|
-
if has_spec or has_plan:
|
|
362
|
-
if has_plan:
|
|
363
|
-
return {
|
|
364
|
-
"recommended_action": "start_implementation",
|
|
365
|
-
"recommended_phase": "implement",
|
|
366
|
-
"scenario": "B",
|
|
367
|
-
"reason": "spec and plan exist, no code changes yet",
|
|
368
|
-
"remaining_work": f"{tasks_total} tasks + review + commit",
|
|
369
|
-
}
|
|
370
|
-
else:
|
|
371
|
-
return {
|
|
372
|
-
"recommended_action": "generate_plan",
|
|
373
|
-
"recommended_phase": "plan",
|
|
374
|
-
"scenario": "B",
|
|
375
|
-
"reason": "spec exists but no plan — session interrupted during planning",
|
|
376
|
-
"remaining_work": "plan + implement + review + commit",
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
# Scenario C: Code changes but no artifacts
|
|
380
|
-
if has_code:
|
|
381
|
-
return {
|
|
382
|
-
"recommended_action": "adopt_and_continue",
|
|
383
|
-
"recommended_phase": "review",
|
|
384
|
-
"scenario": "C",
|
|
385
|
-
"reason": "code changes found but no prizmkit artifacts — possible manual work or artifacts cleaned",
|
|
386
|
-
"remaining_work": "review + commit",
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
# Scenario E: Nothing found
|
|
390
|
-
return {
|
|
391
|
-
"recommended_action": "start_fresh",
|
|
392
|
-
"recommended_phase": "none",
|
|
393
|
-
"scenario": "E",
|
|
394
|
-
"reason": "no artifacts, no code changes, no commits — feature was never executed or fully cleaned",
|
|
395
|
-
"remaining_work": "full pipeline run",
|
|
396
|
-
}
|
|
397
|
-
|
|
361
|
+
# ---------------------------------------------------------------------------
|
|
362
|
+
# Main
|
|
363
|
+
# ---------------------------------------------------------------------------
|
|
398
364
|
|
|
399
365
|
def main():
|
|
400
366
|
parser = argparse.ArgumentParser(
|
|
401
|
-
description="
|
|
402
|
-
)
|
|
403
|
-
parser.add_argument("--feature-id", required=True, help="Feature ID (e.g., F-007)")
|
|
404
|
-
parser.add_argument(
|
|
405
|
-
"--feature-list",
|
|
406
|
-
default="feature-list.json",
|
|
407
|
-
help="Path to feature-list.json (default: feature-list.json)",
|
|
408
|
-
)
|
|
409
|
-
parser.add_argument(
|
|
410
|
-
"--state-dir",
|
|
411
|
-
default=None,
|
|
412
|
-
help="Pipeline state directory (default: dev-pipeline/state)",
|
|
367
|
+
description="Auto-detect interrupted workflow state for recovery"
|
|
413
368
|
)
|
|
414
369
|
parser.add_argument(
|
|
415
370
|
"--project-root",
|
|
@@ -426,54 +381,57 @@ def main():
|
|
|
426
381
|
git_root = run_git(["rev-parse", "--show-toplevel"])
|
|
427
382
|
project_root = git_root if git_root else os.getcwd()
|
|
428
383
|
|
|
429
|
-
|
|
430
|
-
state_dir = args.state_dir or os.path.join(project_root, "dev-pipeline", "state")
|
|
384
|
+
main_branch = detect_main_branch(project_root)
|
|
431
385
|
|
|
432
|
-
#
|
|
433
|
-
|
|
434
|
-
if not os.path.isabs(feature_list_path):
|
|
435
|
-
feature_list_path = os.path.join(project_root, feature_list_path)
|
|
386
|
+
# Step 1: Detect workflow type (also caches branch name)
|
|
387
|
+
workflow_type, context = detect_workflow_type(project_root)
|
|
436
388
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
sys.exit(1)
|
|
444
|
-
|
|
445
|
-
feature = find_feature(feature_list_path, args.feature_id)
|
|
446
|
-
if not feature:
|
|
447
|
-
print(
|
|
448
|
-
json.dumps(
|
|
449
|
-
{
|
|
450
|
-
"error": f"Feature {args.feature_id} not found in {feature_list_path}"
|
|
451
|
-
}
|
|
389
|
+
if workflow_type is None:
|
|
390
|
+
report = {
|
|
391
|
+
"detected": False,
|
|
392
|
+
"message": (
|
|
393
|
+
"No interrupted workflow detected. "
|
|
394
|
+
"Use /feature-workflow, /bug-fix-workflow, or /refactor-workflow to start."
|
|
452
395
|
),
|
|
453
|
-
|
|
454
|
-
)
|
|
396
|
+
}
|
|
397
|
+
print(json.dumps(report, indent=2))
|
|
455
398
|
sys.exit(1)
|
|
456
399
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
400
|
+
# Step 2: Collect git state and code changes once (shared across phase inference + report)
|
|
401
|
+
cached_branch = context.get("branch")
|
|
402
|
+
git_state = detect_git_state(project_root, main_branch, cached_branch=cached_branch)
|
|
403
|
+
code_changes = detect_code_changes(project_root, main_branch)
|
|
404
|
+
|
|
405
|
+
# Step 3: Infer phase within the detected workflow
|
|
406
|
+
if workflow_type == "bug-fix-workflow":
|
|
407
|
+
phase, phase_name, artifacts, reason, remaining = \
|
|
408
|
+
infer_bugfix_phase(project_root, context.get("bug_id"),
|
|
409
|
+
code_changes, git_state["commits_ahead_of_main"])
|
|
410
|
+
elif workflow_type == "feature-workflow":
|
|
411
|
+
phase, phase_name, artifacts, reason, remaining = \
|
|
412
|
+
infer_feature_phase(project_root)
|
|
413
|
+
elif workflow_type == "refactor-workflow":
|
|
414
|
+
phase, phase_name, artifacts, reason, remaining = \
|
|
415
|
+
infer_refactor_phase(project_root)
|
|
416
|
+
else:
|
|
417
|
+
# Should never reach here
|
|
418
|
+
print(json.dumps({"detected": False, "message": "Unknown workflow type"}), file=sys.stderr)
|
|
419
|
+
sys.exit(1)
|
|
466
420
|
|
|
467
|
-
# Build report
|
|
421
|
+
# Step 4: Build report
|
|
468
422
|
report = {
|
|
469
|
-
"
|
|
470
|
-
"
|
|
471
|
-
"
|
|
472
|
-
"
|
|
423
|
+
"detected": True,
|
|
424
|
+
"workflow_type": workflow_type,
|
|
425
|
+
"phase": phase,
|
|
426
|
+
"phase_name": phase_name,
|
|
427
|
+
"context": context,
|
|
473
428
|
"artifacts": artifacts,
|
|
474
429
|
"git": git_state,
|
|
475
430
|
"code": code_changes,
|
|
476
|
-
"recovery":
|
|
431
|
+
"recovery": {
|
|
432
|
+
"reason": reason,
|
|
433
|
+
"remaining_work": remaining,
|
|
434
|
+
},
|
|
477
435
|
}
|
|
478
436
|
|
|
479
437
|
print(json.dumps(report, indent=2))
|