aidevops 3.12.0 → 3.13.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.
- package/README.md +17 -0
- package/VERSION +1 -1
- package/aidevops.sh +185 -5
- package/package.json +1 -1
- package/setup-modules/schedulers-linux.sh +386 -0
- package/setup-modules/schedulers-platform.sh +1024 -0
- package/setup-modules/schedulers-pulse.sh +978 -0
- package/setup-modules/schedulers.sh +345 -2330
- package/setup-modules/tool-install.sh +89 -0
- package/setup.sh +139 -48
|
@@ -0,0 +1,1024 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
# SPDX-FileCopyrightText: 2025-2026 Marcus Quinn
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# Schedulers Platform Sub-Library -- Platform scheduler setup for contribution
|
|
6
|
+
# watch, complexity scan, pulse merge routine, profile README, OAuth token
|
|
7
|
+
# refresh, OpenCode DB maintenance, repo sync, repo health, and peer
|
|
8
|
+
# productivity monitor.
|
|
9
|
+
# =============================================================================
|
|
10
|
+
# This sub-library is sourced by setup-modules/schedulers.sh (the orchestrator).
|
|
11
|
+
# It covers:
|
|
12
|
+
# - Contribution watch (t1554): passive monitoring of external issues/PRs
|
|
13
|
+
# - Complexity scan (t2903): decoupled weekly complexity scan
|
|
14
|
+
# - Pulse merge routine (t2862, GH#20919): fast 120s merge pass
|
|
15
|
+
# - Draft responses (t1555): private repo + local draft storage
|
|
16
|
+
# - Profile README: auto-create and scheduled update
|
|
17
|
+
# - OAuth token refresh: launchd/systemd/cron/schtasks
|
|
18
|
+
# - OpenCode DB maintenance (r913, t2183): weekly checkpoint/vacuum
|
|
19
|
+
# - Repo sync: daily fast-forward pull
|
|
20
|
+
# - Repo aidevops health (r914): daily drift keeper
|
|
21
|
+
# - Peer productivity monitor (t2932): adaptive cross-runner coordination
|
|
22
|
+
#
|
|
23
|
+
# Usage: source "${SCRIPT_DIR}/schedulers-platform.sh"
|
|
24
|
+
#
|
|
25
|
+
# Dependencies:
|
|
26
|
+
# - shared-constants.sh (print_info, print_warning, print_error)
|
|
27
|
+
# - schedulers-linux.sh (_install_scheduler_linux, _uninstall_scheduler)
|
|
28
|
+
# - schedulers-pulse.sh (_resolve_modern_bash)
|
|
29
|
+
#
|
|
30
|
+
# Part of aidevops framework: https://aidevops.sh
|
|
31
|
+
|
|
32
|
+
# Apply strict mode only when executed directly (not when sourced)
|
|
33
|
+
[[ "${BASH_SOURCE[0]}" == "${0}" ]] && set -euo pipefail
|
|
34
|
+
|
|
35
|
+
# Include guard
|
|
36
|
+
[[ -n "${_SCHEDULERS_PLATFORM_LIB_LOADED:-}" ]] && return 0
|
|
37
|
+
_SCHEDULERS_PLATFORM_LIB_LOADED=1
|
|
38
|
+
|
|
39
|
+
# SCRIPT_DIR fallback — needed when sourced from test harnesses that don't set it.
|
|
40
|
+
if [[ -z "${SCRIPT_DIR:-}" ]]; then
|
|
41
|
+
_sched_platform_lib_path="${BASH_SOURCE[0]%/*}"
|
|
42
|
+
[[ "$_sched_platform_lib_path" == "${BASH_SOURCE[0]}" ]] && _sched_platform_lib_path="."
|
|
43
|
+
SCRIPT_DIR="$(cd "$_sched_platform_lib_path" && pwd)"
|
|
44
|
+
unset _sched_platform_lib_path
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# --- Functions ---
|
|
48
|
+
|
|
49
|
+
# Resolve and validate the log directory from config for contribution watch.
|
|
50
|
+
# Reads paths.log_dir from jsonc config, validates characters, expands tilde.
|
|
51
|
+
# Prints the resolved absolute path. Returns 1 on invalid characters.
|
|
52
|
+
_resolve_cw_log_dir() {
|
|
53
|
+
local _cw_log_dir
|
|
54
|
+
# shellcheck disable=SC2088 # Tilde is intentionally literal here; expanded below via ${/#\~/$HOME}
|
|
55
|
+
if type _jsonc_get &>/dev/null; then
|
|
56
|
+
_cw_log_dir=$(_jsonc_get "paths.log_dir" "~/.aidevops/logs")
|
|
57
|
+
else
|
|
58
|
+
_cw_log_dir="~/.aidevops/logs"
|
|
59
|
+
fi
|
|
60
|
+
# Whitelist: only allow characters safe in shell paths and cron lines.
|
|
61
|
+
# Reject anything outside [A-Za-z0-9_./ ~-] (tilde allowed before expansion).
|
|
62
|
+
# Store regex in variable — bash [[ =~ ]] requires unquoted RHS for regex,
|
|
63
|
+
# and a variable avoids quoting issues with special chars in the pattern.
|
|
64
|
+
local _cw_log_dir_re='^[A-Za-z0-9_./ ~-]+$'
|
|
65
|
+
if ! [[ "$_cw_log_dir" =~ $_cw_log_dir_re ]]; then
|
|
66
|
+
# Redirect to stderr so $() captures only the path result
|
|
67
|
+
print_error "Invalid characters in paths.log_dir (only [A-Za-z0-9_./ ~-] allowed): $_cw_log_dir" >&2
|
|
68
|
+
return 1
|
|
69
|
+
fi
|
|
70
|
+
_cw_log_dir="${_cw_log_dir/#\~/$HOME}"
|
|
71
|
+
printf '%s' "$_cw_log_dir"
|
|
72
|
+
return 0
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# Install contribution watch via launchd (macOS).
|
|
76
|
+
# Args: $1=label, $2=script path, $3=log dir
|
|
77
|
+
_install_cw_launchd() {
|
|
78
|
+
local cw_label="$1"
|
|
79
|
+
local cw_script="$2"
|
|
80
|
+
local _cw_log_dir="$3"
|
|
81
|
+
local cw_plist="$HOME/Library/LaunchAgents/${cw_label}.plist"
|
|
82
|
+
|
|
83
|
+
local _xml_cw_script _xml_cw_home _xml_cw_log_dir
|
|
84
|
+
_xml_cw_script=$(_xml_escape "$cw_script")
|
|
85
|
+
_xml_cw_home=$(_xml_escape "$HOME")
|
|
86
|
+
_xml_cw_log_dir=$(_xml_escape "$_cw_log_dir")
|
|
87
|
+
|
|
88
|
+
local cw_plist_content
|
|
89
|
+
cw_plist_content=$(
|
|
90
|
+
cat <<CW_PLIST
|
|
91
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
92
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
93
|
+
<plist version="1.0">
|
|
94
|
+
<dict>
|
|
95
|
+
<key>Label</key>
|
|
96
|
+
<string>${cw_label}</string>
|
|
97
|
+
<key>ProgramArguments</key>
|
|
98
|
+
<array>
|
|
99
|
+
<string>$(_xml_escape "$(_resolve_modern_bash)")</string>
|
|
100
|
+
<string>${_xml_cw_script}</string>
|
|
101
|
+
<string>scan</string>
|
|
102
|
+
</array>
|
|
103
|
+
<key>StartInterval</key>
|
|
104
|
+
<integer>3600</integer>
|
|
105
|
+
<key>StandardOutPath</key>
|
|
106
|
+
<string>${_xml_cw_log_dir}/contribution-watch.log</string>
|
|
107
|
+
<key>StandardErrorPath</key>
|
|
108
|
+
<string>${_xml_cw_log_dir}/contribution-watch.log</string>
|
|
109
|
+
<key>EnvironmentVariables</key>
|
|
110
|
+
<dict>
|
|
111
|
+
<key>PATH</key>
|
|
112
|
+
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
|
113
|
+
<key>HOME</key>
|
|
114
|
+
<string>${_xml_cw_home}</string>
|
|
115
|
+
</dict>
|
|
116
|
+
<key>RunAtLoad</key>
|
|
117
|
+
<false/>
|
|
118
|
+
<key>KeepAlive</key>
|
|
119
|
+
<false/>
|
|
120
|
+
<key>ProcessType</key>
|
|
121
|
+
<string>Background</string>
|
|
122
|
+
<key>LowPriorityBackgroundIO</key>
|
|
123
|
+
<true/>
|
|
124
|
+
<key>Nice</key>
|
|
125
|
+
<integer>10</integer>
|
|
126
|
+
</dict>
|
|
127
|
+
</plist>
|
|
128
|
+
CW_PLIST
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
if _launchd_install_if_changed "$cw_label" "$cw_plist" "$cw_plist_content"; then
|
|
132
|
+
print_info "Contribution watch enabled (launchd, hourly scan)"
|
|
133
|
+
else
|
|
134
|
+
print_warning "Failed to load contribution watch LaunchAgent"
|
|
135
|
+
fi
|
|
136
|
+
return 0
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# Install contribution watch via systemd or cron (Linux).
|
|
140
|
+
# Args: $1=script path, $2=log dir
|
|
141
|
+
_install_cw_linux() {
|
|
142
|
+
local cw_script="$1"
|
|
143
|
+
local _cw_log_dir="$2"
|
|
144
|
+
local cw_systemd="aidevops-contribution-watch"
|
|
145
|
+
_install_scheduler_linux \
|
|
146
|
+
"$cw_systemd" \
|
|
147
|
+
"aidevops: contribution-watch" \
|
|
148
|
+
"$CRON_HOURLY" \
|
|
149
|
+
"\"${cw_script}\" scan" \
|
|
150
|
+
"3600" \
|
|
151
|
+
"${_cw_log_dir}/contribution-watch.log" \
|
|
152
|
+
"" \
|
|
153
|
+
"Contribution watch enabled (hourly scan)" \
|
|
154
|
+
"Failed to install contribution watch scheduler" \
|
|
155
|
+
"false" \
|
|
156
|
+
"true"
|
|
157
|
+
return 0
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# Setup contribution watch — monitors external issues/PRs for new activity (t1554).
|
|
161
|
+
# Auto-seeds on first run (discovers authored/commented issues/PRs), then installs
|
|
162
|
+
# a launchd/systemd/cron job to scan periodically. Requires gh CLI authenticated.
|
|
163
|
+
# No consent needed — this is passive monitoring (read-only notifications API),
|
|
164
|
+
# not autonomous action. Comment bodies are never processed by LLM in automated context.
|
|
165
|
+
# Respects config: aidevops config set orchestration.contribution_watch false
|
|
166
|
+
setup_contribution_watch() {
|
|
167
|
+
local cw_script="$HOME/.aidevops/agents/scripts/contribution-watch-helper.sh"
|
|
168
|
+
local cw_label="sh.aidevops.contribution-watch"
|
|
169
|
+
local cw_state="$HOME/.aidevops/cache/contribution-watch.json"
|
|
170
|
+
if ! [[ -x "$cw_script" ]] || ! is_feature_enabled orchestration.contribution_watch 2>/dev/null || ! command -v gh &>/dev/null || ! gh auth status &>/dev/null 2>&1; then
|
|
171
|
+
return 0
|
|
172
|
+
fi
|
|
173
|
+
|
|
174
|
+
# Resolve and validate log directory
|
|
175
|
+
local _cw_log_dir
|
|
176
|
+
_cw_log_dir=$(_resolve_cw_log_dir) || return 1
|
|
177
|
+
mkdir -p "$HOME/.aidevops/cache" "$_cw_log_dir"
|
|
178
|
+
|
|
179
|
+
# Auto-seed on first run (populates state file with existing contributions)
|
|
180
|
+
if [[ ! -f "$cw_state" ]]; then
|
|
181
|
+
print_info "Discovering external contributions for contribution watch..."
|
|
182
|
+
if bash "$cw_script" seed >/dev/null 2>&1; then
|
|
183
|
+
print_info "Contribution watch seeded (external issues/PRs discovered)"
|
|
184
|
+
else
|
|
185
|
+
print_warning "Contribution watch seed failed (non-fatal, will retry on next run)"
|
|
186
|
+
fi
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
# Install/update scheduled scanner
|
|
190
|
+
if [[ "$(uname -s)" == "Darwin" ]]; then
|
|
191
|
+
_install_cw_launchd "$cw_label" "$cw_script" "$_cw_log_dir"
|
|
192
|
+
else
|
|
193
|
+
_install_cw_linux "$cw_script" "$_cw_log_dir"
|
|
194
|
+
fi
|
|
195
|
+
return 0
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
# Install complexity scan via launchd (macOS).
|
|
199
|
+
# Args: $1=label, $2=script path, $3=log dir
|
|
200
|
+
# (t2903) Extracted from pulse dispatch preflight — independent schedule so
|
|
201
|
+
# the 200-470s scan never starves dispatch or downstream scanners.
|
|
202
|
+
_install_complexity_scan_launchd() {
|
|
203
|
+
local cs_label="$1"
|
|
204
|
+
local cs_script="$2"
|
|
205
|
+
local _cs_log_dir="$3"
|
|
206
|
+
local cs_plist="$HOME/Library/LaunchAgents/${cs_label}.plist"
|
|
207
|
+
|
|
208
|
+
local _xml_cs_script _xml_cs_home _xml_cs_log_dir
|
|
209
|
+
_xml_cs_script=$(_xml_escape "$cs_script")
|
|
210
|
+
_xml_cs_home=$(_xml_escape "$HOME")
|
|
211
|
+
_xml_cs_log_dir=$(_xml_escape "$_cs_log_dir")
|
|
212
|
+
|
|
213
|
+
local cs_plist_content
|
|
214
|
+
cs_plist_content=$(
|
|
215
|
+
cat <<CS_PLIST
|
|
216
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
217
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
218
|
+
<plist version="1.0">
|
|
219
|
+
<dict>
|
|
220
|
+
<key>Label</key>
|
|
221
|
+
<string>${cs_label}</string>
|
|
222
|
+
<key>ProgramArguments</key>
|
|
223
|
+
<array>
|
|
224
|
+
<string>$(_xml_escape "$(_resolve_modern_bash)")</string>
|
|
225
|
+
<string>${_xml_cs_script}</string>
|
|
226
|
+
<string>run</string>
|
|
227
|
+
</array>
|
|
228
|
+
<key>StartInterval</key>
|
|
229
|
+
<integer>3600</integer>
|
|
230
|
+
<key>StandardOutPath</key>
|
|
231
|
+
<string>${_xml_cs_log_dir}/complexity-scan-runner.log</string>
|
|
232
|
+
<key>StandardErrorPath</key>
|
|
233
|
+
<string>${_xml_cs_log_dir}/complexity-scan-runner.log</string>
|
|
234
|
+
<key>EnvironmentVariables</key>
|
|
235
|
+
<dict>
|
|
236
|
+
<key>PATH</key>
|
|
237
|
+
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
|
238
|
+
<key>HOME</key>
|
|
239
|
+
<string>${_xml_cs_home}</string>
|
|
240
|
+
</dict>
|
|
241
|
+
<key>RunAtLoad</key>
|
|
242
|
+
<true/>
|
|
243
|
+
<key>KeepAlive</key>
|
|
244
|
+
<false/>
|
|
245
|
+
<key>ProcessType</key>
|
|
246
|
+
<string>Background</string>
|
|
247
|
+
<key>LowPriorityBackgroundIO</key>
|
|
248
|
+
<true/>
|
|
249
|
+
<key>Nice</key>
|
|
250
|
+
<integer>10</integer>
|
|
251
|
+
</dict>
|
|
252
|
+
</plist>
|
|
253
|
+
CS_PLIST
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
if _launchd_install_if_changed "$cs_label" "$cs_plist" "$cs_plist_content"; then
|
|
257
|
+
print_info "Complexity scan enabled (launchd, hourly run)"
|
|
258
|
+
else
|
|
259
|
+
print_warning "Failed to load complexity scan LaunchAgent"
|
|
260
|
+
fi
|
|
261
|
+
return 0
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
# Install complexity scan via systemd or cron (Linux).
|
|
265
|
+
# Args: $1=script path, $2=log dir
|
|
266
|
+
_install_complexity_scan_linux() {
|
|
267
|
+
local cs_script="$1"
|
|
268
|
+
local _cs_log_dir="$2"
|
|
269
|
+
local cs_systemd="aidevops-complexity-scan"
|
|
270
|
+
_install_scheduler_linux \
|
|
271
|
+
"$cs_systemd" \
|
|
272
|
+
"aidevops: complexity-scan" \
|
|
273
|
+
"$CRON_HOURLY" \
|
|
274
|
+
"\"${cs_script}\" run" \
|
|
275
|
+
"3600" \
|
|
276
|
+
"${_cs_log_dir}/complexity-scan-runner.log" \
|
|
277
|
+
"" \
|
|
278
|
+
"Complexity scan enabled (hourly run)" \
|
|
279
|
+
"Failed to install complexity scan scheduler" \
|
|
280
|
+
"true" \
|
|
281
|
+
"true"
|
|
282
|
+
return 0
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
# Setup complexity scan (t2903) — extracts the weekly complexity scan from
|
|
286
|
+
# pulse dispatch preflight into its own launchd/cron schedule. The scan was
|
|
287
|
+
# observed consuming 200-470s per pulse cycle (26%+ of the 1800s pulse stale
|
|
288
|
+
# ceiling), starving downstream scanners. Promoting it to its own schedule
|
|
289
|
+
# decouples it from dispatch entirely. The runner reuses run_weekly_complexity_scan
|
|
290
|
+
# from pulse-simplification.sh, which has internal 15-min cadence gating
|
|
291
|
+
# (COMPLEXITY_SCAN_INTERVAL=900) so hourly launchd ticks are always safe.
|
|
292
|
+
setup_complexity_scan() {
|
|
293
|
+
local cs_script="$HOME/.aidevops/agents/scripts/complexity-scan-runner.sh"
|
|
294
|
+
local cs_label="sh.aidevops.complexity-scan"
|
|
295
|
+
if ! [[ -x "$cs_script" ]]; then
|
|
296
|
+
return 0
|
|
297
|
+
fi
|
|
298
|
+
|
|
299
|
+
# Reuse contribution-watch's log-dir resolver (same logic, same config key).
|
|
300
|
+
local _cs_log_dir
|
|
301
|
+
_cs_log_dir=$(_resolve_cw_log_dir) || return 1
|
|
302
|
+
mkdir -p "$_cs_log_dir"
|
|
303
|
+
|
|
304
|
+
# Install/update scheduled runner
|
|
305
|
+
if [[ "$(uname -s)" == "Darwin" ]]; then
|
|
306
|
+
_install_complexity_scan_launchd "$cs_label" "$cs_script" "$_cs_log_dir"
|
|
307
|
+
else
|
|
308
|
+
_install_complexity_scan_linux "$cs_script" "$_cs_log_dir"
|
|
309
|
+
fi
|
|
310
|
+
return 0
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
# Install pulse-merge-routine launchd plist (macOS).
|
|
314
|
+
# Args: $1=label $2=script $3=log_dir
|
|
315
|
+
_install_pulse_merge_routine_launchd() {
|
|
316
|
+
local pmr_label="$1"
|
|
317
|
+
local pmr_script="$2"
|
|
318
|
+
local _pmr_log_dir="$3"
|
|
319
|
+
local pmr_plist="$HOME/Library/LaunchAgents/${pmr_label}.plist"
|
|
320
|
+
|
|
321
|
+
local _xml_pmr_script _xml_pmr_home _xml_pmr_log_dir
|
|
322
|
+
_xml_pmr_script=$(_xml_escape "$pmr_script")
|
|
323
|
+
_xml_pmr_home=$(_xml_escape "$HOME")
|
|
324
|
+
_xml_pmr_log_dir=$(_xml_escape "$_pmr_log_dir")
|
|
325
|
+
|
|
326
|
+
local pmr_plist_content
|
|
327
|
+
pmr_plist_content=$(
|
|
328
|
+
cat <<PMR_PLIST
|
|
329
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
330
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
331
|
+
<plist version="1.0">
|
|
332
|
+
<dict>
|
|
333
|
+
<key>Label</key>
|
|
334
|
+
<string>${pmr_label}</string>
|
|
335
|
+
<key>ProgramArguments</key>
|
|
336
|
+
<array>
|
|
337
|
+
<string>$(_xml_escape "$(_resolve_modern_bash)")</string>
|
|
338
|
+
<string>${_xml_pmr_script}</string>
|
|
339
|
+
<string>run</string>
|
|
340
|
+
</array>
|
|
341
|
+
<key>StartInterval</key>
|
|
342
|
+
<integer>120</integer>
|
|
343
|
+
<key>StandardOutPath</key>
|
|
344
|
+
<string>${_xml_pmr_log_dir}/pulse-merge-routine.log</string>
|
|
345
|
+
<key>StandardErrorPath</key>
|
|
346
|
+
<string>${_xml_pmr_log_dir}/pulse-merge-routine.log</string>
|
|
347
|
+
<key>EnvironmentVariables</key>
|
|
348
|
+
<dict>
|
|
349
|
+
<key>PATH</key>
|
|
350
|
+
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
|
351
|
+
<key>HOME</key>
|
|
352
|
+
<string>${_xml_pmr_home}</string>
|
|
353
|
+
</dict>
|
|
354
|
+
<key>RunAtLoad</key>
|
|
355
|
+
<true/>
|
|
356
|
+
<key>KeepAlive</key>
|
|
357
|
+
<false/>
|
|
358
|
+
<key>ProcessType</key>
|
|
359
|
+
<string>Background</string>
|
|
360
|
+
<key>LowPriorityBackgroundIO</key>
|
|
361
|
+
<true/>
|
|
362
|
+
<key>Nice</key>
|
|
363
|
+
<integer>10</integer>
|
|
364
|
+
</dict>
|
|
365
|
+
</plist>
|
|
366
|
+
PMR_PLIST
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
if _launchd_install_if_changed "$pmr_label" "$pmr_plist" "$pmr_plist_content"; then
|
|
370
|
+
print_info "Pulse merge routine enabled (launchd, every 2 min)"
|
|
371
|
+
else
|
|
372
|
+
print_warning "Failed to load pulse merge routine LaunchAgent"
|
|
373
|
+
fi
|
|
374
|
+
return 0
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
# Install pulse-merge-routine via systemd or cron (Linux).
|
|
378
|
+
# Args: $1=script path, $2=log dir
|
|
379
|
+
_install_pulse_merge_routine_linux() {
|
|
380
|
+
local pmr_script="$1"
|
|
381
|
+
local _pmr_log_dir="$2"
|
|
382
|
+
local pmr_systemd="aidevops-pulse-merge-routine"
|
|
383
|
+
_install_scheduler_linux \
|
|
384
|
+
"$pmr_systemd" \
|
|
385
|
+
"aidevops: pulse-merge-routine" \
|
|
386
|
+
"*/2 * * * *" \
|
|
387
|
+
"\"${pmr_script}\" run" \
|
|
388
|
+
"120" \
|
|
389
|
+
"${_pmr_log_dir}/pulse-merge-routine.log" \
|
|
390
|
+
"" \
|
|
391
|
+
"Pulse merge routine enabled (every 2 min)" \
|
|
392
|
+
"Failed to install pulse merge routine scheduler" \
|
|
393
|
+
"true" \
|
|
394
|
+
"true"
|
|
395
|
+
return 0
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
# Setup pulse merge routine (t2862, GH#20919) — runs merge_ready_prs_all_repos()
|
|
399
|
+
# as a fast 120s standalone routine, decoupled from the monolithic pulse cycle.
|
|
400
|
+
# The pulse cycle's preflight stack (60-470s) meant the merge pass ran only ~7
|
|
401
|
+
# times/24h despite ~40+ cycles. This routine ensures green PRs merge within ~3
|
|
402
|
+
# min of CI completion. The in-cycle merge call in pulse-wrapper.sh is kept as
|
|
403
|
+
# defense-in-depth but short-circuits when this routine ran within the last 60s.
|
|
404
|
+
setup_pulse_merge_routine() {
|
|
405
|
+
local pmr_script="$HOME/.aidevops/agents/scripts/pulse-merge-routine.sh"
|
|
406
|
+
local pmr_label="sh.aidevops.pulse-merge-routine"
|
|
407
|
+
if ! [[ -x "$pmr_script" ]]; then
|
|
408
|
+
return 0
|
|
409
|
+
fi
|
|
410
|
+
|
|
411
|
+
# Reuse contribution-watch's log-dir resolver (same logic, same config key).
|
|
412
|
+
local _pmr_log_dir
|
|
413
|
+
_pmr_log_dir=$(_resolve_cw_log_dir) || return 1
|
|
414
|
+
mkdir -p "$_pmr_log_dir"
|
|
415
|
+
|
|
416
|
+
# Install/update scheduled runner
|
|
417
|
+
if [[ "$(uname -s)" == "Darwin" ]]; then
|
|
418
|
+
_install_pulse_merge_routine_launchd "$pmr_label" "$pmr_script" "$_pmr_log_dir"
|
|
419
|
+
else
|
|
420
|
+
_install_pulse_merge_routine_linux "$pmr_script" "$_pmr_log_dir"
|
|
421
|
+
fi
|
|
422
|
+
return 0
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
# Setup draft responses — private repo + local draft storage for reviewing
|
|
426
|
+
# AI-drafted replies to external contributions (t1555).
|
|
427
|
+
# Respects config: aidevops config set orchestration.draft_responses false
|
|
428
|
+
setup_draft_responses() {
|
|
429
|
+
local dr_script="$HOME/.aidevops/agents/scripts/draft-response-helper.sh"
|
|
430
|
+
if [[ -x "$dr_script" ]] && is_feature_enabled orchestration.draft_responses 2>/dev/null && is_feature_enabled orchestration.contribution_watch 2>/dev/null && command -v gh &>/dev/null && gh auth status &>/dev/null 2>&1; then
|
|
431
|
+
mkdir -p "$HOME/.aidevops/.agent-workspace/draft-responses"
|
|
432
|
+
if bash "$dr_script" init >/dev/null 2>&1; then
|
|
433
|
+
print_info "Draft responses ready (private repo + local drafts)"
|
|
434
|
+
else
|
|
435
|
+
print_warning "Draft responses repo setup failed (non-fatal, local drafts still work)"
|
|
436
|
+
fi
|
|
437
|
+
fi
|
|
438
|
+
return 0
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
# Setup profile README — auto-create repo and seed README if not already set up.
|
|
442
|
+
# Requires gh CLI authenticated. Creates username/username repo, seeds README
|
|
443
|
+
# with stat markers, registers in repos.json with priority: "profile".
|
|
444
|
+
_profile_readme_ready() {
|
|
445
|
+
local pr_script="$1"
|
|
446
|
+
if ! [[ -x "$pr_script" ]]; then
|
|
447
|
+
return 1
|
|
448
|
+
fi
|
|
449
|
+
if ! command -v gh &>/dev/null; then
|
|
450
|
+
return 1
|
|
451
|
+
fi
|
|
452
|
+
if ! gh auth status &>/dev/null; then
|
|
453
|
+
return 1
|
|
454
|
+
fi
|
|
455
|
+
return 0
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
_run_profile_readme_init() {
|
|
459
|
+
local pr_script="$1"
|
|
460
|
+
print_info "Checking GitHub profile README..."
|
|
461
|
+
if bash "$pr_script" init; then
|
|
462
|
+
print_info "Profile README ready."
|
|
463
|
+
else
|
|
464
|
+
print_warning "Profile README setup failed (non-fatal, skipping)"
|
|
465
|
+
fi
|
|
466
|
+
return 0
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
_install_profile_readme_launchd() {
|
|
470
|
+
local pr_label="$1"
|
|
471
|
+
local pr_script="$2"
|
|
472
|
+
local pr_plist="$HOME/Library/LaunchAgents/${pr_label}.plist"
|
|
473
|
+
local _xml_pr_script _xml_pr_home
|
|
474
|
+
_xml_pr_script=$(_xml_escape "$pr_script")
|
|
475
|
+
_xml_pr_home=$(_xml_escape "$HOME")
|
|
476
|
+
|
|
477
|
+
local pr_plist_content
|
|
478
|
+
pr_plist_content=$(
|
|
479
|
+
cat <<PR_PLIST
|
|
480
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
481
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
482
|
+
<plist version="1.0">
|
|
483
|
+
<dict>
|
|
484
|
+
<key>Label</key>
|
|
485
|
+
<string>${pr_label}</string>
|
|
486
|
+
<key>ProgramArguments</key>
|
|
487
|
+
<array>
|
|
488
|
+
<string>$(_xml_escape "$(_resolve_modern_bash)")</string>
|
|
489
|
+
<string>${_xml_pr_script}</string>
|
|
490
|
+
<string>update</string>
|
|
491
|
+
</array>
|
|
492
|
+
<key>StartInterval</key>
|
|
493
|
+
<integer>3600</integer>
|
|
494
|
+
<key>StandardOutPath</key>
|
|
495
|
+
<string>${_xml_pr_home}/.aidevops/.agent-workspace/logs/profile-readme-update.log</string>
|
|
496
|
+
<key>StandardErrorPath</key>
|
|
497
|
+
<string>${_xml_pr_home}/.aidevops/.agent-workspace/logs/profile-readme-update.log</string>
|
|
498
|
+
<key>EnvironmentVariables</key>
|
|
499
|
+
<dict>
|
|
500
|
+
<key>PATH</key>
|
|
501
|
+
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
|
502
|
+
<key>HOME</key>
|
|
503
|
+
<string>${_xml_pr_home}</string>
|
|
504
|
+
</dict>
|
|
505
|
+
<key>RunAtLoad</key>
|
|
506
|
+
<false/>
|
|
507
|
+
<key>KeepAlive</key>
|
|
508
|
+
<false/>
|
|
509
|
+
<key>ProcessType</key>
|
|
510
|
+
<string>Background</string>
|
|
511
|
+
<key>LowPriorityBackgroundIO</key>
|
|
512
|
+
<true/>
|
|
513
|
+
<key>Nice</key>
|
|
514
|
+
<integer>10</integer>
|
|
515
|
+
</dict>
|
|
516
|
+
</plist>
|
|
517
|
+
PR_PLIST
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
if _launchd_install_if_changed "$pr_label" "$pr_plist" "$pr_plist_content"; then
|
|
521
|
+
print_info "Profile README update enabled (launchd, hourly)"
|
|
522
|
+
else
|
|
523
|
+
print_warning "Failed to load profile README update LaunchAgent"
|
|
524
|
+
fi
|
|
525
|
+
return 0
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
_install_profile_readme_scheduler() {
|
|
529
|
+
local pr_label="$1"
|
|
530
|
+
local pr_systemd="$2"
|
|
531
|
+
local pr_script="$3"
|
|
532
|
+
local pr_log="$4"
|
|
533
|
+
|
|
534
|
+
if [[ "$(uname -s)" == "Darwin" ]]; then
|
|
535
|
+
_install_profile_readme_launchd "$pr_label" "$pr_script"
|
|
536
|
+
return 0
|
|
537
|
+
fi
|
|
538
|
+
|
|
539
|
+
_install_scheduler_linux \
|
|
540
|
+
"$pr_systemd" \
|
|
541
|
+
"aidevops: profile-readme-update" \
|
|
542
|
+
"$CRON_HOURLY" \
|
|
543
|
+
"\"${pr_script}\" update" \
|
|
544
|
+
"3600" \
|
|
545
|
+
"$pr_log" \
|
|
546
|
+
"" \
|
|
547
|
+
"Profile README update enabled (hourly)" \
|
|
548
|
+
"Failed to install profile README update scheduler" \
|
|
549
|
+
"false" \
|
|
550
|
+
"true"
|
|
551
|
+
return 0
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
setup_profile_readme() {
|
|
555
|
+
local pr_script="$HOME/.aidevops/agents/scripts/profile-readme-helper.sh"
|
|
556
|
+
local pr_label="sh.aidevops.profile-readme-update"
|
|
557
|
+
if ! _profile_readme_ready "$pr_script"; then
|
|
558
|
+
return 0
|
|
559
|
+
fi
|
|
560
|
+
|
|
561
|
+
# Initialize profile repo if not already set up.
|
|
562
|
+
# Always run init — it's idempotent and handles:
|
|
563
|
+
# - Fresh installs (no profile repo)
|
|
564
|
+
# - Missing markers (injects them into existing README)
|
|
565
|
+
# - Diverged history (repo deleted and recreated on GitHub)
|
|
566
|
+
# - Already-initialized repos (returns early with no changes)
|
|
567
|
+
_run_profile_readme_init "$pr_script"
|
|
568
|
+
|
|
569
|
+
# Profile README auto-update scheduled job.
|
|
570
|
+
# Installed whenever gh CLI is available — the update script self-heals
|
|
571
|
+
# (discovers/creates the profile repo on first run via _resolve_profile_repo).
|
|
572
|
+
# macOS: launchd plist (hourly) | Linux: systemd timer or cron (hourly)
|
|
573
|
+
local pr_systemd="aidevops-profile-readme-update"
|
|
574
|
+
local pr_log="$HOME/.aidevops/.agent-workspace/logs/profile-readme-update.log"
|
|
575
|
+
mkdir -p "$HOME/.aidevops/.agent-workspace/logs"
|
|
576
|
+
|
|
577
|
+
_install_profile_readme_scheduler "$pr_label" "$pr_systemd" "$pr_script" "$pr_log"
|
|
578
|
+
return 0
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
# Detect Windows Git Bash / MINGW64 / MSYS2 environment.
|
|
582
|
+
# WSL reports "Linux" from uname -s and uses the cron path — correct behaviour.
|
|
583
|
+
# Returns 0 (true) on Windows Git Bash/MINGW/MSYS/Cygwin, 1 otherwise.
|
|
584
|
+
_is_windows() {
|
|
585
|
+
case "$(uname -s)" in
|
|
586
|
+
MINGW* | MSYS* | CYGWIN*)
|
|
587
|
+
return 0
|
|
588
|
+
;;
|
|
589
|
+
*)
|
|
590
|
+
return 1
|
|
591
|
+
;;
|
|
592
|
+
esac
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
# Install OAuth token refresh via Windows Task Scheduler (schtasks).
|
|
596
|
+
# Args: $1=tr_script (Unix path), $2=log_dir (Unix path)
|
|
597
|
+
# Runs every 30 minutes, matching macOS launchd and Linux cron behaviour.
|
|
598
|
+
# Uses bash.exe from Git for Windows to execute the shell script.
|
|
599
|
+
_install_token_refresh_schtasks() {
|
|
600
|
+
local tr_script="$1"
|
|
601
|
+
local log_dir="$2"
|
|
602
|
+
local task_name="aidevops-token-refresh"
|
|
603
|
+
|
|
604
|
+
# Resolve bash.exe — Git for Windows ships it alongside git.exe
|
|
605
|
+
local bash_exe
|
|
606
|
+
bash_exe=$(command -v bash.exe 2>/dev/null || command -v bash 2>/dev/null || echo "bash")
|
|
607
|
+
|
|
608
|
+
# Convert Unix paths to Windows paths for schtasks (requires cygpath from Git Bash)
|
|
609
|
+
local tr_script_win log_dir_win bash_exe_win
|
|
610
|
+
if command -v cygpath &>/dev/null; then
|
|
611
|
+
tr_script_win=$(cygpath -w "$tr_script")
|
|
612
|
+
log_dir_win=$(cygpath -w "$log_dir")
|
|
613
|
+
bash_exe_win=$(cygpath -w "$bash_exe")
|
|
614
|
+
else
|
|
615
|
+
# Fallback: manual conversion (replace /c/ with C:\, forward to backslash)
|
|
616
|
+
tr_script_win=$(echo "$tr_script" | sed 's|^/\([a-zA-Z]\)/|\1:\\|; s|/|\\|g')
|
|
617
|
+
log_dir_win=$(echo "$log_dir" | sed 's|^/\([a-zA-Z]\)/|\1:\\|; s|/|\\|g')
|
|
618
|
+
bash_exe_win="bash.exe"
|
|
619
|
+
fi
|
|
620
|
+
|
|
621
|
+
# Remove existing task (idempotent — ignore error if not present)
|
|
622
|
+
schtasks /Delete /TN "$task_name" /F >/dev/null 2>&1 || true
|
|
623
|
+
|
|
624
|
+
# Create scheduled task: every 30 minutes, run at logon, run whether logged on or not
|
|
625
|
+
# /SC MINUTE /MO 30 = every 30 minutes
|
|
626
|
+
# /RL HIGHEST = run with highest available privileges (needed for token writes)
|
|
627
|
+
# /F = force creation (overwrite if exists)
|
|
628
|
+
# The action runs bash.exe with -c to chain both refresh calls
|
|
629
|
+
local action_cmd
|
|
630
|
+
action_cmd="\"${bash_exe_win}\" -c \"'${tr_script_win}' refresh anthropic >> '${log_dir_win}\\token-refresh.log' 2>&1; '${tr_script_win}' refresh openai >> '${log_dir_win}\\token-refresh.log' 2>&1\""
|
|
631
|
+
|
|
632
|
+
if schtasks /Create \
|
|
633
|
+
/TN "$task_name" \
|
|
634
|
+
/TR "$action_cmd" \
|
|
635
|
+
/SC MINUTE \
|
|
636
|
+
/MO 30 \
|
|
637
|
+
/RL HIGHEST \
|
|
638
|
+
/F \
|
|
639
|
+
>/dev/null 2>&1; then
|
|
640
|
+
print_info "OAuth token refresh enabled (schtasks, every 30 min)"
|
|
641
|
+
# Run immediately to refresh any expired tokens
|
|
642
|
+
schtasks /Run /TN "$task_name" >/dev/null 2>&1 || true
|
|
643
|
+
else
|
|
644
|
+
print_warning "Failed to create token refresh scheduled task. Run manually: schtasks /Create /TN aidevops-token-refresh /TR \"bash '${tr_script_win}' refresh anthropic\" /SC MINUTE /MO 30"
|
|
645
|
+
fi
|
|
646
|
+
return 0
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
# Remove OAuth token refresh Windows scheduled task (uninstall path).
|
|
650
|
+
_uninstall_token_refresh_schtasks() {
|
|
651
|
+
local task_name="aidevops-token-refresh"
|
|
652
|
+
if schtasks /Query /TN "$task_name" >/dev/null 2>&1; then
|
|
653
|
+
schtasks /Delete /TN "$task_name" /F >/dev/null 2>&1 || true
|
|
654
|
+
print_info "OAuth token refresh disabled (schtasks task removed)"
|
|
655
|
+
fi
|
|
656
|
+
return 0
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
# Setup OAuth token refresh scheduled job.
|
|
660
|
+
# Refreshes expired/expiring tokens every 30 min so sessions never hit
|
|
661
|
+
# "invalid x-api-key". Also runs at load to catch tokens that expired
|
|
662
|
+
# while the machine was off.
|
|
663
|
+
# macOS: launchd plist | Linux/WSL: systemd timer or cron | Windows Git Bash: schtasks
|
|
664
|
+
_oauth_token_refresh_ready() {
|
|
665
|
+
local tr_script="$1"
|
|
666
|
+
if ! [[ -x "$tr_script" ]]; then
|
|
667
|
+
return 1
|
|
668
|
+
fi
|
|
669
|
+
if ! [[ -f "$HOME/.aidevops/oauth-pool.json" ]]; then
|
|
670
|
+
return 1
|
|
671
|
+
fi
|
|
672
|
+
return 0
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
_install_token_refresh_launchd() {
|
|
676
|
+
local tr_label="$1"
|
|
677
|
+
local tr_script="$2"
|
|
678
|
+
local tr_plist="$HOME/Library/LaunchAgents/${tr_label}.plist"
|
|
679
|
+
local _xml_tr_script _xml_tr_home
|
|
680
|
+
_xml_tr_script=$(_xml_escape "$tr_script")
|
|
681
|
+
_xml_tr_home=$(_xml_escape "$HOME")
|
|
682
|
+
|
|
683
|
+
local tr_plist_content
|
|
684
|
+
tr_plist_content=$(
|
|
685
|
+
cat <<TR_PLIST
|
|
686
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
687
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
688
|
+
<plist version="1.0">
|
|
689
|
+
<dict>
|
|
690
|
+
<key>Label</key>
|
|
691
|
+
<string>${tr_label}</string>
|
|
692
|
+
<key>ProgramArguments</key>
|
|
693
|
+
<array>
|
|
694
|
+
<string>$(_xml_escape "$(_resolve_modern_bash)")</string>
|
|
695
|
+
<string>-c</string>
|
|
696
|
+
<string>"${_xml_tr_script}" refresh anthropic; "${_xml_tr_script}" refresh openai</string>
|
|
697
|
+
</array>
|
|
698
|
+
<key>StartInterval</key>
|
|
699
|
+
<integer>1800</integer>
|
|
700
|
+
<key>StandardOutPath</key>
|
|
701
|
+
<string>${_xml_tr_home}/.aidevops/.agent-workspace/logs/token-refresh.log</string>
|
|
702
|
+
<key>StandardErrorPath</key>
|
|
703
|
+
<string>${_xml_tr_home}/.aidevops/.agent-workspace/logs/token-refresh.log</string>
|
|
704
|
+
<key>EnvironmentVariables</key>
|
|
705
|
+
<dict>
|
|
706
|
+
<key>PATH</key>
|
|
707
|
+
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
|
708
|
+
<key>HOME</key>
|
|
709
|
+
<string>${_xml_tr_home}</string>
|
|
710
|
+
</dict>
|
|
711
|
+
<key>RunAtLoad</key>
|
|
712
|
+
<true/>
|
|
713
|
+
<key>KeepAlive</key>
|
|
714
|
+
<false/>
|
|
715
|
+
<key>ProcessType</key>
|
|
716
|
+
<string>Background</string>
|
|
717
|
+
<key>LowPriorityBackgroundIO</key>
|
|
718
|
+
<true/>
|
|
719
|
+
<key>Nice</key>
|
|
720
|
+
<integer>10</integer>
|
|
721
|
+
</dict>
|
|
722
|
+
</plist>
|
|
723
|
+
TR_PLIST
|
|
724
|
+
)
|
|
725
|
+
|
|
726
|
+
if _launchd_install_if_changed "$tr_label" "$tr_plist" "$tr_plist_content"; then
|
|
727
|
+
print_info "OAuth token refresh enabled (launchd, every 30 min)"
|
|
728
|
+
else
|
|
729
|
+
print_warning "Failed to load token refresh LaunchAgent"
|
|
730
|
+
fi
|
|
731
|
+
return 0
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
setup_oauth_token_refresh() {
|
|
735
|
+
local tr_script="$HOME/.aidevops/agents/scripts/oauth-pool-helper.sh"
|
|
736
|
+
local tr_label="sh.aidevops.token-refresh"
|
|
737
|
+
if ! _oauth_token_refresh_ready "$tr_script"; then
|
|
738
|
+
return 0
|
|
739
|
+
fi
|
|
740
|
+
|
|
741
|
+
local tr_log_dir="$HOME/.aidevops/.agent-workspace/logs"
|
|
742
|
+
mkdir -p "$tr_log_dir"
|
|
743
|
+
|
|
744
|
+
if [[ "$(uname -s)" == "Darwin" ]]; then
|
|
745
|
+
_install_token_refresh_launchd "$tr_label" "$tr_script"
|
|
746
|
+
elif _is_windows; then
|
|
747
|
+
# Windows Git Bash / MINGW64 / MSYS2: use Task Scheduler (schtasks)
|
|
748
|
+
_install_token_refresh_schtasks "$tr_script" "$tr_log_dir"
|
|
749
|
+
else
|
|
750
|
+
# Linux / WSL without systemd: systemd timer or cron fallback
|
|
751
|
+
_install_scheduler_linux \
|
|
752
|
+
"aidevops-token-refresh" \
|
|
753
|
+
"aidevops: token-refresh" \
|
|
754
|
+
"*/30 * * * *" \
|
|
755
|
+
"\"${tr_script}\" refresh anthropic; \"${tr_script}\" refresh openai" \
|
|
756
|
+
"1800" \
|
|
757
|
+
"${tr_log_dir}/token-refresh.log" \
|
|
758
|
+
"" \
|
|
759
|
+
"OAuth token refresh enabled (every 30 min)" \
|
|
760
|
+
"Failed to install token refresh scheduler" \
|
|
761
|
+
"true" \
|
|
762
|
+
"true"
|
|
763
|
+
fi
|
|
764
|
+
return 0
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
# Setup opencode DB maintenance scheduler (r913, t2183).
|
|
768
|
+
# Runs weekly (Sun 04:00 local) to checkpoint/optimize/vacuum opencode.db.
|
|
769
|
+
# The helper self-noops on missing DB, so installing unconditionally is safe —
|
|
770
|
+
# a non-opencode machine wakes up weekly, sees no DB, exits 0 silently.
|
|
771
|
+
#
|
|
772
|
+
# Platform split (mirrors the pattern for token-refresh):
|
|
773
|
+
# macOS — helper owns its plist generation via cmd_install (Approach B).
|
|
774
|
+
# Linux — _install_scheduler_linux with cron `0 4 * * 0` + systemd
|
|
775
|
+
# OnCalendar `Sun *-*-* 04:00:00` for accurate wall-clock firing.
|
|
776
|
+
# Windows — TODO(t2183-followup): opencode on Windows is rare and the
|
|
777
|
+
# helper self-noops on missing DB, so leaving unscheduled is
|
|
778
|
+
# low-risk for this iteration.
|
|
779
|
+
setup_opencode_db_maintenance() {
|
|
780
|
+
local ocdbm_script="$HOME/.aidevops/agents/scripts/opencode-db-maintenance-helper.sh"
|
|
781
|
+
if ! [[ -x "$ocdbm_script" ]]; then
|
|
782
|
+
return 0
|
|
783
|
+
fi
|
|
784
|
+
|
|
785
|
+
local ocdbm_log_dir="$HOME/.aidevops/.agent-workspace/logs"
|
|
786
|
+
mkdir -p "$ocdbm_log_dir"
|
|
787
|
+
|
|
788
|
+
if [[ "$(uname -s)" == "Darwin" ]]; then
|
|
789
|
+
# Helper owns its own plist generation (Approach B, like repo-sync).
|
|
790
|
+
# Quiet the helper's multi-line output and emit one consolidated line
|
|
791
|
+
# to match the style of setup_profile_readme / setup_oauth_token_refresh.
|
|
792
|
+
if bash "$ocdbm_script" install >/dev/null 2>&1; then
|
|
793
|
+
print_info "OpenCode DB maintenance enabled (launchd, weekly Sun 04:00)"
|
|
794
|
+
else
|
|
795
|
+
print_warning "Failed to install opencode DB maintenance LaunchAgent"
|
|
796
|
+
fi
|
|
797
|
+
elif _is_windows; then
|
|
798
|
+
# Windows scheduling deferred — helper self-noops on missing DB so
|
|
799
|
+
# the cost of leaving unscheduled is ~0 until opencode lands on
|
|
800
|
+
# Windows in quantity.
|
|
801
|
+
return 0
|
|
802
|
+
else
|
|
803
|
+
# Linux / WSL: prefer systemd user timer, fall back to cron.
|
|
804
|
+
# Weekly Sunday 04:00 local — cron: `0 4 * * 0`; systemd OnCalendar
|
|
805
|
+
# ensures wall-clock firing even across suspends/reboots.
|
|
806
|
+
_install_scheduler_linux \
|
|
807
|
+
"aidevops-opencode-db-maintenance" \
|
|
808
|
+
"aidevops: opencode-db-maintenance" \
|
|
809
|
+
"0 4 * * 0" \
|
|
810
|
+
"\"${ocdbm_script}\" auto" \
|
|
811
|
+
"604800" \
|
|
812
|
+
"${ocdbm_log_dir}/opencode-db-maintenance.log" \
|
|
813
|
+
"" \
|
|
814
|
+
"OpenCode DB maintenance enabled (weekly Sun 04:00)" \
|
|
815
|
+
"Failed to install opencode DB maintenance scheduler" \
|
|
816
|
+
"false" \
|
|
817
|
+
"true" \
|
|
818
|
+
"Sun *-*-* 04:00:00"
|
|
819
|
+
fi
|
|
820
|
+
return 0
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
# Setup repo-sync scheduler if not already installed.
|
|
824
|
+
# Keeps local git repos up to date with daily ff-only pulls.
|
|
825
|
+
# Respects config: aidevops config set orchestration.repo_sync false
|
|
826
|
+
setup_repo_sync() {
|
|
827
|
+
local repo_sync_script="$HOME/.aidevops/agents/scripts/repo-sync-helper.sh"
|
|
828
|
+
if ! [[ -x "$repo_sync_script" ]] || ! is_feature_enabled repo_sync 2>/dev/null; then
|
|
829
|
+
return 0
|
|
830
|
+
fi
|
|
831
|
+
|
|
832
|
+
local _repo_sync_installed=false
|
|
833
|
+
if _launchd_has_agent "com.aidevops.aidevops-repo-sync"; then
|
|
834
|
+
_repo_sync_installed=true
|
|
835
|
+
elif _launchd_has_agent "sh.aidevops.repo-sync"; then
|
|
836
|
+
_repo_sync_installed=true
|
|
837
|
+
elif crontab -l 2>/dev/null | grep -qF "aidevops-repo-sync"; then
|
|
838
|
+
_repo_sync_installed=true
|
|
839
|
+
elif command -v systemctl >/dev/null 2>&1 &&
|
|
840
|
+
systemctl --user is-enabled "aidevops-repo-sync.timer" >/dev/null 2>&1; then
|
|
841
|
+
_repo_sync_installed=true
|
|
842
|
+
fi
|
|
843
|
+
if [[ "$_repo_sync_installed" == "false" ]]; then
|
|
844
|
+
if [[ "$NON_INTERACTIVE" == "true" ]]; then
|
|
845
|
+
bash "$repo_sync_script" enable >/dev/null 2>&1 || true
|
|
846
|
+
print_info "Repo sync enabled (daily). Disable: aidevops repo-sync disable"
|
|
847
|
+
else
|
|
848
|
+
echo ""
|
|
849
|
+
echo "Repo sync keeps your local git repos up to date by running"
|
|
850
|
+
echo "git pull --ff-only daily on clean repos on their default branch."
|
|
851
|
+
echo ""
|
|
852
|
+
setup_prompt enable_repo_sync "Enable daily repo sync? [Y/n]: " "Y"
|
|
853
|
+
if [[ "$enable_repo_sync" =~ ^[Yy]?$ || -z "$enable_repo_sync" ]]; then
|
|
854
|
+
bash "$repo_sync_script" enable
|
|
855
|
+
else
|
|
856
|
+
print_info "Skipped. Enable later: aidevops repo-sync enable"
|
|
857
|
+
fi
|
|
858
|
+
fi
|
|
859
|
+
fi
|
|
860
|
+
return 0
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
# Setup r914 repo-aidevops-health scheduler if not already installed.
|
|
864
|
+
# Daily drift keeper for repos.json: bumps stale .aidevops.json versions
|
|
865
|
+
# and surfaces missing-folder / no-init drift for human triage.
|
|
866
|
+
# Respects config: aidevops config set orchestration.repo_aidevops_health false
|
|
867
|
+
setup_repo_aidevops_health() {
|
|
868
|
+
local repo_health_script="$HOME/.aidevops/agents/scripts/repo-aidevops-health-helper.sh"
|
|
869
|
+
if ! [[ -x "$repo_health_script" ]] || ! is_feature_enabled repo_aidevops_health 2>/dev/null; then
|
|
870
|
+
return 0
|
|
871
|
+
fi
|
|
872
|
+
|
|
873
|
+
local _repo_health_installed=false
|
|
874
|
+
if _launchd_has_agent "sh.aidevops.repo-aidevops-health"; then
|
|
875
|
+
_repo_health_installed=true
|
|
876
|
+
elif crontab -l 2>/dev/null | grep -qF "aidevops-repo-aidevops-health"; then
|
|
877
|
+
_repo_health_installed=true
|
|
878
|
+
elif command -v systemctl >/dev/null 2>&1 &&
|
|
879
|
+
systemctl --user is-enabled "aidevops-repo-aidevops-health.timer" >/dev/null 2>&1; then
|
|
880
|
+
_repo_health_installed=true
|
|
881
|
+
fi
|
|
882
|
+
if [[ "$_repo_health_installed" == "false" ]]; then
|
|
883
|
+
if [[ "$NON_INTERACTIVE" == "true" ]]; then
|
|
884
|
+
bash "$repo_health_script" enable >/dev/null 2>&1 || true
|
|
885
|
+
print_info "r914 repo-aidevops-health enabled (daily @03:30). Disable: aidevops repo-aidevops-health disable"
|
|
886
|
+
else
|
|
887
|
+
echo ""
|
|
888
|
+
echo "r914 keeps \`.aidevops.json\` versions current across all registered"
|
|
889
|
+
echo "repos and surfaces registry drift (missing folders, unregistered git"
|
|
890
|
+
echo "repos) for human triage. Runs daily at 03:30."
|
|
891
|
+
echo ""
|
|
892
|
+
setup_prompt enable_repo_health "Enable daily r914 repo-aidevops-health? [Y/n]: " "Y"
|
|
893
|
+
if [[ "$enable_repo_health" =~ ^[Yy]?$ || -z "$enable_repo_health" ]]; then
|
|
894
|
+
bash "$repo_health_script" enable
|
|
895
|
+
else
|
|
896
|
+
print_info "Skipped. Enable later: aidevops repo-aidevops-health enable"
|
|
897
|
+
fi
|
|
898
|
+
fi
|
|
899
|
+
fi
|
|
900
|
+
return 0
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
# ============================================================================
|
|
904
|
+
# Peer productivity monitor (t2932)
|
|
905
|
+
# ============================================================================
|
|
906
|
+
#
|
|
907
|
+
# Adaptive cross-runner dispatch coordination: observes peer GitHub activity
|
|
908
|
+
# every 30 min and updates ~/.config/aidevops/dispatch-override.conf to
|
|
909
|
+
# `ignore` peers whose pulse is broken (claims issues but never PRs) and
|
|
910
|
+
# back to `honour` when they recover. Self-healing across the ecosystem —
|
|
911
|
+
# each runner observes peers independently, no central coordinator needed.
|
|
912
|
+
# Manual entries in dispatch-override.conf above the auto-managed marker
|
|
913
|
+
# always take precedence.
|
|
914
|
+
|
|
915
|
+
# Install peer-productivity-monitor launchd plist (macOS).
|
|
916
|
+
# Args: $1=label $2=script $3=log_dir
|
|
917
|
+
_install_peer_productivity_monitor_launchd() {
|
|
918
|
+
local ppm_label="$1"
|
|
919
|
+
local ppm_script="$2"
|
|
920
|
+
local _ppm_log_dir="$3"
|
|
921
|
+
local ppm_plist="$HOME/Library/LaunchAgents/${ppm_label}.plist"
|
|
922
|
+
|
|
923
|
+
local _xml_ppm_script _xml_ppm_home _xml_ppm_log_dir
|
|
924
|
+
_xml_ppm_script=$(_xml_escape "$ppm_script")
|
|
925
|
+
_xml_ppm_home=$(_xml_escape "$HOME")
|
|
926
|
+
_xml_ppm_log_dir=$(_xml_escape "$_ppm_log_dir")
|
|
927
|
+
|
|
928
|
+
local ppm_plist_content
|
|
929
|
+
ppm_plist_content=$(
|
|
930
|
+
cat <<PPM_PLIST
|
|
931
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
932
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
933
|
+
<plist version="1.0">
|
|
934
|
+
<dict>
|
|
935
|
+
<key>Label</key>
|
|
936
|
+
<string>${ppm_label}</string>
|
|
937
|
+
<key>ProgramArguments</key>
|
|
938
|
+
<array>
|
|
939
|
+
<string>$(_xml_escape "$(_resolve_modern_bash)")</string>
|
|
940
|
+
<string>${_xml_ppm_script}</string>
|
|
941
|
+
<string>observe</string>
|
|
942
|
+
</array>
|
|
943
|
+
<key>StartInterval</key>
|
|
944
|
+
<integer>1800</integer>
|
|
945
|
+
<key>StandardOutPath</key>
|
|
946
|
+
<string>${_xml_ppm_log_dir}/peer-productivity-launchd.log</string>
|
|
947
|
+
<key>StandardErrorPath</key>
|
|
948
|
+
<string>${_xml_ppm_log_dir}/peer-productivity-launchd.log</string>
|
|
949
|
+
<key>EnvironmentVariables</key>
|
|
950
|
+
<dict>
|
|
951
|
+
<key>PATH</key>
|
|
952
|
+
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
|
953
|
+
<key>HOME</key>
|
|
954
|
+
<string>${_xml_ppm_home}</string>
|
|
955
|
+
</dict>
|
|
956
|
+
<key>RunAtLoad</key>
|
|
957
|
+
<true/>
|
|
958
|
+
<key>KeepAlive</key>
|
|
959
|
+
<false/>
|
|
960
|
+
<key>ProcessType</key>
|
|
961
|
+
<string>Background</string>
|
|
962
|
+
<key>LowPriorityBackgroundIO</key>
|
|
963
|
+
<true/>
|
|
964
|
+
<key>Nice</key>
|
|
965
|
+
<integer>10</integer>
|
|
966
|
+
</dict>
|
|
967
|
+
</plist>
|
|
968
|
+
PPM_PLIST
|
|
969
|
+
)
|
|
970
|
+
|
|
971
|
+
if _launchd_install_if_changed "$ppm_label" "$ppm_plist" "$ppm_plist_content"; then
|
|
972
|
+
print_info "Peer productivity monitor enabled (launchd, every 30 min)"
|
|
973
|
+
else
|
|
974
|
+
print_warning "Failed to load peer-productivity-monitor LaunchAgent"
|
|
975
|
+
fi
|
|
976
|
+
return 0
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
# Install peer-productivity-monitor via systemd or cron (Linux).
|
|
980
|
+
# Args: $1=script path, $2=log dir
|
|
981
|
+
_install_peer_productivity_monitor_linux() {
|
|
982
|
+
local ppm_script="$1"
|
|
983
|
+
local _ppm_log_dir="$2"
|
|
984
|
+
local ppm_systemd="aidevops-peer-productivity-monitor"
|
|
985
|
+
_install_scheduler_linux \
|
|
986
|
+
"$ppm_systemd" \
|
|
987
|
+
"aidevops: peer-productivity-monitor" \
|
|
988
|
+
"*/30 * * * *" \
|
|
989
|
+
"\"${ppm_script}\" observe" \
|
|
990
|
+
"1800" \
|
|
991
|
+
"${_ppm_log_dir}/peer-productivity-launchd.log" \
|
|
992
|
+
"" \
|
|
993
|
+
"Peer productivity monitor enabled (every 30 min)" \
|
|
994
|
+
"Failed to install peer-productivity-monitor scheduler" \
|
|
995
|
+
"true" \
|
|
996
|
+
"true"
|
|
997
|
+
return 0
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
# Setup peer-productivity-monitor (t2932) — observes peer GitHub activity
|
|
1001
|
+
# every 30 min and updates ~/.config/aidevops/dispatch-override.conf so the
|
|
1002
|
+
# local pulse competes with broken peers and collaborates with healthy ones.
|
|
1003
|
+
# Manual entries in dispatch-override.conf above the auto-managed marker
|
|
1004
|
+
# always take precedence.
|
|
1005
|
+
setup_peer_productivity_monitor() {
|
|
1006
|
+
local ppm_script="$HOME/.aidevops/agents/scripts/peer-productivity-monitor.sh"
|
|
1007
|
+
local ppm_label="sh.aidevops.peer-productivity-monitor"
|
|
1008
|
+
if ! [[ -x "$ppm_script" ]]; then
|
|
1009
|
+
return 0
|
|
1010
|
+
fi
|
|
1011
|
+
|
|
1012
|
+
# Reuse contribution-watch's log-dir resolver (same logic, same config key).
|
|
1013
|
+
local _ppm_log_dir
|
|
1014
|
+
_ppm_log_dir=$(_resolve_cw_log_dir) || return 1
|
|
1015
|
+
mkdir -p "$_ppm_log_dir"
|
|
1016
|
+
|
|
1017
|
+
# Install/update scheduled runner
|
|
1018
|
+
if [[ "$(uname -s)" == "Darwin" ]]; then
|
|
1019
|
+
_install_peer_productivity_monitor_launchd "$ppm_label" "$ppm_script" "$_ppm_log_dir"
|
|
1020
|
+
else
|
|
1021
|
+
_install_peer_productivity_monitor_linux "$ppm_script" "$_ppm_log_dir"
|
|
1022
|
+
fi
|
|
1023
|
+
return 0
|
|
1024
|
+
}
|