aidevops 3.34.8 → 3.34.10

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/README.md CHANGED
@@ -25,7 +25,7 @@ token efficiency, and quality control built in.**
25
25
  [![Maintainability](https://qlty.sh/gh/marcusquinn/projects/aidevops/maintainability.svg)](https://qlty.sh/gh/marcusquinn/projects/aidevops)
26
26
  [![Codacy Badge](https://app.codacy.com/project/badge/Grade/2b1adbd66c454dae92234341e801b984)](https://app.codacy.com/gh/marcusquinn/aidevops/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
27
27
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
28
- [![Version](https://img.shields.io/badge/Version-3.34.8-blue.svg)](https://github.com/marcusquinn/aidevops/releases)
28
+ [![Version](https://img.shields.io/badge/Version-3.34.10-blue.svg)](https://github.com/marcusquinn/aidevops/releases)
29
29
  [![npm version](https://img.shields.io/npm/v/aidevops)](https://www.npmjs.com/package/aidevops)
30
30
  [![Homebrew](https://img.shields.io/badge/homebrew-marcusquinn%2Ftap-orange)](https://github.com/marcusquinn/homebrew-tap)
31
31
 
package/VERSION CHANGED
@@ -1 +1 @@
1
- 3.34.8
1
+ 3.34.10
package/aidevops.sh CHANGED
@@ -5,7 +5,7 @@
5
5
  # AI DevOps Framework CLI
6
6
  # Usage: aidevops <command> [options]
7
7
  #
8
- # Version: 3.34.8
8
+ # Version: 3.34.10
9
9
 
10
10
  set -euo pipefail
11
11
 
@@ -310,7 +310,17 @@ _run_update_source_access_reconciliation() {
310
310
  return 0
311
311
  fi
312
312
 
313
- print_warning "AIDEVOPS_DEFERRED_ACTION action=source-access-reconcile command='aidevops setup --scope source-access'"
313
+ if [[ -t 0 && -t 1 ]]; then
314
+ print_info "Reconciling the protected source-access broker..."
315
+ if AIDEVOPS_SOURCE_ACCESS_INTERACTIVE=true bash "$INSTALL_DIR/setup.sh" --stage source-access; then
316
+ print_success "Protected source-access broker reconciled"
317
+ return 0
318
+ fi
319
+ print_warning "Source-access broker maintenance was blocked; it will retry during the next interactive update"
320
+ return 0
321
+ fi
322
+
323
+ print_warning "Source-access broker maintenance requires an attached terminal for privilege confirmation; the next interactive update will retry automatically"
314
324
  return 0
315
325
  }
316
326
 
@@ -347,19 +357,10 @@ cmd_update_help() {
347
357
  }
348
358
 
349
359
  cmd_update() {
350
- # Update is operational, not compositional. Do not let any reachable Git,
351
- # package-manager, migration, or setup subprocess inherit an editor or pager.
352
- # Local declarations restore the caller's environment when this function exits.
353
- local EDITOR=/usr/bin/false
354
- local VISUAL=/usr/bin/false
355
- local GIT_EDITOR=/usr/bin/false
356
- local GIT_SEQUENCE_EDITOR=/usr/bin/false
357
- local GIT_PAGER=cat
358
- local PAGER=cat
359
- local GIT_TERMINAL_PROMPT=0
360
+ local EDITOR=/usr/bin/false VISUAL=/usr/bin/false GIT_EDITOR=/usr/bin/false
361
+ local GIT_SEQUENCE_EDITOR=/usr/bin/false GIT_PAGER="cat" PAGER="cat" GIT_TERMINAL_PROMPT=0
360
362
  export EDITOR VISUAL GIT_EDITOR GIT_SEQUENCE_EDITOR GIT_PAGER PAGER GIT_TERMINAL_PROMPT
361
363
  local skip_project_sync=false
362
- local reconcile_repo_verify=false
363
364
  local update_output_mode="${AIDEVOPS_OUTPUT_MODE:-auto}"
364
365
  local arg
365
366
  for arg in "$@"; do
@@ -375,122 +376,128 @@ cmd_update() {
375
376
  current_version=$(get_version)
376
377
  print_info "Current version: $current_version"
377
378
  print_info "Fetching latest version..."
379
+ _AIDEVOPS_UPDATE_RECONCILE_REPO_VERIFY=false
380
+ _update_install_framework "$current_version" "$update_output_mode" || return 1
381
+ _update_finish "$skip_project_sync"
382
+ return 0
383
+ }
378
384
 
385
+ _update_install_framework() {
386
+ local current_version="$1" update_output_mode="$2"
379
387
  if check_dir "$INSTALL_DIR/.git"; then
380
- cd "$INSTALL_DIR" || exit 1
381
- if ! git diff --quiet 2>/dev/null || ! git diff --cached --quiet 2>/dev/null; then
382
- print_error "Refusing to update: tracked local changes exist in $INSTALL_DIR"
383
- git status --short
384
- print_info "Commit or stash these changes, then rerun aidevops update."
385
- return 1
386
- fi
387
- local current_branch
388
- current_branch=$(git branch --show-current 2>/dev/null || echo "")
389
- if [[ "$current_branch" != "main" ]]; then
390
- print_info "Switching to main branch..."
391
- if ! git checkout main --quiet 2>/dev/null && ! git checkout -b main origin/main --quiet 2>/dev/null; then
392
- print_error "Failed to switch to main; refusing to update the active '$current_branch' branch."
393
- return 1
394
- fi
395
- fi
396
- local local_hash
397
- local_hash=$(git rev-parse HEAD) || return 1
398
- if ! _update_fetch_main main; then
399
- print_error "Failed to fetch origin/main; no update was applied."
400
- return 1
401
- fi
402
- local remote_hash
403
- remote_hash=$(git rev-parse origin/main) || return 1
404
- if [[ "$local_hash" != "$remote_hash" ]] && git merge-base --is-ancestor "$remote_hash" "$local_hash" 2>/dev/null; then
405
- print_error "Refusing to update: local commits exist on main."
406
- print_info "Preserve or reconcile the local history in $INSTALL_DIR, then rerun aidevops update."
388
+ _update_existing_install "$current_version" "$update_output_mode" || return 1
389
+ else
390
+ _update_fresh_install || return 1
391
+ fi
392
+ return 0
393
+ }
394
+
395
+ _update_existing_install() {
396
+ local current_version="$1" update_output_mode="$2"
397
+ cd "$INSTALL_DIR" || exit 1
398
+ if ! git diff --quiet 2>/dev/null || ! git diff --cached --quiet 2>/dev/null; then
399
+ print_error "Refusing to update: tracked local changes exist in $INSTALL_DIR"
400
+ git status --short
401
+ print_info "Commit or stash these changes, then rerun aidevops update."
402
+ return 1
403
+ fi
404
+ local current_branch
405
+ current_branch=$(git branch --show-current 2>/dev/null || echo "")
406
+ if [[ "$current_branch" != "main" ]]; then
407
+ print_info "Switching to main branch..."
408
+ if ! git checkout main --quiet 2>/dev/null && ! git checkout -b main origin/main --quiet 2>/dev/null; then
409
+ print_error "Failed to switch to main; refusing to update the active '$current_branch' branch."
407
410
  return 1
408
411
  fi
409
- if [[ "$local_hash" == "$remote_hash" ]]; then
410
- print_success "Framework already up to date!"
411
- local repo_version deployed_version
412
- repo_version=$(cat "$INSTALL_DIR/VERSION" 2>/dev/null || echo "unknown")
413
- deployed_version=$(cat "$HOME/.aidevops/agents/VERSION" 2>/dev/null || echo "none")
414
- if [[ "$repo_version" != "$deployed_version" ]]; then
415
- print_warning "Deployed agents ($deployed_version) don't match repo ($repo_version)"
416
- print_info "Re-running incremental setup to sync agents..."
417
- _run_update_setup_transaction "$update_output_mode" "$local_hash" || return 1
418
- else
419
- # t2706: VERSION matches but .deployed-sha may lag HEAD when
420
- # fixes land between releases. Detect and redeploy on framework
421
- # code drift (inline so bootstrap doesn't depend on the deployed
422
- # aidevops-update-check.sh already carrying the same check).
423
- # Docs-only drift is intentionally skipped — no runtime impact.
424
- local stamp_file="$HOME/.aidevops/.deployed-sha"
425
- if [[ -f "$stamp_file" ]]; then
426
- local deployed_sha has_code_drift=0
427
- deployed_sha=$(tr -d '[:space:]' <"$stamp_file" 2>/dev/null) || deployed_sha=""
428
- if [[ -n "$deployed_sha" && "$deployed_sha" != "$local_hash" ]]; then
429
- if _update_repo_verify_files_changed "$deployed_sha" "$local_hash"; then reconcile_repo_verify=true; fi
430
- # Per Gemini code-review on PR #20342: use git's path filter +
431
- # `grep -q .` to detect drift across the full set of deploy-affecting
432
- # paths (not just .agents/ subdirs — also setup.sh, .agents/scripts/setup/modules/,
433
- # and aidevops.sh itself, which are deployed/sourced by setup).
434
- if git -C "$INSTALL_DIR" diff --name-only "$deployed_sha" "$local_hash" -- \
435
- .agents/scripts/ .agents/agents/ .agents/workflows/ .agents/prompts/ .agents/hooks/ \
436
- setup.sh .agents/scripts/setup/modules/ aidevops.sh 2>/dev/null | grep -q .; then
437
- has_code_drift=1
438
- fi
439
- if [[ "$has_code_drift" -eq 1 ]]; then
440
- print_warning "Deployed scripts drifted (${deployed_sha:0:7}→${local_hash:0:7})"
441
- print_info "Re-running incremental setup to deploy latest scripts..."
442
- _run_update_setup_transaction "$update_output_mode" "$local_hash" || return 1
443
- fi
444
- # GH#21735: workflow templates can change between
445
- # releases without triggering has_code_drift (templates
446
- # live outside the deploy-affecting paths). Check the
447
- # template subset separately and surface drift.
448
- _update_check_workflow_drift "$deployed_sha" "$local_hash"
449
- fi
450
- fi
451
- fi
452
- else
453
- print_info "Applying latest changes..."
454
- local old_hash
455
- old_hash="$local_hash"
456
- if [[ "${_AIDEVOPS_UPDATE_CANONICAL_FAST_FORWARDED:-false}" == "true" ]]; then
457
- :
458
- elif git merge --ff-only "$remote_hash" --quiet; then
459
- :
460
- else
461
- print_error "Fast-forward update failed; preserving local history."
462
- print_info "Review $INSTALL_DIR, resolve the divergence, then rerun aidevops update."
463
- return 1
464
- fi
465
- local new_version new_hash
466
- new_version=$(get_version)
467
- new_hash=$(git rev-parse HEAD)
468
- if [[ "$new_hash" != "$remote_hash" ]]; then
469
- print_error "Updated HEAD does not match the fetched origin/main commit; refusing to run setup."
470
- return 1
471
- fi
472
- if [[ "$old_hash" != "$new_hash" ]]; then
473
- if _update_repo_verify_files_changed "$old_hash" "$new_hash"; then reconcile_repo_verify=true; fi
474
- _update_render_changelog "$old_hash" "$new_hash" "$current_version"
475
- # GH#21735: surface workflow template drift so the
476
- # operator can resync downstream callers before CI bites.
477
- _update_check_workflow_drift "$old_hash" "$new_hash"
478
- fi
479
- echo ""
480
- # Verify supply chain integrity before applying changes
481
- _update_verify_signature
482
- echo ""
483
- print_info "Running incremental setup to apply changes (falls back to full setup if needed)..."
484
- _run_update_setup_transaction "$update_output_mode" "$new_hash" || return 1
485
- print_success "Updated to version $new_version (agents deployed)"
486
- fi
412
+ fi
413
+ local local_hash remote_hash
414
+ local_hash=$(git rev-parse HEAD) || return 1
415
+ if ! _update_fetch_main main; then
416
+ print_error "Failed to fetch origin/main; no update was applied."
417
+ return 1
418
+ fi
419
+ remote_hash=$(git rev-parse origin/main) || return 1
420
+ if [[ "$local_hash" != "$remote_hash" ]] && git merge-base --is-ancestor "$remote_hash" "$local_hash" 2>/dev/null; then
421
+ print_error "Refusing to update: local commits exist on main."
422
+ print_info "Preserve or reconcile the local history in $INSTALL_DIR, then rerun aidevops update."
423
+ return 1
424
+ fi
425
+ if [[ "$local_hash" == "$remote_hash" ]]; then
426
+ _update_already_current "$local_hash" "$update_output_mode"
487
427
  else
488
- _update_fresh_install || return 1
428
+ _update_apply_remote "$local_hash" "$remote_hash" "$current_version" "$update_output_mode"
489
429
  fi
430
+ return 0
431
+ }
432
+
433
+ _update_already_current() {
434
+ local local_hash="$1" update_output_mode="$2"
435
+ print_success "Framework already up to date!"
436
+ local repo_version deployed_version
437
+ repo_version=$(cat "$INSTALL_DIR/VERSION" 2>/dev/null || echo "unknown")
438
+ deployed_version=$(cat "$HOME/.aidevops/agents/VERSION" 2>/dev/null || echo "none")
439
+ if [[ "$repo_version" != "$deployed_version" ]]; then
440
+ print_warning "Deployed agents ($deployed_version) don't match repo ($repo_version)"
441
+ print_info "Re-running incremental setup to sync agents..."
442
+ _run_update_setup_transaction "$update_output_mode" "$local_hash"
443
+ return $?
444
+ fi
445
+ local stamp_file="$HOME/.aidevops/.deployed-sha" deployed_sha=""
446
+ [[ -f "$stamp_file" ]] || return 0
447
+ deployed_sha=$(tr -d '[:space:]' <"$stamp_file" 2>/dev/null) || deployed_sha=""
448
+ [[ -n "$deployed_sha" && "$deployed_sha" != "$local_hash" ]] || return 0
449
+ if _update_repo_verify_files_changed "$deployed_sha" "$local_hash"; then _AIDEVOPS_UPDATE_RECONCILE_REPO_VERIFY=true; fi
450
+ if git -C "$INSTALL_DIR" diff --name-only "$deployed_sha" "$local_hash" -- .agents/scripts/ .agents/agents/ .agents/workflows/ .agents/prompts/ .agents/hooks/ setup.sh .agents/scripts/setup/modules/ aidevops.sh 2>/dev/null | grep -q .; then
451
+ print_warning "Deployed scripts drifted (${deployed_sha:0:7}→${local_hash:0:7})"
452
+ print_info "Re-running incremental setup to deploy latest scripts..."
453
+ _run_update_setup_transaction "$update_output_mode" "$local_hash" || return 1
454
+ fi
455
+ _update_check_workflow_drift "$deployed_sha" "$local_hash"
456
+ return 0
457
+ }
458
+
459
+ _update_apply_remote() {
460
+ local old_hash="$1" remote_hash="$2" current_version="$3" update_output_mode="$4"
461
+ print_info "Applying latest changes..."
462
+ if [[ "${_AIDEVOPS_UPDATE_CANONICAL_FAST_FORWARDED:-false}" != "true" ]] && ! git merge --ff-only "$remote_hash" --quiet; then
463
+ print_error "Fast-forward update failed; preserving local history."
464
+ print_info "Review $INSTALL_DIR, resolve the divergence, then rerun aidevops update."
465
+ return 1
466
+ fi
467
+ local new_version new_hash
468
+ new_version=$(get_version)
469
+ new_hash=$(git rev-parse HEAD)
470
+ if [[ "$new_hash" != "$remote_hash" ]]; then
471
+ print_error "Updated HEAD does not match the fetched origin/main commit; refusing to run setup."
472
+ return 1
473
+ fi
474
+ if [[ "$old_hash" != "$new_hash" ]]; then
475
+ if _update_repo_verify_files_changed "$old_hash" "$new_hash"; then _AIDEVOPS_UPDATE_RECONCILE_REPO_VERIFY=true; fi
476
+ _update_render_changelog "$old_hash" "$new_hash" "$current_version"
477
+ _update_check_workflow_drift "$old_hash" "$new_hash"
478
+ fi
479
+ echo ""
480
+ _update_verify_signature
481
+ echo ""
482
+ print_info "Running incremental setup to apply changes (falls back to full setup if needed)..."
483
+ _run_update_setup_transaction "$update_output_mode" "$new_hash" || return 1
484
+ print_success "Updated to version $new_version (agents deployed)"
485
+ return 0
486
+ }
487
+
488
+ _update_start_pulse() {
489
+ [[ "${AIDEVOPS_SKIP_PULSE_RESTART:-0}" != "1" ]] || return 0
490
+ local pulse_helper="${AGENTS_DIR}/scripts/pulse-lifecycle-helper.sh"
491
+ [[ -x "$pulse_helper" ]] && "$pulse_helper" start >/dev/null 2>&1 || print_warning "Pulse start failed (non-fatal)"
492
+ return 0
493
+ }
494
+
495
+ _update_finish() {
496
+ local skip_project_sync="$1"
490
497
 
491
498
  _run_update_source_access_reconciliation
492
499
  _update_sync_projects "$skip_project_sync" "$(get_version)"
493
- if [[ "$skip_project_sync" != "$_AIDEVOPS_UPDATE_TRUE" && "$reconcile_repo_verify" == "$_AIDEVOPS_UPDATE_TRUE" ]]; then
500
+ if [[ "$skip_project_sync" != "$_AIDEVOPS_UPDATE_TRUE" && "${_AIDEVOPS_UPDATE_RECONCILE_REPO_VERIFY:-false}" == "$_AIDEVOPS_UPDATE_TRUE" ]]; then
494
501
  _update_reconcile_repo_verify
495
502
  fi
496
503
  _update_check_homebrew
@@ -532,13 +539,7 @@ cmd_update() {
532
539
  # 'start' subcommand does not check it directly — only 'restart' and
533
540
  # 'restart-if-running' do). Non-fatal: a pulse start failure should
534
541
  # not fail the update.
535
- if [[ "${AIDEVOPS_SKIP_PULSE_RESTART:-0}" != "1" ]]; then
536
- local _pulse_helper="${AGENTS_DIR}/scripts/pulse-lifecycle-helper.sh"
537
- if [[ -x "$_pulse_helper" ]]; then
538
- "$_pulse_helper" start >/dev/null 2>&1 || print_warning "Pulse start failed (non-fatal)"
539
- fi
540
- fi
541
-
542
+ _update_start_pulse
542
543
  return 0
543
544
  }
544
545
 
@@ -1180,7 +1181,7 @@ _help_commands() {
1180
1181
  return 0
1181
1182
  }
1182
1183
 
1183
- _help_detailed_sections() {
1184
+ _help_security_sections() {
1184
1185
  echo "Security:"
1185
1186
  echo " aidevops security # Run ALL checks (posture + hygiene + supply chain)"
1186
1187
  echo " aidevops security posture # Interactive security posture setup (gopass, gh, SSH)"
@@ -1241,6 +1242,11 @@ _help_detailed_sections() {
1241
1242
  echo " aidevops client-format extract # 1. Re-extract constants"
1242
1243
  echo " aidevops client-format canary # 2. Verify against real CLI"
1243
1244
  echo ""
1245
+ return 0
1246
+ }
1247
+
1248
+ _help_detailed_sections_content() {
1249
+ _help_security_sections
1244
1250
  echo "Secrets:"
1245
1251
  echo " aidevops secret set NAME # Store a secret (hidden input)"
1246
1252
  echo " aidevops secret list # List secret names (never values)"
@@ -1249,26 +1255,7 @@ _help_detailed_sections() {
1249
1255
  echo " aidevops secret import # Import from credentials.sh to gopass"
1250
1256
  echo " aidevops secret status # Show backend status"
1251
1257
  echo ""
1252
- echo "Vault:"
1253
- echo " aidevops vault init # Create local encrypted Vault metadata"
1254
- echo " aidevops vault status # Show uninitialized/locked/unlocked/corrupted"
1255
- echo " aidevops vault unlock # Unlock into an in-memory local broker"
1256
- echo " aidevops vault lock # Stop broker and forget in-memory keys"
1257
- echo " aidevops vault read NAME # Read protected data through broker"
1258
- echo " aidevops vault update NAME # Encrypt stdin value through broker"
1259
- echo ""
1260
- echo "GitHub App Auth:"
1261
- echo " aidevops github-app-auth status --json # Show active auth mode and budgets"
1262
- echo " aidevops github-app-auth route issue-list # Explain route decision"
1263
- echo " aidevops github-app-auth rate-limit --json # Show cached per-pool budgets"
1264
- echo ""
1265
- echo "Feature Toggles:"
1266
- echo " aidevops config list # List all toggles with current values"
1267
- echo " aidevops config get <key> # Get a toggle value"
1268
- echo " aidevops config set <k> <v> # Set a toggle (true/false)"
1269
- echo " aidevops config reset [key] # Reset toggle(s) to defaults"
1270
- echo " aidevops config path # Show config file path"
1271
- echo ""
1258
+ _help_vault_and_config_sections
1272
1259
  echo "Knowledge Plane:"
1273
1260
  echo " aidevops knowledge init repo # Provision _knowledge/ in current repo"
1274
1261
  echo " aidevops knowledge init personal # Provision at ~/.aidevops/.agent-workspace/knowledge/"
@@ -1343,6 +1330,11 @@ _help_detailed_sections() {
1343
1330
  return 0
1344
1331
  }
1345
1332
 
1333
+ _help_detailed_sections() {
1334
+ _help_detailed_sections_content
1335
+ return 0
1336
+ }
1337
+
1346
1338
  _help_management_sections() {
1347
1339
  echo "Auto-Update:"
1348
1340
  echo " aidevops auto-update enable # Poll for updates every 10 min"
@@ -1830,48 +1822,81 @@ _cmd_client_format() {
1830
1822
  }
1831
1823
 
1832
1824
  # Main entry point
1825
+ _main_badges() {
1826
+ local subcommand="${1:-}"
1827
+ local sync_helper="badges-sync-helper.sh"
1828
+ case "$subcommand" in
1829
+ render)
1830
+ shift
1831
+ local helper="$HOME/.aidevops/agents/scripts/readme-badges-helper.sh"
1832
+ [[ -f "$helper" ]] || helper="$AGENTS_DIR/scripts/readme-badges-helper.sh"
1833
+ if [[ -f "$helper" ]]; then bash "$helper" render "$@"; else print_error "readme-badges-helper.sh not found. Run: aidevops update"; return 1; fi
1834
+ ;;
1835
+ check) shift; _dispatch_helper "badges-check-helper.sh" "badges-check-helper.sh" "$@" ;;
1836
+ sync) shift; _dispatch_helper "$sync_helper" "$sync_helper" "$@" ;;
1837
+ install) shift; _dispatch_helper "$sync_helper" "$sync_helper" --workflow-only "$@" ;;
1838
+ help | --help | -h | "")
1839
+ echo ""
1840
+ echo "aidevops badges — README badge block and repo metrics workflow management (t2975)"
1841
+ echo ""
1842
+ echo "Subcommands:"
1843
+ echo " render <slug> Print canonical badge block for a repo"
1844
+ echo " check [--repo SLUG] [--json] Detect badge drift across managed repos"
1845
+ echo " sync [--repo SLUG] [--apply] Inject badge block + generate metrics + install workflow"
1846
+ echo " install [--repo SLUG] [--apply] Install repo metrics refresh workflow only"
1847
+ echo ""
1848
+ echo "Options (check/sync/install):"
1849
+ echo " --repo SLUG Limit to a single repo"
1850
+ echo " --apply Actually perform the sync (default: dry-run)"
1851
+ echo " --json Machine-readable output"
1852
+ echo " --verbose Show diff summaries (check only)"
1853
+ echo ""
1854
+ echo "Examples:"
1855
+ echo " aidevops badges check # scan all repos for badge drift"
1856
+ echo " aidevops badges check --json | jq '.[]' # machine-readable output"
1857
+ echo " aidevops badges render owner/repo # print badge block"
1858
+ echo " aidevops badges sync # dry-run sync across all repos"
1859
+ echo " aidevops badges sync --repo owner/r --apply # apply to a single repo"
1860
+ echo " aidevops metrics generate [PATH] # generate local docs/metrics artifacts"
1861
+ echo ""
1862
+ ;;
1863
+ *) print_error "Unknown badges subcommand: $subcommand (try render|check|sync|install|help)"; return 1 ;;
1864
+ esac
1865
+ return 0
1866
+ }
1867
+
1833
1868
  main() {
1834
1869
  local command="${1:-help}"
1835
1870
  local check_workflows_helper="check-workflows-helper.sh"
1871
+ local update_help="help"
1836
1872
  shift || true
1837
-
1838
- # Help for inventory commands must bypass repository auto-detection and
1839
- # version checks. Those startup checks may read the repository registry,
1840
- # while help must remain bounded and side-effect-free.
1841
- if [[ "$command" == "check-workflows" || "$command" == "workflows" ]]; then
1842
- local arg
1843
- for arg in "$@"; do
1844
- case "$arg" in
1845
- -h | --help)
1846
- _dispatch_helper "$check_workflows_helper" "$check_workflows_helper" "$@"
1847
- return $?
1848
- ;;
1849
- esac
1850
- done
1851
- fi
1852
-
1853
- # Update help must return before startup checks or cmd_update(), which can
1854
- # inspect repositories, fetch remotes, deploy agents, and synchronize projects.
1855
1873
  if [[ "$command" == "update" || "$command" == "upgrade" || "$command" == "u" ]]; then
1856
1874
  local arg
1857
1875
  for arg in "$@"; do
1858
- case "$arg" in
1859
- help | -h | --help)
1860
- cmd_update_help
1861
- return $?
1862
- ;;
1863
- esac
1876
+ if [[ "$arg" == "$update_help" || "$arg" == "-h" || "$arg" == "--help" ]]; then cmd_update_help; return $?; fi
1864
1877
  done
1878
+ _main_check_unregistered "$command"
1879
+ cmd_update "$@"
1880
+ return $?
1865
1881
  fi
1866
-
1867
- # Auto-detect unregistered repo on any command (silent check)
1882
+ _main_early_help "$command" "$check_workflows_helper" "$@" && return 0
1868
1883
  _main_check_unregistered "$command"
1884
+ _main_check_version
1885
+ _main_dispatch "$command" "$check_workflows_helper" "$@"
1886
+ }
1869
1887
 
1870
- # Check if agents need updating (skip for update command itself)
1871
- if [[ "$command" != "update" && "$command" != "upgrade" && "$command" != "u" ]]; then
1872
- _main_check_version
1873
- fi
1888
+ _main_early_help() {
1889
+ local command="$1" check_workflows_helper="$2" arg
1890
+ shift 2
1891
+ for arg in "$@"; do
1892
+ if [[ "$command" == "check-workflows" || "$command" == "workflows" ]] && [[ "$arg" == "-h" || "$arg" == "--help" ]]; then _dispatch_helper "$check_workflows_helper" "$check_workflows_helper" "$@"; return $?; fi
1893
+ done
1894
+ return 1
1895
+ }
1874
1896
 
1897
+ _main_dispatch() {
1898
+ local command="$1" check_workflows_helper="$2"
1899
+ shift 2
1875
1900
  case "$command" in
1876
1901
  init | i) cmd_init "$@" ;;
1877
1902
  setup) cmd_setup "$@" ;;
@@ -1897,76 +1922,7 @@ main() {
1897
1922
  check-workflows | workflows) _dispatch_helper "$check_workflows_helper" "$check_workflows_helper" "$@" ;;
1898
1923
  sync-workflows) _dispatch_helper "sync-workflows-helper.sh" "sync-workflows-helper.sh" "$@" ;;
1899
1924
  metrics) _dispatch_helper "repo-metrics-helper.sh" "repo-metrics-helper.sh" "$@" ;;
1900
- badges)
1901
- # Badge management: render | check | sync | install (t2975)
1902
- # Bare 'aidevops badges' with no subcommand shows a usage summary.
1903
- # Subcommands:
1904
- # render <slug> — render canonical badge block for a repo
1905
- # check [--repo SLUG] [--json] [--verbose] — cross-repo drift check
1906
- # sync [--repo SLUG] [--apply] — inject badge block + generate metrics + install workflow
1907
- # install [--repo SLUG] [--apply] — install repo metrics refresh workflow only
1908
- local _badges_sub="${1:-help}"
1909
- local _badges_check_h="badges-check-helper.sh"
1910
- local _badges_sync_h="badges-sync-helper.sh"
1911
- case "$_badges_sub" in
1912
- render)
1913
- shift
1914
- local _render_helper
1915
- _render_helper=$(bash -c '
1916
- d="$HOME/.aidevops/agents/scripts/readme-badges-helper.sh"
1917
- l="'"$AGENTS_DIR"'/scripts/readme-badges-helper.sh"
1918
- [[ -f "$d" ]] && echo "$d" || echo "$l"
1919
- ')
1920
- if [[ -f "$_render_helper" ]]; then
1921
- bash "$_render_helper" render "$@"
1922
- else
1923
- print_error "readme-badges-helper.sh not found. Run: aidevops update"
1924
- exit 1
1925
- fi
1926
- ;;
1927
- check)
1928
- shift
1929
- _dispatch_helper "$_badges_check_h" "$_badges_check_h" "$@"
1930
- ;;
1931
- sync)
1932
- shift
1933
- _dispatch_helper "$_badges_sync_h" "$_badges_sync_h" "$@"
1934
- ;;
1935
- install)
1936
- shift
1937
- _dispatch_helper "$_badges_sync_h" "$_badges_sync_h" --workflow-only "$@"
1938
- ;;
1939
- help | --help | -h | "")
1940
- echo ""
1941
- echo "aidevops badges — README badge block and repo metrics workflow management (t2975)"
1942
- echo ""
1943
- echo "Subcommands:"
1944
- echo " render <slug> Print canonical badge block for a repo"
1945
- echo " check [--repo SLUG] [--json] Detect badge drift across managed repos"
1946
- echo " sync [--repo SLUG] [--apply] Inject badge block + generate metrics + install workflow"
1947
- echo " install [--repo SLUG] [--apply] Install repo metrics refresh workflow only"
1948
- echo ""
1949
- echo "Options (check/sync/install):"
1950
- echo " --repo SLUG Limit to a single repo"
1951
- echo " --apply Actually perform the sync (default: dry-run)"
1952
- echo " --json Machine-readable output"
1953
- echo " --verbose Show diff summaries (check only)"
1954
- echo ""
1955
- echo "Examples:"
1956
- echo " aidevops badges check # scan all repos for badge drift"
1957
- echo " aidevops badges check --json | jq '.[]' # machine-readable output"
1958
- echo " aidevops badges render owner/repo # print badge block"
1959
- echo " aidevops badges sync # dry-run sync across all repos"
1960
- echo " aidevops badges sync --repo owner/r --apply # apply to a single repo"
1961
- echo " aidevops metrics generate [PATH] # generate local docs/metrics artifacts"
1962
- echo ""
1963
- ;;
1964
- *)
1965
- print_error "Unknown badges subcommand: $_badges_sub (try render|check|sync|install|help)"
1966
- exit 1
1967
- ;;
1968
- esac
1969
- ;;
1925
+ badges) _main_badges "$@" ;;
1970
1926
  security) _cmd_security "$@" ;;
