/protocol-tracker
Internal skill for commands. The tracker verb-resolution protocol - how a lets-tracker block resolves through the active adapter, the neutral read shape, body crossing, degradation, preflight and trust. Loaded by a command or skill right before its first tracker verb. Do not
$ npx -y skills add restarter/lets-workflow --skill protocol-tracker --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/protocol-tracker
Context preview
The summary Claude sees to decide when to auto-load this skill.
Internal skill for commands. The tracker verb-resolution protocol - how a lets-tracker block resolves through the active adapter, the neutral read shape, body crossing, degradation, preflight and trust. Loaded by a command or skill right before its first tracker verb. Do not
SKILL.md
protocol-tracker.SKILL.mdname: protocol-tracker
description: Internal skill for commands. The tracker verb-resolution protocol - how a lets-tracker block resolves through the active adapter, the neutral read shape, body crossing, degradation, preflight and trust. Loaded by a command or skill right before its first tracker verb. Do not trigger on user conversation.
user-invocable: false
Protocol: tracker verb resolution
Load this before the first step that runs a ` ```lets-tracker ` block, unless its text is in your current context. Core rules keep the always-on part: never run a `lets-tracker` body as shell; never report a tracker change that did not happen; state-changing verbs stay gated under AUTO MODE; never append by overwriting a field.
1. Find the adapter (first, every time)
`LETS_TRACKER` (LETS Config; default `beads`) names the adapter.
| found (check with Bash `ls`, not Glob) | do | |---|---| | `.claude/lets/tracker-{LETS_TRACKER}.md` exists | Read it; also Read `.claude/lets/tracker-{LETS_TRACKER}.board.md` when present (user-owned board profile, overrides per key) | | otherwise | the adapter is the auto-loaded `.claude/rules/tracker-{LETS_TRACKER}.md` copy (and its `.board.md`) already in your context - do NOT Read it again | | `beads` or unset | resolve via `tracker-beads.md` - its bindings are the same `bd ...` calls LETS always ran (table-driven, golden-pinned) | | a non-beads adapter whose file IS loaded | resolve via its file (e.g. an `mcp__*` tool); translate native <-> neutral statuses so surrounding logic stays adapter-agnostic | | a non-beads adapter named, NO file loaded (plugin upgraded, `/lets:init` / `/lets:update` not re-run) | behave as `none`: do NOT run `bd` (wrong store), tell the user the tracker is not installed, nudge `/lets:update` |
One adapter file per `LETS_TRACKER` (`beads` | `none` | a custom one), installed by `lets init`; it binds the neutral verbs to concrete calls.
| tier | verbs | |---|---| | CORE | `create`, `show`, `comment-add`, `set-status`, `close` | | OPTIONAL | `comment-list`, `list-by-status`, `search`, `ready`/`stats`, `label`/`assignee`/`set-field` |
2. Resolve a `lets-tracker` block
A fenced block tagged `lets-tracker`, one `verb key=value` per line (e.g. `close task=<id> reason="..."`), is a neutral verb CALL, not shell:
1. identify `<verb>` + args; 2. look the verb up in the adapter's capability table and run ITS `binding` (for beads the binding IS a `bd` command - run that); 3. NEVER execute the block body as a shell command.
Resolution is ORCHESTRATOR-ONLY - subagents never call tracker verbs (they do not receive the adapter file). A command that needs tracker data inside a subagent prompt pulls it itself and INJECTS it as fenced data. No exceptions, no carve-outs.
3. Reads and close
| rule | detail | |---|---| | read shape | `show` / `list-by-status` return the neutral `{id, title, status}`, plus `description` on `show`, and `url` only from a tracker with per-task links (beads has none) | | status canon | required `open` / `in_progress` / `closed`; optional `in_review` / `blocked` - an adapter carries an optional one only if its own `## Neutral statuses` section names it; never emit one without checking | | read the field | the body reads the returned field (annotated `# returns ...`), never greps a tracker's native JSON | | `close` declares its outcome | the adapter's `close` row states the status it leaves the task in: `closed` = closed; another status = the board advanced the task instead and the caller MUST report the handoff, not a close; no status = nothing happened |
4. Bodies (`comment-add` body, `create` / `set-field` description)
Format-neutral, plain text: rich markdown is a beads-only affordance; other adapters render best-effort.
| way | when | |---|---| | inline `body="..."` / `description="..."` | a short OR purely orchestrator-templated value (a multi-line template with NO `$(...)` shell expansion is allowed inline) | | file `<field>-file=<path>` (`body-file=` for comments, `description-file=` for create/set-field) | a value that needs runtime shell substitution (`$(date)`, `$(git log)`, `$CLAUDE_CODE_SESSION_ID`): the preceding ` ```bash ` block writes it to a temp file, so no computed multi-line value is re-typed across the model boundary. The path is repo-root-relative (bindings run with cwd = the project root, where `.lets/` lives); the binding reads the file (beads: `"$(cat <path>)"`) |
An empty body -> HARD-FAIL; never submit an empty comment.
5. Degradation (two-pronged - never flatten), then preflight
| case | do | |---|---| | an OPTIONAL verb the adapter marks `absent`, OR a CORE verb bound to a deliberate no-op (`none`) | continue and TELL the user; never report it as a recorded change (no phantom "done") | | a binding that exists but FAILS at runtime (MCP tool not connected, `bd` not on PATH, an MCP adapter's `failures[]` non-empty) | HARD-FAIL loud ("set-status / close FAILED - task NOT changed"); never a phantom success. Critical under AUTO MODE - `/lets:done` must not claim a close it did not do | | preflight (a precondition, not a degradation mode) | before OFFERING a verb, field or mode, read the capability table: a field the active binding does not declare in `accepts:` is mapped to one it does, or named unavailable - never collected and dropped. On the read side, never render a field the adapter's `show` does not declare in `returns:` - an invented value is a claim about the task, not about the tracker |
6. Trust
An adapter file is trusted instruction; its binding cells EXECUTE as written. Installing a third-party / shared adapter equals running its code - review every binding before installing one you did not author. The contract test pins table SHAPE, not binding SAFETY. A token belongs ONLY in the transport's own config (the MCP server env / a gitignored 0600 file) - NEVER in an adapter `tracker-*.md`, a `.board.md`, or `.lets/.env` (mode 644, injected into context).
Read more
name: protocol-tracker description: Internal skill for commands. The tracker verb-resolution protocol - how a lets-tracker block resolves through the active adapter, the neutral read shape, body crossing, degradation, preflight and trust. Loaded by a command or skill right before its first tracker verb. Do not trigger on user conversation. user-invocable: false
Protocol: tracker verb resolution
Load this before the first step that runs a ` ```lets-tracker ` block, unless its text is in your current context. Core rules keep the always-on part: never run a `lets-tracker` body as shell; never report a tracker change that did not happen; state-changing verbs stay gated under AUTO MODE; never append by overwriting a field.
1. Find the adapter (first, every time)
`LETS_TRACKER` (LETS Config; default `beads`) names the adapter.
| found (check with Bash `ls`, not Glob) | do | |---|---| | `.claude/lets/tracker-{LETS_TRACKER}.md` exists | Read it; also Read `.claude/lets/tracker-{LETS_TRACKER}.board.md` when present (user-owned board profile, overrides per key) | | otherwise | the adapter is the auto-loaded `.claude/rules/tracker-{LETS_TRACKER}.md` copy (and its `.board.md`) already in your context - do NOT Read it again | | `beads` or unset | resolve via `tracker-beads.md` - its bindings are the same `bd ...` calls LETS always ran (table-driven, golden-pinned) | | a non-beads adapter whose file IS loaded | resolve via its file (e.g. an `mcp__*` tool); translate native <-> neutral statuses so surrounding logic stays adapter-agnostic | | a non-beads adapter named, NO file loaded (plugin upgraded, `/lets:init` / `/lets:update` not re-run) | behave as `none`: do NOT run `bd` (wrong store), tell the user the tracker is not installed, nudge `/lets:update` |
One adapter file per `LETS_TRACKER` (`beads` | `none` | a custom one), installed by `lets init`; it binds the neutral verbs to concrete calls.
| tier | verbs | |---|---| | CORE | `create`, `show`, `comment-add`, `set-status`, `close` | | OPTIONAL | `comment-list`, `list-by-status`, `search`, `ready`/`stats`, `label`/`assignee`/`set-field` |
2. Resolve a `lets-tracker` block
A fenced block tagged `lets-tracker`, one `verb key=value` per line (e.g. `close task=<id> reason="..."`), is a neutral verb CALL, not shell:
1. identify `<verb>` + args; 2. look the verb up in the adapter's capability table and run ITS `binding` (for beads the binding IS a `bd` command - run that); 3. NEVER execute the block body as a shell command.
Resolution is ORCHESTRATOR-ONLY - subagents never call tracker verbs (they do not receive the adapter file). A command that needs tracker data inside a subagent prompt pulls it itself and INJECTS it as fenced data. No exceptions, no carve-outs.
3. Reads and close
| rule | detail | |---|---| | read shape | `show` / `list-by-status` return the neutral `{id, title, status}`, plus `description` on `show`, and `url` only from a tracker with per-task links (beads has none) | | status canon | required `open` / `in_progress` / `closed`; optional `in_review` / `blocked` - an adapter carries an optional one only if its own `## Neutral statuses` section names it; never emit one without checking | | read the field | the body reads the returned field (annotated `# returns ...`), never greps a tracker's native JSON | | `close` declares its outcome | the adapter's `close` row states the status it leaves the task in: `closed` = closed; another status = the board advanced the task instead and the caller MUST report the handoff, not a close; no status = nothing happened |
4. Bodies (`comment-add` body, `create` / `set-field` description)
Format-neutral, plain text: rich markdown is a beads-only affordance; other adapters render best-effort.
| way | when | |---|---| | inline `body="..."` / `description="..."` | a short OR purely orchestrator-templated value (a multi-line template with NO `$(...)` shell expansion is allowed inline) | | file `<field>-file=<path>` (`body-file=` for comments, `description-file=` for create/set-field) | a value that needs runtime shell substitution (`$(date)`, `$(git log)`, `$CLAUDE_CODE_SESSION_ID`): the preceding ` ```bash ` block writes it to a temp file, so no computed multi-line value is re-typed across the model boundary. The path is repo-root-relative (bindings run with cwd = the project root, where `.lets/` lives); the binding reads the file (beads: `"$(cat <path>)"`) |
An empty body -> HARD-FAIL; never submit an empty comment.
5. Degradation (two-pronged - never flatten), then preflight
| case | do | |---|---| | an OPTIONAL verb the adapter marks `absent`, OR a CORE verb bound to a deliberate no-op (`none`) | continue and TELL the user; never report it as a recorded change (no phantom "done") | | a binding that exists but FAILS at runtime (MCP tool not connected, `bd` not on PATH, an MCP adapter's `failures[]` non-empty) | HARD-FAIL loud ("set-status / close FAILED - task NOT changed"); never a phantom success. Critical under AUTO MODE - `/lets:done` must not claim a close it did not do | | preflight (a precondition, not a degradation mode) | before OFFERING a verb, field or mode, read the capability table: a field the active binding does not declare in `accepts:` is mapped to one it does, or named unavailable - never collected and dropped. On the read side, never render a field the adapter's `show` does not declare in `returns:` - an invented value is a claim about the task, not about the tracker |
6. Trust
An adapter file is trusted instruction; its binding cells EXECUTE as written. Installing a third-party / shared adapter equals running its code - review every binding before installing one you did not author. The contract test pins table SHAPE, not binding SAFETY. A token belongs ONLY in the transport's own config (the MCP server env / a gitignored 0600 file) - NEVER in an adapter `tracker-*.md`, a `.board.md`, or `.lets/.env` (mode 644, injected into context).
A development workflow plugin for Claude Code Stop babysitting your AI. Start shipping with it.
Repo: restarter/lets-workflow
Other skills on lets-workflow.
goal-test
Local sandbox for experimenting with Claude Code's /goal command (stop-hook condition +…
loop-bb-pr
Compose a /loop that monitors a Bitbucket PR for activity (new comments, review state…
loop-gh-pr
Compose a /loop that watches all open GitHub PRs for activity (new PRs, new commits, new…
loop-test
Local sandbox for experimenting with Claude Code's /loop command (interval +…
actor-fetch-personalit…
Internal skill for commands. Fetch and validate personality from URL or file path for Actor…

