# /improve-queue

> Run the improvement-queue auto-implementer.


# /improve-queue — daily auto-improver

Closes the loop on the improvement queue. Colleagues' sandboxed Claude sessions write proposals to `~/scratch/improvements/`; the root aggregator (17:00 Sarajevo) bundles them to a Drive doc + `#your-channel` post AND drops a machine-readable `/opt/improvements/pending/<date>.json`. This skill is the step that used to be manual ("Faris pastes the doc and says apply this") — it reads those bundles and acts on them.

## When to trigger

- **Automatic:** your server cron under user `faris`, ~17:30 Sarajevo (after the aggregator). See `shared/your server_setup/install_improver_cron.sh`.
- **Manual:** the user says "run the improvement queue", "process the improvements", "apply queued improvements", "what's in the improvement queue", or invokes `/improve-queue`.

## How to run it

This skill is a thin wrapper around the orchestrator. When invoked:

1. Run the orchestrator from the repo root:
   - Default (safe): `node shared/improvement_queue/improver.mjs`  → **shadow mode** (applies each safe proposal, runs the gate, shows the diff, then reverts — nothing is committed or pushed).
   - Live: `node shared/improvement_queue/improver.mjs --live`  → commits one revertable commit per proposal (fingerprint in the message) and pushes to `main`; the deploy cron pulls into `/opt` within 60s.
2. Relay the digest it prints (auto-applied / would-apply / held-for-ACME Agencyw / already-live / needs-attention). The orchestrator also posts the digest to `#your-channel`.

Do not hand-edit the queue state. The orchestrator owns git + Slack + the gate.

## Triage boundary (who decides what)

Triage is deterministic and **defaults to MANUAL on any doubt** (`triage()` in `improver.mjs`):

| Proposal | Decision |
|---|---|
| **Landing-page edit** (any category — touches `websites/` or describes a live-lander copy/CSS/section/sub-page change) | **bounced** → "route via `/landing-edit`" |
| `doc-gap` (CLAUDE.md / SKILL.md / *.md text) | **auto** |
| `client-config` (clients.json / config JSON backfill) | **auto** |
| `bug`, severity low/medium, scoped to a single `skill:`, no infra/spend keyword | **auto** |
| `bug` that is high-severity, has no skill, or mentions infra/secret/spend | **manual** |
| `new-skill` / `feature-request` / `process` | **manual** |

Infra/spend keywords that force a bug to manual: `.env`, secret/token/credential, oauth/password, bridge, clickup_executor, sandbox_guard, preflight, aggregator, ecosystem, meta_lead_ads / meta ad / ad set / campaign / pixel / spend, google ads, billing/payment, webhook, `git push`.

**Landing edits are bounced before triage** (`isLandingEditProposal()` in `improver.mjs`): a proposal that touches `websites/` or describes a change to a deployed lander never enters the auto-apply path — it's held with a "route via `/landing-edit`" reason and no worker is spawned. Landing edits must go through `/landing-edit` (auto-deploy, no approval); a sandbox session that writes one as an improvement proposal has bypassed that path. `websites/` is also a separate repo and can't be committed from the main repo regardless. Incident: 2026-06-19.

## Safety gate (the real guardrail — enforced in code, not by the LLM)

For every auto item the orchestrator:
1. requires a **clean tracked working tree on `main`** before starting (else it skips the whole run and Slacks why);
2. spawns an **apply-one** worker (see contract below) to make the edit;
3. re-derives the **actually-changed files from git** (ignores the worker's self-report) and **reverts + reclassifies to manual** if any file matches the PROTECTED denylist — `.env`, `.git`, `.claude/hooks`, the bridge, the ClickUp executor, anything under `shared/improvement_queue` or `shared/your server_setup`, `run_claude_subprocess.mjs`, `ecosystem.config.*`, `meta_lead_ads.mjs` / `meta_web_leads.mjs` / `google_ads_optimize.mjs`, **anything under `websites/`** (separate repo + gitignored here — landing edits go through `/landing-edit`, never a proposal), or any secret/credential/token path;
4. runs `node --check` on every changed JS file and `JSON.parse` on every changed JSON file — any failure reverts the item;
5. only then (live mode) commits that one proposal and pushes.

Each proposal starts from a clean tree and ends clean (committed-in-live, or reverted-in-shadow/blocked/failed/**commit-failed**), so items never bleed into each other. A commit/push that fails reverts its own changes too — otherwise the dirty files leaked into the next proposal's `git add` and cascaded the failure (incident 2026-06-19).

## Apply-one worker contract

The orchestrator spawns one `claude -p` worker per auto proposal (sandbox:false, Max auth). Its rules (also embedded in the spawn prompt):

- **Check "already applied" first.** Sandbox clones lag, so many proposals are already live on `main`. If so → make no edits, report `already-done`.
- Make the **minimal** change; no unrelated refactors.
- May edit: docs (`*.md`, CLAUDE.md, SKILL.md), client config JSON, and the single client/skill script the proposal targets.
- Must NOT edit any PROTECTED path (see gate) or run git / Slack / ad-creating skills. If the proposal needs a protected path → report `needs-human`.
- Run `node --check` on changed JS; follow the bug-fix protocol (update the matching CLAUDE.md/SKILL.md + incident line where called for).
- End with one line: `###RESULT### {"status":"applied|already-done|needs-human|failed","summary":"…","files":[…],"reason":"…"}`

## Architecture

```
colleague sandbox sessions → ~/scratch/improvements/*.md
        │  (root cron 17:00)
        ▼
aggregator.mjs → Drive doc + #your-channel post + /opt/improvements/pending/<date>.json (0644)
        │  (faris cron 17:30)
        ▼
improver.mjs (this skill) → triage → apply-one worker → GATE → commit+push (live) / diff+revert (shadow) → digest
        │  (deploy cron, 60s)
        ▼
/opt/agency-os updated → colleagues' clones sync
```

State: `~/.improver/improver-state.jsonl` (one line per processed fingerprint, in faris's home so the cron needs no root). Pending bundles are READ from `/opt/improvements/pending/` (world-readable, written by the root aggregator). Kill switch: `touch ~/.improver/improver.disabled`.

**Deploy (as `faris`, no sudo):** the improver needs no root — `/opt/agency-os` auto-pulls every 60s, and faris's own clone + crontab are user-writable.
```
cd ~/agency-os && git pull
bash shared/your server_setup/install_improver_cron.sh        # shadow (default)
# …after ~a week of clean digests:
bash shared/your server_setup/install_improver_cron.sh --live
```

## Tuning

- Loosen/tighten the auto class: edit `MANUAL_SIGNALS` and `triage()` in `improver.mjs`.
- Add a never-auto path: append to `PROTECTED` in `improver.mjs`.
- Flip from shadow to live: change the cron in `install_improver_cron.sh` (add `--live`) and re-run it on your server, or set `IMPROVER_MODE=live`.

## Rollout

Ships in **shadow mode**. Watch ~a week of digests (it shows exactly what it would auto-apply and what it holds), confirm the classifier, then flip to `--live`.
