prizmkit 1.1.111 → 1.1.112

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 (110) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/agents/prizm-dev-team-reviewer.md +12 -1
  3. package/bundled/dev-pipeline/prizmkit_runtime/interoperability.py +1 -0
  4. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +19 -73
  5. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +283 -421
  6. package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +1 -1
  7. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +259 -481
  8. package/bundled/dev-pipeline/scripts/init-bugfix-pipeline.py +34 -0
  9. package/bundled/dev-pipeline/scripts/init-pipeline.py +12 -0
  10. package/bundled/dev-pipeline/scripts/init-refactor-pipeline.py +24 -1
  11. package/bundled/dev-pipeline/scripts/prompt_framework.py +312 -0
  12. package/bundled/dev-pipeline/scripts/update-bug-status.py +87 -20
  13. package/bundled/dev-pipeline/scripts/update-feature-status.py +82 -16
  14. package/bundled/dev-pipeline/scripts/update-refactor-status.py +68 -28
  15. package/bundled/dev-pipeline/scripts/utils.py +171 -0
  16. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +1 -1
  17. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +10 -18
  18. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +14 -25
  19. package/bundled/dev-pipeline/templates/bug-fix-list-schema.json +9 -1
  20. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +11 -8
  21. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +122 -182
  22. package/bundled/dev-pipeline/templates/sections/bugfix-artifacts.md +16 -0
  23. package/bundled/dev-pipeline/templates/sections/bugfix-critical-paths.md +12 -0
  24. package/bundled/dev-pipeline/templates/sections/bugfix-mission.md +9 -0
  25. package/bundled/dev-pipeline/templates/sections/bugfix-phase-commit-report.md +31 -0
  26. package/bundled/dev-pipeline/templates/sections/bugfix-phase-critic.md +22 -0
  27. package/bundled/dev-pipeline/templates/sections/bugfix-phase-diagnose-plan.md +23 -0
  28. package/bundled/dev-pipeline/templates/sections/bugfix-phase-implement.md +20 -0
  29. package/bundled/dev-pipeline/templates/sections/bugfix-phase-init.md +9 -0
  30. package/bundled/dev-pipeline/templates/sections/bugfix-phase-manual-verification.md +15 -0
  31. package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +29 -0
  32. package/bundled/dev-pipeline/templates/sections/bugfix-reminders.md +11 -0
  33. package/bundled/dev-pipeline/templates/sections/bugfix-session-context.md +7 -0
  34. package/bundled/dev-pipeline/templates/sections/bugfix-session-status.md +31 -0
  35. package/bundled/dev-pipeline/templates/sections/bugfix-task-contract.md +40 -0
  36. package/bundled/dev-pipeline/templates/sections/failure-capture.md +2 -2
  37. package/bundled/dev-pipeline/templates/sections/phase-browser-verification-auto.md +1 -1
  38. package/bundled/dev-pipeline/templates/sections/refactor-artifacts.md +17 -0
  39. package/bundled/dev-pipeline/templates/sections/refactor-critical-paths.md +13 -0
  40. package/bundled/dev-pipeline/templates/sections/refactor-mission.md +9 -0
  41. package/bundled/dev-pipeline/templates/sections/refactor-phase-commit-report.md +37 -0
  42. package/bundled/dev-pipeline/templates/sections/refactor-phase-critic.md +22 -0
  43. package/bundled/dev-pipeline/templates/sections/refactor-phase-implement.md +16 -0
  44. package/bundled/dev-pipeline/templates/sections/refactor-phase-init.md +9 -0
  45. package/bundled/dev-pipeline/templates/sections/refactor-phase-plan.md +22 -0
  46. package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +19 -0
  47. package/bundled/dev-pipeline/templates/sections/refactor-reminders.md +11 -0
  48. package/bundled/dev-pipeline/templates/sections/refactor-session-context.md +7 -0
  49. package/bundled/dev-pipeline/templates/sections/refactor-session-status.md +28 -0
  50. package/bundled/dev-pipeline/templates/sections/refactor-task-contract.md +52 -0
  51. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +17 -5
  52. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +197 -0
  53. package/bundled/dev-pipeline/tests/test_unified_cli.py +6 -2
  54. package/bundled/dev-pipeline/tests/test_utils.py +66 -1
  55. package/bundled/skills/_metadata.json +1 -1
  56. package/bundled/skills/app-planner/SKILL.md +8 -7
  57. package/bundled/skills/app-planner/references/project-brief-guide.md +2 -2
  58. package/bundled/skills/app-planner/references/rules/backend/derivation-rules.md +1 -1
  59. package/bundled/skills/app-planner/references/rules/frontend/derivation-rules.md +1 -1
  60. package/bundled/skills/app-planner/references/rules-configuration.md +53 -26
  61. package/bundled/skills/bug-fix-workflow/SKILL.md +191 -336
  62. package/bundled/skills/bug-planner/SKILL.md +6 -5
  63. package/bundled/skills/bug-planner/references/critic-and-verification.md +3 -3
  64. package/bundled/skills/bug-planner/references/schema-validation.md +2 -1
  65. package/bundled/skills/bug-planner/scripts/validate-bug-list.py +30 -1
  66. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +16 -16
  67. package/bundled/skills/bugfix-pipeline-launcher/references/configuration.md +2 -2
  68. package/bundled/skills/feature-pipeline-launcher/SKILL.md +19 -11
  69. package/bundled/skills/feature-planner/SKILL.md +9 -11
  70. package/bundled/skills/feature-planner/assets/planning-guide.md +1 -1
  71. package/bundled/skills/feature-planner/references/decomposition-patterns.md +1 -1
  72. package/bundled/skills/feature-planner/references/error-recovery.md +2 -1
  73. package/bundled/skills/feature-planner/references/incremental-feature-planning.md +2 -2
  74. package/bundled/skills/feature-planner/references/new-project-planning.md +35 -39
  75. package/bundled/skills/feature-planner/scripts/validate-and-generate.py +1 -1
  76. package/bundled/skills/feature-workflow/SKILL.md +169 -298
  77. package/bundled/skills/prizmkit/SKILL.md +103 -57
  78. package/bundled/skills/prizmkit-code-review/SKILL.md +97 -116
  79. package/bundled/skills/prizmkit-code-review/references/review-report-template.md +1 -0
  80. package/bundled/skills/prizmkit-code-review/references/reviewer-agent-prompt.md +12 -1
  81. package/bundled/skills/prizmkit-committer/SKILL.md +59 -47
  82. package/bundled/skills/prizmkit-deploy/SKILL.md +162 -381
  83. package/bundled/skills/prizmkit-deploy/references/ssh-adapter-flow.md +220 -0
  84. package/bundled/skills/prizmkit-implement/SKILL.md +51 -40
  85. package/bundled/skills/prizmkit-init/SKILL.md +4 -3
  86. package/bundled/skills/prizmkit-plan/SKILL.md +85 -70
  87. package/bundled/skills/prizmkit-prizm-docs/SKILL.md +94 -73
  88. package/bundled/skills/prizmkit-prizm-docs/assets/prizm-docs-format.md +20 -18
  89. package/bundled/skills/prizmkit-prizm-docs/references/op-update.md +18 -14
  90. package/bundled/skills/prizmkit-retrospective/SKILL.md +56 -48
  91. package/bundled/skills/prizmkit-retrospective/references/structural-sync-steps.md +79 -27
  92. package/bundled/skills/prizmkit-test/SKILL.md +138 -141
  93. package/bundled/skills/prizmkit-test/references/examples.md +10 -8
  94. package/bundled/skills/prizmkit-test/references/test-generation-steps.md +1 -1
  95. package/bundled/skills/prizmkit-test/references/test-report-template.md +2 -2
  96. package/bundled/skills/recovery-workflow/SKILL.md +195 -256
  97. package/bundled/skills/recovery-workflow/evals/evals.json +21 -13
  98. package/bundled/skills/recovery-workflow/references/detection.md +48 -39
  99. package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +258 -322
  100. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +29 -11
  101. package/bundled/skills/refactor-planner/SKILL.md +2 -2
  102. package/bundled/skills/refactor-planner/references/behavior-preservation.md +24 -19
  103. package/bundled/skills/refactor-planner/references/fast-path.md +2 -4
  104. package/bundled/skills/refactor-planner/references/planning-phases.md +2 -2
  105. package/bundled/skills/refactor-workflow/SKILL.md +173 -307
  106. package/package.json +1 -1
  107. package/src/scaffold.js +5 -0
  108. package/bundled/dev-pipeline/templates/agent-prompts/reviewer-review.md +0 -6
  109. package/bundled/skills/feature-workflow/references/brainstorm-guide.md +0 -137
  110. package/bundled/skills/refactor-workflow/references/brainstorm-guide.md +0 -116
