prizmkit 1.1.8 → 1.1.9

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 (123) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/adapters/codebuddy/skill-adapter.js +21 -7
  3. package/bundled/agents/prizm-dev-team-reviewer.md +53 -173
  4. package/bundled/dev-pipeline/.env.example +45 -0
  5. package/bundled/dev-pipeline/SCHEMA_ANALYSIS.md +535 -0
  6. package/bundled/dev-pipeline/assets/feature-list-example.json +0 -1
  7. package/bundled/dev-pipeline/launch-bugfix-daemon.sh +57 -12
  8. package/bundled/dev-pipeline/launch-feature-daemon.sh +3 -1
  9. package/bundled/dev-pipeline/launch-refactor-daemon.sh +57 -12
  10. package/bundled/dev-pipeline/lib/branch.sh +6 -1
  11. package/bundled/dev-pipeline/lib/common.sh +71 -0
  12. package/bundled/dev-pipeline/lib/heartbeat.sh +2 -2
  13. package/bundled/dev-pipeline/retry-bugfix.sh +60 -23
  14. package/bundled/dev-pipeline/retry-feature.sh +47 -12
  15. package/bundled/dev-pipeline/retry-refactor.sh +105 -23
  16. package/bundled/dev-pipeline/run-bugfix.sh +265 -44
  17. package/bundled/dev-pipeline/run-feature.sh +35 -1
  18. package/bundled/dev-pipeline/run-refactor.sh +376 -51
  19. package/bundled/dev-pipeline/scripts/check-session-status.py +24 -1
  20. package/bundled/dev-pipeline/scripts/detect-stuck.py +195 -85
  21. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +31 -19
  22. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +19 -3
  23. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +98 -11
  24. package/bundled/dev-pipeline/scripts/init-bugfix-pipeline.py +30 -5
  25. package/bundled/dev-pipeline/scripts/init-pipeline.py +3 -3
  26. package/bundled/dev-pipeline/scripts/init-refactor-pipeline.py +15 -4
  27. package/bundled/dev-pipeline/scripts/parse-stream-progress.py +1 -5
  28. package/bundled/dev-pipeline/scripts/patch-completion-notes.py +191 -0
  29. package/bundled/dev-pipeline/scripts/update-bug-status.py +159 -14
  30. package/bundled/dev-pipeline/scripts/update-feature-status.py +79 -37
  31. package/bundled/dev-pipeline/scripts/update-refactor-status.py +343 -13
  32. package/bundled/dev-pipeline/templates/agent-prompts/dev-fix.md +1 -1
  33. package/bundled/dev-pipeline/templates/agent-prompts/reviewer-review.md +7 -11
  34. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +41 -7
  35. package/bundled/dev-pipeline/templates/bootstrap-tier1.md +27 -3
  36. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +43 -19
  37. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +54 -26
  38. package/bundled/dev-pipeline/templates/bug-fix-list-schema.json +5 -14
  39. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +36 -25
  40. package/bundled/dev-pipeline/templates/feature-list-schema.json +23 -11
  41. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +270 -0
  42. package/bundled/dev-pipeline/templates/refactor-list-schema.json +10 -2
  43. package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +3 -1
  44. package/bundled/dev-pipeline/templates/sections/critical-paths-agent.md +1 -0
  45. package/bundled/dev-pipeline/templates/sections/feature-context.md +2 -0
  46. package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +29 -2
  47. package/bundled/dev-pipeline/templates/sections/phase-commit.md +22 -0
  48. package/bundled/dev-pipeline/templates/sections/phase-deploy-verification.md +2 -2
  49. package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +8 -6
  50. package/bundled/dev-pipeline/templates/sections/phase-review-full.md +7 -5
  51. package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +3 -3
  52. package/bundled/skills/_metadata.json +5 -22
  53. package/bundled/skills/app-planner/SKILL.md +92 -66
  54. package/bundled/skills/app-planner/assets/app-design-guide.md +1 -1
  55. package/bundled/skills/app-planner/references/architecture-decisions.md +1 -1
  56. package/bundled/skills/app-planner/references/project-brief-guide.md +69 -66
  57. package/bundled/skills/bug-fix-workflow/SKILL.md +47 -4
  58. package/bundled/skills/bug-planner/SKILL.md +130 -188
  59. package/bundled/skills/bug-planner/assets/bug-confirmation-template.md +43 -0
  60. package/bundled/skills/bug-planner/references/critic-and-verification.md +44 -0
  61. package/bundled/skills/bug-planner/references/error-recovery.md +73 -0
  62. package/bundled/skills/bug-planner/references/input-formats.md +53 -0
  63. package/bundled/skills/bug-planner/references/schema-validation.md +25 -0
  64. package/bundled/skills/bug-planner/references/severity-rules.md +16 -0
  65. package/bundled/skills/bug-planner/scripts/validate-bug-list.py +1 -5
  66. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +5 -10
  67. package/bundled/skills/feature-pipeline-launcher/SKILL.md +16 -3
  68. package/bundled/skills/feature-planner/SKILL.md +33 -122
  69. package/bundled/skills/feature-planner/assets/evaluation-guide.md +1 -1
  70. package/bundled/skills/feature-planner/assets/planning-guide.md +21 -5
  71. package/bundled/skills/feature-planner/references/browser-interaction.md +2 -4
  72. package/bundled/skills/feature-planner/references/completeness-review.md +57 -0
  73. package/bundled/skills/feature-planner/references/error-recovery.md +15 -34
  74. package/bundled/skills/feature-planner/references/incremental-feature-planning.md +1 -1
  75. package/bundled/skills/feature-planner/references/new-project-planning.md +2 -2
  76. package/bundled/skills/feature-planner/scripts/validate-and-generate.py +1 -2
  77. package/bundled/skills/feature-workflow/SKILL.md +3 -4
  78. package/bundled/skills/prizm-kit/SKILL.md +39 -49
  79. package/bundled/skills/prizmkit-code-review/SKILL.md +51 -64
  80. package/bundled/skills/prizmkit-code-review/rules/dimensions.md +85 -0
  81. package/bundled/skills/prizmkit-code-review/rules/fix-strategy.md +11 -11
  82. package/bundled/skills/prizmkit-committer/SKILL.md +3 -31
  83. package/bundled/skills/prizmkit-deploy/SKILL.md +34 -31
  84. package/bundled/skills/prizmkit-deploy/assets/deploy-template.md +1 -1
  85. package/bundled/skills/prizmkit-implement/SKILL.md +35 -68
  86. package/bundled/skills/prizmkit-init/SKILL.md +112 -65
  87. package/bundled/skills/prizmkit-init/assets/project-brief-template.md +82 -0
  88. package/bundled/skills/prizmkit-plan/SKILL.md +120 -79
  89. package/bundled/skills/prizmkit-plan/assets/plan-template.md +28 -18
  90. package/bundled/skills/prizmkit-plan/assets/spec-template.md +28 -11
  91. package/bundled/skills/prizmkit-plan/references/clarify-guide.md +3 -3
  92. package/bundled/skills/prizmkit-plan/references/verification-checklist.md +60 -0
  93. package/bundled/skills/prizmkit-prizm-docs/SKILL.md +10 -81
  94. package/bundled/skills/prizmkit-prizm-docs/assets/{PRIZM-SPEC.md → prizm-docs-format.md} +41 -526
  95. package/bundled/skills/prizmkit-prizm-docs/references/op-init.md +46 -0
  96. package/bundled/skills/prizmkit-prizm-docs/references/op-rebuild.md +16 -0
  97. package/bundled/skills/prizmkit-prizm-docs/references/op-status.md +14 -0
  98. package/bundled/skills/prizmkit-prizm-docs/references/op-update.md +19 -0
  99. package/bundled/skills/prizmkit-prizm-docs/references/op-validate.md +17 -0
  100. package/bundled/skills/prizmkit-retrospective/SKILL.md +27 -65
  101. package/bundled/skills/prizmkit-retrospective/references/knowledge-injection-steps.md +3 -4
  102. package/bundled/skills/prizmkit-retrospective/references/structural-sync-steps.md +7 -25
  103. package/bundled/skills/recovery-workflow/SKILL.md +8 -8
  104. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +17 -9
  105. package/bundled/skills/refactor-planner/SKILL.md +23 -41
  106. package/bundled/skills/refactor-workflow/SKILL.md +1 -2
  107. package/bundled/team/prizm-dev-team.json +1 -1
  108. package/bundled/{skills/prizm-kit/assets → templates}/project-memory-template.md +1 -1
  109. package/package.json +1 -1
  110. package/src/clean.js +0 -1
  111. package/src/gitignore-template.js +0 -1
  112. package/src/scaffold.js +10 -3
  113. package/bundled/dev-pipeline/templates/agent-prompts/reviewer-analyze.md +0 -5
  114. package/bundled/dev-pipeline/templates/sections/phase-analyze-agent.md +0 -19
  115. package/bundled/dev-pipeline/templates/sections/phase-analyze-full.md +0 -19
  116. package/bundled/skills/app-planner/references/project-conventions.md +0 -93
  117. package/bundled/skills/prizmkit-analyze/SKILL.md +0 -207
  118. package/bundled/skills/prizmkit-code-review/rules/dimensions-bugfix.md +0 -25
  119. package/bundled/skills/prizmkit-code-review/rules/dimensions-feature.md +0 -43
  120. package/bundled/skills/prizmkit-code-review/rules/dimensions-refactor.md +0 -25
  121. package/bundled/skills/prizmkit-implement/references/deploy-guide-protocol.md +0 -69
  122. package/bundled/skills/prizmkit-verify/SKILL.md +0 -281
  123. package/bundled/skills/prizmkit-verify/scripts/verify-light.py +0 -402
