Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .agents/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ Read subagents on-demand. Full index: `subagent-index.toon`.
| Bundles | `bundles/*.json`, `scripts/bundle-helper.sh`, `tools/context/model-routing.md` |
| Model routing | `tools/context/model-routing.md`, `reference/orchestration.md` |
| Orchestration | `reference/orchestration.md`, `tools/ai-assistants/headless-dispatch.md`, `scripts/commands/pulse.md`, `scripts/commands/dashboard.md` |
| Upstream watch | `scripts/upstream-watch-helper.sh`, `.agents/configs/upstream-watch.json` |
| Agent/MCP dev | `tools/build-agent/build-agent.md`, `tools/build-mcp/build-mcp.md`, `tools/mcp-toolkit/mcporter.md` |
| Framework | `aidevops/architecture.md`, `scripts/commands/skills.md` |

Expand All @@ -231,8 +232,9 @@ Key capabilities (details in `reference/orchestration.md`, `reference/services.m
- **Memory**: cross-session SQLite FTS5 (`/remember`, `/recall`)
- **Orchestration**: supervisor dispatch, pulse scheduler, auto-pickup, cross-repo issue/PR/TODO visibility
- **Contribution watch**: monitors external issues/PRs for new comments needing reply. `contribution-watch-helper.sh seed|scan|status|install|uninstall`. Prompt-injection-safe — automated scans are deterministic (no LLM), comment bodies only shown in interactive sessions after `prompt-guard-helper.sh scan`.
- **Upstream watch**: monitors external repos we've borrowed ideas/code from for new releases. `upstream-watch-helper.sh add|remove|check|ack|status`. Shows release diffs and changelogs between our last-seen version and latest. Distinct from skill imports (code we pulled in) and contribution watch (repos we filed issues on) — this tracks "inspiration repos" for passive monitoring. Config: `.agents/configs/upstream-watch.json`.
- **Skills**: `aidevops skills`, `/skills`
- **Auto-update**: GitHub poll + daily skill/repo sync
- **Auto-update**: GitHub poll + daily skill/upstream watch/OpenClaw/tool freshness checks (via `auto-update-helper.sh`). Repo sync runs separately via `aidevops repo-sync` scheduler.
- **Browser**: Playwright, dev-browser (persistent login)
- **Quality**: Write-time per-edit linting → `linters-local.sh` → `/pr review` → `/postflight`. Fix violations at edit time, not commit time. See `prompts/build.txt` "Write-Time Quality Enforcement". Bundle `skip_gates` filter irrelevant checks per project type.
- **Sessions**: `/session-review`, `/checkpoint`, compaction resilience
Expand Down
4 changes: 4 additions & 0 deletions .agents/configs/upstream-watch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$comment": "Upstream repos to watch for releases and significant changes. Managed by upstream-watch-helper.sh (t1426). Entry schema: {slug, description, relevance, default_branch, added_at}. State (last_release_seen, last_commit_seen, last_checked, updates_pending) stored in ~/.aidevops/cache/upstream-watch-state.json.",
"repos": []
}
2 changes: 2 additions & 0 deletions .agents/reference/services.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ Automatic polling for new releases. Checks GitHub every 10 minutes and runs `aid

**Daily skill refresh**: Each auto-update check also runs a 24h-gated skill freshness check. If >24h have passed since the last check, `skill-update-helper.sh --auto-update --quiet` pulls upstream changes for all imported skills. State is tracked in `~/.aidevops/cache/auto-update-state.json` (`last_skill_check`, `skill_updates_applied`). Disable with `AIDEVOPS_SKILL_AUTO_UPDATE=false`; adjust frequency with `AIDEVOPS_SKILL_FRESHNESS_HOURS=<hours>` (default: 24). View skill check status with `aidevops auto-update status`.

**Upstream watch**: Alongside skill refresh, `upstream-watch-helper.sh check` monitors external repos we've borrowed ideas/code from for new releases. Unlike skill imports (code we pulled in) or contribution watch (repos we filed issues on), this tracks "inspiration repos" — repos we want to passively monitor for improvements relevant to our implementation. Config: `.agents/configs/upstream-watch.json`. State: `~/.aidevops/cache/upstream-watch-state.json`. Run `upstream-watch-helper.sh status` to see watched repos, `upstream-watch-helper.sh check` to check for updates, `upstream-watch-helper.sh ack <slug>` after reviewing.

**Repo version wins on update**: When `aidevops update` runs, shared agents in `~/.aidevops/agents/` are overwritten by the repo version. Only `custom/` and `draft/` directories are preserved. Imported skills stored outside these directories will be overwritten. To keep a skill across updates, either re-import it after each update or move it to `custom/`.

## Repo Sync
Expand Down
2 changes: 2 additions & 0 deletions .agents/reference/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ Controls automatic update behavior for aidevops, skills, tools, and OpenClaw.
| `auto_update.tool_idle_hours` | number | `6` | `AIDEVOPS_TOOL_IDLE_HOURS` | Required user idle time (hours) before tool updates run. Prevents updates during active work. |
| `auto_update.openclaw_auto_update` | boolean | `true` | `AIDEVOPS_OPENCLAW_AUTO_UPDATE` | Enable daily OpenClaw update checks (only if openclaw CLI is installed). |
| `auto_update.openclaw_freshness_hours` | number | `24` | `AIDEVOPS_OPENCLAW_FRESHNESS_HOURS` | Hours between OpenClaw update checks. |
| `auto_update.upstream_watch` | boolean | `true` | `AIDEVOPS_UPSTREAM_WATCH` | Enable daily upstream repo watch checks. Monitors external repos for new releases. |
| `auto_update.upstream_watch_hours` | number | `24` | `AIDEVOPS_UPSTREAM_WATCH_HOURS` | Hours between upstream watch checks. |

### supervisor

Expand Down
126 changes: 126 additions & 0 deletions .agents/scripts/auto-update-helper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
# AIDEVOPS_TOOL_AUTO_UPDATE=false Disable 6-hourly tool freshness check
# AIDEVOPS_TOOL_FRESHNESS_HOURS=6 Hours between tool checks (default: 6)
# AIDEVOPS_TOOL_IDLE_HOURS=6 Required user idle hours before tool updates (default: 6)
# AIDEVOPS_UPSTREAM_WATCH=false Disable daily upstream repo watch check
# AIDEVOPS_UPSTREAM_WATCH_HOURS=24 Hours between upstream watch checks (default: 24)
#
# Logs: ~/.aidevops/logs/auto-update.log

Expand Down Expand Up @@ -71,6 +73,7 @@
readonly DEFAULT_OPENCLAW_FRESHNESS_HOURS=24
readonly DEFAULT_TOOL_FRESHNESS_HOURS=6
readonly DEFAULT_TOOL_IDLE_HOURS=6
readonly DEFAULT_UPSTREAM_WATCH_HOURS=24
readonly LAUNCHD_LABEL="com.aidevops.aidevops-auto-update"
readonly LAUNCHD_DIR="$HOME/Library/LaunchAgents"
readonly LAUNCHD_PLIST="${LAUNCHD_DIR}/${LAUNCHD_LABEL}.plist"
Expand Down Expand Up @@ -859,6 +862,120 @@
return 0
}

