Skip to content

rig 3.20.0

Choose a tag to compare

@github-actions github-actions released this 25 Sep 18:51
be7c782

Cut redundant tests and fix two that tested nothing (#174)

An audit of the suite: which tests repeat another's case, and which don't test what they claim.

Two broken tests, fixed

  • pr.test.mjs — "the Direction … is lifted verbatim" wrote into a context doc and read the same file back. rig pr never ran on a written Direction. It now opens a PR on a second work and asserts the body. Checked by breaking prBody: the test fails.
  • installation-update.test.mjs — the "no git on PATH" tests built PATH from 'C:\Windows\System32' in a plain JS string, which reads as C:WindowsSystem32. The environment is now one helper, onPath, with the path escaped and Windows-only. The duplicate doctor run is folded into the node-only doctor test next to it.

Cut: ~20 tests that repeat another test's input and assertions

Removed Still covered by
package — real npm install -g (51s locally) install — install.sh / install.ps1 do the same link + rig help
release — verdict / notes CLI spawns (3) release-e2e runs the same commands
release-e2e — "naming no bump" release unit test of the same case
stage-cut — re-declare refused stages-e2e "and a stage is not declared twice"
next — measured zero; draft entry alone same input, superset assertions, in the same file
dash — window bounds merged --since test in the same file
jira — createIssue markdown the two full-argv createIssue tests (rationale comment moved)
doctor — unclosed work "whichever root" "a work folder that is missing is said once"; the multi-root case is in dataroots
version — two dataMajor({}) tests; "stamp is applyMigrations" merged into one; migration 2's test stamps 1.4.0 → 2.0.0
attach — mirror-ref test folded into the doctor-behind test as a precondition
smoke — dash with no payload the acceptance test runs the same dash (filename assertion moved)
helpers — statusLine phase (plus one assertion for the no-repo-facts call path)
installation-freshness — non-number interval (20s poll) roots unit test of the same normalisation

Also:

  • skills.test.mjs no longer pins the exact list of skill folders, which broke on every new skill. It requires the rig entry point, and checks that every skill rig names in bin/ and in the entry point is a folder it ships.
  • Removes unused imports and fixture exports, and the statusLine re-export from bin/rig.mjs.

No removed title is cited in DESIGN.md's decision log.

Result

18 fewer tests than main, and no change in run time. Measured back to back on one machine: main 53.2s / 56.0s, this branch 53.7s / 61.5s. CI: 58s on the last main push, 52s here. The difference between two runs of the same code is larger than the difference between the two.

node --test runs files in parallel, so wall time is the slowest file, not the sum. The 51s npm install -g test ran beside install.test.mjs and the scenarios, which take about as long. Cutting it saves CPU time without ending the run sooner. An earlier "144s → 114s" in this description was measured on a loaded machine and was wrong.

The case for this PR is correctness and upkeep, not speed.

Two tests that claimed more than they asserted

  • demo.test.mjs — "a merged PR is reported with the stretches it spent" only checked that the section heading rendered. It now asserts each stretch, including the two with no review to measure from. Checked by swapping one stretch's endpoints in bin/demo.mjs: the test fails.
  • stages-e2e.test.mjs — stacked-stage test claimed "the chain orders them, not the array", but its stages are declared in chain order, so it could not tell. The claim is dropped; the out-of-order test after it is the one that proves it.

Looked at and kept

  • scenarios.test.mjs: the scenarios look like repeats of dataroots.test.mjs, but they aren't. dataroots saves and restores the machine file between steps or fabricates the legacy state. The scenarios reach those states the way a machine does: an unnamed first init, then a rename, then a split. That sequence is the reason the file exists.
  • stages-e2e against stage-cut: one file cuts its branches by hand with git, the other has rig cut them with --cut. They cover two routes to the same discovery, and later tests build on each.

Not in this PR

  • Fixture speed: stacked() in worktrees-fixture.mjs builds a fresh remote per test; copying one prepared tree, as #164 did for checkouts-*, would speed those files up.

Restore a work's worktrees on a second machine in one command (#176)

Restore a work's worktrees on a second machine in one command

Tickets: #112, #99, #171

Direction

One command, rig restore [<id>], rebuilds a work's folder from its record and changes nothing
in the record.
It is not an attach (issue comment 2, point 3): attach decides a base, stamps
attachedAt and pushes a repo entry; a restore has all of that already and writes none of it.
work.json is byte-identical afterwards and there is no data-root commit. It is in MUTATING
only for prepareDataRoot's fast-forward, so a second machine restores from the newest records.

Per recorded repo whose worktree is missing (present ones are left alone, so it is idempotent):

  1. Pick the top of the repo's stack. Fetch the mirror, read chain() over the declared stages,
    order with stageOrder, and take the highest branch this repo carries on the remote or in the
    mirror; failing any stage, the work branch. This is what puts infra-tim on its stage branch
    rather than an unpushed, empty work branch.
  2. Never recreate a branch. A new trees().checkOut checks out a branch the remote has, or the
    copy the mirror kept, exactly as cut() does — and answers "absent" where cut() would cut a
    new branch from the remote HEAD. An absent repo is skipped and named with its PR state
    (prForBranch): "no branch on the remote — PR #n CLOSED" or "never pushed, and no PR".
  3. Name the branches built on top that rig does not know. One gh pr list --base <top> per
    restored repo, followed up the chain: the open PRs stacked on it that are not declared stages
    are named, in order, with rig stage <branch> as the way to record them. Reported, not checked
    out — which branch of an unrecorded stack is "the" work is the user's call.
  4. Repeat attach's per-repo preparation, extracted out of attachRepo so both share it:
    user.email from identityFor, copySecrets, and the catalogue's setup printed (--setup
    runs it). Catalogue drafting stays attach-only — a restored repo is already catalogued.
  5. Regenerate the work folder — AGENTS.md, CLAUDE.md, .rig/id, .rig/data — with
    regenerate, which never touches the record.

Around it:

  • rig next offers rig restore <id> first when a missing repo could still come back: its
    PR is neither MERGED nor CLOSED. A repo whose branch went with a closed PR is not offered
    forever; restore has already said why it cannot come back.

  • rig doctor names the remedy on "work folder missing" and "worktree is gone".

  • loadWork, asked for an id the root in hand does not hold, names the root that does
    (--data <name>), so rig restore <id> on a machine with several roots says where to look.

  • rig-handoff places the handoff with rig status --work <id> when the folder is missing,
    and its continue prompt puts rig restore <id> before the cd.

  • rig attach <repo> on a recorded repo whose worktree is gone restores that one repo through
    the same routine instead of saying "nothing to do" (#99). attachedAt is left alone: it records
    when the repo joined the work.

Not doing: recording anything, or picking between the branches of a forked stack.

Agreed with Hugo 2026-09-25: name rig restore; unrecorded stack reported by default, --tip to
check it out; #99 folded in.

Context doc: https://github.com/hugoforte/rig-data/blob/main/work/rig-restore/context.md

Handoff by URL (#171)

rig status prints a handoff line when the work has a handoff.md. It gives the file's URL on the data root's remote, or this machine's path, marked as such, when there is no remote. The rig-handoff continue prompt is built from that line and rig restore <id>, with no cd. Nothing in it belongs to the machine that wrote it.

Fixes #112
Fixes #99
Fixes #171

Full changelog: v3.19.0...v3.20.0