Phase 7 of the Skills + Revisions + Proposals work — documentation pass for the surface added in PR-1 through PR-6. Reference material only; no code changes. ## What's added - `docs/skills.md` — skill model, scoping rules, CLI surface, the `mcpctl config claude --project` setup flow, metadata schema (with the deferred-execution note for hooks/mcpServers/postInstall), the on-disk state file shape, atomic install mechanics, failure semantics, and what's deferred. - `docs/revisions.md` — ResourceRevision model, semver auto-bump rules, contentHash diff key (cross-resource sync), CLI for history / diff / restore, RBAC, audit emission, storage growth note. - `docs/proposals.md` — ResourceProposal model, the reviewer flow (CLI + web UI), atomic-approval mechanics, the propose_prompt / propose_skill MCP tools, the propose-learnings global skill that steers Claude toward engaging with them, and the deferred legacy PromptRequest cutover. ## What's edited - Top-level `CLAUDE.md` — resource cheatsheet adds `skill`, `proposal`, `revision` with cross-references to the new docs. The legacy `promptrequest` entry stays (still on the legacy code path) but notes that new work should use `proposal`. ## What's NOT in this PR - The PromptRequest → ResourceProposal cutover migration. Both run side-by-side today; the focused cutover PR will rename + backfill + drop. Keeping that out of PR-7 means review can stay on docs. - Bundle-backup / `mcpctl apply -f` skill support (deferred from PR-3). - `metadata.hooks` / `metadata.mcpServers` / `metadata.postInstall` execution (deferred from PR-5). - Existing-page UI migration to Tailwind (deferred from PR-6 — old inline-styled pages coexist fine inside the new Layout). These are tracked as future PRs; each is its own focused change. ## Verification `pnpm test:run` whole monorepo: 162 test files / 2157 tests green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5.3 KiB
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
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:
- Look up the pending proposal.
- Dispatch by
resourceTypeto the registered handler (PromptServiceorSkillServiceregisters itself at construction). - Handler upserts the underlying resource — creating it if new, or updating + auto-bumping patch semver if it exists.
- Handler records a
ResourceRevisionlinking back to the proposal. - Proposal status flips to
approved,approvedRevisionIdset.
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_promptfor project-specific knowledge — gotchas, conventions, hidden constraints. Cheap to add, easy to reject.propose_skillfor cross-cutting knowledge — debugging discipline, release hygiene, security review style. Larger blast radius; lean towardpropose_promptunless 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_promptstill POSTs to the legacy/api/v1/projects/:name/promptrequestsURL. - mcplocal's
propose_skill(newer) POSTs to/api/v1/proposalsdirectly. - The legacy
/api/v1/promptrequests*routes remain in mcpd. mcpctl approve promptrequest <name>still works.
A focused follow-up PR will:
- Migrate existing
PromptRequestrows intoResourceProposal(resourceType=prompt). - Rename
PromptRequestto_PromptRequest_legacy. - Update mcplocal's
propose_promptto use/api/v1/proposals. - Keep the legacy URL as a thin translation shim through one release.
- Drop
_PromptRequest_legacyafter 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.