From c28ab93eb8eb5dc5ca8ac7ae05ce5b83400c702b Mon Sep 17 00:00:00 2001 From: marcusquinn <6428977+marcusquinn@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:26:43 +0000 Subject: [PATCH 1/2] feat: add PULSE_SCOPE_REPOS boundary for worker dispatch (t1405) Separate 'observe and report' from 'observe and fix' for pulse-dispatched workers. Workers CAN file issues on any repo (cross-repo self-improvement), but code changes (branches, PRs) are restricted to repos listed in the PULSE_SCOPE_REPOS env var, which is set by pulse-wrapper.sh from repos with pulse: true in repos.json. This prevents the pattern where pulse-dispatched workers create PRs on repos outside the user's managed scope (observed: 4 PRs + a fork on a repo the user doesn't own). Closes #2928 --- .agents/AGENTS.md | 2 ++ .agents/prompts/build.txt | 2 ++ .agents/scripts/commands/full-loop.md | 21 +++++++++++++++++++++ .agents/scripts/commands/pulse.md | 1 + .agents/scripts/pulse-wrapper.sh | 9 +++++++++ 5 files changed, 35 insertions(+) diff --git a/.agents/AGENTS.md b/.agents/AGENTS.md index 268c3b159..253f21bc1 100644 --- a/.agents/AGENTS.md +++ b/.agents/AGENTS.md @@ -60,6 +60,8 @@ Every agent session — interactive, worker, or supervisor — should improve th If uncertain, ask: "Would this fix apply to every repo the framework manages, or only this one?" Framework-wide problems go to aidevops; project-specific problems stay local. Never create framework tasks in a project repo — they become invisible to framework maintainers and pollute the project's task namespace. +**Scope boundary for code changes (t1405, GH#2928).** Separate "observe and report" from "observe and fix". When dispatched by the pulse, the `PULSE_SCOPE_REPOS` env var lists the repo slugs where you may create branches and PRs. Filing issues is always allowed on any repo — cross-repo bug reports are valuable. But code changes (branches, PRs, commits) are restricted to repos in `PULSE_SCOPE_REPOS`. If the target repo is not in scope, file the issue and stop. The issue enters that repo's queue for their maintainers (or their own pulse) to handle. If `PULSE_SCOPE_REPOS` is empty or unset (interactive mode), no scope restriction applies. + **What counts as self-improvement:** - Filing issues for repeated failure patterns - Improving agent prompts when workers consistently misunderstand instructions diff --git a/.agents/prompts/build.txt b/.agents/prompts/build.txt index 6e515db02..a957b9628 100644 --- a/.agents/prompts/build.txt +++ b/.agents/prompts/build.txt @@ -236,6 +236,8 @@ Git is the primary audit trail for all work. Every change should be discoverable **Self-improvement repo routing:** When creating self-improvement tasks (see AGENTS.md "Self-Improvement"), route to the correct repo — not the repo you happen to be running in. Heuristic: if the observation references files under `~/.aidevops/`, framework scripts (`ai-actions.sh`, `ai-lifecycle.sh`, `supervisor/`, `dispatch.sh`, `pre-edit-check.sh`, helper scripts), agent prompt behaviour, or supervisor/pulse/orchestration logic, the task belongs in the aidevops repo. Run `claim-task-id.sh --repo-path ` first — it allocates the task ID and (by default) creates the GitHub issue. Only run `gh issue create --repo ` if `claim-task-id.sh` was invoked with `--no-issue` or its output did not include a `ref=GH#` (or `ref=GL#`) token; otherwise the issue already exists and a second create would duplicate it. Project-specific problems (CI, code, dependencies, domain logic) stay in the current repo. Framework tasks filed in project repos are invisible to framework maintainers and pollute the project's ID namespace. +**Pulse scope boundary (t1405, GH#2928):** When dispatched by the pulse, `PULSE_SCOPE_REPOS` (comma-separated repo slugs) defines which repos you may create branches and PRs on. Filing issues is always allowed on any repo. Code changes (branches, PRs, commits) are restricted to repos in `PULSE_SCOPE_REPOS`. If the target repo is not in scope, file the issue and stop — do not implement the fix. If `PULSE_SCOPE_REPOS` is empty or unset (interactive mode), no scope restriction applies. This prevents pulse-dispatched workers from creating PRs on repos outside the user's managed scope. + **Git-readiness check:** When working in a project directory that is NOT a git repo, or is a git repo without `TODO.md` / aidevops initialisation, flag this to the user: "This project has no git tracking / no aidevops task management. Work here won't be traceable. Consider `git init` + `aidevops init` to enable the full workflow." Use judgment — a throwaway script directory doesn't need this, but any project with ongoing development does. **Pre-edit rules:** diff --git a/.agents/scripts/commands/full-loop.md b/.agents/scripts/commands/full-loop.md index 348eee086..5584dc36d 100644 --- a/.agents/scripts/commands/full-loop.md +++ b/.agents/scripts/commands/full-loop.md @@ -474,6 +474,27 @@ When running as a headless worker (dispatched by the supervisor via `opencode ru Then continue with your assigned task in the current repo. The pulse supervisor will pick up the cross-repo issue on its next cycle. This prevents framework-level work from being tracked in app repos and vice versa. + **Scope boundary for code changes (t1405, GH#2928):** When dispatched by the pulse (headless mode), the `PULSE_SCOPE_REPOS` env var contains a comma-separated list of repo slugs that you are allowed to create branches and PRs on. This is set by `pulse-wrapper.sh` from repos with `pulse: true` in repos.json. + + - **Filing issues**: ALWAYS allowed on any repo, regardless of scope. Cross-repo bug reports are valuable feedback to maintainers. + - **Creating branches, PRs, or committing code**: ONLY allowed on repos listed in `PULSE_SCOPE_REPOS`. If the target repo is not in scope, file the issue and stop — do NOT implement the fix. + - **If `PULSE_SCOPE_REPOS` is empty or unset**: you are in interactive mode (not pulse-dispatched) — no scope restriction applies. + + ```bash + # Check if a target repo is in scope before creating code changes + TARGET_SLUG="owner/repo" + if [[ -n "${PULSE_SCOPE_REPOS:-}" ]]; then + if ! echo ",$PULSE_SCOPE_REPOS," | grep -qF ",$TARGET_SLUG,"; then + echo "Repo $TARGET_SLUG is outside pulse scope — filing issue only, not implementing fix" + gh issue create --repo "$TARGET_SLUG" --title "TITLE" \ + --body "Discovered while working on CURRENT_TASK. DETAILS" + # Do NOT create branches, PRs, or commit code on this repo + fi + fi + ``` + + This prevents the pattern where a pulse-dispatched worker creates PRs on repos the user doesn't manage (observed: 4 PRs + a fork on a repo the user doesn't own). + 10. **Issue-task alignment (MANDATORY)** — Before linking your PR to an issue or claiming a task, verify your work matches the issue's actual description. Workers have hijacked issues by using a task ID for completely unrelated work (e.g., PR "Fix ShellCheck noise" closed issue "Add local dev row to build-plus.md" because both used t1344). **Before creating a PR that references an issue:** diff --git a/.agents/scripts/commands/pulse.md b/.agents/scripts/commands/pulse.md index fc16fc687..44b6bf025 100644 --- a/.agents/scripts/commands/pulse.md +++ b/.agents/scripts/commands/pulse.md @@ -201,6 +201,7 @@ sleep 2 - Use `--dir ` from repos.json - Route non-code tasks with `--agent`: SEO, Content, Marketing, Business, Research (see AGENTS.md "Agent Routing") - **Bundle-aware agent routing (t1364.6):** Before dispatching, check if the target repo has a bundle with `agent_routing` overrides. Run `bundle-helper.sh get agent_routing ` — if the task domain (code, seo, content, marketing) has a non-default agent, use `--agent `. Example: a content-site bundle routes `marketing` tasks to the Marketing agent instead of Build+. Explicit `--agent` flags in the issue body always override bundle defaults. +- **Scope boundary (t1405, GH#2928):** ONLY dispatch workers for repos in the pre-fetched state (i.e., repos with `pulse: true` in repos.json). The `PULSE_SCOPE_REPOS` env var (set by `pulse-wrapper.sh`) contains the comma-separated list of in-scope repo slugs. Workers inherit this env var and use it to restrict code changes (branches, PRs) to scoped repos. Workers CAN still file issues on any repo (cross-repo self-improvement), but the pulse must NEVER dispatch a worker to implement a fix on a repo outside this scope — even if an issue exists there. Issues on non-pulse repos enter that repo's queue for their own maintainers to handle. ### Priority order diff --git a/.agents/scripts/pulse-wrapper.sh b/.agents/scripts/pulse-wrapper.sh index 8d2b5bcf2..d52143ab5 100755 --- a/.agents/scripts/pulse-wrapper.sh +++ b/.agents/scripts/pulse-wrapper.sh @@ -462,6 +462,15 @@ prefetch_state() { # Append active worker snapshot for orphaned PR detection (t216) prefetch_active_workers >>"$STATE_FILE" + # Export PULSE_SCOPE_REPOS — comma-separated list of repo slugs that + # workers are allowed to create PRs/branches on (t1405, GH#2928). + # Workers CAN file issues on any repo (cross-repo self-improvement), + # but code changes (branches, PRs) are restricted to this list. + local scope_slugs + scope_slugs=$(echo "$repo_entries" | cut -d'|' -f1 | paste -sd ',' -) + export PULSE_SCOPE_REPOS="$scope_slugs" + echo "[pulse-wrapper] PULSE_SCOPE_REPOS=${scope_slugs}" >>"$LOGFILE" + local repo_count repo_count=$(echo "$repo_entries" | wc -l | tr -d ' ') echo "[pulse-wrapper] Pre-fetched state for $repo_count repos → $STATE_FILE" >>"$LOGFILE" From ec1c37b72d29147717bfecf24807541dee72519c Mon Sep 17 00:00:00 2001 From: marcusquinn <6428977+marcusquinn@users.noreply.github.com> Date: Thu, 5 Mar 2026 18:13:48 +0000 Subject: [PATCH 2/2] fix: add exit after out-of-scope issue filing and filter empty slugs (t1405) - Add explicit exit 0 after gh issue create in scope guard to enforce 'file the issue and stop' behavior (CodeRabbit CHANGES_REQUESTED) - Fix indentation in scope guard code block - Filter empty slugs from PULSE_SCOPE_REPOS to prevent malformed entries from bypassing scope check (Gemini review suggestion) --- .agents/scripts/commands/full-loop.md | 7 ++++--- .agents/scripts/pulse-wrapper.sh | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.agents/scripts/commands/full-loop.md b/.agents/scripts/commands/full-loop.md index 5584dc36d..f3b7f2943 100644 --- a/.agents/scripts/commands/full-loop.md +++ b/.agents/scripts/commands/full-loop.md @@ -486,9 +486,10 @@ When running as a headless worker (dispatched by the supervisor via `opencode ru if [[ -n "${PULSE_SCOPE_REPOS:-}" ]]; then if ! echo ",$PULSE_SCOPE_REPOS," | grep -qF ",$TARGET_SLUG,"; then echo "Repo $TARGET_SLUG is outside pulse scope — filing issue only, not implementing fix" - gh issue create --repo "$TARGET_SLUG" --title "TITLE" \ - --body "Discovered while working on CURRENT_TASK. DETAILS" - # Do NOT create branches, PRs, or commit code on this repo + gh issue create --repo "$TARGET_SLUG" --title "TITLE" \ + --body "Discovered while working on CURRENT_TASK. DETAILS" + echo "BLOCKED: target repo out of pulse scope; issue filed — stopping." + exit 0 fi fi ``` diff --git a/.agents/scripts/pulse-wrapper.sh b/.agents/scripts/pulse-wrapper.sh index d52143ab5..4865179d8 100755 --- a/.agents/scripts/pulse-wrapper.sh +++ b/.agents/scripts/pulse-wrapper.sh @@ -467,7 +467,7 @@ prefetch_state() { # Workers CAN file issues on any repo (cross-repo self-improvement), # but code changes (branches, PRs) are restricted to this list. local scope_slugs - scope_slugs=$(echo "$repo_entries" | cut -d'|' -f1 | paste -sd ',' -) + scope_slugs=$(echo "$repo_entries" | cut -d'|' -f1 | grep . | paste -sd ',' -) export PULSE_SCOPE_REPOS="$scope_slugs" echo "[pulse-wrapper] PULSE_SCOPE_REPOS=${scope_slugs}" >>"$LOGFILE"