Skip to content
Closed
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`, `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: `configs/upstream-watch.json`.
- **Skills**: `aidevops skills`, `/skills`
- **Auto-update**: GitHub poll + daily skill/repo sync
- **Auto-update**: GitHub poll + daily skill/repo sync + upstream watch check
- **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 (t1424). 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: `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_SKILL_FRESHNESS_HOURS=24
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 @@ update_tool_check_timestamp() {
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 2>/dev/null; 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")
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
Comment on lines +963 to +974
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This function has two issues related to file writing:

  1. Silent Failures: The jq command on line 966 suppresses stderr with 2>/dev/null. If jq fails (e.g., due to a corrupted state file), the error is hidden, and the timestamp is not updated. This would cause the upstream watch check to run on every pulse, which is inefficient.
  2. Non-Atomic Write: The else block on line 968 writes directly to $STATE_FILE. This is not an atomic operation. If the script is interrupted or the disk is full, it could result in a corrupted or empty state file.

I suggest refactoring this to use a temporary file for both cases and to log any errors from jq for better diagnostics.

Suggested change
if [[ -f "$STATE_FILE" ]]; then
jq --arg ts "$timestamp" \
'. + {last_upstream_watch_check: $ts}' \
"$STATE_FILE" >"$tmp_state" 2>/dev/null && mv "$tmp_state" "$STATE_FILE"
else
jq -n --arg ts "$timestamp" \
'{last_upstream_watch_check: $ts}' >"$STATE_FILE"
fi
if [[ -f "$STATE_FILE" ]]; then
jq --arg ts "$timestamp" \
'. + {last_upstream_watch_check: $ts}' \
"$STATE_FILE" >"$tmp_state" 2>>"$LOG_FILE" && mv "$tmp_state" "$STATE_FILE"
else
jq -n --arg ts "$timestamp" \
'{last_upstream_watch_check: $ts}' >"$tmp_state" 2>>"$LOG_FILE
References
  1. When reporting errors for failed file operations in shell scripts, such as 'jq' writes, include the file path in the error message. Avoid suppressing stderr with '2>/dev/null' to ensure that diagnostic information about malformed files or write failures is visible.
  2. Avoid using '2>/dev/null' for blanket suppression of command errors in shell scripts to ensure that authentication, syntax, or system issues remain visible for debugging.

fi
return 0
}

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

Expand All @@ -906,6 +1024,7 @@ cmd_check() {
check_skill_freshness
check_openclaw_freshness
check_tool_freshness
check_upstream_watch
return 0
fi

Expand All @@ -920,6 +1039,7 @@ cmd_check() {
check_skill_freshness
check_openclaw_freshness
check_tool_freshness
check_upstream_watch
return 1
fi

Expand All @@ -930,6 +1050,7 @@ cmd_check() {
check_skill_freshness
check_openclaw_freshness
check_tool_freshness
check_upstream_watch
return 1
fi

Expand All @@ -939,6 +1060,7 @@ cmd_check() {
check_skill_freshness
check_openclaw_freshness
check_tool_freshness
check_upstream_watch
return 1
fi

Expand All @@ -955,6 +1077,7 @@ cmd_check() {
check_skill_freshness
check_openclaw_freshness
check_tool_freshness
check_upstream_watch
return 1
fi

Expand All @@ -967,6 +1090,9 @@ cmd_check() {
# 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
Loading
Loading