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.
- package/bundled/VERSION.json +3 -3
- package/bundled/adapters/codebuddy/skill-adapter.js +21 -7
- package/bundled/agents/prizm-dev-team-reviewer.md +53 -173
- package/bundled/dev-pipeline/.env.example +45 -0
- package/bundled/dev-pipeline/SCHEMA_ANALYSIS.md +535 -0
- package/bundled/dev-pipeline/assets/feature-list-example.json +0 -1
- package/bundled/dev-pipeline/launch-bugfix-daemon.sh +57 -12
- package/bundled/dev-pipeline/launch-feature-daemon.sh +3 -1
- package/bundled/dev-pipeline/launch-refactor-daemon.sh +57 -12
- package/bundled/dev-pipeline/lib/branch.sh +6 -1
- package/bundled/dev-pipeline/lib/common.sh +71 -0
- package/bundled/dev-pipeline/lib/heartbeat.sh +2 -2
- package/bundled/dev-pipeline/retry-bugfix.sh +60 -23
- package/bundled/dev-pipeline/retry-feature.sh +47 -12
- package/bundled/dev-pipeline/retry-refactor.sh +105 -23
- package/bundled/dev-pipeline/run-bugfix.sh +265 -44
- package/bundled/dev-pipeline/run-feature.sh +35 -1
- package/bundled/dev-pipeline/run-refactor.sh +376 -51
- package/bundled/dev-pipeline/scripts/check-session-status.py +24 -1
- package/bundled/dev-pipeline/scripts/detect-stuck.py +195 -85
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +31 -19
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +19 -3
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +98 -11
- package/bundled/dev-pipeline/scripts/init-bugfix-pipeline.py +30 -5
- package/bundled/dev-pipeline/scripts/init-pipeline.py +3 -3
- package/bundled/dev-pipeline/scripts/init-refactor-pipeline.py +15 -4
- package/bundled/dev-pipeline/scripts/parse-stream-progress.py +1 -5
- package/bundled/dev-pipeline/scripts/patch-completion-notes.py +191 -0
- package/bundled/dev-pipeline/scripts/update-bug-status.py +159 -14
- package/bundled/dev-pipeline/scripts/update-feature-status.py +79 -37
- package/bundled/dev-pipeline/scripts/update-refactor-status.py +343 -13
- package/bundled/dev-pipeline/templates/agent-prompts/dev-fix.md +1 -1
- package/bundled/dev-pipeline/templates/agent-prompts/reviewer-review.md +7 -11
- package/bundled/dev-pipeline/templates/bootstrap-prompt.md +41 -7
- package/bundled/dev-pipeline/templates/bootstrap-tier1.md +27 -3
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +43 -19
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +54 -26
- package/bundled/dev-pipeline/templates/bug-fix-list-schema.json +5 -14
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +36 -25
- package/bundled/dev-pipeline/templates/feature-list-schema.json +23 -11
- package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +270 -0
- package/bundled/dev-pipeline/templates/refactor-list-schema.json +10 -2
- package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +3 -1
- package/bundled/dev-pipeline/templates/sections/critical-paths-agent.md +1 -0
- package/bundled/dev-pipeline/templates/sections/feature-context.md +2 -0
- package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +29 -2
- package/bundled/dev-pipeline/templates/sections/phase-commit.md +22 -0
- package/bundled/dev-pipeline/templates/sections/phase-deploy-verification.md +2 -2
- package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +8 -6
- package/bundled/dev-pipeline/templates/sections/phase-review-full.md +7 -5
- package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +3 -3
- package/bundled/skills/_metadata.json +5 -22
- package/bundled/skills/app-planner/SKILL.md +92 -66
- package/bundled/skills/app-planner/assets/app-design-guide.md +1 -1
- package/bundled/skills/app-planner/references/architecture-decisions.md +1 -1
- package/bundled/skills/app-planner/references/project-brief-guide.md +69 -66
- package/bundled/skills/bug-fix-workflow/SKILL.md +47 -4
- package/bundled/skills/bug-planner/SKILL.md +130 -188
- package/bundled/skills/bug-planner/assets/bug-confirmation-template.md +43 -0
- package/bundled/skills/bug-planner/references/critic-and-verification.md +44 -0
- package/bundled/skills/bug-planner/references/error-recovery.md +73 -0
- package/bundled/skills/bug-planner/references/input-formats.md +53 -0
- package/bundled/skills/bug-planner/references/schema-validation.md +25 -0
- package/bundled/skills/bug-planner/references/severity-rules.md +16 -0
- package/bundled/skills/bug-planner/scripts/validate-bug-list.py +1 -5
- package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +5 -10
- package/bundled/skills/feature-pipeline-launcher/SKILL.md +16 -3
- package/bundled/skills/feature-planner/SKILL.md +33 -122
- package/bundled/skills/feature-planner/assets/evaluation-guide.md +1 -1
- package/bundled/skills/feature-planner/assets/planning-guide.md +21 -5
- package/bundled/skills/feature-planner/references/browser-interaction.md +2 -4
- package/bundled/skills/feature-planner/references/completeness-review.md +57 -0
- package/bundled/skills/feature-planner/references/error-recovery.md +15 -34
- package/bundled/skills/feature-planner/references/incremental-feature-planning.md +1 -1
- package/bundled/skills/feature-planner/references/new-project-planning.md +2 -2
- package/bundled/skills/feature-planner/scripts/validate-and-generate.py +1 -2
- package/bundled/skills/feature-workflow/SKILL.md +3 -4
- package/bundled/skills/prizm-kit/SKILL.md +39 -49
- package/bundled/skills/prizmkit-code-review/SKILL.md +51 -64
- package/bundled/skills/prizmkit-code-review/rules/dimensions.md +85 -0
- package/bundled/skills/prizmkit-code-review/rules/fix-strategy.md +11 -11
- package/bundled/skills/prizmkit-committer/SKILL.md +3 -31
- package/bundled/skills/prizmkit-deploy/SKILL.md +34 -31
- package/bundled/skills/prizmkit-deploy/assets/deploy-template.md +1 -1
- package/bundled/skills/prizmkit-implement/SKILL.md +35 -68
- package/bundled/skills/prizmkit-init/SKILL.md +112 -65
- package/bundled/skills/prizmkit-init/assets/project-brief-template.md +82 -0
- package/bundled/skills/prizmkit-plan/SKILL.md +120 -79
- package/bundled/skills/prizmkit-plan/assets/plan-template.md +28 -18
- package/bundled/skills/prizmkit-plan/assets/spec-template.md +28 -11
- package/bundled/skills/prizmkit-plan/references/clarify-guide.md +3 -3
- package/bundled/skills/prizmkit-plan/references/verification-checklist.md +60 -0
- package/bundled/skills/prizmkit-prizm-docs/SKILL.md +10 -81
- package/bundled/skills/prizmkit-prizm-docs/assets/{PRIZM-SPEC.md → prizm-docs-format.md} +41 -526
- package/bundled/skills/prizmkit-prizm-docs/references/op-init.md +46 -0
- package/bundled/skills/prizmkit-prizm-docs/references/op-rebuild.md +16 -0
- package/bundled/skills/prizmkit-prizm-docs/references/op-status.md +14 -0
- package/bundled/skills/prizmkit-prizm-docs/references/op-update.md +19 -0
- package/bundled/skills/prizmkit-prizm-docs/references/op-validate.md +17 -0
- package/bundled/skills/prizmkit-retrospective/SKILL.md +27 -65
- package/bundled/skills/prizmkit-retrospective/references/knowledge-injection-steps.md +3 -4
- package/bundled/skills/prizmkit-retrospective/references/structural-sync-steps.md +7 -25
- package/bundled/skills/recovery-workflow/SKILL.md +8 -8
- package/bundled/skills/refactor-pipeline-launcher/SKILL.md +17 -9
- package/bundled/skills/refactor-planner/SKILL.md +23 -41
- package/bundled/skills/refactor-workflow/SKILL.md +1 -2
- package/bundled/team/prizm-dev-team.json +1 -1
- package/bundled/{skills/prizm-kit/assets → templates}/project-memory-template.md +1 -1
- package/package.json +1 -1
- package/src/clean.js +0 -1
- package/src/gitignore-template.js +0 -1
- package/src/scaffold.js +10 -3
- package/bundled/dev-pipeline/templates/agent-prompts/reviewer-analyze.md +0 -5
- package/bundled/dev-pipeline/templates/sections/phase-analyze-agent.md +0 -19
- package/bundled/dev-pipeline/templates/sections/phase-analyze-full.md +0 -19
- package/bundled/skills/app-planner/references/project-conventions.md +0 -93
- package/bundled/skills/prizmkit-analyze/SKILL.md +0 -207
- package/bundled/skills/prizmkit-code-review/rules/dimensions-bugfix.md +0 -25
- package/bundled/skills/prizmkit-code-review/rules/dimensions-feature.md +0 -43
- package/bundled/skills/prizmkit-code-review/rules/dimensions-refactor.md +0 -25
- package/bundled/skills/prizmkit-implement/references/deploy-guide-protocol.md +0 -69
- package/bundled/skills/prizmkit-verify/SKILL.md +0 -281
- 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", "
|
|
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
|
|
2
|
+
"""Detect stuck items in the dev-pipeline (features, bugs, or refactors).
|
|
3
3
|
|
|
4
|
-
Checks each
|
|
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
|
|
8
|
-
4. Dependency deadlock (depends on a failed
|
|
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
|
-
|
|
11
|
+
items are found, 0 otherwise.
|
|
12
12
|
|
|
13
13
|
Usage:
|
|
14
|
-
python3 detect-stuck.py --state-dir <path> [--
|
|
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
|
|
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 (
|
|
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="
|
|
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
|
|
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
|
|
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
|
|
75
|
-
"""Return a sorted list of
|
|
76
|
-
|
|
77
|
-
if not os.path.isdir(
|
|
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(
|
|
81
|
-
|
|
82
|
-
if os.path.isdir(
|
|
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(
|
|
88
|
-
"""Return session-status.json data for all sessions of
|
|
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(
|
|
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(
|
|
142
|
-
"""Check 1: Has the
|
|
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 =
|
|
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
|
|
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(
|
|
161
|
-
"""Check 2: Is the
|
|
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(
|
|
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(
|
|
189
|
-
"""Check 3: Is the heartbeat stale or missing for an in_progress
|
|
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
|
|
192
|
-
Uses last_session_id from the
|
|
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 =
|
|
197
|
-
|
|
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
|
|
201
|
-
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,
|
|
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": "
|
|
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(
|
|
245
|
-
"""Check 4: Does this
|
|
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
|
|
292
|
+
if task_list_data is None:
|
|
250
293
|
return None
|
|
251
294
|
|
|
252
|
-
|
|
253
|
-
if not isinstance(
|
|
295
|
+
items = task_list_data.get(items_key, [])
|
|
296
|
+
if not isinstance(items, list):
|
|
254
297
|
return None
|
|
255
298
|
|
|
256
|
-
# Find this
|
|
299
|
+
# Find this item in the list to get its dependencies
|
|
257
300
|
deps = None
|
|
258
|
-
for
|
|
259
|
-
if not isinstance(
|
|
301
|
+
for item in items:
|
|
302
|
+
if not isinstance(item, dict):
|
|
260
303
|
continue
|
|
261
|
-
if
|
|
262
|
-
deps =
|
|
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,
|
|
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,
|
|
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
|
|
288
|
-
"""Attempt to locate and load
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
314
|
-
|
|
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
|
-
|
|
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
|
-
|
|
319
|
-
status_path = os.path.join(
|
|
320
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
#
|
|
359
|
-
|
|
360
|
-
|
|
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
|
-
|
|
471
|
+
item_ids = discover_item_ids(state_dir, items_subdir)
|
|
363
472
|
|
|
364
|
-
# Load
|
|
365
|
-
|
|
366
|
-
|
|
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
|
-
|
|
477
|
+
task_list_data = find_task_list(state_dir)
|
|
370
478
|
|
|
371
|
-
|
|
372
|
-
for
|
|
373
|
-
reports =
|
|
374
|
-
|
|
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
|
-
|
|
486
|
+
stuck_items.append(
|
|
378
487
|
{
|
|
379
|
-
|
|
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
|
-
"
|
|
388
|
-
"
|
|
389
|
-
"
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
310
|
+
if not sections:
|
|
297
311
|
return "- (no completed dependencies yet)"
|
|
298
|
-
return "\n".join(
|
|
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,
|