# 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 # full detail mcpctl review diff # before/after diff mcpctl review approve # POST /proposals/:id/approve mcpctl review reject --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 ` 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.