@@ -2,11 +2,12 @@
2
2
  """
3
3
  detect-recovery-state.py — Universal workflow recovery detector.
4
4
 
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.
5
+ Auto-detects which interactive workflow or pipeline session was interrupted
6
+ by inspecting git branch names, durable planning artifacts, pipeline state,
7
+ current-workspace plans, review artifacts, and code changes.
8
8
 
9
- Does NOT run tests — that's left to the skill so the user sees output in real time.
9
+ Does NOT run tests — the skill controls test execution so the user can see
10
+ real-time output.
10
11
 
11
12
  Usage:
12
13
  python3 detect-recovery-state.py [--project-root .]
@@ -18,12 +19,9 @@ import os
18
19
  import re
19
20
  import subprocess
20
21
  import sys
22
+ from pathlib import Path
21
23
 
22
24
 
23
- # ---------------------------------------------------------------------------
24
- # Git helper
25
- # ---------------------------------------------------------------------------
26
-
27
25
  def run_git(args, cwd=None):
28
26
  """Run a git command and return stdout, or empty string on failure."""
29
27
  try:
@@ -48,16 +46,37 @@ def detect_main_branch(project_root):
48
46
  return "main"
49
47
 
50
48
 
51
- # ---------------------------------------------------------------------------
52
- # Workflow signature detection (priority-ordered)
53
- # ---------------------------------------------------------------------------
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
+
54
62
 
55
63
  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)
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 "")
58
66
  return match.group(1) if match else None
59
67
 
60
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):
72
+ return None
73
+ bug_ids = sorted(
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
+
61
80
  def detect_workflow_type(project_root):
62
81
  """Priority-ordered signature matching.
