lobstah 0.6.4 → 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
@@ -770,7 +770,8 @@ var DEFAULT_SOAK = {
770
770
  deferSecs: 90,
771
771
  ttlSecs: 1800,
772
772
  beat: true,
773
- pausedTtlSecs: 86400
773
+ pausedTtlSecs: 86400,
774
+ signOffGraceSecs: 600
774
775
  };
775
776
  var DEFAULT_HELM = {
776
777
  ttlSecs: 1800,
@@ -1741,10 +1742,10 @@ function chainPr(id, lane) {
1741
1742
  // packages/core/dist/pr-repair.js
1742
1743
  var PUSH_RETRIES = 3;
1743
1744
  var PUSH_REJECTED = "push rejected:";
1744
- function pushRule(branch, id = "<dispatch id>", mode = "rebase") {
1745
+ function pushRule(branch, id = "<dispatch id>", mode = "rebase", also = []) {
1745
1746
  const b = branch ?? "the PR's head branch";
1746
1747
  return [
1747
- `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}.`,
1748
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.`,
1749
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.",
1750
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.`,
@@ -107,6 +107,7 @@ send a continuation.
107
107
  | `deferSecs` | `90` | A soaking session whose park heartbeat is this fresh holds unaddressed matching bait — the daemon waits instead of spawning. Addressed bait (`--for session:<id>`) waits regardless, until the registration is gone. |
108
108
  | `ttlSecs` | `1800` | Heartbeat age past which a registration is a ghost trap: the sweep removes it and requeues an unfinished catch, finalizes done/failed in `done/`, or finalizes a cancelled catch as failed. A fresh `lobstah report` or beat counts as liveness. If the daemon's own tick gap exceeds this TTL, it grants a full TTL after resume before sweeping any traps. |
109
109
  | `beat` | `true` | The post-tool hook (`lobstah soak beat`) refreshes a soaking session's liveness and writes its catch's activity, at most once per 30 seconds per trap. With `false` the hook does nothing, and a trap's liveness comes from its reports and its park only. |
110
+ | `signOffGraceSecs` | `600` | After a trap signs off, its address is held this long: messages to it wait for a re-soak of the same worktree instead of bouncing, `send` to it queues with a warning, and work addressed to it raises no `bait-orphaned` notice. After the grace the daemon bounces the held messages. `0` bounces at sign-off. |
110
111
  | `pausedTtlSecs` | `86400` (24 hours) | A trap whose catch last reported `paused` is kept out of the ghost sweep this long after the report. `report paused --until <iso|duration>` sets the expiry instead. After it, the sweep removes the trap as usual, and the notice says the pause expired. |
111
112
 
112
113
  ## `[helm]` — the orchestrator seat (`lobstah man helm`)
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
 
@@ -1,9 +1,9 @@
1
1
  # lobstah in Claude Code
2
2
 
3
- Use `/lobstah:man` to load the lobstah man skill. `/lobstah:helm` signs on
4
- without loading the skill. You can also say "Lobstah man, take the helm."
3
+ Use `/lobstah:man` to take the helm. You can also say "Lobstah man, take
4
+ the helm."
5
5
 
6
- This page covers the plugin's commands, skills, session id, and Stop hook.
6
+ This page covers the plugin's skills, session id, and Stop hook.
7
7
  See [docs/man.md](../man.md) for the full pattern.
8
8
 
9
9
  The lobstah man skill brings up the glass and prints its URL. Open it in the
@@ -41,18 +41,17 @@ row, and the session-start brief says when the plugin is behind.
41
41
  `lobstah send <id> "<instruction>"` steers live or queued work and wakes a
42
42
  finished dispatch as a follow-up.
43
43
 
44
- Slash commands, each a shortcut for a CLI verb:
44
+ The plugin has four skills and no slash commands:
45
45
 
46
- | Command | CLI equivalent |
47
- | ------- | -------------- |
48
- | `/lobstah:helm [grounds]` | `lobstah man helm` |
49
- | `/lobstah:relieve` | `lobstah man relieve` |
50
- | `/lobstah:tend` | `lobstah man tend` |
51
- | `/lobstah:soak` | `lobstah soak` |
52
- | `/lobstah:stow` | `lobstah stow` |
46
+ | Skill | What it does |
47
+ | ----- | ------------ |
48
+ | `/lobstah:man` | Take the helm: `lobstah man helm`, the glass, then the orchestrator flow. |
49
+ | `/lobstah:trap` | Sign on as a trap: `lobstah soak`, then the worker flow. |
50
+ | `/lobstah:stow` | Sign the trap off with `lobstah stow` and report which trap was stowed. |
51
+ | `/lobstah:relieve` | Step down with `lobstah man relieve`; afterwards no `man wait` or `man report`, and no re-take unless asked. |
53
52
 
54
- Claude Code also loads the `man` and `trap` skills on its own when you ask
55
- for that kind of work ("take the helm", "work as a trap").
53
+ Claude Code also loads these skills on its own when you ask for that kind of
54
+ work ("take the helm", "work as a trap", "stow", "step down").
56
55
 
57
56
  ## The session id
58
57
 
@@ -101,7 +100,7 @@ Without the plugin, `lobstah man init` merges the Stop hook into
101
100
 
102
101
  ## CLI vs desktop app
103
102
 
104
- The plugin, hooks, skills, and slash commands are the same whether you run
103
+ The plugin, hooks, and skills are the same whether you run
105
104
  `claude` in a terminal or use the Code tab of the Claude desktop app. What
106
105
  differs:
107
106
 
@@ -59,8 +59,13 @@ mentions below. Plain-language requests load the same skills:
59
59
  /skills
60
60
  $lobstah:man take the helm for this repo
61
61
  $lobstah:trap work as a trap in this linked worktree
62
+ $lobstah:stow sign this trap off
63
+ $lobstah:relieve step down from the helm
62
64
  ```
63
65
 
66
+ The plugin has these four skills and no commands; the Claude Code plugin has
67
+ the same four.
68
+
64
69
  The skill instructions lead to `lobstah man helm` and `lobstah soak`,
65
70
  respectively. This verification stopped before running either sign-on
66
71
  command. The desktop task's supplied skill catalog names
package/docs/man.md CHANGED
@@ -229,7 +229,7 @@ of the stack are recorded too:
229
229
 
230
230
  The report prints `prs` with every PR it recorded. `lobstah catch` and
231
231
  `lobstah status <id>` list each PR (`prs`), `man tend` lists each with its
232
- state in the story's `pr` column, and the glass links each by number. Each
232
+ state in the story's `pr` column, and the glass lists each in stack order with its state. Each
233
233
  PR has its own record, so `pr:*` attention and the merged/closed notice are
234
234
  per PR. `[limits].releaseOnMerge` keeps the worktree while another PR of
235
235
  the dispatch is open. What you get depends on what runs:
@@ -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
@@ -675,9 +695,21 @@ watcher that is still starting, so arming and ending the turn at once is safe.
675
695
  wait for attention itself. Without a Stop hook, use the
676
696
  [foreground loop below](#without-a-plugin).
677
697
 
678
- The hook is a CLI command — `lobstah man haul` (the lobstah man hauls the
679
- trapline; every orchestrator-facing command lives under `lobstah man`).
680
- Install it from the project you'll run the lobstah man in:
698
+ Each plugin hook runs one CLI command, `lobstah hook <event>`:
699
+
700
+ | Hook | Command | What it does |
701
+ | ---- | ------- | ------------ |
702
+ | SessionStart | `lobstah hook session-start` | Announces the session id and a one-line fleet state; for the helm, its charter; for a trap, its worktree. A session with neither role gets its id and the sign-on commands. |
703
+ | Stop | `lobstah hook stop` | Parks the helm or a trap: blocks with standing attention, asks for a watcher in arm mode, or waits in the hook with `--park` (`--timeout <secs>`). Inert for a session with neither role. |
704
+ | PostToolUse | `lobstah hook post-tool-use` | A trap's beat: liveness, activity, pushes, and its PR. Inert for a session with neither role. |
705
+ | SessionEnd | `lobstah hook session-end` | Signs a trap off and keeps its worktree. Inert for a session with neither role. |
706
+
707
+ Each command detects the session's role: the helm (a helm registration, or a
708
+ session opted in with `LOBSTAH_MAN=1` or `.lobstah-man`), a trap (a soaking
709
+ registration), or neither. The older commands stay as aliases and do the same:
710
+ `lobstah man brief`, `lobstah man haul`, `lobstah soak beat`, and
711
+ `lobstah stow --quiet`. Install the Stop hook from the project you'll run the
712
+ lobstah man in:
681
713
 
682
714
  ```bash
683
715
  # Easiest: the plugin ships the hooks + the man and trap skills, no settings
@@ -694,10 +726,26 @@ Idempotent, and it only appends to `hooks.Stop` — existing hooks and settings
694
726
  are preserved verbatim. What it writes:
695
727
 
696
728
  ```json
697
- { "hooks": { "Stop": [{ "hooks": [{ "type": "command", "command": "lobstah man haul", "timeout": 14400 }] }] } }
729
+ { "hooks": { "Stop": [{ "hooks": [{ "type": "command", "command": "lobstah hook stop", "timeout": 14400 }] }] } }
698
730
  ```
699
731
 
700
- `haul` applies to a signed-on helm or trap, or a session opted in with
732
+ **A trap's park renews.** When a trap parks in the Stop hook (park mode:
733
+ `--park`, `[helm].park = "block"`, or the default for a Codex trap) and the
734
+ park reaches its `--timeout` with nothing to act on, the hook blocks the stop
735
+ with one instruction: park again, by ending the turn. The next Stop hook
736
+ parks again. Both harnesses take this as a Stop hook's
737
+ `{"decision":"block","reason":…}` output. The hook stops renewing:
738
+
739
+ - when the trap is stowed or its registration is gone (silently);
740
+ - when the daemon is unreachable (no heartbeat in the last 90 s);
741
+ - after 3 parks in a row that each returned within 5 seconds.
742
+
743
+ In the last two cases it prints a `systemMessage` that says why and how to
744
+ park again. A trap in arm mode (the Claude Code default) needs no renewal:
745
+ its background `soak --wait` is re-armed at each turn end.
746
+
747
+ A Stop hook that runs `lobstah man haul` already counts as installed.
748
+ The Stop hook applies to a signed-on helm or trap, or a session opted in with
701
749
  `LOBSTAH_MAN=1` or `.lobstah-man`. Queued dispatches count as work in flight.
702
750
 
703
751
  **Delivery guarantee.** Attention wakes are at-least-once with backoff. An
@@ -851,8 +899,9 @@ lobstah soak --wait # hookless sessions: listen in the foreground
851
899
  lobstah soak --link <url> # store this session's link for the glass's ↗ open button
852
900
  lobstah focus <trap> # focus a live trap from the CLI
853
901
  lobstah stow # sign off; an open catch requeues, unread
854
- # messages bounce back to the helm; removes
855
- # the worktree when soak created it
902
+ # messages wait [soak].signOffGraceSecs for a
903
+ # re-soak, then bounce back to the helm;
904
+ # removes the worktree when soak created it
856
905
  lobstah stow --keep # sign off and keep the worktree
857
906
  lobstah soak --name amber-gull # choose or change this trap's two-word name
858
907
  ```
@@ -914,6 +963,29 @@ upstream: on some remote branch);
914
963
  otherwise it prints `branchKept: <branch> (<reason>)`. A deleted branch
915
964
  prints as `branchDeleted:`. `stow --wt <id>` follows the same rules. The
916
965
  SessionEnd hook (`lobstah stow --quiet`) signs off and keeps the worktree.
966
+
967
+ **Only the trap's own session signs it off from a hook.** `lobstah hook
968
+ session-end` (and `stow --quiet`) stows a trap only when the ending session
969
+ is the one registered as the trap. Another session in the same worktree (a
970
+ second window, a child session) ending leaves the trap signed on. The
971
+ PostToolUse and SessionStart hooks never beat or adopt a trap for a session
972
+ that is not the trap's.
973
+
974
+ **A sign-off holds the address for a while.** A session that restarts or
975
+ compacts can end (SessionEnd stows it) and sign on again a minute later.
976
+ SessionEnd cannot tell that from an exit: Claude Code's `reason` is `clear`,
977
+ `resume`, `logout`, `prompt_input_exit`, or `other`, and Codex's is always
978
+ `other`. So for `[soak].signOffGraceSecs` (default 600) after any sign-off:
979
+
980
+ - unread messages stay in the trap's inbox, and `send wt:<trap>` queues
981
+ more with a warning instead of refusing;
982
+ - work addressed to the trap raises no `bait-orphaned` notice;
983
+ - a re-soak of the same worktree releases the hold, and the messages deliver
984
+ at its next park.
985
+
986
+ After the grace, the daemon bounces the held messages to the helm
987
+ (`message-bounced`), and `bait-orphaned` is raised as before. A stow that
988
+ removes the worktree, or a grace of `0`, bounces at once.
917
989
  Releasing a claim whose last report is `done` or `failed` moves it to `done/`;
918
990
  only an unfinished catch requeues (a cancelled catch finalizes as failed).
919
991
 
@@ -1011,7 +1083,7 @@ The output holds a one-time ticket and the command that starts the session in
1011
1083
  the repo's primary checkout:
1012
1084
 
1013
1085
  ```bash
1014
- cd <repo> && CLAUDE_CODE_DISABLE_TERMINAL_TITLE=1 claude "/lobstah:soak --ticket <ticket>"
1086
+ cd <repo> && CLAUDE_CODE_DISABLE_TERMINAL_TITLE=1 claude "/lobstah:trap soak --ticket <ticket>"
1015
1087
  cd <repo> && codex '$lobstah:trap soak --ticket <ticket>'
1016
1088
  ```
1017
1089
 
@@ -1065,6 +1137,28 @@ redeemed or withdrawn. The glass sends it only to a page on this machine's
1065
1137
  own glass address, only on the starting card, and no notice or log carries
1066
1138
  it.
1067
1139
 
1140
+ ### The sign-on title
1141
+
1142
+ Sign-on asks the session to apply a title: `lobstah soak` prints
1143
+ `step: Apply this title: <title>` (the trap's name). The session applies it
1144
+ with its harness's title tool when there is one (`set_session_title` in the
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.
1161
+
1068
1162
  ### The terminal tab name
1069
1163
 
1070
1164
  At sign-on, soak names the session's terminal tab after the trap. It finds the
@@ -298,6 +298,41 @@ session id's UUID version (v7 codex, v4 claude), else the descriptor.
298
298
  | `fail` | Broken configuration or missing requirement — fix before relying on lobstah. |
299
299
  | `skip` | The check does not apply on this host (e.g. no harness plugin installed). Never fails the run. |
300
300
 
301
+ ### Hook readiness
302
+
303
+ Parks and wakes run through the plugin's hooks, so doctor checks them. It
304
+ prints one `hooks claude` and one `hooks codex` row for each harness whose
305
+ lobstah plugin is installed (`skip` otherwise). The row starts with the
306
+ readiness of each role: the helm needs `Stop` and `SessionStart`; a trap
307
+ needs all four (`helm ready, trap not ready (needs PostToolUse)`). Then, for
308
+ each hook (`Stop`, `SessionStart`, `PostToolUse`, `SessionEnd`), it says:
309
+
310
+ - **installed**: the plugin's `hooks/hooks.json` declares it (`missing`
311
+ otherwise), and whether the harness has it turned off (`installed but
312
+ disabled`);
313
+ - **trusted**: Codex runs a plugin hook only after the user trusts it in
314
+ `/hooks`, and records trust in `config.toml` as
315
+ `[hooks.state."lobstah@lobstah:hooks/hooks.json:<event>:<group>:<handler>"]`
316
+ with a `trusted_hash`, a hash of the hook's definition. The row says
317
+ `trusted` when that hash matches the hook as the plugin declares it now,
318
+ `changed since trusted` when the hook changed after it was trusted (a
319
+ plugin update; Codex asks again), and `untrusted` without an entry. Claude
320
+ Code has no per-hook trust: `trust n/a`;
321
+ - **last run**: when lobstah last saw the hook run (`never run` when it has
322
+ not). Each hook run stamps `~/.lobstah/hook-runs.json` (`<harness>:<event>`
323
+ → time; a hook that runs on every tool call stamps at most every 30 s).
324
+
325
+ A hook that is missing, untrusted, or off makes the row `fail`, and the row
326
+ ends with the step to take, for example `In Codex, open /hooks and trust
327
+ lobstah's Stop and SessionStart hooks.` Doctor reads the harness config and
328
+ never writes it.
329
+
330
+ A `listeners` row follows: for each signed-on helm, whether it listens (a
331
+ live `man wait`, or a park in the Stop hook), and for each trap whether it is
332
+ `parked`, `working a catch`, or `not listening`. A helm or trap that is not
333
+ listening makes the row `warn`: no automatic wake reaches it until it parks
334
+ again.
335
+
301
336
  ## Watch contract
302
337
 
303
338
  A **watch** is a standing outbound poll on something external (a ume review
@@ -354,7 +389,7 @@ evidence).
354
389
  | `draft` | Draft flipped (`value`). Evidence only. |
355
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. |
356
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. |
357
- | 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. |
358
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. |
359
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. |
360
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`. |
@@ -431,11 +466,14 @@ worktree is refused (the session lock); a stale one is adopted.
431
466
  | Word | Meaning |
432
467
  | ---- | ------- |
433
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. |
434
- | `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 bounce to the helm. 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 and keeps the worktree. |
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
+ | `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
+ | 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`). |
435
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. |
436
473
  | message | `send wt:<trap> "<text>"` — a conversational continuation, not work: no branch, no catch, no report obligation. Delivered before bait at the trap's next park, stamped with its sender (`helm` / `session:<id>` / `terminal`); undeliverable messages bounce to the helm as notices. |
437
474
  | catch | The active dispatch a trap claimed (`claim.json`, `by: wt:<id>`). One catch per trap; one active item per worktree. The daemon never spawns or restarts it — the session's reports are its liveness. |
438
- | beat | `lobstah soak beat`, run by the plugin's post-tool hook after every tool call. It resolves the trap from the working directory, else from the session id (files only: no git, no network), refreshes the trap's beat (`soaking/<trap>.beat`, separate from the registration), and writes the claimed catch's [activity](#activity). Throttled to one per 30 seconds per trap. Inert in a session that is not soaking, or with `[soak].beat = false`. Always exits 0; errors go to `logs/beat.log`. |
475
+ | hook command | `lobstah hook session-start\|stop\|post-tool-use\|session-end`: the command each plugin hook runs. Each detects the session's role (helm, trap, or neither) and runs what `man brief`, `man haul`, `soak beat`, and `stow --quiet` run; those stay as aliases. Stop, PostToolUse, and SessionEnd are inert for a session with neither role; SessionStart announces the session id to every session. |
476
+ | beat | `lobstah soak beat` (`lobstah hook post-tool-use`), run by the plugin's post-tool hook after every tool call. It resolves the trap from the working directory, else from the session id (files only: no git, no network), refreshes the trap's beat (`soaking/<trap>.beat`, separate from the registration), and writes the claimed catch's [activity](#activity). Throttled to one per 30 seconds per trap. Inert in a session that is not soaking, or with `[soak].beat = false`. Always exits 0; errors go to `logs/beat.log`. |
439
477
  | ghost trap | A registration whose heartbeat **and** beat lapsed past `[soak].ttlSecs` **after having parked at least once**. The sweep removes the registration, requeues only unfinished catches, finalizes done/failed in `done/`, and posts a `trap-ghosted` notice. It removes a soak-created checkout only when clean and pushed to its upstream; dirty, unpushed, no-upstream, or unreadable checkouts stay, protected from later culls too. The notice includes path, branch, modified-file and unpushed-commit counts, or `unknown` when unavailable. A kept anchor preserves the address on re-soaking. A fresh report or beat keeps a working session out of the sweep; a fresh beat also holds the session lock. A catch whose last report is `paused` keeps its trap until the pause expires ([Waiting on](#waiting-on)). A daemon tick gap longer than the soak TTL starts one full TTL of resume grace before any ghost sweep. |
440
478
  | defective enlistment | A stale registration that **never parked** — signed on but never listened (usually no Stop hook). Not swept: the helm gets a `trap-defective` notice with the remedy (`soak --wait`), and the registration stays so the address keeps protecting its work. |
441
479
  | reserved trap | A trap `lobstah trap reserve` created before any session signs on as it: a name, a `wt:` id, and a one-time ticket, shown as `starting`. Work addressed to it waits. A session redeems the ticket with `soak --ticket` (or `LOBSTAH_TRAP_TICKET`) and signs on as that trap. Unredeemed past its deadline, it fails with a `trap-start-failed` notice and its work stays queued. `stow --wt <name>` withdraws it. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lobstah",
3
- "version": "0.6.4",
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",