prizmkit 1.1.6 → 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.
Files changed (37) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/README.md +1 -1
  3. package/bundled/dev-pipeline/assets/feature-list-example.json +2 -2
  4. package/bundled/dev-pipeline/reset-bug.sh +304 -0
  5. package/bundled/dev-pipeline/run-bugfix.sh +55 -8
  6. package/bundled/dev-pipeline/run-feature.sh +12 -4
  7. package/bundled/dev-pipeline/run-refactor.sh +5 -2
  8. package/bundled/dev-pipeline/scripts/init-pipeline.py +19 -5
  9. package/bundled/dev-pipeline/scripts/update-bug-status.py +2 -2
  10. package/bundled/dev-pipeline/scripts/update-feature-status.py +6 -6
  11. package/bundled/dev-pipeline/templates/bug-fix-list-schema.json +111 -31
  12. package/bundled/dev-pipeline/templates/feature-list-schema.json +5 -5
  13. package/bundled/dev-pipeline/templates/refactor-list-schema.json +107 -28
  14. package/bundled/dev-pipeline/tests/test_auto_skip.py +1 -1
  15. package/bundled/skills/_metadata.json +10 -2
  16. package/bundled/skills/app-planner/SKILL.md +14 -3
  17. package/bundled/skills/bug-fix-workflow/SKILL.md +2 -0
  18. package/bundled/skills/bug-planner/SKILL.md +59 -4
  19. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +9 -4
  20. package/bundled/skills/feature-planner/SKILL.md +73 -1
  21. package/bundled/skills/feature-planner/references/error-recovery.md +1 -1
  22. package/bundled/skills/feature-planner/scripts/validate-and-generate.py +7 -6
  23. package/bundled/skills/feature-workflow/SKILL.md +4 -1
  24. package/bundled/skills/prizmkit-committer/SKILL.md +1 -0
  25. package/bundled/skills/prizmkit-deploy/SKILL.md +1 -0
  26. package/bundled/skills/prizmkit-deploy/assets/deploy-template.md +1 -1
  27. package/bundled/skills/prizmkit-implement/SKILL.md +1 -1
  28. package/bundled/skills/prizmkit-implement/references/deploy-guide-protocol.md +4 -4
  29. package/bundled/skills/prizmkit-plan/SKILL.md +3 -3
  30. package/bundled/skills/prizmkit-retrospective/SKILL.md +40 -3
  31. package/bundled/skills/prizmkit-verify/SKILL.md +281 -0
  32. package/bundled/skills/prizmkit-verify/scripts/verify-light.py +402 -0
  33. package/bundled/skills/recovery-workflow/SKILL.md +1 -0
  34. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +7 -3
  35. package/bundled/skills/refactor-planner/SKILL.md +51 -1
  36. package/bundled/skills/refactor-workflow/SKILL.md +4 -0
  37. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  {
2
- "frameworkVersion": "1.1.6",
3
- "bundledAt": "2026-04-04T16:32:42.297Z",
4
- "bundledFrom": "e0cb3ab"
2
+ "frameworkVersion": "1.1.7",
3
+ "bundledAt": "2026-04-05T07:42:19.846Z",
4
+ "bundledFrom": "b4e7b96"
5
5
  }
@@ -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: `app_name` (string), `features` (non-empty array)
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
- "app_name": "TaskFlow",
4
- "app_description": "A modern task management application with team collaboration, real-time updates, and analytics dashboard",
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
- # Reset bug status
441
- python3 "$SCRIPTS_DIR/update-bug-status.py" \
442
- --bug-list "$bug_list" \
443
- --state-dir "$STATE_DIR" \
444
- --bug-id "$bug_id" \
445
- --action reset >/dev/null 2>&1 || true
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) || true
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) || true
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) || true
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
- if "app_name" not in data:
80
- errors.append("Missing required field: app_name")
81
- elif not isinstance(data["app_name"], str) or not data["app_name"].strip():
82
- errors.append("app_name must be a non-empty string")
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 = {"high": 0, "medium": 1, "low": 2}
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"), 2)
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 (high first)
528
- _PRIORITY_ORDER = {"high": 0, "medium": 1, "low": 2}
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"), 2)
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"), 2)
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(" App: {}".format(app_name), inner) + " ║")
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) + " ║")