@@ -16,7 +16,9 @@ import sys
16
16
  from utils import setup_logging
17
17
 
18
18
 
19
- REQUIRED_FIELDS = ["session_id", "feature_id", "status", "timestamp"]
19
+ REQUIRED_FIELDS = ["session_id", "status", "timestamp"]
20
+ # At least one of these ID fields must be present (depends on pipeline type)
21
+ ID_FIELDS = ["feature_id", "bug_id", "refactor_id"]
20
22
 
21
23
  LOGGER = setup_logging("check-session-status")
22
24
 
@@ -52,6 +54,8 @@ def validate_required_fields(data):
52
54
  """Check that all required fields are present and non-empty.
53
55
 
54
56
  Returns a list of missing/invalid field names.
57
+ In addition to the always-required fields, at least one of
58
+ feature_id / bug_id / refactor_id must be present.
55
59
  """
56
60
  missing = []
57
61
  for field in REQUIRED_FIELDS:
@@ -59,6 +63,15 @@ def validate_required_fields(data):
59
63
  missing.append(field)
60
64
  elif not isinstance(data[field], str) or not data[field].strip():
61
65
  missing.append(field)
66
+
67
+ # Check that at least one ID field is present and non-empty
68
+ has_id = any(
69
+ field in data and isinstance(data[field], str) and data[field].strip()
70
+ for field in ID_FIELDS
71
+ )
72
+ if not has_id:
73
+ missing.append("feature_id|bug_id|refactor_id")
74
+
62
75
  return missing
