prizmkit 1.1.91 → 1.1.92
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/lib/branch.sh +8 -5
- package/bundled/dev-pipeline/lib/common.sh +122 -0
- package/bundled/dev-pipeline/lib/heartbeat.sh +13 -3
- package/bundled/dev-pipeline/run-bugfix.sh +150 -39
- package/bundled/dev-pipeline/run-feature.sh +98 -33
- package/bundled/dev-pipeline/run-refactor.sh +150 -39
- package/bundled/dev-pipeline/scripts/update-bug-status.py +39 -4
- package/bundled/dev-pipeline/scripts/update-feature-status.py +43 -6
- package/bundled/dev-pipeline/scripts/update-refactor-status.py +36 -4
- package/bundled/dev-pipeline/tests/test_auto_skip.py +92 -3
- package/bundled/dev-pipeline-windows/lib/branch.ps1 +17 -4
- package/bundled/dev-pipeline-windows/lib/common.ps1 +9 -0
- package/bundled/dev-pipeline-windows/lib/pipeline.ps1 +87 -29
- package/bundled/dev-pipeline-windows/scripts/update-bug-status.py +39 -4
- package/bundled/dev-pipeline-windows/scripts/update-feature-status.py +43 -6
- package/bundled/dev-pipeline-windows/scripts/update-refactor-status.py +36 -4
- package/bundled/skills/_metadata.json +1 -1
- package/package.json +1 -1
|
@@ -9,13 +9,14 @@ set -euo pipefail
|
|
|
9
9
|
# from a feature list.
|
|
10
10
|
#
|
|
11
11
|
# Usage:
|
|
12
|
-
# ./run-feature.sh run [.prizmkit/plans/feature-list.json]
|
|
12
|
+
# ./run-feature.sh run [.prizmkit/plans/feature-list.json] [options] Run all features
|
|
13
13
|
# ./run-feature.sh run <feature-id> [options] Run a single feature
|
|
14
14
|
# ./run-feature.sh status [.prizmkit/plans/feature-list.json] Show pipeline status
|
|
15
15
|
# ./run-feature.sh reset Clear all state
|
|
16
16
|
#
|
|
17
17
|
# Environment Variables:
|
|
18
|
-
# MAX_RETRIES Max retries per feature (default: 3)
|
|
18
|
+
# MAX_RETRIES Max code retries per feature (default: 3)
|
|
19
|
+
# MAX_INFRA_RETRIES Max infrastructure/provider retries per feature (default: 3)
|
|
19
20
|
# SESSION_TIMEOUT Session timeout in seconds (default: 0 = no limit)
|
|
20
21
|
# AI_CLI AI CLI command name (override; also readable from .prizmkit/config.json)
|
|
21
22
|
# CODEBUDDY_CLI Legacy alias for AI_CLI (deprecated, use AI_CLI instead)
|
|
@@ -45,6 +46,7 @@ SCRIPTS_DIR="$SCRIPT_DIR/scripts"
|
|
|
45
46
|
|
|
46
47
|
# Configuration (override via environment variables)
|
|
47
48
|
MAX_RETRIES=${MAX_RETRIES:-3}
|
|
49
|
+
MAX_INFRA_RETRIES=${MAX_INFRA_RETRIES:-3}
|
|
48
50
|
SESSION_TIMEOUT=${SESSION_TIMEOUT:-0}
|
|
49
51
|
HEARTBEAT_STALE_THRESHOLD=${HEARTBEAT_STALE_THRESHOLD:-600}
|
|
50
52
|
HEARTBEAT_INTERVAL=${HEARTBEAT_INTERVAL:-30}
|
|
@@ -162,9 +164,23 @@ spawn_and_wait_session() {
|
|
|
162
164
|
# Check for stale-kill marker (heartbeat killed the process due to no progress)
|
|
163
165
|
local stale_kill_marker="$session_dir/logs/stale-kill.json"
|
|
164
166
|
local was_stale_killed=false
|
|
167
|
+
local stale_kill_reason=""
|
|
165
168
|
if [[ -f "$stale_kill_marker" ]]; then
|
|
166
169
|
was_stale_killed=true
|
|
167
|
-
|
|
170
|
+
stale_kill_reason=$(python3 - "$stale_kill_marker" <<'PY' 2>/dev/null || true
|
|
171
|
+
import json, sys
|
|
172
|
+
try:
|
|
173
|
+
with open(sys.argv[1], encoding="utf-8") as fh:
|
|
174
|
+
print(json.load(fh).get("reason") or "")
|
|
175
|
+
except Exception:
|
|
176
|
+
pass
|
|
177
|
+
PY
|
|
178
|
+
)
|
|
179
|
+
if [[ "$stale_kill_reason" == "error_loop" ]]; then
|
|
180
|
+
log_warn "Session was killed by heartbeat monitor due to repeated read-offset/wasted-call errors"
|
|
181
|
+
else
|
|
182
|
+
log_warn "Session was stale-killed by heartbeat monitor (no progress for too long)"
|
|
183
|
+
fi
|
|
168
184
|
fi
|
|
169
185
|
|
|
170
186
|
local was_infra_error=false
|
|
@@ -221,8 +237,12 @@ spawn_and_wait_session() {
|
|
|
221
237
|
log_warn "Infrastructure errors are retried without consuming code retry budget"
|
|
222
238
|
session_status="infra_error"
|
|
223
239
|
elif [[ "$was_stale_killed" == true ]]; then
|
|
224
|
-
|
|
225
|
-
|
|
240
|
+
if [[ "$stale_kill_reason" == "error_loop" ]]; then
|
|
241
|
+
log_warn "Session killed due to repeated read-offset/wasted-call error loop"
|
|
242
|
+
else
|
|
243
|
+
log_warn "Session stale-killed (no progress for ${STALE_KILL_THRESHOLD}s)"
|
|
244
|
+
fi
|
|
245
|
+
log_warn "Heartbeat-killed sessions are treated as failed; dev branch is preserved for inspection"
|
|
226
246
|
session_status="crashed"
|
|
227
247
|
elif [[ $exit_code -ne 0 ]]; then
|
|
228
248
|
log_warn "Session exited with code $exit_code"
|
|
@@ -240,10 +260,10 @@ spawn_and_wait_session() {
|
|
|
240
260
|
# No commits found — check if there are uncommitted changes (session
|
|
241
261
|
# did work but didn't commit, e.g. context window exhausted)
|
|
242
262
|
local uncommitted=""
|
|
243
|
-
uncommitted=$(
|
|
263
|
+
uncommitted=$(prizm_git_status_safe "$project_root" | head -1 || true)
|
|
244
264
|
if [[ -n "$uncommitted" ]]; then
|
|
245
265
|
log_warn "Session exited cleanly but produced no commits (uncommitted changes found) — auto-committing..."
|
|
246
|
-
|
|
266
|
+
prizm_git_add_all_safe "$project_root" || true
|
|
247
267
|
if git -C "$project_root" commit --no-verify -m "chore($feature_id): auto-commit session work" 2>/dev/null; then
|
|
248
268
|
log_info "Auto-commit succeeded"
|
|
249
269
|
session_status="success"
|
|
@@ -262,10 +282,10 @@ spawn_and_wait_session() {
|
|
|
262
282
|
if [[ "$session_status" == "success" ]]; then
|
|
263
283
|
if git -C "$project_root" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
264
284
|
local dirty_files=""
|
|
265
|
-
dirty_files=$(
|
|
285
|
+
dirty_files=$(prizm_git_status_safe "$project_root" || true)
|
|
266
286
|
if [[ -n "$dirty_files" ]]; then
|
|
267
287
|
log_info "Auto-committing remaining session artifacts..."
|
|
268
|
-
|
|
288
|
+
prizm_git_add_all_safe "$project_root" || true
|
|
269
289
|
git -C "$project_root" commit --no-verify --amend --no-edit 2>/dev/null \
|
|
270
290
|
|| git -C "$project_root" commit --no-verify -m "chore($feature_id): include remaining session artifacts" 2>/dev/null \
|
|
271
291
|
|| true
|
|
@@ -385,8 +405,9 @@ finalize_feature_status_after_branch_return() {
|
|
|
385
405
|
local session_id="$3"
|
|
386
406
|
local session_status="$4"
|
|
387
407
|
local max_retries="$5"
|
|
388
|
-
local
|
|
389
|
-
local
|
|
408
|
+
local max_infra_retries="$6"
|
|
409
|
+
local session_dir="$7"
|
|
410
|
+
local base_branch="${8:-main}"
|
|
390
411
|
|
|
391
412
|
local feature_slug="${_SPAWN_FEATURE_SLUG:-}"
|
|
392
413
|
local progress_json="$session_dir/logs/progress.json"
|
|
@@ -422,6 +443,7 @@ finalize_feature_status_after_branch_return() {
|
|
|
422
443
|
--session-status "$session_status" \
|
|
423
444
|
--session-id "$session_id" \
|
|
424
445
|
--max-retries "$max_retries" \
|
|
446
|
+
--max-infra-retries "$max_infra_retries" \
|
|
425
447
|
--action update 2>&1) || {
|
|
426
448
|
log_error "Failed to update feature status: $update_output"
|
|
427
449
|
log_error ".prizmkit/plans/feature-list.json may be out of sync. Manual intervention needed."
|
|
@@ -467,6 +489,8 @@ for feat in data.get('features', []):
|
|
|
467
489
|
--state-dir "$STATE_DIR" \
|
|
468
490
|
--feature-id "$_interrupted_id" \
|
|
469
491
|
--session-status "failed" \
|
|
492
|
+
--max-retries "$MAX_RETRIES" \
|
|
493
|
+
--max-infra-retries "$MAX_INFRA_RETRIES" \
|
|
470
494
|
--action update 2>/dev/null || true
|
|
471
495
|
log_info "Feature $_interrupted_id marked as failed due to interrupt"
|
|
472
496
|
fi
|
|
@@ -592,6 +616,16 @@ run_one() {
|
|
|
592
616
|
SESSION_TIMEOUT="$1"
|
|
593
617
|
shift
|
|
594
618
|
;;
|
|
619
|
+
--max-infra-retries)
|
|
620
|
+
shift
|
|
621
|
+
if [[ $# -eq 0 ]]; then
|
|
622
|
+
log_error "--max-infra-retries requires a value"
|
|
623
|
+
exit 1
|
|
624
|
+
fi
|
|
625
|
+
prizm_require_positive_int "--max-infra-retries" "$1"
|
|
626
|
+
MAX_INFRA_RETRIES="$1"
|
|
627
|
+
shift
|
|
628
|
+
;;
|
|
595
629
|
F-*|f-*)
|
|
596
630
|
feature_id="$1"
|
|
597
631
|
shift
|
|
@@ -916,6 +950,8 @@ else:
|
|
|
916
950
|
--state-dir "$STATE_DIR" \
|
|
917
951
|
--feature-id "$feature_id" \
|
|
918
952
|
--session-status "failed" \
|
|
953
|
+
--max-retries "$MAX_RETRIES" \
|
|
954
|
+
--max-infra-retries "$MAX_INFRA_RETRIES" \
|
|
919
955
|
--action update 2>/dev/null || true
|
|
920
956
|
log_info "Feature $feature_id marked as failed due to interrupt"
|
|
921
957
|
fi
|
|
@@ -949,12 +985,13 @@ else:
|
|
|
949
985
|
if branch_create "$_proj_root" "$_branch_name" "$_source_branch"; then
|
|
950
986
|
_DEV_BRANCH_NAME="$_branch_name"
|
|
951
987
|
else
|
|
952
|
-
|
|
988
|
+
log_error "Failed to create branch: $_branch_name from $_source_branch — aborting to avoid running on $_source_branch"
|
|
989
|
+
return 1
|
|
953
990
|
fi
|
|
954
991
|
|
|
955
992
|
spawn_and_wait_session \
|
|
956
993
|
"$feature_id" "$feature_list" "$session_id" \
|
|
957
|
-
"$bootstrap_prompt" "$session_dir"
|
|
994
|
+
"$bootstrap_prompt" "$session_dir" "$MAX_RETRIES" "$feature_model" "$_ORIGINAL_BRANCH"
|
|
958
995
|
local session_status="$_SPAWN_RESULT"
|
|
959
996
|
|
|
960
997
|
# Merge dev branch back to original on success
|
|
@@ -976,12 +1013,11 @@ else:
|
|
|
976
1013
|
branch_ensure_return "$_proj_root" "$_ORIGINAL_BRANCH"
|
|
977
1014
|
|
|
978
1015
|
finalize_feature_status_after_branch_return \
|
|
979
|
-
"$feature_id" "$feature_list" "$session_id" "$session_status"
|
|
1016
|
+
"$feature_id" "$feature_list" "$session_id" "$session_status" "$MAX_RETRIES" "$MAX_INFRA_RETRIES" "$session_dir" "$_ORIGINAL_BRANCH"
|
|
980
1017
|
|
|
981
1018
|
# Commit feature status update on the original branch (after guaranteed return)
|
|
982
1019
|
if ! git -C "$_proj_root" diff --quiet "$feature_list" 2>/dev/null; then
|
|
983
|
-
|
|
984
|
-
git -C "$_proj_root" commit --no-verify -m "chore($feature_id): update feature status" 2>/dev/null || true
|
|
1020
|
+
prizm_git_commit_paths "$_proj_root" "chore($feature_id): update feature status" "$feature_list"
|
|
985
1021
|
fi
|
|
986
1022
|
|
|
987
1023
|
echo ""
|
|
@@ -1080,7 +1116,8 @@ main() {
|
|
|
1080
1116
|
if [[ -n "$features_filter" ]]; then
|
|
1081
1117
|
log_info "Features filter: $features_filter"
|
|
1082
1118
|
fi
|
|
1083
|
-
log_info "Max retries per feature: $MAX_RETRIES"
|
|
1119
|
+
log_info "Max code retries per feature: $MAX_RETRIES"
|
|
1120
|
+
log_info "Max infrastructure retries per feature: $MAX_INFRA_RETRIES"
|
|
1084
1121
|
if [[ $SESSION_TIMEOUT -gt 0 ]]; then
|
|
1085
1122
|
log_info "Session timeout: ${SESSION_TIMEOUT}s"
|
|
1086
1123
|
else
|
|
@@ -1259,28 +1296,41 @@ DEPLOY_PROMPT_EOF
|
|
|
1259
1296
|
fi
|
|
1260
1297
|
|
|
1261
1298
|
# Parse feature info
|
|
1262
|
-
local feature_id feature_title retry_count resume_phase
|
|
1299
|
+
local feature_id feature_title retry_count infra_error_count resume_phase
|
|
1263
1300
|
feature_id=$(echo "$next_feature" | jq -r '.feature_id')
|
|
1264
1301
|
feature_title=$(echo "$next_feature" | jq -r '.title')
|
|
1265
1302
|
retry_count=$(echo "$next_feature" | jq -r '.retry_count // 0')
|
|
1303
|
+
infra_error_count=$(echo "$next_feature" | jq -r '.infra_error_count // 0')
|
|
1266
1304
|
resume_phase=$(echo "$next_feature" | jq -r '.resume_from_phase // "null"')
|
|
1267
1305
|
|
|
1268
1306
|
echo ""
|
|
1269
1307
|
echo -e "${BOLD}────────────────────────────────────────────────────${NC}"
|
|
1270
1308
|
log_info "Feature: ${BOLD}$feature_id${NC} — $feature_title"
|
|
1271
|
-
log_info "
|
|
1309
|
+
log_info "Code retry: $retry_count / $MAX_RETRIES"
|
|
1310
|
+
log_info "Infrastructure retry: $infra_error_count / $MAX_INFRA_RETRIES"
|
|
1272
1311
|
if [[ "$resume_phase" != "null" ]]; then
|
|
1273
1312
|
log_info "Resuming from Phase $resume_phase"
|
|
1274
1313
|
fi
|
|
1275
1314
|
echo -e "${BOLD}────────────────────────────────────────────────────${NC}"
|
|
1276
1315
|
|
|
1277
|
-
#
|
|
1316
|
+
# Do not auto-commit an arbitrary dirty baseline on the original branch.
|
|
1317
|
+
# Only pipeline bookkeeping is committed explicitly; user/source WIP,
|
|
1318
|
+
# submodule dirt, specs, and root-level hidden tool temporary worktrees
|
|
1319
|
+
# must never be swept into main by a broad git add.
|
|
1278
1320
|
local _dirty_files=""
|
|
1279
|
-
_dirty_files=$(
|
|
1321
|
+
_dirty_files=$(prizm_git_status_non_bookkeeping "$_proj_root" "$STATE_DIR" "$feature_list" || true)
|
|
1280
1322
|
if [[ -n "$_dirty_files" ]]; then
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1323
|
+
log_error "Dirty working tree detected before $feature_id; refusing to commit it on $_ORIGINAL_BRANCH."
|
|
1324
|
+
log_error "Commit/stash your changes or start from a clean branch, then rerun the pipeline."
|
|
1325
|
+
printf '%s\n' "$_dirty_files" | sed 's/^/ /'
|
|
1326
|
+
break
|
|
1327
|
+
fi
|
|
1328
|
+
|
|
1329
|
+
local _bookkeeping_dirty=""
|
|
1330
|
+
_bookkeeping_dirty=$(prizm_git_status_bookkeeping "$_proj_root" "$STATE_DIR" "$feature_list" || true)
|
|
1331
|
+
if [[ -n "$_bookkeeping_dirty" ]]; then
|
|
1332
|
+
log_info "Committing pending pipeline bookkeeping before $feature_id..."
|
|
1333
|
+
prizm_git_commit_paths "$_proj_root" "chore($feature_id): include pipeline bookkeeping" "$feature_list" "$STATE_DIR"
|
|
1284
1334
|
fi
|
|
1285
1335
|
|
|
1286
1336
|
# Mark feature as in-progress BEFORE creating dev branch
|
|
@@ -1293,8 +1343,7 @@ DEPLOY_PROMPT_EOF
|
|
|
1293
1343
|
--action start >/dev/null 2>&1 || true
|
|
1294
1344
|
# Commit the in_progress status on the original branch
|
|
1295
1345
|
if ! git -C "$_proj_root" diff --quiet "$feature_list" 2>/dev/null; then
|
|
1296
|
-
|
|
1297
|
-
git -C "$_proj_root" commit --no-verify -m "chore($feature_id): mark in_progress" 2>/dev/null || true
|
|
1346
|
+
prizm_git_commit_paths "$_proj_root" "chore($feature_id): mark in_progress" "$feature_list"
|
|
1298
1347
|
fi
|
|
1299
1348
|
|
|
1300
1349
|
# Create per-feature dev branch (from the now-updated original branch)
|
|
@@ -1303,8 +1352,9 @@ DEPLOY_PROMPT_EOF
|
|
|
1303
1352
|
_DEV_BRANCH_NAME="$_feature_branch"
|
|
1304
1353
|
log_info "Dev branch: $_feature_branch"
|
|
1305
1354
|
else
|
|
1306
|
-
|
|
1355
|
+
log_error "Failed to create dev branch: $_feature_branch from $_ORIGINAL_BRANCH — aborting to avoid running on $_ORIGINAL_BRANCH"
|
|
1307
1356
|
_DEV_BRANCH_NAME=""
|
|
1357
|
+
break
|
|
1308
1358
|
fi
|
|
1309
1359
|
|
|
1310
1360
|
# Generate session ID and bootstrap prompt
|
|
@@ -1397,20 +1447,20 @@ DEPLOY_PROMPT_EOF
|
|
|
1397
1447
|
branch_ensure_return "$_proj_root" "$_ORIGINAL_BRANCH"
|
|
1398
1448
|
|
|
1399
1449
|
finalize_feature_status_after_branch_return \
|
|
1400
|
-
"$feature_id" "$feature_list" "$session_id" "$session_status" "$MAX_RETRIES" "$session_dir" "$_ORIGINAL_BRANCH"
|
|
1450
|
+
"$feature_id" "$feature_list" "$session_id" "$session_status" "$MAX_RETRIES" "$MAX_INFRA_RETRIES" "$session_dir" "$_ORIGINAL_BRANCH"
|
|
1401
1451
|
local item_status_after_session="${_SPAWN_ITEM_STATUS:-}"
|
|
1402
1452
|
|
|
1403
1453
|
# Commit feature status update on the original branch (after guaranteed return)
|
|
1404
1454
|
if ! git -C "$_proj_root" diff --quiet "$feature_list" 2>/dev/null; then
|
|
1405
|
-
|
|
1406
|
-
git -C "$_proj_root" commit --no-verify -m "chore($feature_id): update feature status" 2>/dev/null || true
|
|
1455
|
+
prizm_git_commit_paths "$_proj_root" "chore($feature_id): update feature status" "$feature_list"
|
|
1407
1456
|
fi
|
|
1408
1457
|
|
|
1409
1458
|
session_count=$((session_count + 1))
|
|
1410
1459
|
total_subagent_calls=$((total_subagent_calls + _SUBAGENT_COUNT))
|
|
1411
1460
|
|
|
1412
1461
|
# Stop-on-failure: abort only after the task is actually marked failed.
|
|
1413
|
-
# Pending retry outcomes
|
|
1462
|
+
# Pending retry outcomes keep running; both code and infra failures have
|
|
1463
|
+
# bounded budgets in update-feature-status.py.
|
|
1414
1464
|
if [[ "$session_status" != "success" && "$STOP_ON_FAILURE" == "1" && "$item_status_after_session" == "failed" ]]; then
|
|
1415
1465
|
echo ""
|
|
1416
1466
|
log_error "════════════════════════════════════════════════════"
|
|
@@ -1420,7 +1470,7 @@ DEPLOY_PROMPT_EOF
|
|
|
1420
1470
|
log_error "════════════════════════════════════════════════════"
|
|
1421
1471
|
break
|
|
1422
1472
|
elif [[ "$session_status" != "success" && "$STOP_ON_FAILURE" == "1" ]]; then
|
|
1423
|
-
log_info "STOP_ON_FAILURE: $feature_id is ${item_status_after_session:-unknown}; retry budget not exhausted, continuing."
|
|
1473
|
+
log_info "STOP_ON_FAILURE: $feature_id is ${item_status_after_session:-unknown}; code/infra retry budget not exhausted, continuing."
|
|
1424
1474
|
fi
|
|
1425
1475
|
|
|
1426
1476
|
# Brief pause before next iteration
|
|
@@ -1459,9 +1509,14 @@ show_help() {
|
|
|
1459
1509
|
echo " --clean Delete artifacts and reset before running"
|
|
1460
1510
|
echo " --no-reset Skip feature status reset step"
|
|
1461
1511
|
echo " --timeout N Session timeout in seconds (default: 0 = no limit)"
|
|
1512
|
+
echo " --max-infra-retries N Max infrastructure/provider retries (default: 3)"
|
|
1513
|
+
echo ""
|
|
1514
|
+
echo "Pipeline Options (run all):"
|
|
1515
|
+
echo " --max-infra-retries N Max infrastructure/provider retries (default: 3)"
|
|
1462
1516
|
echo ""
|
|
1463
1517
|
echo "Environment Variables:"
|
|
1464
|
-
echo " MAX_RETRIES Max retries per feature (default: 3)"
|
|
1518
|
+
echo " MAX_RETRIES Max code retries per feature (default: 3)"
|
|
1519
|
+
echo " MAX_INFRA_RETRIES Max infrastructure/provider retries per feature (default: 3)"
|
|
1465
1520
|
echo " SESSION_TIMEOUT Session timeout in seconds (default: 0 = no limit)"
|
|
1466
1521
|
echo " AI_CLI AI CLI command name (auto-detected: cbc, claude, or codex)"
|
|
1467
1522
|
echo " MODEL AI model ID (e.g. claude-opus-4.6, claude-sonnet-4.6, claude-haiku-4.5)"
|
|
@@ -1512,6 +1567,16 @@ case "${1:-run}" in
|
|
|
1512
1567
|
_run_features_filter="$1"
|
|
1513
1568
|
shift
|
|
1514
1569
|
;;
|
|
1570
|
+
--max-infra-retries)
|
|
1571
|
+
shift
|
|
1572
|
+
if [[ $# -eq 0 ]]; then
|
|
1573
|
+
log_error "--max-infra-retries requires a value"
|
|
1574
|
+
exit 1
|
|
1575
|
+
fi
|
|
1576
|
+
prizm_require_positive_int "--max-infra-retries" "$1"
|
|
1577
|
+
MAX_INFRA_RETRIES="$1"
|
|
1578
|
+
shift
|
|
1579
|
+
;;
|
|
1515
1580
|
*)
|
|
1516
1581
|
_run_feature_list="$1"
|
|
1517
1582
|
shift
|