1971
1927
  doctor | doc) _dispatch_helper "doctor-helper.sh" "doctor-helper.sh" "$@" ;;
1972
1928
  detect | scan) cmd_detect ;;
@@ -1974,32 +1930,7 @@ main() {
1974
1930
  model-accounts-pool | map) _dispatch_helper "oauth-pool-helper.sh" "oauth-pool-helper.sh" "$@" ;;
1975
1931
  gpt56-context | gpt56_context) _dispatch_helper "gpt56-context-helper.sh" "gpt56-context-helper.sh" "$@" ;;
1976
1932
  astra-context | astra_context) _dispatch_helper "astra-context-helper.sh" "astra-context-helper.sh" "$@" ;;
1977
- cleanup)
1978
- local _cleanup_sub="${1:-help}"
1979
- case "$_cleanup_sub" in
1980
- branches | remote-branches)
1981
- shift || true
1982
- _dispatch_helper "remote-branch-cleanup-helper.sh" "remote-branch-cleanup-helper.sh" "$_cleanup_sub" "$@"
1983
- ;;
1984
- help | --help | -h | "")
1985
- echo "Usage: aidevops cleanup <branches|remote-branches> [options]"
1986
- echo ""
1987
- echo "Cleanup commands:"
1988
- echo " branches Audit stale remote branches (dry-run default)"
1989
- echo " remote-branches Alias for branches"
1990
- echo ""
1991
- echo "Options:"
1992
- echo " --repo PATH Repository path (default: current directory)"
1993
- echo " --remote NAME Remote to audit (default: origin)"
1994
- echo " --apply Delete safe candidates"
1995
- echo ""
1996
- ;;
1997
- *)
1998
- print_error "Unknown cleanup subcommand: $_cleanup_sub (try branches|remote-branches|help)"
1999
- exit 1
2000
- ;;
2001
- esac
2002
- ;;
1933
+ cleanup) _main_dispatch_cleanup "$@" ;;
2003
1934
  client-format) _cmd_client_format "$@" ;;
