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>
4.5 KiB
Resource Revisions
mcpctl keeps an append-only revision log for every Prompt and Skill — so you can answer "who changed prompt X and when," diff between any two versions, and restore an earlier state without losing the audit chain.
Model
ResourceRevision is a single shared table keyed by
(resourceType, resourceId) — the type discriminator allows the same
infrastructure to cover both prompts and skills (and any future
resource that wants version history).
| Field | Purpose |
|---|---|
id |
cuid; the revision's stable identity. |
resourceType |
'prompt' | 'skill'. Validated app-layer. |
resourceId |
Soft FK — survives deletion of the underlying resource. |
semver |
Author-visible version (X.Y.Z). |
contentHash |
sha256 of the canonicalised body. Stable diff key. |
body |
Snapshot of the resource at this revision. |
authorUserId |
Who made the change (null for system writes). |
authorSessionId |
Session that proposed it (when applicable). |
note |
Free-text reviewer or author note. |
createdAt |
When the revision was recorded. |
The resource row itself (Prompt/Skill) keeps the inline content —
revisions are an audit log, not the source of truth. Hot read paths
(the gate plugin, mcpctl skills sync, prompt indexing) never need
to consult the revision log.
Prompt.currentRevisionId and Skill.currentRevisionId are soft
pointers to the latest revision so the UI can answer "which version is
live" in one query.
Semver semantics
Auto-patch on every successful save where the body changed:
0.1.0 → save with content change → 0.1.1
0.1.1 → save with content change → 0.1.2
Authors can override:
mcpctl edit prompt foo --bump minor # 0.1.x → 0.2.0
mcpctl edit prompt foo --bump major # 0.x.x → 1.0.0
mcpctl edit prompt foo --semver 1.2.3 # explicit
mcpctl edit prompt foo --note "fixed the gotcha" # adds note to revision
Invalid semver values fall back to 0.1.0 rather than throwing —
the revision write is best-effort and we don't want a corrupted
existing semver to break the prompt save.
contentHash
sha256 of the JSON-canonicalised body (keys sorted at every object
level). Two revisions with the same hash are byte-identical. Used by
mcpctl skills sync as the diff key against on-disk state — re-publish
under the same semver still triggers a sync if the contentHash changed.
The server-side hash and the client-side hash are computed from the
same canonical shape, so they match exactly. See
src/mcpd/src/services/resource-revision.service.ts for the canonical
JSON encoder.
CLI
View history
mcpctl get revisions prompt my-prompt
mcpctl get revisions skill demo-skill
View one
mcpctl describe revision <id>
Diff
The HTTP API returns a unified-format diff:
GET /api/v1/revisions/<id>/diff?against=<other-id|live>
The web UI's revision history tab on a Skill detail page renders the diff inline (color-coded add/remove rows).
Restore
Restore a prompt or skill to an earlier revision. This writes a new revision whose body is the old one — preserving the audit chain rather than deleting later revisions.
mcpctl restore prompt my-prompt --revision <revision-id>
The CLI subcommand is wired through to POST /api/v1/prompts/:id/restore-revision (and the symmetric
/api/v1/skills/:id/restore-revision).
RBAC
Revisions piggyback on the underlying resource's RBAC permission. If
you can view:prompts, you can read prompt history; if you can
edit:prompts, you can restore.
Audit emission
Each revision write emits a structured audit event captured by the existing audit-event pipeline. The event includes the revision id, contentHash, semver, and author/session — sufficient to answer "what changed" and "who" without joining tables manually.
Storage size
A revision body is the resource snapshot — for prompts that's a few
KB; for skills with large files maps it can be tens of KB. The audit
log grows linearly with edits. v1 has no rotation; if a single resource
sees thousands of revisions per day this will need a retention policy
(out of scope today).