onsite-propose · git:20260722.b7fa052 · 2026-07-22 · sha256 1d452aed8ee9b69c

onsite-propose git:20260722.b7fa052A

Immutable. This exact content is served forever at /api/v1/blob/1d452aed8ee9b69c.

---
name: onsite-propose
description: Use to turn audit findings or signals into concrete gated change proposals - "propose fixes for /pricing", /organic-os:propose.
---

# Propose on-page changes (creates gated items; applies nothing)

1. Input: audit findings, a signal reference, or a user request naming URLs.
2. For each change, draft the exact after-state: new title (<= 60 chars),
   new meta description (<= 155 chars), canonical, focus keyword, schema
   JSON-LD payload, or a content edit (quote the exact before/after text).
2b. Decision check, BEFORE any `create_item` call. For every target, search
   the brain's decision memory:
   `PYTHONPATH="$CLAUDE_PLUGIN_ROOT/lib" python3 -c "..."` snippet importing
   `core.decisions` - `search(<brain>, [<page path>, <focus keyword>,
   <what the fix does>])`. It returns prior decisions newest first with
   `date`, `choice`, `scope`, `item`, and the `rationale`.
   A hit whose `choice` is `rejected` means a human already refused this
   work. Exactly two paths are allowed, never a third:
   - SKIP it, and say so in the run report: name the page, the decision
     date, and the recorded reason.
   - Or CREATE it with a line in the proposal body reading "previously
     rejected on <date> because <reason>; proposing again because <what
     changed>". What changed must be a concrete new fact - a new signal, a
     ranking move, a competitor or site change - never "worth another
     look".
   Silently re-creating a rejected proposal is forbidden. A hit with any
   other `choice` is context to quote in the body, not a blocker.
3. One proposal item per page: `create_item(kind="onpage-fix", target=<url>,
   body=<before/after table + rationale + expected effect + falsifiability>)`.
   `create_item` is the ONLY birth path - items are born `proposed` with an
   empty approvals list; never write a `proposals/` file directly or set
   any other status at creation.
4. Rebuild queue. Notify per the profile approval channel. Only send items
   where `core.contracts.is_notified(item)` is false; call
   `core.contracts.mark_notified(path)` immediately after a successful send
   so re-runs never re-notify the same item:
   - in-session: present now with AskUserQuestion (approve/reject each)
   - telegram: send via core.telegram `send_item` (token from env file), then
     poll with `core.approval.process_telegram_decisions` on the next run to
     collect replies (offset persisted automatically)
   - pr-merge: commit the proposal file on a branch, open a PR (gh pr create)
   - slack/email: post/send a summary via the available connector; approval
     happens in-session or by channel reply read at the next run
4b. Image and alt-text fixes (action type: image-fix). Audit findings
   from the images checks - a missing or empty alt (skills/onsite-audit
   step 2), an imageless 500+ word explainer (step 3.3) - become one
   `onpage-fix` proposal per page whose body declares `action:
   image-fix` and carries one line per image:
   - Missing alt text: the image's src and media id (from the CMS
     adapter's `get_media`), the current alt (empty), and the proposed
     alt text. Ground the proposed alt in the surrounding content:
     describe what the image shows for someone who cannot see it, in
     one sentence. Never keyword-stuffed - alt text is accessibility
     text first, and a keyword appears only when the image is genuinely
     about it.
   - Missing in-content image: reference the ce-image brief path
     (skills/ce-image writes `<slug>-image-brief.md`) as the creation
     route, and name where in the post the image belongs. Creation
     happens through that brief, outside the apply path: apply only
     places a file that already exists (see the image-fix section in
     skills/onsite-apply), so the proposal must be executable once the
     asset exists, and honest about waiting until it does.
   Same gate as every proposal: born proposed via `create_item`,
   approved by a human, executed by onsite-apply.
5. Record any in-session decisions immediately via the contract CLI:
   `PYTHONPATH="$CLAUDE_PLUGIN_ROOT/lib" python3 -m core approve <item-path>
   --actor <user> --channel in-session` (or `reject`, with `--note` for any
   reason given). Never edit brain frontmatter directly. The contract CLI is
   the only write path for status and approvals.