2004
1935
  github-app-auth | github-app | gh-auth) _dispatch_helper "github-app-auth-helper.sh" "github-app-auth-helper.sh" "$@" ;;
2005
1936
  opencode-db | oc-db) _dispatch_helper "opencode-db-maintenance-helper.sh" "opencode-db-maintenance-helper.sh" "$@" ;;
@@ -2021,12 +1952,7 @@ main() {
2021
1952
  worktree | wt) _dispatch_helper "worktree-helper.sh" "worktree-helper.sh" "$@" ;;
2022
1953
  issue) _dispatch_helper "interactive-session-helper.sh" "interactive-session-helper.sh" "$@" ;;
2023
1954
  signing) _dispatch_helper "signing-setup.sh" "signing-setup.sh" "$@" ;;
2024
- contributions | contrib)
2025
- # Bare `aidevops contributions` defaults to status (most common use).
2026
- # Other subcommands (seed, scan, stop, restart, install, uninstall) forward verbatim.
2027
- [[ $# -eq 0 ]] && set -- status
2028
- _dispatch_helper "contribution-watch-helper.sh" "contribution-watch-helper.sh" "$@"
2029
- ;;
1955
+ contributions | contrib) _main_dispatch_contributions "$@" ;;
2030
1956
  inbox)
2031
1957
  # Bare `aidevops inbox` defaults to status (most common use).
