Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 170 additions & 0 deletions .claude/skills/release-deckmark/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
---
name: release-deckmark
description: Use when releasing a new version of deckmark or re-tagging an existing version. Covers the bump-or-retag decision, three-file version sync, CI wait, post-release npx-cache wipe, and install verification — everything needed to ship a release that real users will actually receive.
---

# release-deckmark

End-to-end checklist for cutting a deckmark release. Follow top-to-bottom;
skip nothing. Every step here exists because forgetting it has bitten us
at least once.

## Pre-flight (1 minute)

Run these in the repo root before touching versions or tags.

```
git checkout main && git pull --ff-only
npm test
npm run build
```

All three must succeed. If any fails, fix before continuing — a broken
release is worse than no release.

Then decide:

- **Bumping to a new version** (e.g., 1.0.0 → 1.0.1)? Use the "Bump"
path below. **Default to this.** Semver says published artifacts under
a given version shouldn't change.
- **Re-tagging the same version**? Use the "Re-tag" path. Only do this
if the release happened minutes ago and you're confident no one has
cached the bad bytes yet. Tell the user the trade-off and confirm
before proceeding.

## Path A: Bump (default)

1. **Sync three version files**. CI will reject the release if any
disagree with the tag:
- `package.json` → `"version"`
- `.claude-plugin/plugin.json` → `"version"`
- `.claude-plugin/marketplace.json` → the entry's `"version"`

2. **Commit and tag**:
```
git add package.json .claude-plugin/plugin.json .claude-plugin/marketplace.json
git commit -m "chore(release): X.Y.Z"
git tag vX.Y.Z
```

3. **Push commit AND tag** (separately — pushing the branch doesn't
push tags):
```
git push origin main
git push origin vX.Y.Z
```

The tag push triggers `.github/workflows/release.yml`.

## Path B: Re-tag same version

Only when re-publishing within minutes of the original release.

1. **Delete the existing GitHub Release in the browser**. The `gh` CLI
isn't installed locally — navigate to the release URL on GitHub and
click the trash icon → "Delete this release". This removes the
Release but leaves the tag.

2. **Delete the tag remotely, then locally**:
```
git push origin :refs/tags/vX.Y.Z
git tag -d vX.Y.Z
```

3. **Sync main and re-tag at HEAD**:
```
git checkout main && git pull --ff-only
git tag vX.Y.Z
git push origin vX.Y.Z
```

## Wait for CI (~2 minutes)

The workflow runs install → build → test → pack → upload tarball. It
will fail loudly if:

- The tag doesn't match `package.json#version`.
- Tests fail on Ubuntu Node 22 (we don't have Windows CI yet — watch
for path-separator bugs that pass locally but fail there).

Verify CI passed before continuing. The release is live when
`https://github.com/sowenzhang/deckmark/releases/latest/download/deckmark.tgz`
serves the new tarball.

## Post-release verification (3 minutes — DO NOT SKIP)

The release being on GitHub doesn't mean users will see it. `npx` keys
its cache by URL, not contents, so the same URL with new bytes is still
served from cache.

1. **Confirm the tarball URL resolves** to the new release:
```powershell
curl -sIL https://github.com/sowenzhang/deckmark/releases/latest/download/deckmark.tgz |
Select-String -Pattern '(?i)^(location|content-length):' | Select-Object -First 6
```
Expect a `302` redirecting to `releases/download/vX.Y.Z/deckmark.tgz`.

2. **Wipe the npx cache**:
```powershell
Remove-Item -Recurse -Force "$env:LOCALAPPDATA\npm-cache\_npx"
```
(macOS/Linux: `rm -rf ~/.npm/_npx`.)

3. **Refresh the marketplace mirror inside Claude Code**:
```
/plugin marketplace update deckmark-marketplace
```

4. **Restart Claude Code** so the MCP server re-spawns and re-fetches
the tarball.