#######################################
# Check upstream-watched repos for new releases (24h gate)
# Called from cmd_check after tool freshness check.
# Respects config: aidevops config set updates.upstream_watch false
#######################################
check_upstream_watch() {
# Opt-out via config (env var or config file)
if ! is_feature_enabled upstream_watch; then
log_info "Upstream watch disabled via config"
return 0
fi

local freshness_hours
freshness_hours=$(get_feature_toggle upstream_watch_hours "$DEFAULT_UPSTREAM_WATCH_HOURS")
if ! [[ "$freshness_hours" =~ ^[0-9]+$ ]] || [[ "$freshness_hours" -eq 0 ]]; then
log_warn "updates.upstream_watch_hours='${freshness_hours}' is not a positive integer — using default (${DEFAULT_UPSTREAM_WATCH_HOURS}h)"
freshness_hours="$DEFAULT_UPSTREAM_WATCH_HOURS"
fi
local freshness_seconds=$((freshness_hours * 3600))

# Read last upstream watch check timestamp from state file
local last_upstream_check=""
if [[ -f "$STATE_FILE" ]] && command -v jq &>/dev/null; then
last_upstream_check=$(jq -r '.last_upstream_watch_check // empty' "$STATE_FILE" 2>/dev/null || true)
fi

# Determine if check is needed
local needs_check=true
if [[ -n "$last_upstream_check" ]]; then
local last_epoch now_epoch elapsed
if [[ "$(uname)" == "Darwin" ]]; then
last_epoch=$(TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%SZ" "$last_upstream_check" "+%s" 2>/dev/null || echo "0")

Check warning on line 896 in .agents/scripts/auto-update-helper.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of using the literal '%Y-%m-%dT%H:%M:%SZ' 4 times.

See more on https://sonarcloud.io/project/issues?id=marcusquinn_aidevops&issues=AZzUP55CTBjk2HgQy6O0&open=AZzUP55CTBjk2HgQy6O0&pullRequest=4003
else
last_epoch=$(date -d "$last_upstream_check" "+%s" 2>/dev/null || echo "0")
fi
now_epoch=$(date +%s)
elapsed=$((now_epoch - last_epoch))

if [[ $elapsed -lt $freshness_seconds ]]; then
log_info "Upstream watch checked ${elapsed}s ago (gate: ${freshness_seconds}s) — skipping"
needs_check=false
fi
fi

if [[ "$needs_check" != "true" ]]; then
return 0
fi

# Locate upstream-watch-helper.sh (respect AIDEVOPS_AGENTS_DIR)
local agents_dir="${AIDEVOPS_AGENTS_DIR:-$HOME/.aidevops/agents}"
local upstream_watch_script="${agents_dir}/scripts/upstream-watch-helper.sh"
if [[ ! -x "$upstream_watch_script" ]]; then
upstream_watch_script="$INSTALL_DIR/.agents/scripts/upstream-watch-helper.sh"
fi

if [[ ! -x "$upstream_watch_script" ]]; then
log_info "upstream-watch-helper.sh not found — skipping upstream watch check"
return 0
fi

# Check if upstream-watch.json has any repos
local watch_config="${agents_dir}/configs/upstream-watch.json"
if [[ ! -f "$watch_config" ]]; then
log_info "No upstream watch config found — skipping"
update_upstream_watch_timestamp
return 0
fi

local repo_count
repo_count=$(jq '.repos | length' "$watch_config" 2>/dev/null || echo "0")
if [[ "$repo_count" -eq 0 ]]; then
log_info "No repos in upstream watchlist — skipping"
update_upstream_watch_timestamp
return 0
fi

log_info "Running daily upstream watch check (${repo_count} repos)..."
if "$upstream_watch_script" check >>"$LOG_FILE" 2>&1; then
log_info "Upstream watch check complete"
update_upstream_watch_timestamp
else
log_warn "Upstream watch check had errors (exit code: $?) — will retry next run"
fi
return 0
}

#######################################
# Record last_upstream_watch_check timestamp in state file
#######################################
update_upstream_watch_timestamp() {
local timestamp
timestamp="$(date -u +%Y-%m-%dT%H:%M:%SZ)"

if command -v jq &>/dev/null; then
local tmp_state
tmp_state=$(mktemp)
trap 'rm -f "${tmp_state:-}"' RETURN

if [[ -f "$STATE_FILE" ]]; then
if ! jq --arg ts "$timestamp" \
'. + {last_upstream_watch_check: $ts}' \
"$STATE_FILE" >"$tmp_state" 2>&1; then
log_warn "Failed to update upstream watch timestamp (jq error on state file)"
return 1
fi
mv "$tmp_state" "$STATE_FILE"
else
jq -n --arg ts "$timestamp" \
'{last_upstream_watch_check: $ts}' >"$STATE_FILE"
fi
fi
return 0
}

#######################################
# One-shot check and update
# This is what the cron job calls
Expand Down Expand Up @@ -897,6 +1014,7 @@
check_skill_freshness
check_openclaw_freshness
check_tool_freshness
check_upstream_watch
return 0
fi

Expand Down Expand Up @@ -927,6 +1045,7 @@
check_skill_freshness
check_openclaw_freshness
check_tool_freshness
check_upstream_watch
return 0
fi

Expand All @@ -941,6 +1060,7 @@
check_skill_freshness
check_openclaw_freshness
check_tool_freshness
check_upstream_watch
return 1
fi

Expand All @@ -951,6 +1071,7 @@
check_skill_freshness
check_openclaw_freshness
check_tool_freshness
check_upstream_watch
return 1
fi

Expand All @@ -960,6 +1081,7 @@
check_skill_freshness
check_openclaw_freshness
check_tool_freshness
check_upstream_watch
return 1
fi

Expand All @@ -986,6 +1108,7 @@
check_skill_freshness
check_openclaw_freshness
check_tool_freshness
check_upstream_watch
return 1
fi

Expand All @@ -998,6 +1121,9 @@
# Run 6-hourly tool freshness check (idle-gated)
check_tool_freshness

# Run daily upstream watch check (24h gate)
check_upstream_watch

return 0
}