2032
1958
  [[ $# -eq 0 ]] && set -- status
@@ -2050,44 +1976,7 @@ main() {
2050
1976
  init-routines) _dispatch_helper "init-routines-helper.sh" "init-routines-helper.sh" "$@" ;;
2051
1977
  parent-status | ps) _dispatch_helper "parent-status-helper.sh" "parent-status-helper.sh" "$@" ;;
2052
1978
  knowledge) _dispatch_helper "knowledge-helper.sh" "knowledge-helper.sh" "$@" ;;
2053
- campaign | campaigns)
2054
- # P1 provisioning: init/provision/status/ls → campaigns-provision-helper.sh
2055
- # P4 asset binary: asset → campaign-asset-helper.sh
2056
- # P2+P6: all other subcommands → campaign-helper.sh
2057
- local _camp_cmd="${1:-help}"
2058
- local _camp_helper="campaign-helper.sh"
2059
- local _camp_provision_helper="campaigns-provision-helper.sh"
2060
- case "$_camp_cmd" in
2061
- init | provision | ls)
2062
- _dispatch_helper "$_camp_provision_helper" "$_camp_provision_helper" "$@"
2063
- ;;
2064
- status)
2065
- if [[ $# -le 1 || -d "${2:-}" || "${2:-}" == .* || "${2:-}" == /* || "${2:-}" == ~* ]]; then
2066
- _dispatch_helper "$_camp_provision_helper" "$_camp_provision_helper" "$@"
2067
- else
2068
- _dispatch_helper "$_camp_helper" "$_camp_helper" "$@"
2069
- fi
2070
- ;;
2071
- asset | assets)
2072
- shift
2073
- _dispatch_helper "campaign-asset-helper.sh" "campaign-asset-helper.sh" "$@"
2074
- ;;
2075
- grow | growth)
2076
- shift
2077
- local _growth_helper="${AIDEVOPS_CLI_MODULES_DIR%/aidevops-cli}/campaign-growth-helper.py"
2078
- [[ ! -f "$_growth_helper" ]] && _growth_helper="$AGENTS_DIR/scripts/campaign-growth-helper.py"
2079
- if [[ -f "$_growth_helper" ]]; then
2080
- python3 "$_growth_helper" "$@"
2081
- else
2082
- print_error "campaign-growth-helper.py not found. Run: aidevops update"
2083
- return 1
2084
- fi
2085
- ;;
2086
- *)
2087
- _dispatch_helper "$_camp_helper" "$_camp_helper" "$@"
2088
- ;;
2089
- esac
2090
- ;;
1979
+ campaign | campaigns) _main_dispatch_campaign "$@" ;;
2091
1980
  performance) _dispatch_helper "performance-helper.sh" "performance-helper.sh" "$@" ;;
