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.
Files changed (99) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/adapters/claude/agent-adapter.js +18 -0
  3. package/bundled/adapters/claude/command-adapter.js +1 -27
  4. package/bundled/agents/prizm-dev-team-critic.md +2 -0
  5. package/bundled/agents/prizm-dev-team-dev.md +2 -0
  6. package/bundled/agents/prizm-dev-team-reviewer.md +2 -0
  7. package/bundled/dev-pipeline/README.md +63 -63
  8. package/bundled/dev-pipeline/assets/feature-list-example.json +1 -1
  9. package/bundled/dev-pipeline/assets/prizm-dev-team-integration.md +1 -1
  10. package/bundled/dev-pipeline/{launch-daemon.sh → launch-feature-daemon.sh} +33 -33
  11. package/bundled/dev-pipeline/launch-refactor-daemon.sh +454 -0
  12. package/bundled/dev-pipeline/lib/branch.sh +1 -1
  13. package/bundled/dev-pipeline/reset-feature.sh +3 -3
  14. package/bundled/dev-pipeline/reset-refactor.sh +312 -0
  15. package/bundled/dev-pipeline/{retry-bug.sh → retry-bugfix.sh} +47 -59
  16. package/bundled/dev-pipeline/retry-feature.sh +41 -54
  17. package/bundled/dev-pipeline/retry-refactor.sh +358 -0
  18. package/bundled/dev-pipeline/run-bugfix.sh +6 -0
  19. package/bundled/dev-pipeline/{run.sh → run-feature.sh} +31 -31
  20. package/bundled/dev-pipeline/run-refactor.sh +787 -0
  21. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +177 -10
  22. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +419 -0
  23. package/bundled/dev-pipeline/scripts/init-refactor-pipeline.py +393 -0
  24. package/bundled/dev-pipeline/scripts/update-refactor-status.py +726 -0
  25. package/bundled/dev-pipeline/templates/agent-prompts/critic-code-challenge.md +13 -0
  26. package/bundled/dev-pipeline/templates/agent-prompts/critic-plan-challenge.md +7 -0
  27. package/bundled/dev-pipeline/templates/agent-prompts/dev-fix.md +7 -0
  28. package/bundled/dev-pipeline/templates/agent-prompts/dev-implement.md +26 -0
  29. package/bundled/dev-pipeline/templates/agent-prompts/dev-resume.md +5 -0
  30. package/bundled/dev-pipeline/templates/agent-prompts/reviewer-analyze.md +5 -0
  31. package/bundled/dev-pipeline/templates/agent-prompts/reviewer-review.md +12 -0
  32. package/bundled/dev-pipeline/templates/bootstrap-tier1.md +29 -2
  33. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +8 -7
  34. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +11 -10
  35. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +2 -3
  36. package/bundled/dev-pipeline/templates/feature-list-schema.json +1 -1
  37. package/bundled/dev-pipeline/templates/refactor-list-schema.json +159 -0
  38. package/bundled/dev-pipeline/templates/sections/ac-verification-checklist.md +13 -0
  39. package/bundled/dev-pipeline/templates/sections/feature-context.md +1 -1
  40. package/bundled/dev-pipeline/templates/sections/phase-analyze-agent.md +9 -8
  41. package/bundled/dev-pipeline/templates/sections/phase-analyze-full.md +9 -8
  42. package/bundled/dev-pipeline/templates/sections/phase-browser-verification.md +2 -1
  43. package/bundled/dev-pipeline/templates/sections/phase-critic-code.md +8 -10
  44. package/bundled/dev-pipeline/templates/sections/phase-critic-plan-full.md +9 -10
  45. package/bundled/dev-pipeline/templates/sections/phase-critic-plan.md +8 -9
  46. package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +7 -10
  47. package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +8 -15
  48. package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +7 -12
  49. package/bundled/dev-pipeline/templates/sections/phase-review-full.md +8 -19
  50. package/bundled/dev-pipeline/templates/sections/test-failure-recovery.md +75 -0
  51. package/bundled/skills/_metadata.json +33 -6
  52. package/bundled/skills/app-planner/SKILL.md +105 -320
  53. package/bundled/skills/app-planner/assets/app-design-guide.md +101 -0
  54. package/bundled/skills/app-planner/references/frontend-design-guide.md +1 -1
  55. package/bundled/skills/app-planner/references/project-brief-guide.md +49 -80
  56. package/bundled/skills/bug-fix-workflow/SKILL.md +2 -2
  57. package/bundled/skills/bug-planner/SKILL.md +68 -5
  58. package/bundled/skills/bug-planner/scripts/validate-bug-list.py +3 -2
  59. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +19 -5
  60. package/bundled/skills/{dev-pipeline-launcher → feature-pipeline-launcher}/SKILL.md +32 -32
  61. package/bundled/skills/feature-planner/SKILL.md +337 -0
  62. package/bundled/skills/{app-planner → feature-planner}/assets/evaluation-guide.md +4 -4
  63. package/bundled/skills/{app-planner → feature-planner}/assets/planning-guide.md +3 -171
  64. package/bundled/skills/{app-planner → feature-planner}/references/browser-interaction.md +6 -5
  65. package/bundled/skills/feature-planner/references/decomposition-patterns.md +75 -0
  66. package/bundled/skills/{app-planner → feature-planner}/references/error-recovery.md +8 -8
  67. package/bundled/skills/{app-planner → feature-planner}/references/incremental-feature-planning.md +1 -1
  68. package/bundled/skills/{app-planner/references/new-app-planning.md → feature-planner/references/new-project-planning.md} +1 -1
  69. package/bundled/skills/{app-planner → feature-planner}/scripts/validate-and-generate.py +4 -4
  70. package/bundled/skills/feature-workflow/SKILL.md +23 -23
  71. package/bundled/skills/prizm-kit/SKILL.md +1 -3
  72. package/bundled/skills/prizmkit-analyze/SKILL.md +2 -5
  73. package/bundled/skills/prizmkit-code-review/SKILL.md +2 -2
  74. package/bundled/skills/prizmkit-committer/SKILL.md +4 -8
  75. package/bundled/skills/prizmkit-deploy/SKILL.md +1 -5
  76. package/bundled/skills/prizmkit-implement/SKILL.md +3 -50
  77. package/bundled/skills/prizmkit-init/SKILL.md +5 -77
  78. package/bundled/skills/prizmkit-plan/SKILL.md +1 -12
  79. package/bundled/skills/prizmkit-prizm-docs/SKILL.md +6 -24
  80. package/bundled/skills/prizmkit-prizm-docs/assets/PRIZM-SPEC.md +21 -0
  81. package/bundled/skills/prizmkit-retrospective/SKILL.md +12 -117
  82. package/bundled/skills/recovery-workflow/SKILL.md +166 -316
  83. package/bundled/skills/recovery-workflow/evals/evals.json +29 -13
  84. package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +232 -274
  85. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +352 -0
  86. package/bundled/skills/refactor-planner/SKILL.md +436 -0
  87. package/bundled/skills/refactor-planner/assets/planning-guide.md +292 -0
  88. package/bundled/skills/refactor-planner/references/behavior-preservation.md +301 -0
  89. package/bundled/skills/refactor-planner/references/refactor-scoping-guide.md +221 -0
  90. package/bundled/skills/refactor-planner/scripts/validate-and-generate-refactor.py +786 -0
  91. package/bundled/skills/refactor-workflow/SKILL.md +299 -319
  92. package/package.json +1 -1
  93. package/src/clean.js +3 -3
  94. package/src/scaffold.js +6 -6
  95. package/bundled/skills/prizmkit-plan/assets/spec-template.md +0 -56
  96. package/bundled/skills/prizmkit-plan/references/clarify-guide.md +0 -67
  97. package/src/config.js +0 -504
  98. package/src/prompts.js +0 -210
  99. /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 — Scan filesystem for partial work from an interrupted