63
82
 
@@ -65,294 +84,273 @@ def detect_workflow_type(project_root):
65
84
  """
66
85
  branch = run_git(["branch", "--show-current"], cwd=project_root)
67
86
 
68
- # Priority 1: fix/* branch → bug-fix-workflow
69
87
  if branch.startswith("fix/"):
70
- bug_id = extract_bug_id_from_branch(branch)
71
- return ("bug-fix-workflow", {"bug_id": bug_id, "branch": branch})
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
+ })
72
115
 
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})
82
-
83
- # Priority 3: refactor/* branch → refactor-workflow
84
116
  if branch.startswith("refactor/"):
85
- return ("refactor-workflow", {"branch": branch})
86
-
87
- # Priority 4: refactor-list.json exists → refactor-workflow
88
- # Check both new and old paths for backward compatibility
89
- new_refactor = os.path.join(project_root, ".prizmkit", "plans", "refactor-list.json")
90
- old_refactor = os.path.join(project_root, "refactor-list.json")
91
- if os.path.isfile(new_refactor):
92
- return ("refactor-workflow", {"branch": branch})
93
- elif os.path.isfile(old_refactor):
94
- print(f"⚠️ Migration notice: refactor-list.json found in root. "
95
- f"Please move to .prizmkit/plans/refactor-list.json", file=sys.stderr)
96
- return ("refactor-workflow", {"branch": branch})
97
-
98
- # Priority 5: feat/* branch → feature-workflow
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"})
124
+
99
125
  if branch.startswith("feat/"):
100
- return ("feature-workflow", {"branch": branch})
101
-
102
- # Priority 6: feature-list.json exists → feature-workflow
103
- # Check both new and old paths for backward compatibility
104
- new_feature = os.path.join(project_root, ".prizmkit", "plans", "feature-list.json")
105
- old_feature = os.path.join(project_root, "feature-list.json")
106
- if os.path.isfile(new_feature):
107
- return ("feature-workflow", {"branch": branch})
108
- elif os.path.isfile(old_feature):
109
- print(f"⚠️ Migration notice: feature-list.json found in root. "
110
- f"Please move to .prizmkit/plans/feature-list.json", file=sys.stderr)
111
- return ("feature-workflow", {"branch": branch})
112
-
113
- # No match
126
+ return ("feature-workflow", {"branch": branch, "signal": "feature branch"})
127
+
128
+ if file_exists(project_root, ".prizmkit", "plans", "feature-list.json"):
129
+ return ("feature-workflow", {"branch": branch, "signal": "feature-list.json"})
130
+
131
+ if dir_has_content(project_root, ".prizmkit", "state", "features"):
132
+ return ("feature-workflow", {"branch": branch, "signal": "feature pipeline state"})
133
+
134
+ if file_exists(project_root, "spec.md") or file_exists(project_root, "plan.md"):
135
+ return ("unknown-fast-path", {"branch": branch, "signal": "spec.md or plan.md"})
136
+
114
137
  return (None, None)
115
138
 
116
139
 
117
140
  def detect_other_workflows(project_root, primary_type):
118
- """Scan for other interrupted workflow signals beyond the primary match.
119
-
120
- Returns a list of workflow type strings that also have signals present,
121
- excluding the primary_type already detected.
122
- """
141
+ """Scan for secondary workflow signals beyond the primary match."""
123
142
  others = []
124
143
  branch = run_git(["branch", "--show-current"], cwd=project_root)
125
144
 
126
- # Bug-fix signals
127
- if primary_type != "bug-fix-workflow":
128
- if branch.startswith("fix/"):
129
- others.append("bug-fix-workflow")
130
- else:
131
- bugfix_dir = os.path.join(project_root, ".prizmkit", "bugfix")
132
- if os.path.isdir(bugfix_dir):
133
- bug_ids = [
134
- d for d in os.listdir(bugfix_dir)
135
- if os.path.isdir(os.path.join(bugfix_dir, d))
136
- ]
137
- if bug_ids:
138
- others.append("bug-fix-workflow")
139
-
140
- # Refactor signals
141
- if primary_type != "refactor-workflow":
142
- if branch.startswith("refactor/"):
143
- others.append("refactor-workflow")
144
- else:
145
- for path in [
146
- os.path.join(project_root, ".prizmkit", "plans", "refactor-list.json"),
147
- os.path.join(project_root, "refactor-list.json"),
148
- ]:
149
- if os.path.isfile(path):
150
- others.append("refactor-workflow")
151
- break
152
-
153
- # Feature signals
154
- if primary_type != "feature-workflow":
155
- if branch.startswith("feat/"):
156
- others.append("feature-workflow")
157
- else:
158
- for path in [
159
- os.path.join(project_root, ".prizmkit", "plans", "feature-list.json"),
160
- os.path.join(project_root, "feature-list.json"),
161
- ]:
162
- if os.path.isfile(path):
163
- others.append("feature-workflow")
164
- break
165
-
145
+ signals = [
146
+ ("bug-fix-workflow", [
147
+ branch.startswith("fix/"),
148
+ file_exists(project_root, ".prizmkit", "plans", "bug-fix-list.json"),
149
+ dir_has_content(project_root, ".prizmkit", "state", "bugfix"),
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
+ ]
163
+
164
+ for workflow, checks in signals:
165
+ if workflow != primary_type and any(checks):
166
+ others.append(workflow)
166
167
  return others
167
168
 
168
169
 
169
- # ---------------------------------------------------------------------------
170
- # Phase inference — one function per workflow
171
- # ---------------------------------------------------------------------------
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
172
176
 
173
- def infer_bugfix_phase(project_root, bug_id, code_changes, commits_ahead):
174
- """Infer bug-fix-workflow phase from artifacts and git state.
175
177
 