2092
1981
  reach) _dispatch_helper "reach-helper.sh" "reach-helper.sh" "$@" ;;
2093
1982
  config | configure) _dispatch_config "$@" ;;
@@ -2103,4 +1992,84 @@ main() {
2103
1992
  esac
2104
1993
  }
2105
1994
 
1995
+ _main_dispatch_cleanup() {
1996
+ local cleanup_subcommand="${1:-help}"
1997
+ case "$cleanup_subcommand" in
1998
+ branches | remote-branches)
1999
+ shift || true
2000
+ _dispatch_helper "remote-branch-cleanup-helper.sh" "remote-branch-cleanup-helper.sh" "$cleanup_subcommand" "$@"
2001
+ ;;
2002
+ help | --help | -h | "")
2003
+ echo "Usage: aidevops cleanup <branches|remote-branches> [options]"
2004
+ echo ""
2005
+ echo "Cleanup commands:"
2006
+ echo " branches Audit stale remote branches (dry-run default)"
2007
+ echo " remote-branches Alias for branches"
2008
+ echo ""
2009
+ echo "Options:"
2010
+ echo " --repo PATH Repository path (default: current directory)"
2011
+ echo " --remote NAME Remote to audit (default: origin)"
2012
+ echo " --apply Delete safe candidates"
2013
+ echo ""
2014
+ ;;
2015
+ *) print_error "Unknown cleanup subcommand: $cleanup_subcommand (try branches|remote-branches|help)"; return 1 ;;
2016
+ esac
2017
+ return 0
2018
+ }
2019
+
2020
+ _help_vault_and_config_sections() {
2021
+ echo "Vault:"
2022
+ echo " aidevops vault init # Create local encrypted Vault metadata"
2023
+ echo " aidevops vault status # Show uninitialized/locked/unlocked/corrupted"
2024
+ echo " aidevops vault unlock # Unlock into an in-memory local broker"
2025
+ echo " aidevops vault lock # Stop broker and forget in-memory keys"
2026
+ echo " aidevops vault read NAME # Read protected data through broker"
2027
+ echo " aidevops vault update NAME # Encrypt stdin value through broker"
2028
+ echo ""
2029
+ echo "GitHub App Auth:"
2030
+ echo " aidevops github-app-auth status --json # Show active auth mode and budgets"
2031
+ echo " aidevops github-app-auth route issue-list # Explain route decision"
2032
+ echo " aidevops github-app-auth rate-limit --json # Show cached per-pool budgets"
2033
+ echo ""
2034
+ echo "Feature Toggles:"
2035
+ echo " aidevops config list # List all toggles with current values"
2036
+ echo " aidevops config get <key> # Get a toggle value"
2037
+ echo " aidevops config set <k> <v> # Set a toggle (true/false)"
2038
+ echo " aidevops config reset [key] # Reset toggle(s) to defaults"
2039
+ echo " aidevops config path # Show config file path"
2040
+ echo ""
2041
+ return 0
2042
+ }
2043
+
2044
+ _main_dispatch_contributions() {
2045
+ [[ $# -eq 0 ]] && set -- status
2046
+ _dispatch_helper "contribution-watch-helper.sh" "contribution-watch-helper.sh" "$@"
2047
+ return $?
2048
+ }
2049
+
2050
+ _main_dispatch_campaign() {
2051
+ local campaign_command="${1:-help}" campaign_helper="campaign-helper.sh"
2052
+ local provision_helper="campaigns-provision-helper.sh"
2053
+ case "$campaign_command" in
2054
+ init | provision | ls) _dispatch_helper "$provision_helper" "$provision_helper" "$@" ;;
2055
+ status)
2056
+ if [[ $# -le 1 || -d "${2:-}" || "${2:-}" == .* || "${2:-}" == /* || "${2:-}" == ~* ]]; then
2057
+ _dispatch_helper "$provision_helper" "$provision_helper" "$@"
2058
+ else
2059
+ _dispatch_helper "$campaign_helper" "$campaign_helper" "$@"
2060
+ fi
2061
+ ;;
2062
+ asset | assets) shift; _dispatch_helper "campaign-asset-helper.sh" "campaign-asset-helper.sh" "$@" ;;
2063
+ grow | growth)
2064
+ shift
2065
+ local growth_helper="${AIDEVOPS_CLI_MODULES_DIR%/aidevops-cli}/campaign-growth-helper.py"
2066
+ [[ ! -f "$growth_helper" ]] && growth_helper="$AGENTS_DIR/scripts/campaign-growth-helper.py"
2067
+ [[ -f "$growth_helper" ]] || { print_error "campaign-growth-helper.py not found. Run: aidevops update"; return 1; }
2068
+ python3 "$growth_helper" "$@"
2069
+ ;;
2070
+ *) _dispatch_helper "$campaign_helper" "$campaign_helper" "$@" ;;
2071
+ esac
2072
+ return 0
2073
+ }
2074
+
2106
2075
  main "$@"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aidevops",