Expand Down
4 changes: 3 additions & 1 deletion .agents/scripts/shared-constants.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1446,6 +1446,8 @@ _ft_env_map() {
repo_sync) echo "AIDEVOPS_REPO_SYNC" ;;
openclaw_auto_update) echo "AIDEVOPS_OPENCLAW_AUTO_UPDATE" ;;
openclaw_freshness_hours) echo "AIDEVOPS_OPENCLAW_FRESHNESS_HOURS" ;;
upstream_watch) echo "AIDEVOPS_UPSTREAM_WATCH" ;;
upstream_watch_hours) echo "AIDEVOPS_UPSTREAM_WATCH_HOURS" ;;
max_interactive_sessions) echo "AIDEVOPS_MAX_SESSIONS" ;;
*) echo "" ;;
esac
Expand Down Expand Up @@ -1478,7 +1480,7 @@ _load_feature_toggles_legacy() {
done <"$FEATURE_TOGGLES_USER"
fi

local toggle_keys="auto_update update_interval skill_auto_update skill_freshness_hours tool_auto_update tool_freshness_hours tool_idle_hours supervisor_pulse repo_sync openclaw_auto_update openclaw_freshness_hours manage_opencode_config manage_claude_config session_greeting safety_hooks shell_aliases onboarding_prompt"
local toggle_keys="auto_update update_interval skill_auto_update skill_freshness_hours tool_auto_update tool_freshness_hours tool_idle_hours supervisor_pulse repo_sync openclaw_auto_update openclaw_freshness_hours upstream_watch upstream_watch_hours max_interactive_sessions manage_opencode_config manage_claude_config session_greeting safety_hooks shell_aliases onboarding_prompt"
local tk env_var env_val
for tk in $toggle_keys; do
env_var=$(_ft_env_map "$tk")
Expand Down
Loading