lobstah 0.6.5 → 0.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +327 -78
- package/dist/runner.js +2 -2
- package/docs/design.md +9 -4
- package/docs/man.md +38 -10
- package/docs/vocabulary.md +2 -2
- package/package.json +1 -1
package/dist/runner.js
CHANGED
|
@@ -1742,10 +1742,10 @@ function chainPr(id, lane) {
|
|
|
1742
1742
|
// packages/core/dist/pr-repair.js
|
|
1743
1743
|
var PUSH_RETRIES = 3;
|
|
1744
1744
|
var PUSH_REJECTED = "push rejected:";
|
|
1745
|
-
function pushRule(branch, id = "<dispatch id>", mode = "rebase") {
|
|
1745
|
+
function pushRule(branch, id = "<dispatch id>", mode = "rebase", also = []) {
|
|
1746
1746
|
const b = branch ?? "the PR's head branch";
|
|
1747
1747
|
return [
|
|
1748
|
-
`Push only to the existing branch ${b}.`,
|
|
1748
|
+
also.length ? `Push only to the existing branch ${b} and the branches of the PRs stacked on it named below.` : `Push only to the existing branch ${b}.`,
|
|
1749
1749
|
mode === "merge" ? `If the push is rejected as non-fast-forward because ${b} moved, fetch ${b}, merge the moved head into your branch, and push again with a normal push. Retry at most ${PUSH_RETRIES} times. Never force-push.` : `If the push is rejected as non-fast-forward because ${b} moved, fetch ${b}, rebase your commits onto the moved head again, and push with \`--force-with-lease=${b}:<the head you just fetched>\`. Retry at most ${PUSH_RETRIES} times.`,
|
|
1750
1750
|
"If a push hook fails with a real test or type error, do not retry the push: fix the error, commit, and push again.",
|
|
1751
1751
|
`If you still cannot push, report \`lobstah report ${id} failed "${PUSH_REJECTED} <rejection text>; moved head <full sha of the head you fetched>"\` and leave the PR as it was.`,
|
package/docs/design.md
CHANGED
|
@@ -694,7 +694,7 @@ with [Preact](https://preactjs.com) and [htm](https://github.com/developit/htm)
|
|
|
694
694
|
| `src/store.ts` | the one store: the latest snapshot, the route, the prefs, the open modal, the stale flag, the lob state |
|
|
695
695
|
| `src/actions.ts` | every change the page can make (open or close a modal, set a preference, hide a lob, receive a snapshot) |
|
|
696
696
|
| `src/main.ts` | the entry: builds the store from localStorage and the URL, renders `App` into the body on every change, starts polling |
|
|
697
|
-
| `src/poll.ts` | `/data` polling: every 2s, one request at a time, paused while the tab is hidden |
|
|
697
|
+
| `src/poll.ts` | `/data` polling: every 2s, one request at a time, paused while the tab is hidden; a 304 applies only the beats (server time and heartbeats) |
|
|
698
698
|
| `src/route.ts` | the hash tabs (`#deck`, `#dispatches`, `#traps`, `#prs`, `#notices`) |
|
|
699
699
|
| `src/prefs.ts` | the per-browser preferences and lob hides, in localStorage |
|
|
700
700
|
| `src/components/*.ts` | `App`, `Header`, `Deck`, `Dispatches`, `Traps`, `PRs`, `Notices`, `Modal` (dispatch, trap, helm, PR, settings), `Lobs` |
|
|
@@ -710,9 +710,14 @@ the active tab's section renders.
|
|
|
710
710
|
The pure helpers stay beside the server in `apps/cli/src` and are imported by
|
|
711
711
|
both sides: `glass-diff.ts` (the per-section selectors, tab route, filters,
|
|
712
712
|
PR modal data) and `glass-lobs.ts` (which lobs walk). The `/data` payload
|
|
713
|
-
type, `GlassSnapshot`, lives in `@lobstah/core`: `
|
|
714
|
-
|
|
715
|
-
on both sides.
|
|
713
|
+
type, `GlassSnapshot`, lives in `@lobstah/core`: `glass-poll.ts` builds it
|
|
714
|
+
from `buildGlassSnapshot()` and every component reads it, so a renamed field
|
|
715
|
+
fails typecheck on both sides. `/data` carries dispatch summaries and the
|
|
716
|
+
last day's history (at least the newest few of each kind), with an ETag that
|
|
717
|
+
leaves out heartbeats and ages; those ride in the `x-lobstah-beats` header
|
|
718
|
+
of every answer, a 304 too. A dispatch modal fetches the rest from
|
|
719
|
+
`/data/dispatch/<id>`, and `/data/older?kind=dispatches|notices|prs&offset=<n>`
|
|
720
|
+
pages in older history. The client typechecks under its own
|
|
716
721
|
`apps/cli/glass/tsconfig.json` (DOM lib). `preact` and `htm` are dev
|
|
717
722
|
dependencies of `apps/cli`: they exist only inside the bundled page.
|
|
718
723
|
|
package/docs/man.md
CHANGED
|
@@ -344,9 +344,11 @@ and the glass show the repair's PR, chore lane, worker, and trap wait.
|
|
|
344
344
|
PR's watch. `lobstah watch hold <key> [--for <id>]`
|
|
345
345
|
holds one PR's watch; with `--for`, the hold ends when that dispatch
|
|
346
346
|
ends. `lobstah watch release <key>` ends any hold.
|
|
347
|
+
- A repair of a PR below this one in the same stack is queued or running
|
|
348
|
+
(`heldBy: stack`): that repair also updates this PR.
|
|
347
349
|
|
|
348
350
|
A waiting repair is recorded on the PR record as `repair.status: waiting`,
|
|
349
|
-
with `heldBy` (`wt:<trap>`, `dispatch:<id8>`, `helm`, `hold`, `settle`,
|
|
351
|
+
with `heldBy` (`wt:<trap>`, `dispatch:<id8>`, `helm`, `hold`, `settle`, `stack`,
|
|
350
352
|
`checks`, `human-gate`, or `repaired`) and `reason`. `repaired` means each
|
|
351
353
|
failing check already had its round at this head; a new commit ends it.
|
|
352
354
|
Unlike the other waits, `repaired` also raises `pr:checks` attention with
|
|
@@ -378,7 +380,25 @@ base is another PR's branch) rebases onto that base and pushes with
|
|
|
378
380
|
`--force-with-lease`; on a rejection it rebases onto the moved head again
|
|
379
381
|
and pushes with a lease on the head just fetched. A checks or review
|
|
380
382
|
repair follows the rebase rule on a rejection. Each retries at most three
|
|
381
|
-
times.
|
|
383
|
+
times.
|
|
384
|
+
|
|
385
|
+
**A repair updates the PRs stacked on it.** When open PRs are stacked on
|
|
386
|
+
the repaired PR (lobstah's PR records show PRs whose base is its head
|
|
387
|
+
branch, and theirs), the brief names each one, in order, and the push rule
|
|
388
|
+
allows their branches. After pushing the repaired PR, the worker brings
|
|
389
|
+
each one up to date with its base:
|
|
390
|
+
|
|
391
|
+
- After a merge-based repair (a standalone PR, or any checks or review
|
|
392
|
+
repair), it merges the updated base into each one and pushes normally,
|
|
393
|
+
never with force.
|
|
394
|
+
- After a rebase-based repair (a conflict repair of a stacked PR), it
|
|
395
|
+
rebases each one onto its updated base and pushes with
|
|
396
|
+
`--force-with-lease`, with the same retries.
|
|
397
|
+
|
|
398
|
+
An update that conflicts is aborted and left as it was, with the PRs above
|
|
399
|
+
it. lobstah repairs that PR as its own conflict repair, under the same
|
|
400
|
+
rules (`repairSettleSecs`, the attempt limit, and the no-progress breaker).
|
|
401
|
+
The list stops below a PR that a live worker holds. A push hook that fails with a real test or
|
|
382
402
|
type error is not retried: the worker fixes the error and pushes again.
|
|
383
403
|
For code already on main, the repair keeps main's version and only this
|
|
384
404
|
PR's own changes. It does not change behavior. If a conflict resolution
|
|
@@ -1122,14 +1142,22 @@ it.
|
|
|
1122
1142
|
Sign-on asks the session to apply a title: `lobstah soak` prints
|
|
1123
1143
|
`step: Apply this title: <title>` (the trap's name). The session applies it
|
|
1124
1144
|
with its harness's title tool when there is one (`set_session_title` in the
|
|
1125
|
-
Claude desktop Code tab, `set_thread_title` in the Codex app)
|
|
1126
|
-
`lobstah
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1145
|
+
Claude desktop Code tab, `set_thread_title` in the Codex app) and skips that
|
|
1146
|
+
part when it has none. Then it runs `lobstah soak title-set` (`--session
|
|
1147
|
+
<id>` from outside the worktree), which prints `signOn: complete`.
|
|
1148
|
+
`lobstah trap title-set` does the same, but Claude Code's worktree isolation
|
|
1149
|
+
refuses any command line with the word `trap` in it (it reads it as the
|
|
1150
|
+
shell builtin), so a trap runs `soak title-set`.
|
|
1151
|
+
|
|
1152
|
+
Until then, sign-on is not complete. The SessionStart hook adds the
|
|
1153
|
+
reminder to the session brief, and the Stop hook blocks the stop once per
|
|
1154
|
+
turn with it: `lobstah: sign-on is not complete. Apply this title: <title>
|
|
1155
|
+
(skip this if you have no tool that sets the session title). Then run
|
|
1156
|
+
\`lobstah soak title-set\`.` A turn the Stop hook already continued
|
|
1157
|
+
(`stop_hook_active`) is not reminded again. After three reminders (the
|
|
1158
|
+
third says it is the last), both hooks stop asking; `soak title-set` still
|
|
1159
|
+
completes sign-on later. A session that signs on again keeps a title it
|
|
1160
|
+
confirmed.
|
|
1133
1161
|
|
|
1134
1162
|
### The terminal tab name
|
|
1135
1163
|
|
package/docs/vocabulary.md
CHANGED
|
@@ -389,7 +389,7 @@ evidence).
|
|
|
389
389
|
| `draft` | Draft flipped (`value`). Evidence only. |
|
|
390
390
|
| `merged` / `closed` | Terminal; the check sets `done`, and the watch retires once delivered. Emitted only on an open → terminal change, never on the first observation. |
|
|
391
391
|
| evidence `pr` | `{ url, number, state, draft, reviewDecision, mergeStateStatus, headSha, checks: { total, passed, failed, pending, unknown? }, review: { unresolvedThreads, changesRequested, lastReviewAt }, observedAt }`. Check counts use only the latest run per check name and app/workflow. `CANCELLED` and `STALE` latest runs are unknown, not failed. The PR record also stores repair status, attempts, and reason. `prBadge` shows `repairing: conflict (attempt 1 of 2)` while a repair is in flight, and ends in `repair waits: <heldBy>` while a repair waits; tend, `catch`, and the glass share the badge. |
|
|
392
|
-
| waiting repair | `repair.status: waiting` on a PR record: a repair is due but is not queued. `heldBy` names the holder: `wt:<trap>` or `dispatch:<id8>` (a live worker holds the PR's head branch or the head branch of a PR below it in the stack), `helm` (the helm cancelled a repair of this PR), `hold` or `dispatch:<id8>` (`watch hold`), `settle` (the head, base head, or failing checks changed less than `[watch].repairSettleSecs` ago, or, for a conflict repair, the PR was approved less than that ago; `until` says when), or `checks` (the latest run of a failing check is in progress or passed). `reason` says what holds it. A wait is not an attempt and raises no attention item. |
|
|
392
|
+
| waiting repair | `repair.status: waiting` on a PR record: a repair is due but is not queued. `heldBy` names the holder: `wt:<trap>` or `dispatch:<id8>` (a live worker holds the PR's head branch or the head branch of a PR below it in the stack), `helm` (the helm cancelled a repair of this PR), `stack` (a repair of a PR below it in the stack is queued or running, and updates this PR too), `hold` or `dispatch:<id8>` (`watch hold`), `settle` (the head, base head, or failing checks changed less than `[watch].repairSettleSecs` ago, or, for a conflict repair, the PR was approved less than that ago; `until` says when), or `checks` (the latest run of a failing check is in progress or passed). `reason` says what holds it. A wait is not an attempt and raises no attention item. |
|
|
393
393
|
| repair chore | A daemon repair or rebase runs in the `chore` lane under `[limits].choreConcurrent`. A repair chore for a trap-built PR waits for its live owning trap up to `[watch].repairTrapWaitSecs` (default 600), then runs headless in its own PR-branch checkout. A headless-built PR's repair runs headless in its origin worktree when safe. No headless chore uses a trap's worktree. This bounded fallback applies only to system repair chores; a person's addressed work is sticky and never falls back. Tend, daemon status, doctor, and the glass show the PR, lane, worker, and trap wait. |
|
|
394
394
|
| live worker | An active headless dispatch, or a trap with an open catch. It holds a branch that its worktree has checked out, that its current branch tracks, or that it pushed during its current dispatch (evidence `pushes`). It holds a PR that its evidence names or that its chain owns. |
|
|
395
395
|
| evidence `pushes` | `[{ branch, at }]`: the branches a dispatch pushed, as lobstah saw them. The runner records its own pushes; the runner records a headless worker's `git push` from the harness event stream (branch names only); `soak beat` records a trap's `git push`. |
|
|
@@ -466,7 +466,7 @@ worktree is refused (the session lock); a stale one is adopted.
|
|
|
466
466
|
| Word | Meaning |
|
|
467
467
|
| ---- | ------- |
|
|
468
468
|
| `soak` | Sign the worktree's trap on and take matching work. From a primary checkout, or with `--repo <key>` from outside any configured repo, it first creates a linked worktree like a dispatch does (fetch trunk, new branch from `origin/<trunk>`, `setup` commands), prints `worktree:`, `created: true`, `branch:`, and, when the session is elsewhere, `instruction: cd <path> ...`; the session works in that directory. If creation fails, nothing is signed on and the partial worktree and branch are removed. `--repo` in a linked worktree must match its repo. A session that already mans a trap re-uses it (resolved from the session id) and never gets a second worktree. `--session` is needed only on first sign-on. `--one` stows after the first catch. `--wait` registers a watcher and waits for work; a quiet timeout exits 3. Workers never run `man` verbs. |
|
|
469
|
-
| `trap title-set` | Confirm this session applied the title sign-on printed (`step: Apply this title: <title>`). The registration records `titleSet` and `titleSetAt` and drops `titlePending`; sign-on is complete. Until then, SessionStart and Stop remind the session once per turn. |
|
|
469
|
+
| `soak title-set` (alias `trap title-set`) | Confirm this session applied the title sign-on printed (`step: Apply this title: <title>`). The registration records `titleSet` and `titleSetAt` and drops `titlePending`; sign-on is complete. Until then, SessionStart and Stop remind the session once per turn, at most three times. A trap runs `soak title-set`: Claude Code's worktree isolation refuses a command line containing `trap`. |
|
|
470
470
|
| `stow` | Sign the trap off (in the worktree, or elsewhere by session id: `--session <id>` or `$CLAUDE_CODE_SESSION_ID`); an unfinished catch requeues, done/failed finalizes in `done/`, and a cancelled catch finalizes as failed. Unread messages are held for `[soak].signOffGraceSecs` (default 600): they deliver if the same worktree re-soaks in time, else the daemon bounces them to the helm (at once when stow removed the worktree or the grace is `0`). A trap always stows itself freely; stowing someone else's (`--wt`) is steering — the claimed helm's alone. Stow closes the seat, never the session: an opted-in session can only be asked to stop, and a still-looping worker re-enlists visibly (`trap-signed-on`). By default stow removes the worktree when soak created it (`createdWorktree: true`) and prints `worktree: removed`, `path:`, and `returnTo:`; `--keep` leaves it. It keeps the worktree (`worktree: kept`, `reason:`) when soak did not create it, or when it holds uncommitted changes, untracked files that are not ignored, commits absent from its upstream, or no upstream. `--force` explicitly allows discarding unsaved checkout files; `--keep` still keeps it. The branch is deleted (`branchDeleted:`) only when all its commits are on its upstream (with no upstream: on some remote branch); otherwise `branchKept: <branch> (<reason>)`. `--wt` follows the same rules. The SessionEnd hook (`stow --quiet`) signs off only when the ending session is the trap's registered session (another session in the worktree ending is a no-op) and keeps the worktree; SessionEnd cannot tell a restart or compaction from an exit (Claude Code's `reason` is `clear`, `resume`, `logout`, `prompt_input_exit`, or `other`; Codex's is always `other`), so the grace covers a session that signs on again. |
|
|
471
471
|
| sign-off grace | For `[soak].signOffGraceSecs` after a trap signs off (`~/.lobstah/signed-off/<trap>.json`), its address is held: `send wt:<trap>` queues and warns instead of refusing, held messages wait, and work addressed to it raises no `bait-orphaned` notice. A re-soak of the same worktree releases the hold; after the grace, the daemon bounces the held messages (`message-bounced`). |
|
|
472
472
|
| address | `--for wt:<trap>` targets one trap; `session:<id>` is an alias resolved to the trap at dispatch time. **Sticky:** addressed work is never the daemon's — it waits for its trap; an orphan (trap gone) surfaces as a `bait-orphaned` notice for the helm to re-address, release, or cancel. Delivery stamps a receipt (`deliveredTo`/`deliveredAt`) into evidence. Unaddressed work defers to a parked matching trap for `[soak].deferSecs`, then the daemon spawns headless. |
|