/update
Sync this project with the current LETS release - a self-driving one-step loop; self-heals .env + rules (rules deferred while the plugin is behind), installs the binary in-session on approval, and points at the single next action until everything is on the same version
$ npx -y skills add restarter/lets-workflow --agent claude-codeHow it fires
How this command gets triggered: by you, by Claude, or both.
- Fires itselfClaude auto-loads it when your prompt matches the work.
- You can call itInvoke it directly when you want it.
- Slash command
/update
Context preview
What this command does when you run it.
Sync this project with the current LETS release - a self-driving one-step loop; self-heals .env + rules (rules deferred while the plugin is behind), installs the binary in-session on approval, and points at the single next action until everything is on the same version
Command definition
update.mddescription: Sync this project with the current LETS release - a self-driving one-step loop; self-heals .env + rules (rules deferred while the plugin is behind), installs the binary in-session on approval, and points at the single next action until everything is on the same version
Update LETS
Sync the drift-able LETS artifacts (four core + two optional: the user-scope global rules and the active `tracker-<name>.md` adapter) with the current release. Bridges to `lets update --json` (Go binary): auto-syncs `.lets/.env` (header refresh when `LETS_ENV_VERSION` is stale), `.claude/rules/lets-rules.md` (re-copy when outdated/missing - but **deferred** when the plugin is behind, so rules never sync to a stale plugin), and `~/.claude/rules/lets-rules.md` (user-scope global rules - the `user-rules` row appears only when that file exists). It then computes a single ordered `next_action` (the self-driving loop): the binary step can be run in-session (approval-gated), the plugin step is a user-only Claude Code slash command, and a fully-synced machine prints `✓ Everything on vX.Y.Z`.
> **MANDATORY:** Execute every Step's bash block **literally as written**. Do not substitute output from earlier `ls`/`cat` in this conversation - `.env` and other dotfiles are invisible to plain `ls`. The `test -f` checks below ARE the contract.
Difference from `/lets:init`: `/lets:init` is first-time setup (it also asks config questions and sets up the statusline + beads). `/lets:update` only syncs what a new release changes - it never prompts and never touches `settings.json` or beads.
Step 1: Pre-checks
command -v lets >/dev/null 2>&1 || { echo "NO_LETS_BINARY"; exit 0; }
LETS_PROJECT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || { echo "NOT_GIT_REPO"; exit 0; }
test -f "$LETS_PROJECT_ROOT/.lets/.env" && echo "ENV_EXISTS" || echo "ENV_ABSENT"Branch on output:
- `NO_LETS_BINARY` → tell user: "`lets` binary not found on `$PATH`. Install it — `! curl -fsSL https://raw.githubusercontent.com/restarter/lets-workflow/main/scripts/install.sh | bash` (the leading `!` runs it in this session; or the same command without `!` in a terminal). See the README → Quick Start." NO LETS box. STOP.
- `NOT_GIT_REPO` → tell user: "Not a git repository. `/lets:update` runs inside a LETS project." NO LETS box. STOP.
- `ENV_ABSENT` → not initialized. Tell user: "This project hasn't been set up - run `/lets:init` first." Then still run Step 2 (it reports `.env: not-initialized` plus the binary/plugin status, which is useful). In Step 4 show the LETS box pointing at `/lets:init` instead of `/lets:start`.
- `ENV_EXISTS` → normal path. Continue to Step 2.
(No bash worktree pre-check here - `lets update` itself runs the robust `DetectInsideWorktree()` check and exits with `ok == false` + a clear `error`; surfacing that via Step 3 is enough, and it avoids the fragile `*"/worktrees/"*` substring heuristic. `init.md` does the same - no bash worktree check.)
Step 2: Exec
LETS_PROJECT_ROOT=$(git rev-parse --show-toplevel)
lets update --json --plugin-root="${CLAUDE_PLUGIN_ROOT}"Capture stdout (single JSON object). On network trouble the binary degrades gracefully: `binary`/`plugin` come back `unknown` with an explanatory `detail`; it never fails the run for that. If you're in a git worktree, `lets update` exits with `ok == false` and an `error` explaining `.claude/` isn't shared into worktrees - render that per Step 3 (no LETS box).
Step 3: Render
Parse JSON.
`lets update` is a **self-driving loop**: each run advances ONE step and tells you the single next thing to do. Do that one thing, re-run `/lets:update`, repeat until it prints `✓ Everything on vX.Y.Z`.
1. **Artifact table** - one line per `artifacts[]` entry: `<name> v<current_version> <status> (latest v<latest_version>) - <detail>` Omit `(latest …)` / `- <detail>` when those fields are empty; print `?` for an empty `current_version` (`dev` prints as-is, not `vdev`). **When `next_action.kind == "done"`, SKIP the table entirely** - print only the `✓ Everything on v<next_action.version>` line (no status matrix on a fully-synced machine). Status vocabulary: `.env`/`rules` report `in-sync` - they track a *local* source (the `lets` binary for `.env`, the plugin for `rules`), not the latest release. `binary`/`plugin` report `up-to-date`/`outdated` against the *latest release*. So `.env` and `rules` can sit at different versions and both be `in-sync` - expected, not a contradiction; their `detail` names what they track and flags "itself behind latest v…" when that source is itself stale. `rules`/`user-rules` may report `deferred` - the plugin is behind, so syncing the rules now would write a stale lower version; the row's `detail` explains it and `next_action` steers you to the plugin step. Not an error, not a contradiction. `user-rules` (only present with a user-scope install) joins the `in-sync` frame: it tracks the *installed plugin*, same as `rules`. Its `ahead` status means the global file is newer than the plugin (customized or newer release) - deliberately NOT overwritten; relay the `detail` and do not treat it as an error. `rules` may also report `delegated` (`LETS_RULES_SCOPE=user`): the project deliberately has no own copy and lives on the global rules - a healthy state, not an error. `tracker-rules` (present when `LETS_TRACKER` names a shipped adapter, OR a user-authored/unshipped adapter with an installed `.claude/rules/tracker-<name>.md` copy) joins the same frame - for a shipped adapter it tracks the *installed plugin* like `rules` and reports `in-sync` / `deferred` / `updated`, and for a user-authored adapter with no shipped source it reports `delegated` ("left as-is", a healthy green state, not plugin-managed); relay its `detail` switch notes verbatim (a failed stale-adapter removal is a user action item). Relay any `detail` hints verbatim (duplication / missing-global) - they are the user's action items
Read more
description: Sync this project with the current LETS release - a self-driving one-step loop; self-heals .env + rules (rules deferred while the plugin is behind), installs the binary in-session on approval, and points at the single next action until everything is on the same version
Update LETS
Sync the drift-able LETS artifacts (four core + two optional: the user-scope global rules and the active `tracker-<name>.md` adapter) with the current release. Bridges to `lets update --json` (Go binary): auto-syncs `.lets/.env` (header refresh when `LETS_ENV_VERSION` is stale), `.claude/rules/lets-rules.md` (re-copy when outdated/missing - but **deferred** when the plugin is behind, so rules never sync to a stale plugin), and `~/.claude/rules/lets-rules.md` (user-scope global rules - the `user-rules` row appears only when that file exists). It then computes a single ordered `next_action` (the self-driving loop): the binary step can be run in-session (approval-gated), the plugin step is a user-only Claude Code slash command, and a fully-synced machine prints `✓ Everything on vX.Y.Z`.
> **MANDATORY:** Execute every Step's bash block **literally as written**. Do not substitute output from earlier `ls`/`cat` in this conversation - `.env` and other dotfiles are invisible to plain `ls`. The `test -f` checks below ARE the contract.
Difference from `/lets:init`: `/lets:init` is first-time setup (it also asks config questions and sets up the statusline + beads). `/lets:update` only syncs what a new release changes - it never prompts and never touches `settings.json` or beads.
Step 1: Pre-checks
command -v lets >/dev/null 2>&1 || { echo "NO_LETS_BINARY"; exit 0; }
LETS_PROJECT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || { echo "NOT_GIT_REPO"; exit 0; }
test -f "$LETS_PROJECT_ROOT/.lets/.env" && echo "ENV_EXISTS" || echo "ENV_ABSENT"Branch on output:
- `NO_LETS_BINARY` → tell user: "`lets` binary not found on `$PATH`. Install it — `! curl -fsSL https://raw.githubusercontent.com/restarter/lets-workflow/main/scripts/install.sh | bash` (the leading `!` runs it in this session; or the same command without `!` in a terminal). See the README → Quick Start." NO LETS box. STOP.
- `NOT_GIT_REPO` → tell user: "Not a git repository. `/lets:update` runs inside a LETS project." NO LETS box. STOP.
- `ENV_ABSENT` → not initialized. Tell user: "This project hasn't been set up - run `/lets:init` first." Then still run Step 2 (it reports `.env: not-initialized` plus the binary/plugin status, which is useful). In Step 4 show the LETS box pointing at `/lets:init` instead of `/lets:start`.
- `ENV_EXISTS` → normal path. Continue to Step 2.
(No bash worktree pre-check here - `lets update` itself runs the robust `DetectInsideWorktree()` check and exits with `ok == false` + a clear `error`; surfacing that via Step 3 is enough, and it avoids the fragile `*"/worktrees/"*` substring heuristic. `init.md` does the same - no bash worktree check.)
Step 2: Exec
LETS_PROJECT_ROOT=$(git rev-parse --show-toplevel)
lets update --json --plugin-root="${CLAUDE_PLUGIN_ROOT}"Capture stdout (single JSON object). On network trouble the binary degrades gracefully: `binary`/`plugin` come back `unknown` with an explanatory `detail`; it never fails the run for that. If you're in a git worktree, `lets update` exits with `ok == false` and an `error` explaining `.claude/` isn't shared into worktrees - render that per Step 3 (no LETS box).
Step 3: Render
Parse JSON.
`lets update` is a **self-driving loop**: each run advances ONE step and tells you the single next thing to do. Do that one thing, re-run `/lets:update`, repeat until it prints `✓ Everything on vX.Y.Z`.
1. **Artifact table** - one line per `artifacts[]` entry: `<name> v<current_version> <status> (latest v<latest_version>) - <detail>` Omit `(latest …)` / `- <detail>` when those fields are empty; print `?` for an empty `current_version` (`dev` prints as-is, not `vdev`). **When `next_action.kind == "done"`, SKIP the table entirely** - print only the `✓ Everything on v<next_action.version>` line (no status matrix on a fully-synced machine). Status vocabulary: `.env`/`rules` report `in-sync` - they track a *local* source (the `lets` binary for `.env`, the plugin for `rules`), not the latest release. `binary`/`plugin` report `up-to-date`/`outdated` against the *latest release*. So `.env` and `rules` can sit at different versions and both be `in-sync` - expected, not a contradiction; their `detail` names what they track and flags "itself behind latest v…" when that source is itself stale. `rules`/`user-rules` may report `deferred` - the plugin is behind, so syncing the rules now would write a stale lower version; the row's `detail` explains it and `next_action` steers you to the plugin step. Not an error, not a contradiction. `user-rules` (only present with a user-scope install) joins the `in-sync` frame: it tracks the *installed plugin*, same as `rules`. Its `ahead` status means the global file is newer than the plugin (customized or newer release) - deliberately NOT overwritten; relay the `detail` and do not treat it as an error. `rules` may also report `delegated` (`LETS_RULES_SCOPE=user`): the project deliberately has no own copy and lives on the global rules - a healthy state, not an error. `tracker-rules` (present when `LETS_TRACKER` names a shipped adapter, OR a user-authored/unshipped adapter with an installed `.claude/rules/tracker-<name>.md` copy) joins the same frame - for a shipped adapter it tracks the *installed plugin* like `rules` and reports `in-sync` / `deferred` / `updated`, and for a user-authored adapter with no shipped source it reports `delegated` ("left as-is", a healthy green state, not plugin-managed); relay its `detail` switch notes verbatim (a failed stale-adapter removal is a user action item). Relay any `detail` hints verbatim (duplication / missing-global) - they are the user's action items
A development workflow plugin for Claude Code Stop babysitting your AI. Start shipping with it.
Other commands on lets-workflow.
- /ask
Ask a single expert agent a question - like a Slack ping to a colleague
Open command - /backlog
Backlog review and cleanup - multi-agent backlog review, quick no-agent pulse (--fast), or interactive triage cleanup
Open command - /check
Quick sanity check - code (inline 6-perspective) or plan (--plan).
Open command - /done
Finish a task - document, create PR or merge, close
Open command - /end
End a work session - a settlement pass that reconciles uncommitted / unpushed work + session context into git, the tracker, and a session snapshot file. --pre-compact skips settlement and only writes the shared snapshot, keeping the session going.
Open command - /execute
Execute implementation plan from /lets:plan - load plan and enter native plan mode
Open command