5. **Verify the installed code matches** what you shipped. Find the
npx-extracted package and grep for an identifier unique to this
release:
```powershell
$deck = Get-ChildItem "$env:LOCALAPPDATA\npm-cache\_npx" -Directory |
Where-Object { Test-Path "$($_.FullName)\node_modules\deckmark" } |
Select-Object -First 1 -ExpandProperty FullName
Select-String -Path "$deck\node_modules\deckmark\dist\runtime\engines\reveal.js" `
-Pattern '<unique-identifier-from-this-release>'
```
For v1.0.0 the load-bearing identifiers are `rejectSymlink`,
`deckmark-build`, `isUnder`, `assertDirOrAbsent`. Pick one that's
new in *your* release.

6. **End-to-end smoke test**. In Claude Code:
- `/mcp` → confirm deckmark shows **connected**.
- Ask the agent: *"use deckmark to make a one-slide deck about
anything, build it, and publish multi-file."*
- Double-click the resulting `published/index.html` and confirm it
renders without manual edits. (This is the file:// regression
check — relative `vendor/reveal/...` paths must work.)

## When something goes wrong

| Symptom | First check |
|---|---|
| CI fails with "tag does not match version" | All three version files in sync? Did you push the tag at the same HEAD as the version-bump commit? |
| Release exists on GitHub but install runs old code | npx cache wasn't wiped. Repeat step 2 in "Post-release verification". |
| MCP server disconnects after release | Marketplace mirror is stale. `/plugin marketplace update`, restart Claude Code. |
| Published HTML opens blank via file:// | Some `vendor/reveal/` reference still has a leading `/`. Grep the released `dist/runtime/engines/reveal.js` for `REVEAL_PREFIX = '/vendor/reveal'` — if found, the wrong code shipped. |

For deeper issues, see `docs/troubleshooting.md`.

## Pitfalls (things that have bitten us)

- **Pushing the branch but not the tag.** `git push origin main` does
NOT push tags. Push the tag explicitly.
- **Re-tagging without deleting the GitHub Release first.** The
`softprops/action-gh-release@v2` action will update an existing
release, but the experience is cleaner if you delete and let it
recreate. Also keeps release notes regenerating from the right
boundary.
- **Forgetting one of the three version files.** CI catches this, but
you'll have wasted a CI run.
- **Trusting "the release is live" without wiping the npx cache.** The
cache is URL-keyed, and the URL doesn't change between versions —
this is the #1 reason "my fix shipped but I'm still seeing the bug."
- **Skipping the file:// smoke test.** The dev server hides this class
of bug because it has its own route. Multi-file publish + double-click
is the only honest check.
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,6 @@ Thumbs.db

# Dogfood artifacts — generated when invoking init_deck on this repo for manual testing
# Anchored to repo root so runtime/templates/ versions are unaffected
/AGENTS.md
/CLAUDE.md
/GEMINI.md
/content.md
/deckmark.config.json
/annotations/
Expand Down
100 changes: 100 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# CLAUDE.md

Working notes for an AI agent (Claude / Codex / Gemini / Copilot) picking
this repo up cold. README.md is for users installing the plugin; this
file is for you contributing to it.

## What this project is

deckmark is an MCP-first plugin: an AI agent that lets users build a
slide deck, opens it in the browser with an annotation overlay, the user
clicks elements to leave comments, and the agent reads those annotations
back and applies them. AI-agnostic (Claude Code, Gemini CLI, Codex,
Copilot, Cursor) because the surface is the MCP tool list, not a CLI.

## Architecture map

| Path | Role |
|---|---|
| `mcp/server.ts` | MCP stdio server entry. Exposes the seven tools. |
| `mcp/tools/` | One file per tool: `init`, `build`, `review`, `annotations`, `publish`. |
| `runtime/engines/reveal.ts` | Markdown → reveal.js HTML. Owns `build_deck`, user-asset sync, the `.deckmark-build` marker. |
| `runtime/server/` | Fastify review server: static file routes, overlay injection, session API. |
| `runtime/overlay/` | Browser-side overlay (TS, bundled by esbuild to `dist/overlay/overlay.js`). |
| `runtime/publish/inline-html.ts` | Single-file publish: inlines reveal CSS/JS + base64 images. |
| `runtime/publish/multi-file.ts` | Multi-file publish: copies buildDir + overlays reveal dist. |
| `runtime/store/` | Annotation session JSON store. |
| `skills/deckmark/SKILL.md` | Agent-facing prompt explaining the workflow. |
| `commands/` | Claude-Code slash commands (e.g. `/deckmark:use-deckmark`). |
| `test/unit/`, `test/integration/` | `node:test`-based suites. Currently 44 tests. |

reveal.js is **vendored at runtime via `require.resolve('reveal.js/dist/reveal.js')`**, not hard-coded. This is load-bearing: when deckmark is installed via npx, reveal.js gets hoisted to a parent `node_modules/`, so any hard-coded path would break.

## Build / test / run

```
npm run build # build:src + typecheck:overlay + build:overlay
npm run build:src # tsc + copy templates/themes — fastest for MCP/engine changes
npm run build:overlay # esbuild bundle for the browser overlay only
npm test # unit + integration; must be green before commit
npm run mcp # spawn the MCP server locally for manual testing
```

There's no watch mode. After each change, rebuild + restart whichever
agent is hosting the MCP server.

## Distribution

- Released via **GitHub Releases**, not npm. The release workflow on
`tags: ['v*']` packs `deckmark.tgz` and uploads it.
- Marketplace + non-Claude agents fetch it via
`npx -y --package https://github.com/sowenzhang/deckmark/releases/latest/download/deckmark.tgz deckmark-mcp`.
- Three files carry the version and must move together:
`package.json`, `.claude-plugin/plugin.json`, `.claude-plugin/marketplace.json`.
The CI workflow refuses to release if they disagree with the tag.
- `npx` keys its cache by URL. Re-releasing the same version with new
bytes does NOT invalidate it. See `docs/troubleshooting.md` for the
cache-wipe procedure.