176
- Detection table (from bug-fix-workflow SKILL.md):
177
- (nothing) → Phase 1: Deep Bug Diagnosis
178
- fix-plan.md only → Phase 4: Fix
179
- fix-plan.md + code changes → Phase 5: Review
180
- all docs + review passed → Phase 6: User Verification
181
- all docs + committed → Phase 7: Merge Decision
182
- """
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
183
223
  bugfix_dir = os.path.join(project_root, ".prizmkit", "bugfix", bug_id) if bug_id else ""
184
- has_fix_plan = bugfix_dir and os.path.isfile(os.path.join(bugfix_dir, "fix-plan.md"))
185
- has_fix_report = bugfix_dir and os.path.isfile(os.path.join(bugfix_dir, "fix-report.md"))
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")))
186
226
 
187
- artifacts = {
227
+ artifacts = detect_fast_path_artifacts(project_root)
228
+ artifacts.update({
188
229
  "fix_plan_exists": has_fix_plan,
189
230
  "fix_report_exists": has_fix_report,
190
- }
231
+ })
191
232
  if has_fix_plan:
192
- artifacts["fix_plan_path"] = os.path.relpath(
193
- os.path.join(bugfix_dir, "fix-plan.md"), project_root
194
- )
233
+ artifacts["fix_plan_path"] = relpath(os.path.join(bugfix_dir, "fix-plan.md"), project_root)
195
234
  if has_fix_report:
196
- artifacts["fix_report_path"] = os.path.relpath(
197
- os.path.join(bugfix_dir, "fix-report.md"), project_root
198
- )
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
199
239
 
200
240
  if commits_ahead > 0:
201
241
  return 7, "Merge Decision", artifacts, \
202
- f"{commits_ahead} commit(s) ahead — fix likely committed", \
203
- "merge decision only"
242
+ f"{commits_ahead} commit(s) ahead — fix may already be committed", \
243
+ "merge decision or branch handoff"
204
244
 
205
- if has_fix_report:
245
+ if has_review_artifact and code_changes["has_changes"]:
206
246
  return 6, "User Verification", artifacts, \
207
- "fix-report.md exists review completed", \
208
- "user verification commit & merge"
247
+ "review artifact found with code changes", \
248
+ "user verification -> commit -> merge decision"
209
249
 
210
- if has_fix_plan and code_changes["has_changes"]:
250
+ if code_changes["has_changes"]:
211
251
  return 5, "Review", artifacts, \
212
- "fix-plan.md exists + code changes present", \
213
- "code review user verification commit & merge"
252
+ "code/test changes present without review artifact", \
253
+ "run tests if safe -> code review -> verification -> commit"
214
254
 
215
- if has_fix_plan:
255
+ if has_plan_artifact:
216
256
  return 4, "Fix", artifacts, \
217
- "fix-plan.md exists, no code changes yet", \
218
- "implement fix review user verification commit & merge"
257
+ "plan artifact found but no implementation changes", \
258
+ "implement fix -> review -> verification -> commit"
219
259
 
220
- # On fix branch but no artifacts
221
- return 1, "Deep Bug Diagnosis", artifacts, \
222
- "on fix branch but no artifacts found", \
223
- "diagnosis → triage → reproduce → fix → review → commit & merge"
260
+ return 1, "Diagnosis / Triage", artifacts, \
261
+ "bug-fix signal found but no durable implementation artifacts", \
262
+ "restore bug context -> diagnose -> choose recovery action"
224
263
 
225
264
 
226
- def _infer_pipeline_workflow_phase(project_root, list_filename, state_subdir, workflow_label):
227
- """Infer phase for pipeline-driven workflows (feature-workflow, refactor-workflow).
228
-
229
- Both follow the same structure:
230
- No list file → Phase 1: Brainstorm
231
- List file, no pipeline state → Phase 3: Launch
232
- List file + pipeline state → Phase 4: Monitor
233
-
234
- Checks new path (.prizmkit/plans/<list_filename>) first, then falls back
235
- to old root-level path with a migration warning.
236
- """
237
- # Check new path first, then old path with fallback warning
238
- new_list_path = os.path.join(project_root, ".prizmkit", "plans", list_filename)
239
- old_list_path = os.path.join(project_root, list_filename)
240
- has_list = os.path.isfile(new_list_path)
241
- if not has_list and os.path.isfile(old_list_path):
242
- has_list = True
243
- print(f"⚠️ Migration notice: {list_filename} found in root. "
244
- f"Please move to .prizmkit/plans/{list_filename}", file=sys.stderr)
245
-
246
- # Check new state path first, then old path with fallback warning
247
- new_state_dir = os.path.join(project_root, ".prizmkit", "state", state_subdir)
248
- old_state_dir = os.path.join(project_root, "dev-pipeline", "state", state_subdir)
249
- has_pipeline_state = os.path.isdir(new_state_dir) and bool(os.listdir(new_state_dir))
250
- if not has_pipeline_state and os.path.isdir(old_state_dir) and bool(os.listdir(old_state_dir)):
251
- has_pipeline_state = True
252
- print(f"⚠️ Migration notice: pipeline state found at dev-pipeline/state/{state_subdir}. "
253
- f"Please move to .prizmkit/state/{state_subdir}", file=sys.stderr)
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)
254
269
 
255
270
  artifacts = {
256
- f"{workflow_label}_list_exists": has_list,
257
- "pipeline_state_exists": has_pipeline_state,
271
+ f"{workflow_label}_list_exists": bool(list_path),
272
+ "pipeline_state_exists": bool(has_state),
273
+ **fast_path,
258
274
  }
275
+ if list_path:
276
+ artifacts[f"{workflow_label}_list_path"] = relpath(list_path, project_root)
259
277
 
260
- if has_list and has_pipeline_state:
261
- return 4, "Monitor", artifacts, \
262
- f".prizmkit/plans/{list_filename} + pipeline state exist", \
263
- "check pipeline status and report results"
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"
264
282
 
265
- if has_list:
283
+ if list_path:
266
284
  return 3, "Launch", artifacts, \
267
- f".prizmkit/plans/{list_filename} exists, no pipeline state", \
268
- "launch pipeline monitor progress"
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"
269
292
 
270
- return 1, "Brainstorm", artifacts, \
271
- f"no .prizmkit/plans/{list_filename} found", \
272
- f"{workflow_label} goal clarification plan → launch → monitor"
293
+ return 1, "Route Selection", artifacts, \
294
+ f"no {list_filename} or pipeline state found", \
295
+ f"clarify {workflow_label} intent and choose path"
273
296
 
274
297
 
275
298
  def infer_feature_phase(project_root):
276
- """Infer feature-workflow phase from artifacts and pipeline state."""
277
- return _infer_pipeline_workflow_phase(
278
- project_root, "feature-list.json", "features", "feature"
279
- )
299
+ return infer_pipeline_workflow_phase(project_root, "feature-list.json", "features", "feature")
280
300
 
281
301
 
282
302
  def infer_refactor_phase(project_root):
283
- """Infer refactor-workflow phase from artifacts and pipeline state."""
284
- return _infer_pipeline_workflow_phase(
285
- project_root, "refactor-list.json", "refactor", "refactor"
286
- )
303
+ return infer_pipeline_workflow_phase(project_root, "refactor-list.json", "refactor", "refactor")
304
+
287
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"
288
315
 
289
- # ---------------------------------------------------------------------------
290
- # Git state helpers
291
- # ---------------------------------------------------------------------------
292
316
 
293
317
  def detect_commits_ahead(project_root, main_branch="main"):
294
- """Count commits ahead of main branch."""
295
- log_output = run_git(
296
- ["log", f"{main_branch}..HEAD", "--oneline"], cwd=project_root
297
- )
318
+ log_output = run_git(["log", f"{main_branch}..HEAD", "--oneline"], cwd=project_root)
298
319
  if log_output:
299
320
  return len(log_output.strip().split("\n"))
300
321
  return 0
301
322
 
302
323
 
303
324
  def detect_git_state(project_root, main_branch="main", cached_branch=None):
304
- """Detect git branch and change state."""
305
325
  current = cached_branch or run_git(["branch", "--show-current"], cwd=project_root)
306
326
 
307
- # Uncommitted changes (working tree)
308
- uncommitted = 0
309
- diff_stat = run_git(["diff", "--stat"], cwd=project_root)
310
- if diff_stat:
311
- lines = diff_stat.strip().split("\n")
312
- uncommitted = max(0, len(lines) - 1)
313
-
314
- # Staged changes
315
- staged = 0
316
- staged_stat = run_git(["diff", "--cached", "--stat"], cwd=project_root)
317
- if staged_stat:
318
- lines = staged_stat.strip().split("\n")
319
- staged = max(0, len(lines) - 1)
320
-
327
+ uncommitted = run_git(["status", "--porcelain"], cwd=project_root)
321
328
  commits_ahead = detect_commits_ahead(project_root, main_branch)
322
329
 
323
330
  return {
324
331
  "current_branch": current,
325
- "uncommitted_files": uncommitted,
326
- "staged_files": staged,
332
+ "changed_paths": len([line for line in uncommitted.splitlines() if line.strip()]),
327
333
  "commits_ahead_of_main": commits_ahead,
328
334
  }
329
335
 
330
336
 
331
- # ---------------------------------------------------------------------------
332
- # Code change detection (reused from original, workflow-agnostic)
333
- # ---------------------------------------------------------------------------
334
-
335
337
  def detect_code_changes(project_root, main_branch="main"):
336
- """Analyze code changes relative to main branch.
337
-
338
- Filters out pipeline/config files that aren't source code — only counts
339
- files that represent actual implementation work.
340
-
341
- Uses a file_statuses dict keyed by filepath to avoid double-counting
342
- files that appear in both committed diff and uncommitted changes.
343
- """
344
- IGNORED_FILES = {
345
- # Basename-matched list files (root-level legacy paths)
338
+ """Analyze implementation changes relative to main and working tree."""
339
+ ignored_files = {
346
340
  "feature-list.json",
347
341
  "bug-fix-list.json",
348
342
  "refactor-list.json",
349
- # Lock files
343
+ "spec.md",
344
+ "plan.md",
345
+ "review-report.md",
346
+ "code-review-report.md",
347
+ "session-summary.md",
348
+ "context-snapshot.md",
350
349
  "package-lock.json",
351
350
  "yarn.lock",
352
351
  "pnpm-lock.yaml",
353
352
  }
354
- # Note: .prizmkit/plans/*.json paths are caught by IGNORED_PREFIXES below
355
- IGNORED_PREFIXES = (
353
+ ignored_prefixes = (
356
354
  ".prizmkit/",
357
355
  ".agents/",
358
356
  ".codex/",
@@ -360,80 +358,46 @@ def detect_code_changes(project_root, main_branch="main"):
360
358
  ".codebuddy/",
361
359
  )
362
360
 
363
- def is_source_file(filepath):
364
- """Return True if this file represents implementation code."""
361
+ def is_implementation_file(filepath):
365
362
  basename = os.path.basename(filepath)
366
- if basename in IGNORED_FILES:
363
+ if basename in ignored_files:
364
+ return False
365
+ if any(filepath.startswith(prefix) for prefix in ignored_prefixes):
367
366
  return False
368
- for prefix in IGNORED_PREFIXES:
369
- if filepath.startswith(prefix):
370
- return False
371
367
  return True
372
368
 
373
- # Track unique file → status to avoid double-counting.
374
- # Later sources (uncommitted, untracked) update the status if the file
375
- # was already seen in a committed diff.
376
- file_statuses = {} # filepath → "M" | "A" | "D"
377
-
378
- # Diff relative to main (committed changes on branch)
379
- diff_output = run_git(
380
- ["diff", main_branch, "--name-status"], cwd=project_root
381
- )
369
+ file_statuses = {}
382
370
 
383
- if diff_output:
384
- for line in diff_output.strip().split("\n"):
385
- if not line.strip():
386
- continue
387
- parts = line.split("\t", 1)
388
- if len(parts) < 2:
389
- continue
390
- status, filepath = parts[0][0], parts[1] # first char of status
391
- if not is_source_file(filepath):
392
- continue
393
- file_statuses[filepath] = status
394
-
395
- # Uncommitted working tree changes — update status for already-seen files
396
- uncommitted = run_git(["diff", "--name-status"], cwd=project_root)
397
- if uncommitted:
398
- for line in uncommitted.strip().split("\n"):
399
- if not line.strip():
400
- continue
371
+ for command in (["diff", main_branch, "--name-status"], ["diff", "--name-status"]):
372
+ output = run_git(command, cwd=project_root)
373
+ if not output:
374
+ continue
375
+ for line in output.splitlines():
401
376
  parts = line.split("\t", 1)
402
377
  if len(parts) < 2:
403
378
  continue
404
379
  status, filepath = parts[0][0], parts[1]
405
- if not is_source_file(filepath):
406
- continue
407
- if filepath not in file_statuses:
408
- file_statuses[filepath] = "M" # uncommitted change = modified
409
- # If already tracked from branch diff, keep the branch-level status
380
+ if is_implementation_file(filepath):
381
+ file_statuses.setdefault(filepath, status)
410
382
 
411
- # Untracked files
412
- untracked = run_git(
413
- ["ls-files", "--others", "--exclude-standard"], cwd=project_root
414
- )
383
+ untracked = run_git(["ls-files", "--others", "--exclude-standard"], cwd=project_root)
415
384
  if untracked:
416
- for filepath in untracked.strip().split("\n"):
385
+ for filepath in untracked.splitlines():
417
386
  filepath = filepath.strip()
418
- if filepath and is_source_file(filepath):
419
- if filepath not in file_statuses:
420
- file_statuses[filepath] = "A" # untracked = added
387
+ if filepath and is_implementation_file(filepath):
388
+ file_statuses.setdefault(filepath, "A")
421
389
 
422
- # Count by status
390
+ test_pattern = re.compile(r"(test|spec|__tests__|\.test\.|\.spec\.)", re.IGNORECASE)
391
+ dirs = set()
423
392
  result = {
424
393
  "files_modified": 0,
425
394
  "files_added": 0,
426
395
  "files_deleted": 0,
427
396
  "test_files_touched": 0,
428
397
  "directories_touched": [],
429
- "has_changes": False,
398
+ "has_changes": bool(file_statuses),
430
399
  }
431
400
 
432
- test_patterns = re.compile(
433
- r"(test|spec|__tests__|\.test\.|\.spec\.)", re.IGNORECASE
434
- )
435
- dirs = set()
436
-
437
401
  for filepath, status in file_statuses.items():
438
402
  if status == "M":
439
403
  result["files_modified"] += 1
@@ -441,8 +405,7 @@ def detect_code_changes(project_root, main_branch="main"):
441
405
  result["files_added"] += 1
442
406
  elif status == "D":
443
407
  result["files_deleted"] += 1
444
-
445
- if test_patterns.search(filepath):
408
+ if test_pattern.search(filepath):
446
409
  result["test_files_touched"] += 1
447
410
  parent = os.path.dirname(filepath)
448
411
  if parent:
@@ -450,28 +413,14 @@ def detect_code_changes(project_root, main_branch="main"):
450
413
  dirs.add(os.sep.join(parts[:2]) + "/")
451
414
 
452
415
  result["directories_touched"] = sorted(dirs)
453
- result["has_changes"] = len(file_statuses) > 0
454
-
455
416
  return result
456
417
 
457
418
 
458
- # ---------------------------------------------------------------------------
459
- # Main
460
- # ---------------------------------------------------------------------------
461
-
462
419
  def main():
463
- parser = argparse.ArgumentParser(
464
- description="Auto-detect interrupted workflow state for recovery"
465
- )
466
- parser.add_argument(
467
- "--project-root",
468
- default=None,
469
- help="Project root directory (default: auto-detect from git)",
470
- )
471
-
420
+ parser = argparse.ArgumentParser(description="Auto-detect interrupted workflow state for recovery")
421
+ parser.add_argument("--project-root", default=None, help="Project root directory (default: auto-detect from git)")
472
422
  args = parser.parse_args()
473
423
 
474
- # Resolve project root
475
424
  if args.project_root:
476
425
  project_root = os.path.abspath(args.project_root)
477
426
  else:
@@ -479,46 +428,34 @@ def main():
479
428
  project_root = git_root if git_root else os.getcwd()
480
429
 
481
430
  main_branch = detect_main_branch(project_root)
482
-
483
- # Step 1: Detect workflow type (also caches branch name)
484
431
  workflow_type, context = detect_workflow_type(project_root)
485
432
 
486
433
  if workflow_type is None:
487
- report = {
434
+ print(json.dumps({
488
435
  "detected": False,
489
- "message": (
490
- "No interrupted workflow detected. "
491
- "Use /feature-workflow, /bug-fix-workflow, or /refactor-workflow to start."
492
- ),
493
- }
494
- print(json.dumps(report, indent=2))
436
+ "message": "No interrupted workflow detected. Use /feature-workflow, /bug-fix-workflow, or /refactor-workflow to start.",
437
+ }, indent=2))
495
438
  sys.exit(0)
496
439
 
497
- # Check for other interrupted workflows (informational)
498
- other_workflows = detect_other_workflows(project_root, workflow_type)
499
-
500
- # Step 2: Collect git state and code changes once (shared across phase inference + report)
501
- cached_branch = context.get("branch")
440
+ cached_branch = context.get("branch") if context else None
502
441
  git_state = detect_git_state(project_root, main_branch, cached_branch=cached_branch)
503
442
  code_changes = detect_code_changes(project_root, main_branch)
443
+ other_workflows = detect_other_workflows(project_root, workflow_type)
504
444
 
505
- # Step 3: Infer phase within the detected workflow
506
445
  if workflow_type == "bug-fix-workflow":
507
- phase, phase_name, artifacts, reason, remaining = \
508
- infer_bugfix_phase(project_root, context.get("bug_id"),
509
- code_changes, git_state["commits_ahead_of_main"])
446
+ phase, phase_name, artifacts, reason, remaining = infer_bugfix_phase(
447
+ project_root, context, code_changes, git_state["commits_ahead_of_main"]
448
+ )
510
449
  elif workflow_type == "feature-workflow":
511
- phase, phase_name, artifacts, reason, remaining = \
512
- infer_feature_phase(project_root)
450
+ phase, phase_name, artifacts, reason, remaining = infer_feature_phase(project_root)
513
451
  elif workflow_type == "refactor-workflow":
514
- phase, phase_name, artifacts, reason, remaining = \
515
- infer_refactor_phase(project_root)
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)
516
455
  else:
517
- # Should never reach here
518
456
  print(json.dumps({"detected": False, "message": "Unknown workflow type"}), file=sys.stderr)
519
457
  sys.exit(1)
520
458
 
521
- # Step 4: Build report
522
459
  report = {
523
460
  "detected": True,
524
461
  "workflow_type": workflow_type,
@@ -533,7 +470,6 @@ def main():
533
470
  "remaining_work": remaining,
534
471
  },
535
472
  }
536
-
537
473
  if other_workflows:
538
474
  report["other_interrupted_workflows"] = other_workflows
539
475