# Resource Proposals

A proposal is a pending change to a Prompt or Skill, submitted by
either a Claude Code session (via the `propose_prompt` / `propose_skill`
MCP tools) or a human (via the web UI / CLI). Reviewers triage the
queue and either approve — at which point the proposal becomes a real
prompt or skill — or reject with a note.

This is the path by which Claude **proposes back** to mcpd: things the
session learned that future sessions would benefit from. The
`propose-learnings` global skill (seeded by mcpd at startup) explains
the discipline to Claude.

## Model

`ResourceProposal` shares the schema's discriminator pattern with
`ResourceRevision` — single table, `resourceType` field disambiguates
prompts vs skills.

| Field                | Purpose                                                |
|----------------------|--------------------------------------------------------|
| `resourceType`       | `'prompt'` \| `'skill'`.                               |
| `name`               | Proposed resource name.                                |
| `body`               | Proposed body (`{ content, priority?, metadata?, … }`).|
| `projectId` / `agentId` | Scope of the proposal (XOR; null/null = global).    |
| `createdBySession`   | mcplocal session that proposed (when from Claude).     |
| `createdByUserId`    | User who proposed (when via UI/CLI).                   |
| `status`             | `'pending'` → `'approved'` \| `'rejected'`.            |
| `reviewerNote`       | Set on approval or rejection.                          |
| `approvedRevisionId` | Set when approved — points at the resulting revision.  |

Two unique constraints — `(resourceType, name, projectId)` and
`(resourceType, name, agentId)` — mirror the Prompt / Skill scoping
rules. The same `?? ''` workaround for nullable-FK lookups applies.

## Reviewer flow

### CLI

```bash
mcpctl review pending                          # list pending
mcpctl review next                             # show oldest pending
mcpctl review show <id>                        # full detail
mcpctl review diff <id>                        # before/after diff
mcpctl review approve <id>                     # POST /proposals/:id/approve
mcpctl review reject <id> --reason "explain"   # rejected with note
```

### Web UI

`/proposals` shows a Pending / Approved / Rejected tab view; the
sidebar nav badge polls every 30 s and shows the pending count in
amber. Click a row to see the full body, the diff against the current
resource (if any), and approve / reject controls.

### Approval is atomic

Approval runs in a single Prisma transaction:

1. Look up the pending proposal.
2. Dispatch by `resourceType` to the registered handler
   (`PromptService` or `SkillService` registers itself at construction).
3. Handler upserts the underlying resource — creating it if new, or
   updating + auto-bumping patch semver if it exists.
4. Handler records a `ResourceRevision` linking back to the proposal.
5. Proposal status flips to `approved`, `approvedRevisionId` set.

If any step fails, the transaction rolls back and the proposal stays
`pending`. There is no half-approved state.

## Claude side: `propose_prompt` and `propose_skill`

Both tools are registered by the `gate` plugin in mcplocal. They post
to `/api/v1/proposals` with the appropriate `resourceType`.

The `propose-learnings` global skill (seeded by mcpd) tells Claude
*when* to use them:

- `propose_prompt` for project-specific knowledge — gotchas,
  conventions, hidden constraints. Cheap to add, easy to reject.
- `propose_skill` for cross-cutting knowledge — debugging discipline,
  release hygiene, security review style. Larger blast radius; lean
  toward `propose_prompt` unless you have a clear cross-project reason.

The `gate-encouragement-propose` system prompt (priority 10, sits in
the gating bundle) is the trigger that makes Claude actually consider
proposing. Without that, the tools exist but Claude rarely engages.

## Backwards compat

PR-1 / PR-2 deferred the cutover from the prompt-only `PromptRequest`
table to `ResourceProposal`. Both run side-by-side today:

- mcplocal's `propose_prompt` still POSTs to the legacy
  `/api/v1/projects/:name/promptrequests` URL.
- mcplocal's `propose_skill` (newer) POSTs to `/api/v1/proposals`
  directly.
- The legacy `/api/v1/promptrequests*` routes remain in mcpd.
- `mcpctl approve promptrequest <name>` still works.

A focused follow-up PR will:

1. Migrate existing `PromptRequest` rows into `ResourceProposal`
   (resourceType=prompt).
2. Rename `PromptRequest` to `_PromptRequest_legacy`.
3. Update mcplocal's `propose_prompt` to use `/api/v1/proposals`.
4. Keep the legacy URL as a thin translation shim through one release.
5. Drop `_PromptRequest_legacy` after that.

This stays separate so the cutover is reviewable independently of
the larger Skills + Revisions + Proposals work.

## RBAC

Proposals piggyback on the `prompts` permission for now — anyone with
`view:prompts` can read the queue, anyone with `edit:prompts` can
approve or reject. Splitting out a dedicated `proposals` permission
(or a "reviewer" role) is straightforward if granularity becomes
useful.

## Audit emission

Proposal create / approve / reject events flow through the existing
audit pipeline. Approval events also reference the resulting
revision id, so you can join "proposal approved at T" against
"revision X created at T" without polling.