4
- feature pipeline session and output a structured recovery report.
3
+ detect-recovery-state.py — Universal workflow recovery detector.
5
4
 
6
- Checks four state categories:
7
- 1. Pipeline state (dev-pipeline/state/)
8
- 2. PrizmKit artifacts (.prizmkit/specs/{slug}/)
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 --feature-id F-007 --feature-list feature-list.json
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 compute_slug(feature_id, title):
42
- """Compute feature slug using the same algorithm as the pipeline."""
43
- numeric = feature_id.replace("F-", "").replace("f-", "").zfill(3)
44
- slug = title.lower()
45
- slug = re.sub(r"[^a-z0-9\s-]", "", slug)
46
- slug = re.sub(r"[\s]+", "-", slug.strip())
47
- slug = re.sub(r"-+", "-", slug).strip("-") or "feature"
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
- def find_feature(feature_list_path, feature_id):
52
- """Find feature in feature-list.json."""
53
- with open(feature_list_path) as f:
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
- status_file = os.path.join(state_dir, "features", feature_id, "status.json")
72
- if not os.path.isfile(status_file):
73
- return result
61
+ def detect_workflow_type(project_root):
62
+ """Priority-ordered signature matching.
74
63
 
75
- try:
76
- with open(status_file) as f:
77
- status_data = json.load(f)
78
- result["has_state"] = True
79
- result["status"] = status_data.get("status", "unknown")
80
- result["retry_count"] = status_data.get("retry_count", 0)
81
-
82
- sessions = status_data.get("sessions", [])
83
- if sessions:
84
- last = sessions[-1]
85
- sid = last.get("session_id", "")
86
- result["last_session_id"] = sid
87
- result["last_session_dir"] = os.path.join(
88
- state_dir, "features", feature_id, "sessions", sid
89
- )
90
- except (json.JSONDecodeError, IOError):
91
- pass
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
- return result
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
- def detect_artifacts(project_root, feature_slug):
97
- """Check .prizmkit/specs/{slug}/ for planning artifacts."""
98
- specs_dir = os.path.join(project_root, ".prizmkit", "specs", feature_slug)
99
- result = {
100
- "spec_exists": False,
101
- "plan_exists": False,
102
- "spec_path": None,
103
- "plan_path": None,
104
- "plan_tasks_total": 0,
105
- "plan_tasks_completed": 0,
106
- "other_artifacts": [],
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 not os.path.isdir(specs_dir):
110
- return result
111
-
112
- spec_path = os.path.join(specs_dir, "spec.md")
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
- return result
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
- def detect_git_state(project_root, feature_slug):
149
- """Check git for branch existence, uncommitted changes, commits ahead."""
150
- result = {
151
- "feature_branch": f"feat/{feature_slug}",
152
- "branch_exists": False,
153
- "on_feature_branch": False,
154
- "uncommitted_files": 0,
155
- "staged_files": 0,
156
- "commits_ahead_of_main": 0,
157
- "current_branch": "",
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
- # Current branch
161
- current = run_git(["branch", "--show-current"], cwd=project_root)
162
- result["current_branch"] = current
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
- # Check if feature branch exists
165
- branch_name = f"feat/{feature_slug}"
166
- branches_output = run_git(["branch", "--list", branch_name], cwd=project_root)
167
- if branches_output.strip():
168
- result["branch_exists"] = True
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
- result["on_feature_branch"] = current == result["feature_branch"]
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
- result["uncommitted_files"] = max(0, len(lines) - 1) # exclude summary line
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
- result["staged_files"] = max(0, len(lines) - 1)
236
+ staged = max(0, len(lines) - 1)
192
237
 
193
- # Commits ahead of main
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
- log_output = run_git(
203
- ["log", f"{main_branch}..HEAD", "--oneline"], cwd=project_root
204
- )
205
- if log_output:
206
- result["commits_ahead_of_main"] = len(log_output.strip().split("\n"))
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
- # Get diff stat relative to main
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
- def determine_recovery(artifacts, git_state, code_changes, pipeline):
322
- """Recommend a recovery action based on detected state."""
323
- has_spec = artifacts["spec_exists"]
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="Detect recovery state for an interrupted feature session"
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
- # Resolve state dir
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
- # Resolve feature list path
433
- feature_list_path = args.feature_list
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
- # Find feature
438
- if not os.path.isfile(feature_list_path):
439
- print(
440
- json.dumps({"error": f"Feature list not found: {feature_list_path}"}),
441
- file=sys.stderr,
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
- file=sys.stderr,
454
- )
396
+ }
397
+ print(json.dumps(report, indent=2))
455
398
  sys.exit(1)
456
399
 
457
- title = feature.get("title", "untitled")
458
- slug = compute_slug(args.feature_id, title)
459
-
460
- # Run all detection phases
461
- pipeline = detect_pipeline_state(state_dir, args.feature_id)
462
- artifacts = detect_artifacts(project_root, slug)
463
- git_state = detect_git_state(project_root, slug)
464
- code_changes = detect_code_changes(project_root)
465
- recovery = determine_recovery(artifacts, git_state, code_changes, pipeline)
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
- "feature_id": args.feature_id,
470
- "feature_title": title,
471
- "feature_slug": slug,
472
- "pipeline": pipeline,
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": recovery,
431
+ "recovery": {
432
+ "reason": reason,
433
+ "remaining_work": remaining,
434
+ },
477
435
  }
478
436
 
479
437
  print(json.dumps(report, indent=2))