63
76
 
64
77
 
@@ -78,8 +91,16 @@ def determine_status(data):
78
91
  return "partial_resumable"
79
92
  else:
80
93
  return "partial_not_resumable"
94
+ elif status == "partial_resumable":
95
+ return "partial_resumable"
96
+ elif status == "partial_not_resumable":
97
+ return "partial_not_resumable"
81
98
  elif status == "failed":
82
99
  return "failed"
100
+ elif status == "crashed":
101
+ return "crashed"
102
+ elif status == "timed_out":
103
+ return "timed_out"
83
104
  elif status == "commit_missing":
84
105
  return "commit_missing"
85
106
  elif status == "docs_missing":
@@ -99,6 +120,8 @@ def build_detail_report(data, resolved_status):
99
120
  return {
100
121
  "status": resolved_status,
101
122
  "feature_id": data.get("feature_id"),
123
+ "bug_id": data.get("bug_id"),
124
+ "refactor_id": data.get("refactor_id"),
102
125
  "completed_phases": data.get("completed_phases", []),
103
126
  "checkpoint_reached": data.get("checkpoint_reached"),
104
127
  "tasks_completed": data.get("tasks_completed", 0),
@@ -1,18 +1,22 @@
1
1
  #!/usr/bin/env python3
2
- """Detect stuck features in the dev-pipeline.
2
+ """Detect stuck items in the dev-pipeline (features, bugs, or refactors).
3
3
 
4
- Checks each feature for conditions that indicate it is stuck:
4
+ Checks each item for conditions that indicate it is stuck:
5
5
  1. Max retries exceeded
6
6
  2. Same checkpoint for consecutive sessions
7
- 3. Stale or missing heartbeat (for in_progress features)
8
- 4. Dependency deadlock (depends on a failed feature)
7
+ 3. Stale or missing heartbeat (for in_progress items)
8
+ 4. Dependency deadlock (depends on a failed item)
9
9
 
10
10
  Outputs a JSON report to stdout and exits with code 1 if any stuck
11
- features are found, 0 otherwise.
11
+ items are found, 0 otherwise.
12
12
 
13
13
  Usage:
14
- python3 detect-stuck.py --state-dir <path> [--feature-id <id>]
14
+ python3 detect-stuck.py --state-dir <path> --pipeline-type feature [--item-id <id>]
15
15
  [--max-retries <n>] [--stale-threshold <seconds>]
16
+ [--task-list <path>]
17
+
18
+ # Legacy feature-only args still supported:
19
+ python3 detect-stuck.py --state-dir <path> [--feature-id <id>]
16
20
  [--feature-list <path>]
17
21
  """
18
22
 
@@ -30,23 +34,45 @@ LOGGER = setup_logging("detect-stuck")
30
34
 
31
35
  def parse_args():
32
36
  parser = argparse.ArgumentParser(
33
- description="Detect stuck features in the dev-pipeline."
37
+ description="Detect stuck items in the dev-pipeline."
34
38
  )
35
39
  parser.add_argument(
36
40
  "--state-dir",
37
41
  required=True,
38
- help="Path to the state directory (default: .prizmkit/state/features)",
42
+ help="Path to the state directory (e.g. .prizmkit/state/features)",
43
+ )
44
+ parser.add_argument(
45
+ "--pipeline-type",
46
+ choices=["feature", "bugfix", "refactor"],
47
+ default=None,
48
+ help="Pipeline type (auto-detected from --feature-id/--bug-id/--refactor-id if omitted)",
49
+ )
50
+ parser.add_argument(
51
+ "--item-id",
52
+ default=None,
53
+ help="Check a specific item ID, or check all if omitted",
39
54
  )
55
+ # Legacy feature-only args (still supported for backward compat)
40
56
  parser.add_argument(
41
57
  "--feature-id",
42
58
  default=None,
43
- help="Check a specific feature ID, or check all if omitted",
59
+ help="(Legacy) Feature ID equivalent to --pipeline-type feature --item-id <id>",
60
+ )
61
+ parser.add_argument(
62
+ "--bug-id",
63
+ default=None,
64
+ help="Bug ID — equivalent to --pipeline-type bugfix --item-id <id>",
65
+ )
66
+ parser.add_argument(
67
+ "--refactor-id",
68
+ default=None,
69
+ help="Refactor ID — equivalent to --pipeline-type refactor --item-id <id>",
44
70
  )
45
71
  parser.add_argument(
46
72
  "--max-retries",
47
73
  type=int,
48
74
  default=3,
49
- help="Maximum allowed retries before a feature is considered stuck (default: 3)",
75
+ help="Maximum allowed retries before an item is considered stuck (default: 3)",
50
76
  )
51
77
  parser.add_argument(
52
78
  "--stale-threshold",
@@ -57,7 +83,22 @@ def parse_args():
57
83
  parser.add_argument(
58
84
  "--feature-list",
59
85
  default=None,
60
- help="Path to .prizmkit/plans/feature-list.json (overrides pipeline.json reference)",
86
+ help="(Legacy) Path to feature-list.json use --task-list instead",
87
+ )
88
+ parser.add_argument(
89
+ "--bug-list",
90
+ default=None,
91
+ help="Path to bug-fix-list.json",
92
+ )
93
+ parser.add_argument(
94
+ "--refactor-list",
95
+ default=None,
96
+ help="Path to refactor-list.json",
97
+ )
98
+ parser.add_argument(
99
+ "--task-list",
100
+ default=None,
101
+ help="Path to the task list JSON (feature-list, bug-fix-list, or refactor-list)",
61
102
  )
62
103
  return parser.parse_args()
63
104
 
@@ -71,25 +112,25 @@ def load_json(path):
71
112
  return None
72
113
 
73
114
 
74
- def discover_feature_ids(state_dir):
75
- """Return a sorted list of feature IDs found in state/features/."""
76
- features_dir = os.path.join(state_dir, "features")
77
- if not os.path.isdir(features_dir):
115
+ def discover_item_ids(state_dir, subdir):
116
+ """Return a sorted list of item IDs found in state/{subdir}/."""
117
+ items_dir = os.path.join(state_dir, subdir)
118
+ if not os.path.isdir(items_dir):
78
119
  return []
79
120
  ids = []
80
- for name in os.listdir(features_dir):
81
- feature_path = os.path.join(features_dir, name)
82
- if os.path.isdir(feature_path):
121
+ for name in os.listdir(items_dir):
122
+ item_path = os.path.join(items_dir, name)
123
+ if os.path.isdir(item_path):
83
124
  ids.append(name)
84
125
  return sorted(ids)
85
126
 
86
127
 
87
- def get_session_statuses(feature_dir):
88
- """Return session-status.json data for all sessions of a feature, sorted by session ID.
128
+ def get_session_statuses(item_dir):
129
+ """Return session-status.json data for all sessions of an item, sorted by session ID.
89
130
 
90
131
  Returns a list of (session_id, data) tuples.
91
132
  """
92
- sessions_dir = os.path.join(feature_dir, "sessions")
133
+ sessions_dir = os.path.join(item_dir, "sessions")
93
134
  if not os.path.isdir(sessions_dir):
94
135
  return []
95
136
  results = []
@@ -138,12 +179,12 @@ def parse_iso_timestamp(ts_str):
138
179
  return None
139
180
 
140
181
 
141
- def check_max_retries(feature_status, max_retries):
142
- """Check 1: Has the feature exceeded the maximum retry count?
182
+ def check_max_retries(item_status, max_retries):
183
+ """Check 1: Has the item exceeded the maximum retry count?
143
184
 
144
185
  Returns a stuck-report dict or None.
145
186
  """
146
- retry_count = feature_status.get("retry_count", 0)
187
+ retry_count = item_status.get("retry_count", 0)
147
188
  if not isinstance(retry_count, int):
148
189
  return None
149
190
  if retry_count >= max_retries:
@@ -152,17 +193,17 @@ def check_max_retries(feature_status, max_retries):
152
193
  "details": "Retry count {} has reached or exceeded max retries {}".format(
153
194
  retry_count, max_retries
154
195
  ),
155
- "suggestion": "Investigate recurring failures and consider resetting the feature or adjusting the approach",
196
+ "suggestion": "Investigate recurring failures and consider resetting the item or adjusting the approach",
156
197
  }
157
198
  return None
158
199
 
159
200
 
160
- def check_stuck_checkpoint(feature_dir):
161
- """Check 2: Is the feature stuck at the same checkpoint for 3 consecutive sessions?
201
+ def check_stuck_checkpoint(item_dir):
202
+ """Check 2: Is the item stuck at the same checkpoint for 3 consecutive sessions?
162
203
 
163
204
  Returns a stuck-report dict or None.
164
205
  """
165
- session_statuses = get_session_statuses(feature_dir)
206
+ session_statuses = get_session_statuses(item_dir)
166
207
  if len(session_statuses) < 3:
167
208
  return None
168
209
 
@@ -185,34 +226,36 @@ def check_stuck_checkpoint(feature_dir):
185
226
  return None
186
227
 
187
228
 
188
- def check_stale_heartbeat(feature_id, feature_status, state_dir, stale_threshold):
189
- """Check 3: Is the heartbeat stale or missing for an in_progress feature?
229
+ def check_stale_heartbeat(item_id, item_status, state_dir, items_subdir, stale_threshold):
230
+ """Check 3: Is the heartbeat stale or missing for an in_progress item?
190
231
 
191
- Only applies to features whose status is 'in_progress'.
192
- Uses last_session_id from the feature's own status to find the active session.
232
+ Only applies to items whose status indicates active work.
233
+ Uses last_session_id from the item's own status to find the active session.
193
234
 
194
235
  Returns a stuck-report dict or None.
195
236
  """
196
- status = feature_status.get("status")
197
- if status != "in_progress":
237
+ status = item_status.get("status")
238
+ # All pipelines now use "in_progress" as the active status
239
+ in_progress_statuses = {"in_progress"}
240
+ if status not in in_progress_statuses:
198
241
  return None
199
242
 
200
- # Use last_session_id from the feature's own status
201
- session_id = feature_status.get("last_session_id")
243
+ # Use last_session_id from the item's own status
244
+ session_id = item_status.get("last_session_id")
202
245
  if not session_id:
203
246
  return None
204
247
 
205
248
  # Check heartbeat file
206
249
  heartbeat_path = os.path.join(
207
- state_dir, "features", feature_id, "sessions", session_id, "heartbeat.json"
250
+ state_dir, items_subdir, item_id, "sessions", session_id, "heartbeat.json"
208
251
  )
209
252
  heartbeat = load_json(heartbeat_path)
210
253
 
211
254
  if heartbeat is None:
212
255
  return {
213
256
  "reason": "no_heartbeat",
214
- "details": "Feature is in_progress but no heartbeat.json found for session {}".format(
215
- session_id
257
+ "details": "Item is {} but no heartbeat.json found for session {}".format(
258
+ status, session_id
216
259
  ),
217
260
  "suggestion": "The agent session may have crashed without writing a heartbeat - check session logs",
218
261
  }
@@ -241,25 +284,25 @@ def check_stale_heartbeat(feature_id, feature_status, state_dir, stale_threshold
241
284
  return None
242
285
 
243
286
 
244
- def check_dependency_deadlock(feature_id, feature_list_data, state_dir):
245
- """Check 4: Does this feature depend on a failed feature?
287
+ def check_dependency_deadlock(item_id, task_list_data, state_dir, items_subdir, items_key):
288
+ """Check 4: Does this item depend on a failed item?
246
289
 
247
290
  Returns a stuck-report dict or None.
248
291
  """
249
- if feature_list_data is None:
292
+ if task_list_data is None:
250
293
  return None
251
294
 
252
- features = feature_list_data.get("features", [])
253
- if not isinstance(features, list):
295
+ items = task_list_data.get(items_key, [])
296
+ if not isinstance(items, list):
254
297
  return None
255
298
 
256
- # Find this feature in the feature list to get its dependencies
299
+ # Find this item in the list to get its dependencies
257
300
  deps = None
258
- for f in features:
259
- if not isinstance(f, dict):
301
+ for item in items:
302
+ if not isinstance(item, dict):
260
303
  continue
261
- if f.get("id") == feature_id:
262
- deps = f.get("dependencies", [])
304
+ if item.get("id") == item_id:
305
+ deps = item.get("dependencies", [])
263
306
  break
264
307
 
265
308
  if not deps or not isinstance(deps, list):
@@ -268,7 +311,7 @@ def check_dependency_deadlock(feature_id, feature_list_data, state_dir):
268
311
  # Check each dependency's status in state
269
312
  for dep_id in deps:
270
313
  dep_status_path = os.path.join(
271
- state_dir, "features", dep_id, "status.json"
314
+ state_dir, items_subdir, dep_id, "status.json"
272
315
  )
273
316
  dep_status = load_json(dep_status_path)
274
317
  if dep_status is None:
@@ -278,16 +321,16 @@ def check_dependency_deadlock(feature_id, feature_list_data, state_dir):
278
321
  return {
279
322
  "reason": "dependency_failed",
280
323
  "details": "Depends on {} which has status 'failed'".format(dep_id),
281
- "suggestion": "Fix or skip {} to unblock {}".format(dep_id, feature_id),
324
+ "suggestion": "Fix or skip {} to unblock {}".format(dep_id, item_id),
282
325
  }
283
326
 
284
327
  return None
285
328
 
286
329
 
287
- def find_feature_list(state_dir):
288
- """Attempt to locate and load .prizmkit/plans/feature-list.json via pipeline.json reference.
330
+ def find_task_list(state_dir):
331
+ """Attempt to locate and load the task list JSON via pipeline.json reference.
289
332
 
290
- Resolves feature_list_path relative to state_dir when it is a relative path,
333
+ Resolves the list path relative to state_dir when it is a relative path,
291
334
  so that pipeline.json is portable across machines and directory structures.
292
335
  """
293
336
  pipeline_path = os.path.join(state_dir, "pipeline.json")
@@ -295,7 +338,12 @@ def find_feature_list(state_dir):
295
338
  if pipeline is None:
296
339
  return None
297
340
 
298
- fl_path = pipeline.get("feature_list_path")
341
+ # Try various path keys used by different pipeline types
342
+ fl_path = (
343
+ pipeline.get("feature_list_path")
344
+ or pipeline.get("bug_list_path")
345
+ or pipeline.get("refactor_list_path")
346
+ )
299
347
  if not fl_path:
300
348
  return None
301
349
 
@@ -310,44 +358,98 @@ def find_feature_list(state_dir):
310
358
  return None
311
359
 
312
360
 
313
- def check_feature(feature_id, state_dir, feature_list_data, max_retries, stale_threshold):
314
- """Run all stuck-detection checks on a single feature.
361
+ # Pipeline type configurations
362
+ PIPELINE_CONFIG = {
363
+ "feature": {"subdir": "features", "items_key": "features", "id_label": "feature_id"},
364
+ "bugfix": {"subdir": "bugs", "items_key": "bugs", "id_label": "bug_id"},
365
+ "refactor": {"subdir": "refactors", "items_key": "refactors", "id_label": "refactor_id"},
366
+ }
367
+
315
368
 
316
- Returns a list of stuck-report dicts (may be empty if feature is not stuck).
369
+ def check_item(item_id, state_dir, items_subdir, items_key, task_list_data, max_retries, stale_threshold):
370
+ """Run all stuck-detection checks on a single item.
371
+
372
+ Returns a list of stuck-report dicts (may be empty if item is not stuck).
317
373
  """
318
- feature_dir = os.path.join(state_dir, "features", feature_id)
319
- status_path = os.path.join(feature_dir, "status.json")
320
- feature_status = load_json(status_path)
374
+ item_dir = os.path.join(state_dir, items_subdir, item_id)
375
+ status_path = os.path.join(item_dir, "status.json")
376
+ item_status = load_json(status_path)
321
377
 
322
- if feature_status is None:
378
+ if item_status is None:
323
379
  # Cannot read status — skip silently
324
380
  return []
325
381
 
326
382
  reports = []
327
383
 
328
384
  # Check 1: Max retries exceeded
329
- result = check_max_retries(feature_status, max_retries)
385
+ result = check_max_retries(item_status, max_retries)
330
386
  if result is not None:
331
387
  reports.append(result)
332
388
 
333
389
  # Check 2: Stuck at same checkpoint
334
- result = check_stuck_checkpoint(feature_dir)
390
+ result = check_stuck_checkpoint(item_dir)
335
391
  if result is not None:
336
392
  reports.append(result)
337
393
 
338
394
  # Check 3: Stale heartbeat
339
- result = check_stale_heartbeat(feature_id, feature_status, state_dir, stale_threshold)
395
+ result = check_stale_heartbeat(item_id, item_status, state_dir, items_subdir, stale_threshold)
340
396
  if result is not None:
341
397
  reports.append(result)
342
398
 
343
399
  # Check 4: Dependency deadlock
344
- result = check_dependency_deadlock(feature_id, feature_list_data, state_dir)
400
+ result = check_dependency_deadlock(item_id, task_list_data, state_dir, items_subdir, items_key)
345
401
  if result is not None:
346
402
  reports.append(result)
347
403
 
348
404
  return reports
349
405
 
350
406
 
407
+ def resolve_pipeline_type(args):
408
+ """Resolve pipeline type, item ID, and task list path from args.
409
+
410
+ Supports both new generic args and legacy feature-only args.
411
+ Returns (pipeline_type, item_id, task_list_path).
412
+ """
413
+ # Explicit --pipeline-type takes precedence
414
+ if args.pipeline_type:
415
+ ptype = args.pipeline_type
416
+ item_id = args.item_id
417
+ task_list = args.task_list
418
+ # Legacy / shorthand: --feature-id, --bug-id, --refactor-id
419
+ elif args.feature_id:
420
+ ptype = "feature"
421
+ item_id = args.feature_id
422
+ task_list = args.feature_list or args.task_list
423
+ elif args.bug_id:
424
+ ptype = "bugfix"
425
+ item_id = args.bug_id
426
+ task_list = args.bug_list or args.task_list
427
+ elif args.refactor_id:
428
+ ptype = "refactor"
429
+ item_id = args.refactor_id
430
+ task_list = args.refactor_list or args.task_list
431
+ # Legacy: --feature-list without --feature-id means check all features
432
+ elif args.feature_list:
433
+ ptype = "feature"
434
+ item_id = None
435
+ task_list = args.feature_list
436
+ elif args.bug_list:
437
+ ptype = "bugfix"
438
+ item_id = None
439
+ task_list = args.bug_list
440
+ elif args.refactor_list:
441
+ ptype = "refactor"
442
+ item_id = None
443
+ task_list = args.refactor_list
444
+ else:
445
+ # Default to feature for backward compat
446
+ ptype = "feature"
447
+ item_id = None
448
+ task_list = args.task_list
449
+
450
+ return ptype, item_id, task_list
451
+
452
+
351
453
  def main():
352
454
  args = parse_args()
353
455
  state_dir = os.path.abspath(args.state_dir)
@@ -355,28 +457,35 @@ def main():
355
457
  if not os.path.isdir(state_dir):
356
458
  error_out("State directory not found: {}".format(state_dir), code=2)
357
459
 
358
- # Determine which features to check
359
- if args.feature_id:
360
- feature_ids = [args.feature_id]
460
+ # Resolve pipeline type and parameters
461
+ ptype, item_id, task_list_path = resolve_pipeline_type(args)
462
+ config = PIPELINE_CONFIG[ptype]
463
+ items_subdir = config["subdir"]
464
+ items_key = config["items_key"]
465
+ id_label = config["id_label"]
466
+
467
+ # Determine which items to check
468
+ if item_id:
469
+ item_ids = [item_id]
361
470
  else:
362
- feature_ids = discover_feature_ids(state_dir)
471
+ item_ids = discover_item_ids(state_dir, items_subdir)
363
472
 
364
- # Load feature list for dependency checks
365
- # Prefer CLI-provided path; fall back to pipeline.json reference
366
- if args.feature_list:
367
- feature_list_data = load_json(os.path.abspath(args.feature_list))
473
+ # Load task list for dependency checks
474
+ if task_list_path:
475
+ task_list_data = load_json(os.path.abspath(task_list_path))
368
476
  else:
369
- feature_list_data = find_feature_list(state_dir)
477
+ task_list_data = find_task_list(state_dir)
370
478
 
371
- stuck_features = []
372
- for fid in feature_ids:
373
- reports = check_feature(
374
- fid, state_dir, feature_list_data, args.max_retries, args.stale_threshold
479
+ stuck_items = []
480
+ for iid in item_ids:
481
+ reports = check_item(
482
+ iid, state_dir, items_subdir, items_key,
483
+ task_list_data, args.max_retries, args.stale_threshold
375
484
  )
376
485
  for report in reports:
377
- stuck_features.append(
486
+ stuck_items.append(
378
487
  {
379
- "feature_id": fid,
488
+ id_label: iid,
380
489
  "reason": report["reason"],
381
490
  "details": report["details"],
382
491
  "suggestion": report["suggestion"],
@@ -384,14 +493,15 @@ def main():
384
493
  )
385
494
 
386
495
  output = {
387
- "stuck_features": stuck_features,
388
- "total_checked": len(feature_ids),
389
- "stuck_count": len(stuck_features),
496
+ "pipeline_type": ptype,
497
+ "stuck_items": stuck_items,
498
+ "total_checked": len(item_ids),
499
+ "stuck_count": len(stuck_items),
390
500
  }
391
501
 
392
502
  print(json.dumps(output, indent=2, ensure_ascii=False))
393
503
 
394
- if stuck_features:
504
+ if stuck_items:
395
505
  sys.exit(1)
396
506
  else:
397
507
  sys.exit(0)
@@ -274,7 +274,12 @@ def format_global_context(global_context, project_root=None):
274
274
 
275
275
 
276
276
  def get_completed_dependencies(features, feature):
277
- """Look up dependency features and list those with status=completed."""
277
+ """Look up dependency features and list those with status=completed.
278
+
279
+ When a completed dependency has completion_notes (written by the AI
280
+ session and propagated by the pipeline runner), include them as rich
281
+ context so the downstream session knows what was built.
282
+ """
278
283
  deps = feature.get("dependencies", [])
279
284
  if not deps:
280
285
  return "- (no dependencies)"
@@ -285,17 +290,26 @@ def get_completed_dependencies(features, feature):
285
290
  if isinstance(f, dict) and "id" in f:
286
291
  feature_map[f["id"]] = f
287
292
 
288
- completed = []
293
+ sections = []
289
294
  for dep_id in deps:
290
295
  dep = feature_map.get(dep_id)
291
296
  if dep and dep.get("status") == "completed":
292
- completed.append("- {} - {} (completed)".format(
297
+ header = "- **{}** {} (completed)".format(
293
298
  dep_id, dep.get("title", "Untitled")
294
- ))
299
+ )
300
+ notes = dep.get("completion_notes", [])
301
+ if notes and isinstance(notes, list):
302
+ note_lines = "\n".join(
303
+ " - {}".format(n) for n in notes
304
+ if isinstance(n, str) and n.strip()
305
+ )
306
+ if note_lines:
307
+ header += "\n" + note_lines
308
+ sections.append(header)
295
309
 
296
- if not completed:
310
+ if not sections:
297
311
  return "- (no completed dependencies yet)"
298
- return "\n".join(completed)
312
+ return "\n".join(sections)
299
313
 
300
314
 
301
315
  def get_prev_session_status(state_dir, feature_id):
@@ -561,7 +575,6 @@ SECTION_TO_SKILL = {
561
575
  ".prizmkit/specs/{slug}/plan.md"]),
562
576
  "phase-plan": ("prizmkit-plan", "Plan & Tasks",
563
577
  [".prizmkit/specs/{slug}/plan.md"]),
564
- "phase-analyze": ("prizmkit-analyze", "Analyze", []),
565
578
  "phase-critic-plan": ("critic-plan-review", "Critic: Plan Review", []),
566
579
  "phase-implement": ("prizmkit-implement", "Implement + Test", []),
567
580
  "phase-critic-code": ("critic-code-review", "Critic: Code Review", []),
@@ -957,16 +970,6 @@ def assemble_sections(pipeline_mode, sections_dir, init_done, is_resume,
957
970
  load_section(sections_dir,
958
971
  "phase-plan-agent.md")))
959
972
 
960
- # --- Analyze (only for agent tiers) ---
961
- if pipeline_mode == "standard":
962
- sections.append(("phase-analyze",
963
- load_section(sections_dir,
964
- "phase-analyze-agent.md")))
965
- elif pipeline_mode == "full":
966
- sections.append(("phase-analyze",
967
- load_section(sections_dir,
968
- "phase-analyze-full.md")))
969
-
970
973
  # --- Critic: Plan Challenge (only if critic enabled) ---
971
974
  if critic_enabled:
972
975
  if pipeline_mode == "full":
@@ -992,12 +995,23 @@ def assemble_sections(pipeline_mode, sections_dir, init_done, is_resume,
992
995
  load_section(sections_dir,
993
996
  "phase-implement-agent.md")))
994
997
 
998
+ # --- Test Failure Recovery Protocol (all tiers) ---
999
+ sections.append(("test-failure-recovery",
1000
+ load_section(sections_dir, "test-failure-recovery.md")))
1001
+
995
1002
  # --- Critic: Code Challenge (only if critic enabled, agent tiers) ---
996
1003
  if critic_enabled and pipeline_mode in ("standard", "full"):
997
1004
  sections.append(("phase-critic-code",
998
1005
  load_section(sections_dir,
999
1006
  "phase-critic-code.md")))
1000
1007
 
1008
+ # --- AC Verification Checklist (all tiers) ---
1009
+ ac_checklist_path = os.path.join(sections_dir, "ac-verification-checklist.md")
1010
+ if os.path.isfile(ac_checklist_path):
1011
+ sections.append(("ac-verification-checklist",
1012
+ load_section(sections_dir,
1013
+ "ac-verification-checklist.md")))
1014
+
1001
1015
  # --- Review (only for agent tiers) ---
1002
1016
  if pipeline_mode == "full":
1003
1017
  sections.append(("phase-review",
@@ -1321,7 +1335,6 @@ def build_replacements(args, feature, features, global_context, script_dir):
1321
1335
  "{{DEV_SUBAGENT_PATH}}": dev_subagent,
1322
1336
  "{{REVIEWER_SUBAGENT_PATH}}": reviewer_subagent,
1323
1337
  "{{CRITIC_SUBAGENT_PATH}}": critic_subagent,
1324
- "{{VALIDATOR_SCRIPTS_DIR}}": validator_scripts_dir,
1325
1338
  "{{INIT_SCRIPT_PATH}}": init_script_path,
1326
1339
  "{{SESSION_STATUS_PATH}}": session_status_abs,
1327
1340
  "{{PROJECT_ROOT}}": project_root,
@@ -1336,7 +1349,6 @@ def build_replacements(args, feature, features, global_context, script_dir):
1336
1349
  "{{INIT_DONE}}": "true" if init_done else "false",
1337
1350
  "{{HAS_SPEC}}": "true" if artifacts["has_spec"] else "false",
1338
1351
  "{{HAS_PLAN}}": "true" if artifacts["has_plan"] else "false",
1339
- "{{ARTIFACTS_COMPLETE}}": "true" if artifacts["all_complete"] else "false",
1340
1352
  "{{BROWSER_URL}}": browser_url,
1341
1353
  "{{BROWSER_SETUP_COMMAND}}": browser_setup_command,
1342
1354
  "{{BROWSER_VERIFY_STEPS}}": browser_verify_steps,