aidevops 3.13.95 → 3.14.1

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.
@@ -1,141 +0,0 @@
1
- #!/usr/bin/env bash
2
- # SPDX-License-Identifier: MIT
3
- # SPDX-FileCopyrightText: 2025-2026 Marcus Quinn
4
- # =============================================================================
5
- # aidevops Status Library — status command helper functions
6
- # =============================================================================
7
- # Helper functions for `aidevops status`, extracted from aidevops.sh to keep
8
- # the CLI orchestrator below the large-file gate while preserving behaviour.
9
- #
10
- # Usage: source "${INSTALL_DIR}/aidevops-status-lib.sh"
11
- # Part of aidevops framework: https://aidevops.sh
12
-
13
- # Apply strict mode only when executed directly (not when sourced)
14
- [[ "${BASH_SOURCE[0]}" == "${0}" ]] && set -euo pipefail
15
-
16
- # Include guard
17
- [[ -n "${_AIDEVOPS_STATUS_LIB_LOADED:-}" ]] && return 0
18
- _AIDEVOPS_STATUS_LIB_LOADED=1
19
-
20
- if [[ -z "${SCRIPT_DIR:-}" ]]; then
21
- _lib_path="${BASH_SOURCE[0]%/*}"
22
- [[ "$_lib_path" == "${BASH_SOURCE[0]}" ]] && _lib_path="."
23
- SCRIPT_DIR="$(cd "$_lib_path" && pwd)"
24
- unset _lib_path
25
- fi
26
-
27
- _status_recommended_tools() {
28
- print_header "Recommended Tools"
29
- if [[ "$(uname)" == "Darwin" ]]; then
30
- check_dir "/Applications/Tabby.app" && print_success "Tabby terminal" || print_warning "Tabby terminal - not installed"
31
- if check_dir "/Applications/Zed.app"; then
32
- print_success "Zed editor"
33
- check_dir "$HOME/Library/Application Support/Zed/extensions/installed/opencode" && print_success " └─ OpenCode extension" || print_warning " └─ OpenCode extension - not installed"
34
- else print_warning "Zed editor - not installed"; fi
35
- else
36
- check_cmd tabby && print_success "Tabby terminal" || print_warning "Tabby terminal - not installed"
37
- if check_cmd zed; then
38
- print_success "Zed editor"
39
- check_dir "$HOME/.local/share/zed/extensions/installed/opencode" && print_success " └─ OpenCode extension" || print_warning " └─ OpenCode extension - not installed"
40
- else print_warning "Zed editor - not installed"; fi
41
- fi
42
- echo ""
43
- return 0
44
- }
45
-
46
- _status_ai_tools() {
47
- print_header "AI Tools & MCPs"
48
- check_cmd opencode && print_success "OpenCode CLI" || print_warning "OpenCode CLI - not installed"
49
- if check_cmd auggie; then
50
- check_file "$HOME/.augment/session.json" && print_success "Augment Context Engine (authenticated)" || print_warning "Augment Context Engine (not authenticated)"
51
- else print_warning "Augment Context Engine - not installed"; fi
52
- check_cmd bd && print_success "Beads CLI (task graph)" || print_warning "Beads CLI (bd) - not installed"
53
- echo ""
54
- return 0
55
- }
56
-
57
- _status_dev_envs() {
58
- print_header "Development Environments"
59
- check_dir "$INSTALL_DIR/python-env/dspy-env" && print_success "DSPy Python environment" || print_warning "DSPy Python environment - not created"
60
- check_cmd dspyground && print_success "DSPyGround" || print_warning "DSPyGround - not installed"
61
- echo ""
62
- return 0
63
- }
64
-
65
- _status_ai_configs() {
66
- print_header "AI Assistant Configurations"
67
- local ai_configs=("$HOME/.config/opencode/opencode.json:OpenCode" "$HOME/.claude/commands:Claude Code CLI" "$HOME/CLAUDE.md:Claude Code memory")
68
- for config in "${ai_configs[@]}"; do
69
- local path="${config%%:*}" name="${config##*:}"
70
- [[ -e "$path" ]] && print_success "$name" || print_warning "$name - not configured"
71
- done
72
- echo ""
73
- return 0
74
- }
75
-
76
- # Status command
77
- cmd_status() {
78
- print_header "AI DevOps Framework Status"
79
- echo "=========================="
80
- echo ""
81
- local current_version
82
- current_version=$(get_version)
83
- local remote_version
84
- remote_version=$(get_remote_version)
85
- print_header "Version"
86
- echo " Installed: $current_version"
87
- echo " Latest: $remote_version"
88
- if [[ "$current_version" != "$remote_version" && "$remote_version" != "unknown" ]]; then
89
- print_warning "Update available! Run: aidevops update"
90
- elif [[ "$current_version" == "$remote_version" ]]; then print_success "Up to date"; fi
91
- echo ""
92
- print_header "Installation"
93
- check_dir "$INSTALL_DIR" && print_success "Repository: $INSTALL_DIR" || print_error "Repository: Not found at $INSTALL_DIR"
94
- if check_dir "$AGENTS_DIR"; then
95
- local agent_count
96
- agent_count=$(find "$AGENTS_DIR" -name "*.md" -type f 2>/dev/null | wc -l | tr -d ' ')
97
- print_success "Agents: $AGENTS_DIR ($agent_count files)"
98
- else print_error "Agents: Not deployed"; fi
99
- echo ""
100
- print_header "Required Dependencies"
101
- for cmd in git curl jq ssh; do check_cmd "$cmd" && print_success "$cmd" || print_error "$cmd - not installed"; done
102
- echo ""
103
- print_header "Optional Dependencies"
104
- check_cmd sshpass && print_success "sshpass" || print_warning "sshpass - not installed (needed for password SSH)"
105
- echo ""
106
- _status_recommended_tools
107
- print_header "Git CLI Tools"
108
- check_cmd gh && print_success "GitHub CLI (gh)" || print_warning "GitHub CLI (gh) - not installed"
109
- check_cmd glab && print_success "GitLab CLI (glab)" || print_warning "GitLab CLI (glab) - not installed"
110
- check_cmd tea && print_success "Gitea CLI (tea)" || print_warning "Gitea CLI (tea) - not installed"
111
- echo ""
112
- _status_ai_tools
113
- _status_dev_envs
114
- _status_ai_configs
115
- print_header "SSH Configuration"
116
- check_file "$HOME/.ssh/id_ed25519" && print_success "Ed25519 SSH key" || print_warning "Ed25519 SSH key - not found"
117
- echo ""
118
- print_header "Commit Signing"
119
- local signing_format signing_key signing_enabled
120
- signing_format=$(git config --global gpg.format 2>/dev/null || echo "")
121
- signing_key=$(git config --global user.signingkey 2>/dev/null || echo "")
122
- signing_enabled=$(git config --global commit.gpgsign 2>/dev/null || echo "")
123
- if [[ "$signing_format" == "ssh" && -n "$signing_key" && "$signing_enabled" == "true" ]]; then
124
- print_success "SSH commit signing enabled"
125
- if check_file "$HOME/.ssh/allowed_signers"; then
126
- print_success "Allowed signers file configured"
127
- else
128
- print_warning "No allowed_signers file — run: aidevops signing setup"
129
- fi
130
- else
131
- print_warning "Commit signing not configured — run: aidevops signing setup"
132
- fi
133
- echo ""
134
- # t2424/GH#20030: Pulse operational counters (pre-dispatch aborts, etc.)
135
- local stats_helper="$AGENTS_DIR/scripts/pulse-stats-helper.sh"
136
- if [[ -x "$stats_helper" ]]; then
137
- print_header "Pulse Stats"
138
- "$stats_helper" status 2>/dev/null || print_info " (no stats recorded yet)"
139
- echo ""
140
- fi
141
- }
@@ -1,512 +0,0 @@
1
- #!/usr/bin/env bash
2
- # SPDX-License-Identifier: MIT
3
- # SPDX-FileCopyrightText: 2025-2026 Marcus Quinn
4
- # =============================================================================
5
- # aidevops Update Library — update command helper functions
6
- # =============================================================================
7
- # Helper functions for `aidevops update`, extracted from aidevops.sh to keep
8
- # the CLI orchestrator below the large-file gate while preserving behaviour.
9
- #
10
- # Usage: source "${INSTALL_DIR}/aidevops-update-lib.sh"
11
- # Part of aidevops framework: https://aidevops.sh
12
-
13
- # Apply strict mode only when executed directly (not when sourced)
14
- [[ "${BASH_SOURCE[0]}" == "${0}" ]] && set -euo pipefail
15
-
16
- # Include guard
17
- [[ -n "${_AIDEVOPS_UPDATE_LIB_LOADED:-}" ]] && return 0
18
- _AIDEVOPS_UPDATE_LIB_LOADED=1
19
-
20
- if [[ -z "${SCRIPT_DIR:-}" ]]; then
21
- _lib_path="${BASH_SOURCE[0]%/*}"
22
- [[ "$_lib_path" == "${BASH_SOURCE[0]}" ]] && _lib_path="."
23
- SCRIPT_DIR="$(cd "$_lib_path" && pwd)"
24
- unset _lib_path
25
- fi
26
-
27
- _AIDEVOPS_UPDATE_TRUE=true
28
-
29
- _update_fresh_install() {
30
- print_warning "Repository not found, performing fresh install..."
31
- local tmp_setup
32
- # t2997: drop .sh — XXXXXX must be at end for BSD mktemp.
33
- tmp_setup=$(mktemp "${TMPDIR:-/tmp}/aidevops-setup-XXXXXX") || {
34
- print_error "Failed to create temp file for setup script"
35
- return 1
36
- }
37
- trap 'rm -f "${tmp_setup:-}"' RETURN
38
- if curl -fsSL "https://raw.githubusercontent.com/marcusquinn/aidevops/main/setup.sh" -o "$tmp_setup" 2>/dev/null && [[ -s "$tmp_setup" ]]; then
39
- chmod +x "$tmp_setup"
40
- bash "$tmp_setup"
41
- local setup_exit=$?
42
- rm -f "$tmp_setup"
43
- [[ $setup_exit -ne 0 ]] && return 1
44
- else
45
- rm -f "$tmp_setup"
46
- print_error "Failed to download setup script"
47
- print_info "Try: git clone https://github.com/marcusquinn/aidevops.git $INSTALL_DIR && bash $INSTALL_DIR/setup.sh"
48
- return 1
49
- fi
50
- return 0
51
- }
52
-
53
- _update_sync_projects() {
54
- local skip="$1" current_ver="$2"
55
- echo ""
56
- print_header "Syncing Initialized Projects"
57
- if [[ "$skip" == "$_AIDEVOPS_UPDATE_TRUE" ]]; then
58
- print_info "Project sync skipped (--skip-project-sync)"
59
- return 0
60
- fi
61
- local repos_needing_upgrade=()
62
- while IFS= read -r repo_path; do
63
- [[ -z "$repo_path" ]] && continue
64
- [[ -d "$repo_path" ]] && check_repo_needs_upgrade "$repo_path" && repos_needing_upgrade+=("$repo_path")
65
- done < <(get_registered_repos)
66
- _update_sync_agent_source_repos "$current_ver" || true
67
- if [[ ${#repos_needing_upgrade[@]} -eq 0 ]]; then
68
- print_success "All registered projects are up to date"
69
- return 0
70
- fi
71
- local synced=0 skipped=0 failed=0
72
- for repo in "${repos_needing_upgrade[@]}"; do
73
- [[ ! -f "$repo/.aidevops.json" ]] && {
74
- skipped=$((skipped + 1))
75
- continue
76
- }
77
- local did_sync=false
78
- if command -v jq &>/dev/null; then
79
- local temp_file="${repo}/.aidevops.json.tmp"
80
- if jq --arg version "$current_ver" '.version = $version' "$repo/.aidevops.json" >"$temp_file" 2>/dev/null && [[ -s "$temp_file" ]]; then
81
- mv "$temp_file" "$repo/.aidevops.json"
82
- local features
83
- features=$(jq -r '[.features | to_entries[] | select(.value == true) | .key] | join(",")' "$repo/.aidevops.json" 2>/dev/null || echo "")
84
- register_repo "$repo" "$current_ver" "$features"
85
- did_sync=true
86
- else rm -f "$temp_file"; fi
87
- fi
88
- if [[ "$did_sync" != "$_AIDEVOPS_UPDATE_TRUE" ]]; then
89
- sed -i '' "s/\"version\": *\"[^\"]*\"/\"version\": \"$current_ver\"/" "$repo/.aidevops.json" 2>/dev/null && did_sync=true
90
- fi
91
- [[ "$did_sync" == "$_AIDEVOPS_UPDATE_TRUE" ]] && synced=$((synced + 1)) || failed=$((failed + 1))
92
- done
93
- [[ $synced -gt 0 ]] && print_success "Synced $synced project(s) to v$current_ver"
94
- [[ $skipped -gt 0 ]] && print_info "Skipped $skipped uninitialized project(s) (run 'aidevops init' in each to enable)"
95
- [[ $failed -gt 0 ]] && print_warning "$failed project(s) failed to sync (jq missing or write error)"
96
- return 0
97
- }
98
-
99
- _update_sync_agent_source_repos() {
100
- local current_ver="$1"
101
- local synced=0 skipped=0 failed=0
102
- local repo
103
-
104
- while IFS= read -r repo; do
105
- [[ -z "$repo" ]] && continue
106
- if [[ ! -d "$repo" ]]; then
107
- skipped=$((skipped + 1))
108
- continue
109
- fi
110
- if seed_agent_source_repo_templates "$repo"; then
111
- synced=$((synced + 1))
112
- if [[ -f "$repo/.aidevops.json" ]] && command -v jq &>/dev/null; then
113
- local temp_file="${repo}/.aidevops.json.tmp"
114
- jq --arg version "$current_ver" '.version = $version | .agent_source = true' "$repo/.aidevops.json" >"$temp_file" 2>/dev/null && mv "$temp_file" "$repo/.aidevops.json" || rm -f "$temp_file"
115
- fi
116
- else
117
- failed=$((failed + 1))
118
- fi
119
- done < <(get_agent_source_repos)
120
-
121
- [[ $synced -gt 0 ]] && print_success "Synced $synced agent-source repo template(s)"
122
- [[ $skipped -gt 0 ]] && print_info "Skipped $skipped unavailable agent-source repo(s)"
123
- [[ $failed -gt 0 ]] && print_warning "$failed agent-source repo template sync(s) failed"
124
- return 0
125
- }
126
-
127
- _update_check_planning() {
128
- echo ""
129
- print_header "Checking Planning Templates"
130
- local repos_needing_planning=()
131
- while IFS= read -r repo_path; do
132
- [[ -z "$repo_path" || ! -d "$repo_path" ]] && continue
133
- if [[ -f "$repo_path/.aidevops.json" ]]; then
134
- local has_planning
135
- has_planning=$(grep -o '"planning": *true' "$repo_path/.aidevops.json" 2>/dev/null || true)
136
- [[ -n "$has_planning" ]] && check_planning_needs_upgrade "$repo_path" && repos_needing_planning+=("$repo_path")
137
- fi
138
- done < <(get_registered_repos)
139
- if [[ ${#repos_needing_planning[@]} -eq 0 ]]; then
140
- print_success "All planning templates are up to date"
141
- return 0
142
- fi
143
- echo ""
144
- print_warning "${#repos_needing_planning[@]} project(s) have outdated planning templates:"
145
- for repo in "${repos_needing_planning[@]}"; do
146
- local repo_name
147
- repo_name=$(basename "$repo")
148
- local todo_ver
149
- todo_ver=$(grep -A1 "TOON:meta" "$repo/TODO.md" 2>/dev/null | tail -1 | cut -d',' -f1)
150
- echo " - $repo_name (v${todo_ver:-none})"
151
- done
152
- local template_ver
153
- template_ver=$(grep -A1 "TOON:meta" "$AGENTS_DIR/templates/todo-template.md" 2>/dev/null | tail -1 | cut -d',' -f1)
154
- echo ""
155
- echo " Latest template: v${template_ver} (adds risk field, active session time estimates)"
156
- echo ""
157
- read -r -p "Upgrade planning templates in these projects? [y/N] " response
158
- if [[ "$response" =~ ^[Yy]$ ]]; then
159
- for repo in "${repos_needing_planning[@]}"; do
160
- print_info "Upgrading $(basename "$repo")..."
161
- (cd "$repo" && cmd_upgrade_planning --force) || print_warning "Failed to upgrade $(basename "$repo")"
162
- done
163
- else print_info "Run 'aidevops upgrade-planning' in each project to upgrade manually"; fi
164
- return 0
165
- }
166
-
167
- _update_check_tools() {
168
- echo ""
169
- print_header "Checking Key Tools"
170
- local tool_check_script="$AGENTS_DIR/scripts/tool-version-check.sh"
171
- if [[ ! -f "$tool_check_script" ]]; then
172
- print_info "Tool version check not available (run setup first)"
173
- return 0
174
- fi
175
- local stale_count=0 stale_tools=""
176
- local key_tool_cmds="opencode gh"
177
- local key_tool_pkgs="opencode-ai brew:gh"
178
- local idx=0
179
- for cmd_name in $key_tool_cmds; do
180
- local pkg_ref
181
- pkg_ref=$(echo "$key_tool_pkgs" | cut -d' ' -f$((idx + 1)))
182
- idx=$((idx + 1))
183
- local installed="" latest=""
184
- command -v "$cmd_name" &>/dev/null || continue
185
- installed=$("$cmd_name" --version 2>/dev/null | head -1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
186
- [[ -z "$installed" ]] && continue
187
- if [[ "$pkg_ref" == brew:* ]]; then
188
- local brew_pkg="${pkg_ref#brew:}"
189
- local brew_bin=""
190
- brew_bin=$(command -v brew 2>/dev/null || true)
191
- if [[ -n "$brew_bin" && -x "$brew_bin" ]]; then
192
- latest=$(_timeout_cmd 30 "$brew_bin" info --json=v2 "$brew_pkg" | jq -r '.formulae[0].versions.stable // empty' || true)
193
- elif [[ "$brew_pkg" == "gh" ]] && command -v gh &>/dev/null; then latest=$(get_public_release_tag "cli/cli"); fi
194
- else latest=$(_timeout_cmd 30 npm view "$pkg_ref" version || true); fi
195
- [[ -z "$latest" ]] && continue
196
- [[ "$installed" != "$latest" ]] && {
197
- stale_tools="${stale_tools:+$stale_tools, }$cmd_name ($installed -> $latest)"
198
- ((++stale_count))
199
- }
200
- done
201
- if [[ "$stale_count" -eq 0 ]]; then
202
- print_success "Key tools are up to date"
203
- else
204
- print_warning "$stale_count tool(s) have updates: $stale_tools"
205
- echo ""
206
- read -r -p "Run full tool update check? [y/N] " response
207
- [[ "$response" =~ ^[Yy]$ ]] && bash "$tool_check_script" --update || print_info "Run 'aidevops update-tools --update' to update later"
208
- fi
209
- return 0
210
- }
211
-
212
- # Check for stale Homebrew-installed copy after git update (GH#11470)
213
- # Self-heal broken OpenCode runtime symlinks (t2172). A single dangling
214
- # symlink in ~/.config/opencode/{command,agent,skills,tool}/ blocks new
215
- # OpenCode sessions with "Failed to parse command ...". Running on every
216
- # update is cheap (find+rm on 4 small dirs) and catches orphans left
217
- # behind when users delete private agent source clones without going
218
- # through `agent-sources-helper.sh remove`. Fail-open — must never
219
- # break the update cron.
220
- _update_sweep_opencode_symlinks() {
221
- local sym_helper="${HOME}/.aidevops/agents/scripts/agent-sources-helper.sh"
222
- [[ -x "$sym_helper" ]] || return 0
223
- "$sym_helper" cleanup-broken-symlinks >/dev/null 2>&1 || true
224
- return 0
225
- }
226
-
227
- _update_check_homebrew() {
228
- command -v brew &>/dev/null || return 0
229
- brew list aidevops &>/dev/null 2>&1 || return 0
230
- local brew_version=""
231
- brew_version=$(brew info aidevops --json=v2 2>/dev/null | jq -r '.formulae[0].installed[0].version // empty' 2>/dev/null || true)
232
- [[ -z "$brew_version" ]] && return 0
233
- local current_version
234
- current_version=$(get_version)
235
- [[ -z "$current_version" ]] && return 0
236
- if [[ "$brew_version" != "$current_version" ]]; then
237
- echo ""
238
- print_warning "Homebrew-installed copy is outdated ($brew_version vs $current_version)"
239
- print_info "The Homebrew wrapper should prefer your git copy, but if your PATH"
240
- print_info "resolves the Homebrew libexec copy directly, you'll run the old version."
241
- echo ""
242
- read -r -p "Run 'brew upgrade aidevops' now? [y/N] " response
243
- if [[ "$response" =~ ^[Yy]$ ]]; then
244
- brew upgrade aidevops 2>&1 || print_warning "brew upgrade failed — run manually: brew upgrade aidevops"
245
- else
246
- print_info "Run 'brew upgrade aidevops' to sync the Homebrew copy"
247
- fi
248
- fi
249
- return 0
250
- }
251
-
252
- # t2926 / GH#21102: Re-check setsid on every 'aidevops update' run.
253
- # setsid (from util-linux) is required to detach pulse workers into their own
254
- # process group — without it, every pulse restart sends SIGHUP to its PGID,
255
- # killing in-flight workers. This check runs even when setup.sh is skipped
256
- # (already up-to-date path), so Homebrew drift doesn't silently break workers.
257
- _update_check_setsid() {
258
- command -v setsid >/dev/null 2>&1 && return 0
259
-
260
- # setsid is missing. On macOS with Homebrew, auto-install util-linux.
261
- # Use a boolean flag to avoid repeating the OS literal string.
262
- local _on_mac=false
263
- [[ "$(uname -s)" == Darwin* ]] && _on_mac=true
264
- if $_on_mac && command -v brew >/dev/null 2>&1; then
265
- print_info "setsid not found — installing util-linux for worker PGID isolation (GH#21102)"
266
- if brew install util-linux 2>&1 | tail -3; then
267
- local brew_prefix=""
268
- brew_prefix="$(brew --prefix 2>/dev/null || true)"
269
- local keg_setsid="${brew_prefix}/opt/util-linux/bin/setsid"
270
- local link_target="${brew_prefix}/bin/setsid"
271
- if [[ -x "$keg_setsid" && ! -e "$link_target" ]]; then
272
- ln -s "$keg_setsid" "$link_target" && \
273
- print_success "Symlinked setsid: $keg_setsid → $link_target"
274
- fi
275
- if command -v setsid >/dev/null 2>&1; then
276
- print_success "setsid installed at $(command -v setsid) (worker PGID isolation enabled)"
277
- else
278
- print_error "util-linux installed but setsid still not in PATH — check brew --prefix"
279
- fi
280
- else
281
- print_error "brew install util-linux failed — workers will share pulse PGID until resolved"
282
- fi
283
- elif $_on_mac; then
284
- print_error "setsid not found — worker isolation broken; install Homebrew then run: brew install util-linux"
285
- else
286
- print_error "setsid not found — worker isolation broken; install util-linux via your distro package manager"
287
- fi
288
-
289
- return 0
290
- }
291
-
292
- # GH#21735: Notify operator when framework workflow templates change.
293
- # When .agents/templates/workflows/*.yml or *-reusable.yml workflows change
294
- # in a framework update, downstream repos that use these as workflow_call
295
- # callers may have drifted from the new template. Detection and remediation
296
- # both already exist (`aidevops check-workflows`, `aidevops sync-workflows
297
- # --apply`); the gap was the notification surface — operators only learned
298
- # of drift when downstream CI failed (canonical incident: a managed
299
- # downstream repo's issue-sync.yml failed silently after the upstream
300
- # template added a new input).
301
- #
302
- # This check inspects the SHA-window diff for changes to workflow caller
303
- # templates and reusable workflows, prints a warning, and emits a daily
304
- # advisory so the next session greeting surfaces it if the operator
305
- # misses the inline output.
306
- #
307
- # Args: $1=old_sha, $2=new_sha
308
- # Returns: 0 (always — informational only, never breaks update)
309
- _update_check_workflow_drift() {
310
- local old_sha="$1"
311
- local new_sha="$2"
312
- [[ -z "$old_sha" || -z "$new_sha" || "$old_sha" == "$new_sha" ]] && return 0
313
- # `.git` is a directory in a regular repo and a file in a worktree;
314
- # `-e` covers both so the helper is testable from a worktree.
315
- [[ ! -e "$INSTALL_DIR/.git" ]] && return 0
316
-
317
- # Files that propagate to downstream caller workflows OR are themselves
318
- # reusable workflow definitions referenced by downstream callers.
319
- # Internal .github/workflows/*.yml hotfixes (e.g. self-test runs) are
320
- # intentionally skipped to avoid false-positive nags.
321
- local relevant_files
322
- relevant_files=$(git -C "$INSTALL_DIR" diff --name-only "$old_sha" "$new_sha" -- \
323
- '.agents/templates/workflows/' \
324
- '.github/workflows/' \
325
- 2>/dev/null \
326
- | grep -E '(\.agents/templates/workflows/.*\.ya?ml$|\.github/workflows/.*-reusable\.ya?ml$)' \
327
- || true)
328
- [[ -z "$relevant_files" ]] && return 0
329
-
330
- local file_count
331
- file_count=$(printf '%s\n' "$relevant_files" | wc -l | tr -d ' ')
332
- echo ""
333
- print_warning "Workflow templates updated ($file_count file(s)) — downstream callers may have drifted."
334
- print_info " Detect drift: aidevops check-workflows"
335
- print_info " Apply fix: aidevops sync-workflows --apply [--repo OWNER/REPO]"
336
-
337
- # Persist as advisory so the next session greeting surfaces it even if
338
- # the operator misses the inline warning. Day-stamped ID makes repeated
339
- # updates within the same day idempotent (one advisory per day);
340
- # 'aidevops security dismiss <id>' silences a specific day's advisory.
341
- _update_emit_workflow_drift_advisory "$relevant_files" || true
342
- return 0
343
- }
344
-
345
- # Companion to _update_check_workflow_drift — separated for testability.
346
- # Args: $1=relevant_files (newline-separated)
347
- # Returns: 0 (always — fail-open; advisory write must never break update)
348
- _update_emit_workflow_drift_advisory() {
349
- local relevant_files="$1"
350
- local advisories_dir="${HOME}/.aidevops/advisories"
351
- local adv_id
352
- adv_id="workflow-drift-$(date +%Y%m%d)"
353
- local dismissed_file="$advisories_dir/dismissed.txt"
354
-
355
- # Skip if today's advisory was already dismissed.
356
- if [[ -f "$dismissed_file" ]] && grep -qxF "$adv_id" "$dismissed_file" 2>/dev/null; then
357
- return 0
358
- fi
359
-
360
- mkdir -p "$advisories_dir" 2>/dev/null || return 0
361
- local adv_file="$advisories_dir/${adv_id}.advisory"
362
-
363
- {
364
- printf 'Workflow templates changed — downstream caller workflows may have drifted.\n'
365
- printf '\n'
366
- printf 'Files changed in this update:\n'
367
- printf '%s\n' "$relevant_files" | sed 's|^| |'
368
- printf '\n'
369
- printf 'Detect drift: aidevops check-workflows\n'
370
- printf 'Apply fix: aidevops sync-workflows --apply [--repo OWNER/REPO]\n'
371
- printf 'Background: reference/reusable-workflows.md\n'
372
- } >"$adv_file" 2>/dev/null || return 0
373
- return 0
374
- }
375
-
376
- # Verify supply chain signature after pulling framework updates.
377
- # Checks that the HEAD commit is signed by the trusted maintainer key.
378
- # Non-blocking: warns on failure, does not abort the update.
379
- _update_verify_signature() {
380
- local signing_helper="$AGENTS_DIR/scripts/signing-setup.sh"
381
-
382
- # Cannot verify if the helper script is not yet deployed
383
- if [[ ! -f "$signing_helper" ]]; then
384
- return 0
385
- fi
386
-
387
- local result
388
- result=$(bash "$signing_helper" verify-update "$INSTALL_DIR" 2>/dev/null || echo "UNKNOWN")
389
-
390
- case "$result" in
391
- VERIFIED)
392
- print_success "Supply chain verified: HEAD commit is signed by trusted maintainer"
393
- ;;
394
- UNSIGNED)
395
- print_warning "HEAD commit is not signed — cannot verify supply chain integrity"
396
- print_info "This is expected for older releases. Signed commits start from v3.6.21+"
397
- ;;
398
- UNTRUSTED)
399
- print_warning "HEAD commit is signed but by an untrusted key"
400
- print_info "Run 'aidevops signing setup' to configure signature verification"
401
- ;;
402
- BAD_SIGNATURE)
403
- print_error "HEAD commit has a BAD signature — update may be compromised"
404
- print_info "Verify manually: cd $INSTALL_DIR && git log --show-signature -1"
405
- ;;
406
- UNVERIFIABLE)
407
- # Signing not configured yet — silent, do not nag
408
- ;;
409
- esac
410
- return 0
411
- }
412
-
413
- # One-shot, idempotent migration of supervisor.* → orchestration.* in settings.json (t2946).
414
- # Safe: reads value from supervisor.* only when orchestration.* key is absent.
415
- # Logs to ~/.aidevops/logs/settings-migration.log.
416
- _migrate_settings_supervisor_to_orchestration() {
417
- local _settings_file="${HOME}/.config/aidevops/settings.json"
418
- local _log_file="${HOME}/.aidevops/logs/settings-migration.log"
419
-
420
- if ! command -v jq >/dev/null 2>&1; then
421
- return 0
422
- fi
423
- if [[ ! -f "$_settings_file" ]]; then
424
- return 0
425
- fi
426
- if ! jq . "$_settings_file" >/dev/null 2>&1; then
427
- return 0
428
- fi
429
-
430
- # Check if supervisor.pulse_interval_seconds exists and orchestration.pulse_interval_seconds is absent.
431
- local _has_sv _has_orch
432
- _has_sv=$(jq -r 'if .supervisor.pulse_interval_seconds != null then "yes" else "no" end' "$_settings_file" 2>/dev/null)
433
- _has_orch=$(jq -r 'if .orchestration.pulse_interval_seconds != null then "yes" else "no" end' "$_settings_file" 2>/dev/null)
434
-
435
- if [[ "$_has_sv" != "yes" ]]; then
436
- return 0
437
- fi
438
-
439
- local _ts
440
- _ts=$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date +%Y-%m-%dT%H:%M:%SZ)
441
- mkdir -p "$(dirname "$_log_file")" 2>/dev/null || true
442
-
443
- local _tmp
444
- _tmp=$(mktemp 2>/dev/null) || return 0
445
-
446
- if [[ "$_has_orch" == "no" ]]; then
447
- # Migrate: copy supervisor.pulse_interval_seconds to orchestration.pulse_interval_seconds,
448
- # then remove supervisor.pulse_interval_seconds.
449
- local _sv_val
450
- _sv_val=$(jq -r '.supervisor.pulse_interval_seconds' "$_settings_file" 2>/dev/null)
451
- if jq --argjson v "$_sv_val" \
452
- '(.orchestration.pulse_interval_seconds) = $v | del(.supervisor.pulse_interval_seconds)' \
453
- "$_settings_file" >"$_tmp" 2>/dev/null && [[ -s "$_tmp" ]]; then
454
- mv "$_tmp" "$_settings_file"
455
- printf '[%s] migrated supervisor.pulse_interval_seconds=%s → orchestration.pulse_interval_seconds\n' \
456
- "$_ts" "$_sv_val" >>"$_log_file" 2>/dev/null || true
457
- print_info "Settings migrated: supervisor.pulse_interval_seconds → orchestration.pulse_interval_seconds ($_sv_val)"
458
- else
459
- rm -f "$_tmp"
460
- fi
461
- else
462
- # Both present: orchestration wins, remove the stale supervisor key.
463
- local _orch_val
464
- _orch_val=$(jq -r '.orchestration.pulse_interval_seconds' "$_settings_file" 2>/dev/null)
465
- if jq 'del(.supervisor.pulse_interval_seconds)' \
466
- "$_settings_file" >"$_tmp" 2>/dev/null && [[ -s "$_tmp" ]]; then
467
- mv "$_tmp" "$_settings_file"
468
- printf '[%s] removed stale supervisor.pulse_interval_seconds (orchestration.pulse_interval_seconds=%s wins)\n' \
469
- "$_ts" "$_orch_val" >>"$_log_file" 2>/dev/null || true
470
- print_info "Settings cleaned: removed stale supervisor.pulse_interval_seconds (orchestration value $_orch_val kept)"
471
- else
472
- rm -f "$_tmp"
473
- fi
474
- fi
475
- return 0
476
- }
477
-
478
- _update_check_daemon_health() {
479
- local helper="$HOME/.aidevops/agents/scripts/auto-update-helper.sh"
480
- [[ -x "$helper" ]] || return 0
481
- local advisory_dir="$HOME/.aidevops/advisories"
482
- local advisory_file="$advisory_dir/daemon-disabled.advisory"
483
-
484
- local hc_rc=0
485
- "$helper" health-check --quiet >/dev/null 2>&1 || hc_rc=$?
486
-
487
- if [[ "$hc_rc" -eq 0 ]]; then
488
- # Healthy — clear any stale advisory.
489
- [[ -f "$advisory_file" ]] && rm -f "$advisory_file"
490
- return 0
491
- fi
492
-
493
- # Unhealthy — warn on stderr and write advisory.
494
- mkdir -p "$advisory_dir" 2>/dev/null || return 0
495
- local fix_cmd="aidevops auto-update enable"
496
- [[ "$hc_rc" -eq 1 ]] && fix_cmd="aidevops auto-update check"
497
- cat >"$advisory_file" <<EOF
498
- auto-update daemon is not running normally on this runner. Without it, this
499
- runner falls behind the fleet and may dispatch workers that fail because of
500
- bugs already fixed upstream. See cross-runner-coordination.md §4.4.
501
-
502
- Diagnose: aidevops auto-update health-check
503
- Fix: ${fix_cmd}
504
- EOF
505
-
506
- if [[ "$hc_rc" -eq 1 ]]; then
507
- print_warning "Auto-update daemon is stalled. Fix: ${fix_cmd}"
508
- else
509
- print_warning "Auto-update daemon is not running. Fix: ${fix_cmd}"
510
- fi
511
- return 0
512
- }