prizmkit 1.1.5 → 1.1.7
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/dev-pipeline/README.md +1 -1
- package/bundled/dev-pipeline/assets/feature-list-example.json +2 -2
- package/bundled/dev-pipeline/reset-bug.sh +304 -0
- package/bundled/dev-pipeline/run-bugfix.sh +55 -8
- package/bundled/dev-pipeline/run-feature.sh +12 -4
- package/bundled/dev-pipeline/run-refactor.sh +5 -2
- package/bundled/dev-pipeline/scripts/init-pipeline.py +19 -5
- package/bundled/dev-pipeline/scripts/update-bug-status.py +2 -2
- package/bundled/dev-pipeline/scripts/update-feature-status.py +6 -6
- package/bundled/dev-pipeline/templates/bug-fix-list-schema.json +111 -31
- package/bundled/dev-pipeline/templates/feature-list-schema.json +5 -5
- package/bundled/dev-pipeline/templates/refactor-list-schema.json +107 -28
- package/bundled/dev-pipeline/tests/test_auto_skip.py +1 -1
- package/bundled/skills/_metadata.json +10 -2
- package/bundled/skills/app-planner/SKILL.md +14 -3
- package/bundled/skills/bug-fix-workflow/SKILL.md +2 -0
- package/bundled/skills/bug-planner/SKILL.md +59 -4
- package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +9 -4
- package/bundled/skills/feature-planner/SKILL.md +73 -1
- package/bundled/skills/feature-planner/references/error-recovery.md +1 -1
- package/bundled/skills/feature-planner/scripts/validate-and-generate.py +7 -6
- package/bundled/skills/feature-workflow/SKILL.md +4 -1
- package/bundled/skills/prizmkit-committer/SKILL.md +1 -0
- package/bundled/skills/prizmkit-deploy/SKILL.md +1 -0
- package/bundled/skills/prizmkit-deploy/assets/deploy-template.md +1 -1
- package/bundled/skills/prizmkit-implement/SKILL.md +1 -1
- package/bundled/skills/prizmkit-implement/references/deploy-guide-protocol.md +4 -4
- package/bundled/skills/prizmkit-plan/SKILL.md +3 -3
- package/bundled/skills/prizmkit-retrospective/SKILL.md +40 -3
- package/bundled/skills/prizmkit-verify/SKILL.md +281 -0
- package/bundled/skills/prizmkit-verify/scripts/verify-light.py +402 -0
- package/bundled/skills/recovery-workflow/SKILL.md +1 -0
- package/bundled/skills/refactor-pipeline-launcher/SKILL.md +7 -3
- package/bundled/skills/refactor-planner/SKILL.md +51 -1
- package/bundled/skills/refactor-workflow/SKILL.md +4 -0
- package/package.json +1 -1
- package/src/scaffold.js +24 -12
package/bundled/VERSION.json
CHANGED
|
@@ -249,7 +249,7 @@ python3 scripts/init-pipeline.py \
|
|
|
249
249
|
|
|
250
250
|
**Validation checks:**
|
|
251
251
|
- Schema: `$schema == "dev-pipeline-feature-list-v1"`
|
|
252
|
-
- Required fields: `
|
|
252
|
+
- Required fields: `project_name` (string), `features` (non-empty array)
|
|
253
253
|
- Per-feature: `id` (F-NNN), `title`, `description`, `priority` (high/medium/low), `dependencies` (array of F-NNN), `acceptance_criteria` (array), `status`
|
|
254
254
|
- Dependency DAG cycle detection (Kahn's algorithm)
|
|
255
255
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "dev-pipeline-feature-list-v1",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
3
|
+
"project_name": "TaskFlow",
|
|
4
|
+
"project_description": "A modern task management application with team collaboration, real-time updates, and analytics dashboard",
|
|
5
5
|
"created_at": "2026-03-04T10:00:00Z",
|
|
6
6
|
"created_by": "feature-planner",
|
|
7
7
|
"source_spec": ".prizmkit/specs/app-spec.md",
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# ============================================================
|
|
5
|
+
# dev-pipeline/reset-bug.sh - Reset a failed/stuck bug fix
|
|
6
|
+
#
|
|
7
|
+
# Clears all state and artifacts for a bug so it can be
|
|
8
|
+
# re-executed from scratch by the pipeline.
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# ./reset-bug.sh <bug-id|range> [options] [bug-fix-list.json]
|
|
12
|
+
#
|
|
13
|
+
# Bug selection:
|
|
14
|
+
# B-007 Single bug
|
|
15
|
+
# B-008:B-013 Range of bugs (inclusive)
|
|
16
|
+
# --auto-skipped All bugs with auto_skipped status
|
|
17
|
+
# --failed All bugs with failed status
|
|
18
|
+
# --stalled All non-completed bugs (failed + auto_skipped)
|
|
19
|
+
#
|
|
20
|
+
# Options:
|
|
21
|
+
# --clean Also delete session history and .prizmkit/bugfix/{BUG_ID}/ artifacts
|
|
22
|
+
# --run After reset, immediately retry via pipeline (only with single bug)
|
|
23
|
+
#
|
|
24
|
+
# Examples:
|
|
25
|
+
# ./reset-bug.sh B-007 # Reset status only
|
|
26
|
+
# ./reset-bug.sh B-007 --clean # Reset + delete artifacts
|
|
27
|
+
# ./reset-bug.sh B-008:B-013 --clean # Reset range
|
|
28
|
+
# ./reset-bug.sh --auto-skipped # Reset all auto_skipped
|
|
29
|
+
# ./reset-bug.sh --failed --clean # Reset all failed + clean
|
|
30
|
+
# ./reset-bug.sh --stalled --clean # Reset all non-completed
|
|
31
|
+
# ./reset-bug.sh B-007 --clean --run # Reset + delete + retry
|
|
32
|
+
# ./reset-bug.sh B-007 --clean my-bugs.json # Custom bug list
|
|
33
|
+
# ============================================================
|
|
34
|
+
|
|
35
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
36
|
+
STATE_DIR="$SCRIPT_DIR/bugfix-state"
|
|
37
|
+
SCRIPTS_DIR="$SCRIPT_DIR/scripts"
|
|
38
|
+
|
|
39
|
+
# Colors
|
|
40
|
+
RED='\033[0;31m'
|
|
41
|
+
GREEN='\033[0;32m'
|
|
42
|
+
YELLOW='\033[1;33m'
|
|
43
|
+
BLUE='\033[0;34m'
|
|
44
|
+
BOLD='\033[1m'
|
|
45
|
+
NC='\033[0m'
|
|
46
|
+
|
|
47
|
+
log_info() { echo -e "${BLUE}[INFO]${NC} $*"; }
|
|
48
|
+
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
|
49
|
+
log_error() { echo -e "${RED}[ERROR]${NC} $*"; }
|
|
50
|
+
log_success() { echo -e "${GREEN}[OK]${NC} $*"; }
|
|
51
|
+
|
|
52
|
+
# ============================================================
|
|
53
|
+
# Parse args
|
|
54
|
+
# ============================================================
|
|
55
|
+
|
|
56
|
+
BUG_ID=""
|
|
57
|
+
BUG_RANGE=""
|
|
58
|
+
BUG_LIST=""
|
|
59
|
+
DO_CLEAN=false
|
|
60
|
+
DO_RUN=false
|
|
61
|
+
FILTER_MODE=""
|
|
62
|
+
|
|
63
|
+
for arg in "$@"; do
|
|
64
|
+
case "$arg" in
|
|
65
|
+
--clean) DO_CLEAN=true ;;
|
|
66
|
+
--run) DO_RUN=true ;;
|
|
67
|
+
--auto-skipped) FILTER_MODE="auto_skipped" ;;
|
|
68
|
+
--failed) FILTER_MODE="failed" ;;
|
|
69
|
+
--stalled) FILTER_MODE="stalled" ;;
|
|
70
|
+
-h|--help)
|
|
71
|
+
echo "Usage: $0 <bug-id|range> [--clean] [--run] [--auto-skipped|--failed|--stalled] [bug-fix-list.json]"
|
|
72
|
+
echo ""
|
|
73
|
+
echo " bug-id Single bug (e.g. B-007)"
|
|
74
|
+
echo " B-008:B-013 Range of bugs (inclusive)"
|
|
75
|
+
echo " --auto-skipped Reset all auto_skipped bugs"
|
|
76
|
+
echo " --failed Reset all failed bugs"
|
|
77
|
+
echo " --stalled Reset all non-completed (failed + auto_skipped)"
|
|
78
|
+
echo " --clean Delete session history and .prizmkit artifacts"
|
|
79
|
+
echo " --run Retry immediately after reset (single bug only)"
|
|
80
|
+
echo " bug-fix-list.json Path to bug fix list (default: bug-fix-list.json)"
|
|
81
|
+
exit 0
|
|
82
|
+
;;
|
|
83
|
+
B-*:B-*|b-*:b-*) BUG_RANGE="$arg" ;;
|
|
84
|
+
B-*|b-*) BUG_ID="$arg" ;;
|
|
85
|
+
*) BUG_LIST="$arg" ;;
|
|
86
|
+
esac
|
|
87
|
+
done
|
|
88
|
+
|
|
89
|
+
if [[ -z "$BUG_ID" && -z "$BUG_RANGE" && -z "$FILTER_MODE" ]]; then
|
|
90
|
+
echo "Usage: $0 <bug-id|range> [--clean] [--run] [--auto-skipped|--failed|--stalled] [bug-fix-list.json]"
|
|
91
|
+
echo ""
|
|
92
|
+
echo " bug-id Single bug (e.g. B-007)"
|
|
93
|
+
echo " B-008:B-013 Range of bugs (inclusive)"
|
|
94
|
+
echo " --auto-skipped Reset all auto_skipped bugs"
|
|
95
|
+
echo " --failed Reset all failed bugs"
|
|
96
|
+
echo " --stalled Reset all non-completed (failed + auto_skipped)"
|
|
97
|
+
echo " --clean Delete session history and .prizmkit artifacts"
|
|
98
|
+
echo " --run Retry immediately after reset (single bug only)"
|
|
99
|
+
echo " bug-fix-list.json Path to bug fix list (default: bug-fix-list.json)"
|
|
100
|
+
exit 1
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
BUG_LIST="${BUG_LIST:-bug-fix-list.json}"
|
|
104
|
+
|
|
105
|
+
# Resolve absolute path
|
|
106
|
+
if [[ ! "$BUG_LIST" = /* ]]; then
|
|
107
|
+
BUG_LIST="$(pwd)/$BUG_LIST"
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
# ============================================================
|
|
111
|
+
# Validation
|
|
112
|
+
# ============================================================
|
|
113
|
+
|
|
114
|
+
if [[ ! -f "$BUG_LIST" ]]; then
|
|
115
|
+
log_error "Bug fix list not found: $BUG_LIST"
|
|
116
|
+
exit 1
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
if [[ ! -f "$STATE_DIR/pipeline.json" ]]; then
|
|
120
|
+
log_error "No pipeline state found. Run './run-bugfix.sh run' first to initialize."
|
|
121
|
+
exit 1
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
# ============================================================
|
|
125
|
+
# Resolve bug IDs to process
|
|
126
|
+
# ============================================================
|
|
127
|
+
|
|
128
|
+
BUG_IDS=()
|
|
129
|
+
|
|
130
|
+
if [[ -n "$FILTER_MODE" ]]; then
|
|
131
|
+
# Filter by status from bugfix-state/bugs/*/status.json
|
|
132
|
+
while IFS= read -r bid; do
|
|
133
|
+
[[ -n "$bid" ]] && BUG_IDS+=("$bid")
|
|
134
|
+
done < <(python3 -c "
|
|
135
|
+
import json, os, sys
|
|
136
|
+
state_dir = '$STATE_DIR'
|
|
137
|
+
filter_mode = '$FILTER_MODE'
|
|
138
|
+
bugs_dir = os.path.join(state_dir, 'bugs')
|
|
139
|
+
if not os.path.isdir(bugs_dir):
|
|
140
|
+
sys.exit(0)
|
|
141
|
+
for bid in sorted(os.listdir(bugs_dir)):
|
|
142
|
+
status_file = os.path.join(bugs_dir, bid, 'status.json')
|
|
143
|
+
if not os.path.isfile(status_file):
|
|
144
|
+
continue
|
|
145
|
+
with open(status_file) as f:
|
|
146
|
+
status = json.load(f).get('status', '')
|
|
147
|
+
if filter_mode == 'auto_skipped' and status == 'auto_skipped':
|
|
148
|
+
print(bid)
|
|
149
|
+
elif filter_mode == 'failed' and status == 'failed':
|
|
150
|
+
print(bid)
|
|
151
|
+
elif filter_mode == 'stalled' and status in ('failed', 'auto_skipped'):
|
|
152
|
+
print(bid)
|
|
153
|
+
" 2>/dev/null)
|
|
154
|
+
|
|
155
|
+
if [[ ${#BUG_IDS[@]} -eq 0 ]]; then
|
|
156
|
+
log_info "No bugs found with status: $FILTER_MODE"
|
|
157
|
+
exit 0
|
|
158
|
+
fi
|
|
159
|
+
log_info "Found ${#BUG_IDS[@]} bug(s) matching --$FILTER_MODE: ${BUG_IDS[*]}"
|
|
160
|
+
|
|
161
|
+
elif [[ -n "$BUG_RANGE" ]]; then
|
|
162
|
+
# Parse range B-NNN:B-MMM
|
|
163
|
+
RANGE_START="${BUG_RANGE%%:*}"
|
|
164
|
+
RANGE_END="${BUG_RANGE##*:}"
|
|
165
|
+
START_NUM=$(echo "$RANGE_START" | sed 's/[Bb]-//' | sed 's/^0*//')
|
|
166
|
+
END_NUM=$(echo "$RANGE_END" | sed 's/[Bb]-//' | sed 's/^0*//')
|
|
167
|
+
|
|
168
|
+
if [[ -z "$START_NUM" || -z "$END_NUM" || "$START_NUM" -gt "$END_NUM" ]]; then
|
|
169
|
+
log_error "Invalid range: $BUG_RANGE (start must be <= end)"
|
|
170
|
+
exit 1
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
for ((i=START_NUM; i<=END_NUM; i++)); do
|
|
174
|
+
BUG_IDS+=("B-$(printf '%03d' "$i")")
|
|
175
|
+
done
|
|
176
|
+
log_info "Range $BUG_RANGE -> ${BUG_IDS[*]}"
|
|
177
|
+
|
|
178
|
+
else
|
|
179
|
+
BUG_IDS=("$BUG_ID")
|
|
180
|
+
fi
|
|
181
|
+
|
|
182
|
+
# --run only works with single bug
|
|
183
|
+
if [[ "$DO_RUN" == true && ${#BUG_IDS[@]} -gt 1 ]]; then
|
|
184
|
+
log_warn "--run is only supported for single bug reset. Use './run-bugfix.sh run' to resume pipeline after batch reset."
|
|
185
|
+
DO_RUN=false
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
# ============================================================
|
|
189
|
+
# Process each bug
|
|
190
|
+
# ============================================================
|
|
191
|
+
|
|
192
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
193
|
+
RESET_COUNT=0
|
|
194
|
+
FAIL_COUNT=0
|
|
195
|
+
|
|
196
|
+
for CUR_BUG_ID in "${BUG_IDS[@]}"; do
|
|
197
|
+
|
|
198
|
+
# Get bug info from bug fix list
|
|
199
|
+
BUG_INFO=$(python3 -c "
|
|
200
|
+
import json, sys
|
|
201
|
+
with open('$BUG_LIST') as f:
|
|
202
|
+
data = json.load(f)
|
|
203
|
+
for bug in data.get('bugs', []):
|
|
204
|
+
if bug.get('id') == '$CUR_BUG_ID':
|
|
205
|
+
title = bug.get('title', '')
|
|
206
|
+
print(json.dumps({'title': title, 'status': bug.get('status', 'unknown'), 'severity': bug.get('severity', 'medium')}))
|
|
207
|
+
sys.exit(0)
|
|
208
|
+
sys.exit(1)
|
|
209
|
+
" 2>/dev/null) || {
|
|
210
|
+
log_warn "Bug $CUR_BUG_ID not found in $BUG_LIST -- skipping"
|
|
211
|
+
FAIL_COUNT=$((FAIL_COUNT + 1))
|
|
212
|
+
continue
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
BUG_TITLE=$(echo "$BUG_INFO" | python3 -c "import sys,json; print(json.load(sys.stdin)['title'])")
|
|
216
|
+
|
|
217
|
+
# -- Show current state --
|
|
218
|
+
echo ""
|
|
219
|
+
echo -e "${BOLD}════════════════════════════════════════════════════${NC}"
|
|
220
|
+
echo -e "${BOLD} Reset: $CUR_BUG_ID — $BUG_TITLE${NC}"
|
|
221
|
+
echo -e "${BOLD}════════════════════════════════════════════════════${NC}"
|
|
222
|
+
|
|
223
|
+
STATUS_FILE="$STATE_DIR/bugs/$CUR_BUG_ID/status.json"
|
|
224
|
+
if [[ -f "$STATUS_FILE" ]]; then
|
|
225
|
+
CURRENT_STATUS=$(python3 -c "import json; d=json.load(open('$STATUS_FILE')); print(d.get('status','?'))")
|
|
226
|
+
CURRENT_RETRY=$(python3 -c "import json; d=json.load(open('$STATUS_FILE')); print(d.get('retry_count',0))")
|
|
227
|
+
SESSION_COUNT=$(python3 -c "import json; d=json.load(open('$STATUS_FILE')); print(len(d.get('sessions',[])))")
|
|
228
|
+
log_info "Current status: $CURRENT_STATUS (retry $CURRENT_RETRY, $SESSION_COUNT sessions)"
|
|
229
|
+
else
|
|
230
|
+
log_info "No status file found (never executed)"
|
|
231
|
+
fi
|
|
232
|
+
|
|
233
|
+
BUGFIX_DIR="$PROJECT_ROOT/.prizmkit/bugfix/$CUR_BUG_ID"
|
|
234
|
+
BUGFIX_COUNT=0
|
|
235
|
+
if [[ -d "$BUGFIX_DIR" ]]; then
|
|
236
|
+
BUGFIX_COUNT=$(find "$BUGFIX_DIR" -type f 2>/dev/null | wc -l | tr -d ' ')
|
|
237
|
+
log_info "PrizmKit artifacts: $BUGFIX_COUNT files in .prizmkit/bugfix/$CUR_BUG_ID/"
|
|
238
|
+
fi
|
|
239
|
+
|
|
240
|
+
SESSIONS_DIR="$STATE_DIR/bugs/$CUR_BUG_ID/sessions"
|
|
241
|
+
SESSIONS_COUNT=0
|
|
242
|
+
if [[ -d "$SESSIONS_DIR" ]]; then
|
|
243
|
+
SESSIONS_COUNT=$(find "$SESSIONS_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l | tr -d ' ')
|
|
244
|
+
log_info "Session history: $SESSIONS_COUNT session(s)"
|
|
245
|
+
fi
|
|
246
|
+
|
|
247
|
+
echo -e "${BOLD}════════════════════════════════════════════════════${NC}"
|
|
248
|
+
|
|
249
|
+
# -- Execute reset --
|
|
250
|
+
if [[ "$DO_CLEAN" == true ]]; then
|
|
251
|
+
log_info "Cleaning $CUR_BUG_ID (reset + delete artifacts)..."
|
|
252
|
+
RESULT=$(python3 "$SCRIPTS_DIR/update-bug-status.py" \
|
|
253
|
+
--bug-list "$BUG_LIST" \
|
|
254
|
+
--state-dir "$STATE_DIR" \
|
|
255
|
+
--bug-id "$CUR_BUG_ID" \
|
|
256
|
+
--project-root "$PROJECT_ROOT" \
|
|
257
|
+
--action clean 2>&1)
|
|
258
|
+
else
|
|
259
|
+
log_info "Resetting $CUR_BUG_ID status..."
|
|
260
|
+
RESULT=$(python3 "$SCRIPTS_DIR/update-bug-status.py" \
|
|
261
|
+
--bug-list "$BUG_LIST" \
|
|
262
|
+
--state-dir "$STATE_DIR" \
|
|
263
|
+
--bug-id "$CUR_BUG_ID" \
|
|
264
|
+
--action reset 2>&1)
|
|
265
|
+
fi
|
|
266
|
+
|
|
267
|
+
# Check for errors
|
|
268
|
+
if echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); sys.exit(0 if 'error' not in d else 1)" 2>/dev/null; then
|
|
269
|
+
RESET_COUNT=$((RESET_COUNT + 1))
|
|
270
|
+
if [[ "$DO_CLEAN" == true ]]; then
|
|
271
|
+
log_success "$CUR_BUG_ID cleaned: status -> pending, $SESSIONS_COUNT session(s) deleted, $BUGFIX_COUNT artifact(s) deleted"
|
|
272
|
+
else
|
|
273
|
+
log_success "$CUR_BUG_ID reset: status -> pending, retry count -> 0"
|
|
274
|
+
fi
|
|
275
|
+
else
|
|
276
|
+
ERROR_MSG=$(echo "$RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('error','unknown'))" 2>/dev/null || echo "$RESULT")
|
|
277
|
+
log_error "Reset $CUR_BUG_ID failed: $ERROR_MSG"
|
|
278
|
+
FAIL_COUNT=$((FAIL_COUNT + 1))
|
|
279
|
+
fi
|
|
280
|
+
|
|
281
|
+
done
|
|
282
|
+
|
|
283
|
+
# ============================================================
|
|
284
|
+
# Summary
|
|
285
|
+
# ============================================================
|
|
286
|
+
|
|
287
|
+
echo ""
|
|
288
|
+
echo -e "${BOLD}════════════════════════════════════════════════════${NC}"
|
|
289
|
+
echo -e "${BOLD} Reset complete: $RESET_COUNT succeeded, $FAIL_COUNT failed${NC}"
|
|
290
|
+
echo -e "${BOLD}════════════════════════════════════════════════════${NC}"
|
|
291
|
+
|
|
292
|
+
echo ""
|
|
293
|
+
echo -e "${BOLD}Next steps:${NC}"
|
|
294
|
+
if [[ "$DO_RUN" == true && ${#BUG_IDS[@]} -eq 1 ]]; then
|
|
295
|
+
log_info "Auto-retrying ${BUG_IDS[0]}..."
|
|
296
|
+
echo ""
|
|
297
|
+
exec "$SCRIPT_DIR/retry-bugfix.sh" "${BUG_IDS[0]}" "$BUG_LIST"
|
|
298
|
+
else
|
|
299
|
+
log_info " ./dev-pipeline/run-bugfix.sh run bug-fix-list.json # Resume pipeline from first pending"
|
|
300
|
+
if [[ ${#BUG_IDS[@]} -eq 1 ]]; then
|
|
301
|
+
log_info " ./dev-pipeline/retry-bugfix.sh ${BUG_IDS[0]} # Retry single bug"
|
|
302
|
+
fi
|
|
303
|
+
fi
|
|
304
|
+
echo ""
|
|
@@ -352,10 +352,14 @@ run_one() {
|
|
|
352
352
|
local bug_id=""
|
|
353
353
|
local bug_list=""
|
|
354
354
|
local dry_run=false
|
|
355
|
+
local do_clean=false
|
|
356
|
+
local no_reset=false
|
|
355
357
|
|
|
356
358
|
while [[ $# -gt 0 ]]; do
|
|
357
359
|
case "$1" in
|
|
358
360
|
--dry-run) dry_run=true; shift ;;
|
|
361
|
+
--clean) do_clean=true; shift ;;
|
|
362
|
+
--no-reset) no_reset=true; shift ;;
|
|
359
363
|
--timeout) shift; SESSION_TIMEOUT="${1:-0}"; shift ;;
|
|
360
364
|
B-*|b-*) bug_id="$1"; shift ;;
|
|
361
365
|
*) bug_list="$1"; shift ;;
|
|
@@ -437,12 +441,48 @@ for bug in data.get('bugs', []):
|
|
|
437
441
|
sys.exit(1)
|
|
438
442
|
" "$bug_list" "$bug_id" 2>/dev/null) || bug_severity="medium"
|
|
439
443
|
|
|
440
|
-
#
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
444
|
+
# Optional Clean
|
|
445
|
+
if [[ "$do_clean" == true ]]; then
|
|
446
|
+
if [[ "$dry_run" == true ]]; then
|
|
447
|
+
log_warn "Dry-run mode: --clean ignored (no artifacts will be deleted)"
|
|
448
|
+
else
|
|
449
|
+
log_info "Cleaning artifacts for $bug_id..."
|
|
450
|
+
|
|
451
|
+
local project_root
|
|
452
|
+
project_root="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
453
|
+
|
|
454
|
+
local bugfix_dir="$project_root/.prizmkit/bugfix/$bug_id"
|
|
455
|
+
if [[ -d "$bugfix_dir" ]]; then
|
|
456
|
+
rm -rf "$bugfix_dir"
|
|
457
|
+
log_info "Removed $bugfix_dir"
|
|
458
|
+
fi
|
|
459
|
+
|
|
460
|
+
local dev_team_dir="$project_root/.dev-team"
|
|
461
|
+
if [[ -d "$dev_team_dir" ]]; then
|
|
462
|
+
rm -rf "$dev_team_dir"
|
|
463
|
+
log_info "Removed $dev_team_dir"
|
|
464
|
+
fi
|
|
465
|
+
|
|
466
|
+
local bug_state_dir="$STATE_DIR/bugs/$bug_id"
|
|
467
|
+
if [[ -d "$bug_state_dir" ]]; then
|
|
468
|
+
rm -rf "$bug_state_dir"
|
|
469
|
+
log_info "Removed $bug_state_dir"
|
|
470
|
+
fi
|
|
471
|
+
fi
|
|
472
|
+
fi
|
|
473
|
+
|
|
474
|
+
# Reset bug status (conditional)
|
|
475
|
+
if [[ "$no_reset" == false && "$dry_run" == false ]]; then
|
|
476
|
+
python3 "$SCRIPTS_DIR/update-bug-status.py" \
|
|
477
|
+
--bug-list "$bug_list" \
|
|
478
|
+
--state-dir "$STATE_DIR" \
|
|
479
|
+
--bug-id "$bug_id" \
|
|
480
|
+
--action reset >/dev/null 2>&1 || {
|
|
481
|
+
log_warn "Failed to reset bug status (may already be pending)"
|
|
482
|
+
}
|
|
483
|
+
elif [[ "$dry_run" == true && "$no_reset" == false ]]; then
|
|
484
|
+
log_info "Dry-run mode: skipping status reset"
|
|
485
|
+
fi
|
|
446
486
|
|
|
447
487
|
# Generate bootstrap prompt
|
|
448
488
|
local run_id session_id session_dir bootstrap_prompt
|
|
@@ -647,11 +687,14 @@ main() {
|
|
|
647
687
|
while true; do
|
|
648
688
|
# Find next bug to process
|
|
649
689
|
local next_bug
|
|
650
|
-
next_bug=$(python3 "$SCRIPTS_DIR/update-bug-status.py" \
|
|
690
|
+
if ! next_bug=$(python3 "$SCRIPTS_DIR/update-bug-status.py" \
|
|
651
691
|
--bug-list "$bug_list" \
|
|
652
692
|
--state-dir "$STATE_DIR" \
|
|
653
693
|
--max-retries "$MAX_RETRIES" \
|
|
654
|
-
--action get_next 2>/dev/null)
|
|
694
|
+
--action get_next 2>/dev/null); then
|
|
695
|
+
log_error "Failed to get next bug"
|
|
696
|
+
break
|
|
697
|
+
fi
|
|
655
698
|
|
|
656
699
|
if [[ "$next_bug" == "PIPELINE_COMPLETE" ]]; then
|
|
657
700
|
echo ""
|
|
@@ -753,6 +796,8 @@ show_help() {
|
|
|
753
796
|
echo ""
|
|
754
797
|
echo "Single Bug Options (run <bug-id>):"
|
|
755
798
|
echo " --dry-run Generate bootstrap prompt only, don't spawn session"
|
|
799
|
+
echo " --clean Delete artifacts and reset before running"
|
|
800
|
+
echo " --no-reset Skip status reset (preserve retry count)"
|
|
756
801
|
echo " --timeout N Session timeout in seconds (default: 0 = no limit)"
|
|
757
802
|
echo ""
|
|
758
803
|
echo "Environment Variables:"
|
|
@@ -769,6 +814,8 @@ show_help() {
|
|
|
769
814
|
echo " ./run-bugfix.sh run # Run all bugs"
|
|
770
815
|
echo " ./run-bugfix.sh run bug-fix-list.json # Custom bug list"
|
|
771
816
|
echo " ./run-bugfix.sh run B-001 --dry-run # Inspect generated prompt"
|
|
817
|
+
echo " ./run-bugfix.sh run B-001 --clean # Clean artifacts + reset + run"
|
|
818
|
+
echo " ./run-bugfix.sh run B-001 --no-reset # Retry without resetting status"
|
|
772
819
|
echo " ./run-bugfix.sh run B-001 --timeout 3600 # 1h timeout"
|
|
773
820
|
echo " ./run-bugfix.sh status # Show status"
|
|
774
821
|
echo " MAX_RETRIES=5 ./run-bugfix.sh run # Custom retries"
|
|
@@ -542,9 +542,12 @@ run_one() {
|
|
|
542
542
|
if [[ ! -f "$STATE_DIR/pipeline.json" ]]; then
|
|
543
543
|
log_info "Initializing pipeline state for single-feature run..."
|
|
544
544
|
local init_result
|
|
545
|
-
init_result=$(python3 "$SCRIPTS_DIR/init-pipeline.py" \
|
|
545
|
+
if ! init_result=$(python3 "$SCRIPTS_DIR/init-pipeline.py" \
|
|
546
546
|
--feature-list "$feature_list" \
|
|
547
|
-
--state-dir "$STATE_DIR" 2>&1)
|
|
547
|
+
--state-dir "$STATE_DIR" 2>&1); then
|
|
548
|
+
log_error "Pipeline initialization failed (script error)"
|
|
549
|
+
exit 1
|
|
550
|
+
fi
|
|
548
551
|
|
|
549
552
|
local init_valid
|
|
550
553
|
init_valid=$(echo "$init_result" | python3 -c "import sys,json; print(json.load(sys.stdin).get('valid', False))" 2>/dev/null || echo "False")
|
|
@@ -965,8 +968,13 @@ for f in data.get('stuck_features', []):
|
|
|
965
968
|
if [[ -n "$features_filter" ]]; then
|
|
966
969
|
_get_next_args+=(--features "$features_filter")
|
|
967
970
|
fi
|
|
968
|
-
next_feature=$(python3 "$SCRIPTS_DIR/update-feature-status.py" \
|
|
969
|
-
"${_get_next_args[@]}" 2>/dev/null)
|
|
971
|
+
if ! next_feature=$(python3 "$SCRIPTS_DIR/update-feature-status.py" \
|
|
972
|
+
"${_get_next_args[@]}" 2>/dev/null); then
|
|
973
|
+
|
|
974
|
+
log_error "Failed to get next feature"
|
|
975
|
+
break
|
|
976
|
+
fi
|
|
977
|
+
|
|
970
978
|
|
|
971
979
|
if [[ "$next_feature" == "PIPELINE_COMPLETE" ]]; then
|
|
972
980
|
echo ""
|
|
@@ -622,11 +622,14 @@ main() {
|
|
|
622
622
|
while true; do
|
|
623
623
|
# Find next refactor to process (dependency-topological order)
|
|
624
624
|
local next_refactor
|
|
625
|
-
next_refactor=$(python3 "$SCRIPTS_DIR/update-refactor-status.py" \
|
|
625
|
+
if ! next_refactor=$(python3 "$SCRIPTS_DIR/update-refactor-status.py" \
|
|
626
626
|
--refactor-list "$refactor_list" \
|
|
627
627
|
--state-dir "$STATE_DIR" \
|
|
628
628
|
--max-retries "$MAX_RETRIES" \
|
|
629
|
-
--action get_next 2>/dev/null)
|
|
629
|
+
--action get_next 2>/dev/null); then
|
|
630
|
+
log_error "Failed to get next refactor"
|
|
631
|
+
break
|
|
632
|
+
fi
|
|
630
633
|
|
|
631
634
|
if [[ "$next_refactor" == "PIPELINE_COMPLETE" ]]; then
|
|
632
635
|
echo ""
|
|
@@ -20,6 +20,7 @@ from datetime import datetime, timezone
|
|
|
20
20
|
EXPECTED_SCHEMA = "dev-pipeline-feature-list-v1"
|
|
21
21
|
FEATURE_ID_PATTERN = re.compile(r"^F-\d{3}$")
|
|
22
22
|
TERMINAL_STATUSES = {"completed", "failed", "skipped"}
|
|
23
|
+
VALID_PRIORITIES = {"critical", "high", "medium", "low"}
|
|
23
24
|
|
|
24
25
|
REQUIRED_FEATURE_FIELDS = [
|
|
25
26
|
"id",
|
|
@@ -75,11 +76,12 @@ def validate_schema(data):
|
|
|
75
76
|
"Invalid $schema: expected '{}', got '{}'".format(EXPECTED_SCHEMA, schema)
|
|
76
77
|
)
|
|
77
78
|
|
|
78
|
-
# Check app_name
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
# Check project_name (supports legacy app_name for backward compatibility)
|
|
80
|
+
project_name = data.get("project_name", data.get("app_name"))
|
|
81
|
+
if project_name is None:
|
|
82
|
+
errors.append("Missing required field: project_name")
|
|
83
|
+
elif not isinstance(project_name, str) or not project_name.strip():
|
|
84
|
+
errors.append("project_name must be a non-empty string")
|
|
83
85
|
|
|
84
86
|
# Check features array
|
|
85
87
|
if "features" not in data:
|
|
@@ -132,6 +134,18 @@ def validate_features(features):
|
|
|
132
134
|
)
|
|
133
135
|
)
|
|
134
136
|
|
|
137
|
+
# Validate priority enum
|
|
138
|
+
priority = feature.get("priority")
|
|
139
|
+
if priority is not None and priority not in VALID_PRIORITIES:
|
|
140
|
+
errors.append(
|
|
141
|
+
"Feature '{}' has invalid priority '{}' "
|
|
142
|
+
"(must be one of: {})".format(
|
|
143
|
+
fid if fid else "index {}".format(i),
|
|
144
|
+
priority,
|
|
145
|
+
", ".join(sorted(VALID_PRIORITIES)),
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
|
|
135
149
|
# Validate acceptance_criteria is a list
|
|
136
150
|
ac = feature.get("acceptance_criteria")
|
|
137
151
|
if ac is not None and not isinstance(ac, list):
|
|
@@ -183,12 +183,12 @@ def action_get_next(bug_list_data, state_dir):
|
|
|
183
183
|
elif bstatus == "pending":
|
|
184
184
|
pending_bugs.append(bug)
|
|
185
185
|
|
|
186
|
-
_PRIORITY_ORDER = {"
|
|
186
|
+
_PRIORITY_ORDER = {"critical": 0, "high": 1, "medium": 2, "low": 3}
|
|
187
187
|
|
|
188
188
|
def sort_key(b):
|
|
189
189
|
severity = b.get("severity", "medium")
|
|
190
190
|
sev_order = SEVERITY_PRIORITY.get(severity, 2)
|
|
191
|
-
priority = _PRIORITY_ORDER.get(b.get("priority", "low"),
|
|
191
|
+
priority = _PRIORITY_ORDER.get(b.get("priority", "low"), 3)
|
|
192
192
|
return (sev_order, priority)
|
|
193
193
|
|
|
194
194
|
if in_progress_bugs:
|
|
@@ -524,19 +524,19 @@ def action_get_next(feature_list_data, state_dir, feature_filter=None):
|
|
|
524
524
|
else:
|
|
525
525
|
pending_features.append(feature)
|
|
526
526
|
|
|
527
|
-
# Priority mapping: string enum → sort order (
|
|
528
|
-
_PRIORITY_ORDER = {"
|
|
527
|
+
# Priority mapping: string enum → sort order (critical first)
|
|
528
|
+
_PRIORITY_ORDER = {"critical": 0, "high": 1, "medium": 2, "low": 3}
|
|
529
529
|
|
|
530
530
|
# Prefer in_progress features, then pending; sort by priority (high > medium > low)
|
|
531
531
|
if in_progress_features:
|
|
532
532
|
candidates = sorted(
|
|
533
533
|
in_progress_features,
|
|
534
|
-
key=lambda f: _PRIORITY_ORDER.get(f.get("priority", "low"),
|
|
534
|
+
key=lambda f: _PRIORITY_ORDER.get(f.get("priority", "low"), 3)
|
|
535
535
|
)
|
|
536
536
|
else:
|
|
537
537
|
candidates = sorted(
|
|
538
538
|
pending_features,
|
|
539
|
-
key=lambda f: _PRIORITY_ORDER.get(f.get("priority", "low"),
|
|
539
|
+
key=lambda f: _PRIORITY_ORDER.get(f.get("priority", "low"), 3)
|
|
540
540
|
)
|
|
541
541
|
|
|
542
542
|
chosen = candidates[0]
|
|
@@ -865,7 +865,7 @@ def action_status(feature_list_data, state_dir, feature_filter=None):
|
|
|
865
865
|
is available.
|
|
866
866
|
"""
|
|
867
867
|
features = feature_list_data.get("features", [])
|
|
868
|
-
app_name = feature_list_data.get("app_name", "Unknown")
|
|
868
|
+
app_name = feature_list_data.get("project_name", feature_list_data.get("app_name", "Unknown"))
|
|
869
869
|
|
|
870
870
|
# Apply feature filter
|
|
871
871
|
if feature_filter is not None:
|
|
@@ -1018,7 +1018,7 @@ def action_status(feature_list_data, state_dir, feature_filter=None):
|
|
|
1018
1018
|
print("╔" + "═" * BOX_WIDTH + "╗")
|
|
1019
1019
|
print("║" + pad_right(COLOR_BOLD + " Dev-Pipeline Status" + COLOR_RESET, inner) + " ║")
|
|
1020
1020
|
print("╠" + "═" * BOX_WIDTH + "╣")
|
|
1021
|
-
print("║" + pad_right("
|
|
1021
|
+
print("║" + pad_right(" Project: {}".format(app_name), inner) + " ║")
|
|
1022
1022
|
print("║" + pad_right(" {}".format(summary_line), inner) + " ║")
|
|
1023
1023
|
print("║" + pad_right(" {}".format(summary_line2), inner) + " ║")
|
|
1024
1024
|
print("║" + pad_right(" {}".format(summary_line3), inner) + " ║")
|