name: updating-lockstep
description: Acts on lockstep.json drift for repos that carry the lockstep manifest. Reads pnpm run lockstep --json, then runs a Workflow that pipelines each mechanical version-pin row through resolve → bump → validate → commit independently. Surfaces file-fork / feature-parity / spec-conformance / lang-parity rows as advisory. Invoked by the updating umbrella skill; can also run standalone.
user-invocable: true
allowed-tools: Workflow, Read, Edit, Grep, Glob, Bash(pnpm:), Bash(npm:), Bash(git:), Bash(node:), Bash(rg:), Bash(grep:), Bash(find:), Bash(ls:), Bash(cat:), Bash(head:), Bash(tail:), Bash(wc:), Bash(diff:*)
model: claude-haiku-4-5
context: fork
updating-lockstep
Acts on drift in lockstep.json. Collects drift inline, then runs a Workflow that pipelines each mechanical version-pin row through resolve → bump → validate → commit on its own timeline; everything else surfaces as advisory notes for human review. Each actioned row becomes its own atomic commit so the PR reviewer can accept / reject per-row.
When to use
- Invoked by the
updatingumbrella skill (weekly-update workflow). - Standalone:
/updating-lockstepto sync just the lockstep manifest. - After manual submodule bumps, to refresh
lockstep.jsonmetadata.
Exits cleanly when lockstep.json is absent. Not every fleet repo has one.
Per-kind policy at a glance
version-pin is mechanical (auto-bump per upgrade_policy). Everything else is advisory. Upstream semantics and local deltas need human judgment.
Full policy table, scripts per phase, and advisory format in reference.md.
Phases
Phases 1–2 (pre-flight + collect drift) run inline — one pnpm run lockstep --json call builds the work-list. Phase 3 (auto-bump) is independent per-row fan-out — each version-pin row resolves and validates on its own timeline — so it runs as a Workflow pipeline(). Phases 4–5 (advisory compose + report) run inline after, since the report needs the full per-row result set.
| # | Phase | Outcome |
|---|---|---|
| 1 | Pre-flight (inline) | Bail if no lockstep.json. Verify scaffolding (lockstep.schema.json, scripts/fleet/lockstep.mts). Clean tree. Detect CI mode. |
| 2 | Collect drift (inline) | pnpm run lockstep --json → split rows into auto (mechanical version-pin bumps) and advisory (everything else with drift). The auto rows are the pipeline work-list. |
| 3 | Auto-bump (pipeline) | Per row: resolve submodule, fetch tags, identify target tag, checkout, update lockstep.json + .gitmodules, validate, commit (chore(deps): bump <upstream> to <tag>). Test before committing in interactive mode. |
| 4 | Advisory (inline) | Compose per-row markdown lines for the PR body. |
| 5 | Report (inline) | Human-readable summary; in CI mode, emit advisory block to $GITHUB_OUTPUT (base64); HANDOFF block per _shared/report-format.md. |
The per-row pipeline: author a Workflow
The skill invoking Workflow is a sanctioned opt-in. Pass the auto (version-pin) row list from Phase 2 as args; the advisory rows stay inline. Author the script inline (don't pre-Write it). Shape:
pipeline(autoRows, resolveTarget, bumpAndCommit)
resolveTargetstage — oneagent()per row: resolve the submodule path, fetch tags, apply the tag-stability filter (drop-rc/-alpha/-beta/-dev/-snapshot/-nightly/-preview), pick the target tag perupgrade_policy. ReturnsROW_SCHEMA:{ upstream, submodulePath, currentTag, targetTag, locked: boolean, skipReason? }. Alockedrow or no newer stable tag returns with askipReasonand no stage-2 work.bumpAndCommitstage — checkout the target tag, updatelockstep.json+ the.gitmodules# <name>-<version>annotation (via Edit, notsed), validate (pnpm run lockstepexits 0 or 2), runpnpm testin interactive mode, then commitchore(deps): bump <upstream> to <tag>. ReturnsRESULT_SCHEMA:{ upstream, targetTag, committed: boolean, state: bumped|skipped-locked|skipped-no-tag|test-failed }. A test failure rolls back the row and the stage throws, dropping the item tonull(filter before the Phase-5 report).
Worktree isolation is not needed: each row touches a distinct submodule path + its own lockstep.json/.gitmodules lines, and commits land sequentially on the same branch. Most repos carry only a handful of version-pin rows, so the pipeline is shallow — the win is per-row streaming (a slow tag-fetch on one upstream doesn't block the others) and validated structured rows for the report.
Hard requirements
- Bail safely on missing manifest: exit 0 cleanly if
lockstep.jsonis absent. - Atomic commits: one commit per auto-bumped row. Conventional Commits format.
.gitmodulesversion comments: keep# <name>-<version>annotations synchronized withpinned_tag.- Stable releases only: filter
-rc/-alpha/-beta/-dev/-snapshot/-nightly/-preview(full pattern inreference.md). - No
npx/pnpm dlx/yarn dlx:pnpm execorpnpm runper CLAUDE.md Tooling. - Edit tool, not
sed: for.gitmodulesannotation updates.
Forbidden
- Auto-editing
file-fork/feature-parity/spec-conformance/lang-parityrows' tracked state. Advisory only. - Bumping a
lockedversion-pinwithout human approval (gated on coordinated upstream change). - Skipping the tag-stability filter.
CI vs interactive mode
- CI (
CI=true/GITHUB_ACTIONS): skip per-row test validation; emit advisory to$GITHUB_OUTPUT. - Interactive (default): run
pnpm testbefore each auto-bump commit; rollback the row on failure and continue.
Success criteria
- All actionable
version-pinrows bumped atomically (one commit per row). - Advisory rows collected for PR body / workflow output.
- No edits to non-
version-pinrow tracked state. pnpm run lockstepexits 0 or 2 at end (never 1; no schema errors introduced)..gitmodulesversion comments synchronized withpinned_tag.
Commands reference
pnpm run lockstep --json: drift report (consumed by this skill).jq: parse + editlockstep.json(structured JSON edits).git submodule status: verify submodule state after bumps.