3
- "version": "3.34.8",
3
+ "version": "3.34.10",
4
4
  "description": "AI DevOps Framework - AI-assisted development workflows, code quality, and deployment automation",
5
5
  "type": "module",
6
6
  "packageManager": "bun@1.3.11",
package/setup.sh CHANGED
@@ -17,7 +17,7 @@ fi
17
17
  # AI Assistant Server Access Framework Setup Script
18
18
  # Helps developers set up the framework for their infrastructure
19
19
  #
20
- # Version: 3.34.8
20
+ # Version: 3.34.10
21
21
  #
22
22
  # Quick Install:
23
23
  # npm install -g aidevops && aidevops update (recommended)
@@ -1116,16 +1116,34 @@ _setup_redact_secret_like_command_values() {
1116
1116
  return 0
1117
1117
  }
1118
1118
 
1119
+ _setup_claim_noninteractive_setup_lock() {
1120
+ local lock_dir="$1"
1121
+ shift
1122
+ local lane_rc=0
1123
+
1124
+ SETUP_NONINTERACTIVE_LOCK_DIR="$lock_dir"
1125
+ SETUP_NONINTERACTIVE_LOCK_HELD=true
1126
+ printf '%s\n' "$$" >"$lock_dir/owner.pid" 2>/dev/null || true
1127
+ printf '%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >"$lock_dir/started_at" 2>/dev/null || true
1128
+ printf '%s\n' "$(date +%s 2>/dev/null || printf '0')" >"$lock_dir/started_at_epoch" 2>/dev/null || true
1129
+ printf '%s\n' "$0 $*" >"$lock_dir/command" 2>/dev/null || true
1130
+ _setup_guard_active_release_lane || lane_rc=$?
1131
+ if [[ "$lane_rc" -ne 0 ]]; then
1132
+ _setup_release_noninteractive_setup_lock
1133
+ return "$lane_rc"
1134
+ fi
1135
+ trap '_setup_cleanup_noninteractive_children; _setup_release_noninteractive_setup_lock' EXIT
1136
+ trap '_setup_noninteractive_signal_exit TERM' TERM
1137
+ trap '_setup_noninteractive_signal_exit INT' INT
1138
+ return 0
1139
+ }
1140
+
1119
1141
  _setup_acquire_noninteractive_setup_lock() {
1120
1142
  local lock_dir="${AIDEVOPS_SETUP_LOCK_DIR:-$HOME/.aidevops/locks/setup-noninteractive.lock.d}"
1121
- # Max seconds to wait for a live, non-stale owner before timing out.
1122
1143
  local wait_ceiling="${AIDEVOPS_SETUP_WAIT_TIMEOUT_S:-900}"
1123
- # Max seconds a live owner may hold the lock before it is treated as
1124
- # stale and reclaimed (0 disables stale-live reclaim).
1125
1144
  local stale_ceiling="${AIDEVOPS_SETUP_STALE_TIMEOUT_S:-1800}"
1126
1145
  local owner_pid="" owner_cmd="" owner_age=0
1127
1146
  local reclaim_attempts=0 waited=0
1128
- local lane_rc=0
1129
1147
  local _diag_stl="$HOME/.aidevops/logs/setup-stage-timings.log"
1130
1148
  local _diag_interval_s="${AIDEVOPS_SETUP_LOCK_DIAG_INTERVAL_S:-60}"
1131
1149
  _setup_guard_active_release_lane || return $?
@@ -1133,24 +1151,10 @@ _setup_acquire_noninteractive_setup_lock() {
1133
1151
  mkdir -p "$(dirname "$lock_dir")" 2>/dev/null || true
1134
1152
  while true; do
1135
1153
  if mkdir "$lock_dir" 2>/dev/null; then
1136
- SETUP_NONINTERACTIVE_LOCK_DIR="$lock_dir"
1137
- SETUP_NONINTERACTIVE_LOCK_HELD=true
1138
- printf '%s\n' "$$" >"$lock_dir/owner.pid" 2>/dev/null || true
1139
- printf '%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >"$lock_dir/started_at" 2>/dev/null || true
1140
- printf '%s\n' "$(date +%s 2>/dev/null || printf '0')" >"$lock_dir/started_at_epoch" 2>/dev/null || true
1141
- printf '%s\n' "$0 $*" >"$lock_dir/command" 2>/dev/null || true
1142
- _setup_guard_active_release_lane || lane_rc=$?
1143
- if [[ "$lane_rc" -ne 0 ]]; then
1144
- _setup_release_noninteractive_setup_lock
1145
- return "$lane_rc"
1146
- fi
1147
- trap '_setup_cleanup_noninteractive_children; _setup_release_noninteractive_setup_lock' EXIT
1148
- trap '_setup_noninteractive_signal_exit TERM' TERM
1149
- trap '_setup_noninteractive_signal_exit INT' INT
1150
- return 0
1154
+ _setup_claim_noninteractive_setup_lock "$lock_dir" "$@"
1155
+ return $?
1151
1156
  fi
1152
1157
 
1153
- # Lock exists — inspect owner.
1154
1158
  owner_pid=""
1155
1159
  if [[ -r "$lock_dir/owner.pid" ]]; then
1156
1160
  owner_pid=$(tr -d '[:space:]' <"$lock_dir/owner.pid" 2>/dev/null || true)
@@ -1174,7 +1178,6 @@ _setup_acquire_noninteractive_setup_lock() {
1174
1178
  fi
1175
1179
 
1176
1180
  if ! _setup_lock_pid_alive "$owner_pid"; then
1177
- # Dead owner — reclaim the stale lock.
1178
1181
  if [[ "$reclaim_attempts" -ge 2 ]]; then
1179
1182
  print_error "Unable to acquire setup.sh --non-interactive lock at ${lock_dir} after ${reclaim_attempts} stale-lock removals"
1180
1183
  return 75
@@ -1198,7 +1201,6 @@ _setup_acquire_noninteractive_setup_lock() {
1198
1201
  continue
1199
1202
  fi
1200
1203
 
1201
- # Owner is alive — compute age and read current setup stage.
1202
1204
  owner_age=$(_setup_lock_owner_age "$lock_dir" "$owner_pid")
1203
1205
  owner_cmd=""
1204
1206
  [[ -r "$lock_dir/command" ]] && owner_cmd=$(tr '\n' ' ' <"$lock_dir/command" 2>/dev/null || true)
@@ -1210,7 +1212,6 @@ _setup_acquire_noninteractive_setup_lock() {
1210
1212
  [[ -n "$_diag_cur_stage" ]] && _diag_stage=", stage: ${_diag_cur_stage}"
1211
1213
  fi
1212
1214
 
1213
- # Stale-live reclaim: owner alive but running far too long.
1214
1215
  if [[ "$stale_ceiling" -gt 0 && "$owner_age" -ge "$stale_ceiling" ]]; then
1215
1216
  if [[ "$reclaim_attempts" -ge 2 ]]; then
1216
1217
  print_error "Unable to acquire setup.sh --non-interactive lock: owner (pid ${owner_pid}, age ${owner_age}s) exceeds stale ceiling but reclaim limit reached. Diagnose: ${_diag_stl}"
@@ -1222,13 +1223,11 @@ _setup_acquire_noninteractive_setup_lock() {
1222
1223
  continue
1223
1224
  fi
1224
1225
 
1225
- # Live non-stale owner — check wait ceiling before sleeping.
1226
1226
  if [[ "$waited" -ge "$wait_ceiling" ]]; then
1227
1227
  print_error "Timed out waiting ${waited}s for setup.sh --non-interactive lock (owner pid ${owner_pid}, age ${owner_age}s${_diag_stage}${owner_cmd:+, command: ${owner_cmd}}). Increase AIDEVOPS_SETUP_WAIT_TIMEOUT_S (current: ${wait_ceiling}s) or kill pid ${owner_pid} to unblock. Diagnose: ${_diag_stl}"
1228
1228
  return 75
1229
1229
  fi
1230
1230
 
1231
- # Emit diagnostics on first block and every diagnostic interval thereafter.
1232
1231
  if [[ "$waited" -eq 0 ]]; then
1233
1232
  print_info "Another setup.sh --non-interactive is running (pid ${owner_pid}, age ${owner_age}s${_diag_stage}${owner_cmd:+, command: ${owner_cmd}}). Waiting up to ${wait_ceiling}s (AIDEVOPS_SETUP_WAIT_TIMEOUT_S). Diagnose: ${_diag_stl}"
1234
1233
  elif [[ $((waited % _diag_interval_s)) -eq 0 ]]; then
@@ -1543,22 +1542,7 @@ reconcile_buzz_desktop_compatibility() {
1543
1542
  esac
1544
1543
  }
1545
1544
 
1546
- _setup_run_non_interactive() {
1547
- print_info "Non-interactive mode: deploying agents and running safe migrations only"
1548
-
1549
- _setup_init_stage_timing_log
1550
-
1551
- _time_step "protect_current_setup_worktree" protect_current_setup_worktree
1552
- _time_step "verify_location" verify_location
1553
- _time_step "check_requirements" check_requirements
1554
- # Run quality tool detection in non-interactive mode too (warn-only path).
1555
- _time_step "check_quality_tools" check_quality_tools
1556
- # Check setsid availability; auto-install util-linux on macOS if missing
1557
- # (GH#21102 / t2926: missing setsid kills workers on every pulse restart).
1558
- _time_step "setup_setsid_advisory" setup_setsid_advisory
1559
- _time_step "check_python_upgrade_available" check_python_upgrade_available
1560
- _time_step "setup_vault_python_env" setup_vault_python_env || print_warning "Vault crypto runtime setup encountered issues; Vault status remains metadata-only"
1561
- _time_step "set_permissions" set_permissions
1545
+ _setup_run_noninteractive_migrations() {
1562
1546
  _time_step "migrate_old_backups" migrate_old_backups
1563
1547
  _time_step "migrate_loop_state_directories" migrate_loop_state_directories
1564
1548
  _time_step "migrate_agent_to_agents_folder" migrate_agent_to_agents_folder
@@ -1576,12 +1560,23 @@ _setup_run_non_interactive() {
1576
1560
  _time_step "cleanup_worktree_entries_in_repos_json" cleanup_worktree_entries_in_repos_json
1577
1561
  _time_step "_cleanup_legacy_model_config" _cleanup_legacy_model_config
1578
1562
  _time_step "cleanup_legacy_dashboard_launchagent" cleanup_legacy_dashboard_launchagent
1579
- # t2888: install/heal opencode-ai. Companion to t2887's runtime canary
1580
- # fail-fast -- t2887 detects when $OPENCODE_BIN_DEFAULT is wrong, this
1581
- # one fixes it by reinstalling opencode-ai@latest (overwriting any bin
1582
- # collision with @anthropic-ai/claude-code or similar). Skipping this
1583
- # in non-interactive mode is the bug PR #20189 introduced and what
1584
- # alex-solovyev's runner spam stemmed from.
1563
+ return 0
1564
+ }
1565
+
1566
+ _setup_run_non_interactive() {
1567
+ print_info "Non-interactive mode: deploying agents and running safe migrations only"
1568
+
1569
+ _setup_init_stage_timing_log
1570
+
1571
+ _time_step "protect_current_setup_worktree" protect_current_setup_worktree
1572
+ _time_step "verify_location" verify_location
1573
+ _time_step "check_requirements" check_requirements
1574
+ _time_step "check_quality_tools" check_quality_tools
1575
+ _time_step "setup_setsid_advisory" setup_setsid_advisory
1576
+ _time_step "check_python_upgrade_available" check_python_upgrade_available
1577
+ _time_step "setup_vault_python_env" setup_vault_python_env || print_warning "Vault crypto runtime setup encountered issues; Vault status remains metadata-only"
1578
+ _time_step "set_permissions" set_permissions
1579
+ _setup_run_noninteractive_migrations
1585
1580
  _time_step "$SETUP_STAGE_OPENCODE" setup_opencode_runtimes
1586
1581
  _time_step "validate_opencode_config" validate_opencode_config
1587
1582
  _time_step "$SETUP_STAGE_AGENTS" deploy_aidevops_agents
@@ -1599,9 +1594,6 @@ _setup_run_non_interactive() {
1599
1594
  fi
1600
1595
  _time_step "init_settings_json" init_settings_json
1601
1596
 
1602
- # Parallelise independent skill operations (t1356: ~84s serial -> ~18s parallel)
1603
- # generate_agent_skills must complete before create_skill_symlinks (symlinks
1604
- # depend on generated SKILL.md files). scan_imported_skills is independent.
1605
1597
  local _pid_symlinks=""
1606
1598
  if _time_step "generate_agent_skills" generate_agent_skills; then
1607
1599
  _time_step "create_skill_symlinks" create_skill_symlinks &