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/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`: `buildGlassSnapshot()`
714
- returns it and every component reads it, so a renamed field fails typecheck
715
- on both sides. The client typechecks under its own
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. A push hook that fails with a real test or
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), then runs
1126
- `lobstah trap title-set` (`--session <id>` from outside the worktree), which
1127
- prints `signOn: complete`. Until then, sign-on is not complete: the
1128
- SessionStart hook adds `lobstah: sign-on is not complete. Apply this title:
1129
- <title>. Then run \`lobstah trap title-set\`.` to the session brief, and the
1130
- Stop hook blocks the stop once with the same text. A turn the Stop hook
1131
- already continued (`stop_hook_active`) is not reminded again. A session that
1132
- signs on again keeps a title it confirmed.
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
 
@@ -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. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lobstah",
3
- "version": "0.6.5",
3
+ "version": "0.6.6",
4
4
  "description": "Harness-agnostic, token-efficient supervision framework for coding agents",
5
5
  "license": "MIT",
6
6
  "author": "aequitas labs LLC",