## Conventions

- **No emojis** anywhere unless the user explicitly asks. Same for the
docs the agent writes.
- **No new doc files unless asked.** ROADMAP, README, troubleshooting
already exist; resist adding more.
- **Comments**: default to none. Only write one when *why* is
non-obvious (a hidden constraint, a workaround for a specific bug,
surprising behavior). Don't restate what the code does.
- **Tests**: TDD friendly. Every security-relevant fix in v1.0 ships
with a regression test (see `test/unit/engines-reveal.test.ts` for
the symlink, traversal, and marker-guard tests as a pattern).
- **Security defaults**: anything that resolves a path from user input
goes through a containment check (`isUnder(root, candidate)` in
`inline-html.ts`). Anything that copies user files rejects symlinks
via the `cp({ filter })` pattern.
- **Destructive ops** (`rm`, force-push, etc.): always guarded. The
`.deckmark-build` marker-file ratchet in `runtime/engines/reveal.ts`
is the canonical example — copy that pattern.
- **Paths in docs**: never hard-code absolute paths like
`C:\projects\...`. Use placeholders or env-var-based
discovery (`$env:USERPROFILE`, `$LOCALAPPDATA`, etc.).

## Where to look first

- A new user task → `README.md`.
- A new release → `docs/troubleshooting.md` § "Release-time issues".
- A reported bug → `docs/troubleshooting.md` matches symptoms to fixes.
- Long-term direction → `docs/ROADMAP.md`.
- Agent workflow when running the seven tools → `skills/deckmark/SKILL.md`.

## What NOT to do

- Don't `npm publish`. The plugin is shipped via GitHub Releases only.
- Don't add a CLI surface. The MCP tool list is the public API.
- Don't change emitted HTML paths back to absolute (`/vendor/reveal/...`).
Relative paths (`vendor/reveal/...`) are load-bearing for the
multi-file publish to work via plain `file://` double-click.
- Don't introduce telemetry, accounts, or hosted state. The plugin is
local-first by design (see "Not planned" in `docs/ROADMAP.md`).
- Don't skip the regression test when patching a security finding. The
v1.0 review cycle added one test per fix; keep that ratio.
Loading
Loading