Files
mcpctl/docs/revisions.md
Michal 56735a5290 docs: skills + revisions + proposals reference, plus cheatsheet update
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>
2026-05-07 17:58:04 +01:00

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).