lobstah 0.7.0 → 0.7.2

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
@@ -1290,7 +1290,7 @@ var DEFAULT_HELM = {
1290
1290
  armGraceSecs: 5
1291
1291
  };
1292
1292
  var DEFAULT_GLASS = { port: 4949 };
1293
- var DEFAULT_WATCH = { maxForksPerCycle: 3, autoRepair: true, conflicts: true, checks: true, maxRepairsPerPr: 2, maxRepairsWithoutProgress: 2, repairSettleSecs: 600, repairTrapWaitSecs: 600 };
1293
+ var DEFAULT_WATCH = { maxForksPerCycle: 3, autoRepair: true, conflicts: true, checks: true, maxRepairsPerPr: 2, maxRepairsWithoutProgress: 2, repairSettleSecs: 600, repairCooldownSecs: 0, repairTrapWaitSecs: 600 };
1294
1294
  var DEFAULT_LIMITS = {
1295
1295
  maxConcurrent: 2,
1296
1296
  choreConcurrent: 1,
@@ -1318,6 +1318,10 @@ function expandHome(p) {
1318
1318
  function loadConfig() {
1319
1319
  const file = configPath();
1320
1320
  const raw = fs7.existsSync(file) ? parse(fs7.readFileSync(file, "utf8")) : {};
1321
+ const watch = { ...DEFAULT_WATCH, ...raw.watch ?? {} };
1322
+ if (typeof watch.repairCooldownSecs !== "number" || !Number.isFinite(watch.repairCooldownSecs) || watch.repairCooldownSecs < 0) {
1323
+ throw new Error(`[watch].repairCooldownSecs must be a non-negative number in ${configPath()}`);
1324
+ }
1321
1325
  const readySettleSecs = raw.readySettleSecs ?? 600;
1322
1326
  if (typeof readySettleSecs !== "number" || !Number.isFinite(readySettleSecs) || readySettleSecs < 0) {
1323
1327
  throw new Error(`readySettleSecs must be a non-negative number in ${configPath()}`);
@@ -1353,7 +1357,7 @@ function loadConfig() {
1353
1357
  soak: parseSoak(raw.soak),
1354
1358
  helm: { ...DEFAULT_HELM, ...raw.helm ?? {} },
1355
1359
  glass: { ...DEFAULT_GLASS, ...raw.glass ?? {} },
1356
- watch: { ...DEFAULT_WATCH, ...raw.watch ?? {} },
1360
+ watch,
1357
1361
  grounds,
1358
1362
  notifyCommand: raw.notifyCommand ? String(raw.notifyCommand) : void 0,
1359
1363
  notifyVerbs: Array.isArray(raw.notifyVerbs) ? raw.notifyVerbs.map(String) : void 0,
@@ -137,6 +137,7 @@ send a continuation.
137
137
  | `checks` | `true` | Repair failed current checks when `autoRepair` is on. Set `false` to show check attention without a repair. |
138
138
  | `maxRepairsPerPr` | `2` | Maximum repair follow-ups for one PR head SHA. When the limit is reached and the issue remains, `pr:conflict`, `pr:checks`, or `pr:review` attention names the limit. A repair that waits is not an attempt. Below this limit, each failing check gets at most one repair round per PR and head SHA: a check that had its round and still fails on the same head waits for a new commit, with `repair.status: waiting` and `heldBy: repaired`, and raises `pr:checks` attention. A CI-fix continuation from `lobstah pick` (`autoRepair = false`) follows the same rule. Human gates (`[repos.<key>].humanGateChecks`, `report --human-gate`) get no round. |
139
139
  | `repairSettleSecs` | `600` | A repair is queued only after the PR's head, its base branch's head, and its failing checks have been unchanged for this many seconds. A conflict repair also waits this many seconds after the PR's latest approval. Until then the PR record shows `repair.status: waiting` with `heldBy: settle` and `until`. |
140
+ | `repairCooldownSecs` | `0` (off) | Minimum seconds between daemon repairs of the same PR, measured from the end of its last repair (conflict, checks or review), across head changes. During the window the PR record and tend show `repair waits: cooldown until <time>`; no attention wake is raised. `lobstah watch release <key>` (or `--all`) also skips the remaining cooldown once, while still resetting stopped repairs; settle time and repair caps still apply. A repair the helm dispatches by hand is never delayed by this setting. |
140
141
  | `maxRepairsWithoutProgress` | `2` | Maximum repairs in a row on one PR that end `done` while the PR stays at the head the repair pushed and does not merge. Past the cap, lobstah stops repairing that PR: the PR record shows `repair.status: gave-up`, `pr:conflict` (or `pr:checks`, `pr:review`) attention names the cap, and one `repair-stopped` notice is posted. The count resets when the PR merges or closes, when a push that is not the repair's moves the head, and on `lobstah watch release <key>` (or `--all`). |
141
142
  | `repairTrapWaitSecs` | `600` | A daemon repair chore addressed to the PR-owning trap waits this many seconds for that trap. If still queued, it becomes headless. A person's addressed work never falls back. |
142
143
 
package/docs/man.md CHANGED
@@ -552,7 +552,9 @@ the helm files to keep.
552
552
  headless dispatch's id, nothing for the helm), its age, and `acked`. A
553
553
  report opens on its own page, `/report/<key>`, in a new tab: from its card
554
554
  or row, a lob, the desktop pet, or its dispatch's modal. The page renders
555
- once and does not refresh; opening it does not ack the report.
555
+ once and does not refresh. Showing it acks the report (`by: glass`):
556
+ state only, with no wake. Fetching it through the CLI or the API does not
557
+ ack it.
556
558
  `#report/<key>` goes to that page. The page shows headings, lists,
557
559
  tables, fenced code, links (in a new tab), bold, italics, and images.
558
560
  Raw HTML in the markdown shows as text. The glass serves the markdown and
@@ -571,7 +573,8 @@ the helm files to keep.
571
573
  `lobstah attention ack <key> --by pet`. The pet's Acknowledge menu entry
572
574
  acks without opening, and `lobstah attention ack <key>` acks a report
573
575
  whether or not `report` is in `attentionKinds`. Opening a report in the
574
- glass does not ack it. A report filed by a follow-up dispatch acks the report
576
+ glass acks it, and `man tend`'s `viewed` column shows when; refiling it
577
+ stands it again until it is opened again. A report filed by a follow-up dispatch acks the report
575
578
  of each dispatch before it in the chain.
576
579
  - **Cull.** `lobstah cull` removes a dispatch's report with the rest of its
577
580
  state. A helm report is culled when it is older than the retention window,
package/docs/pickup.md CHANGED
@@ -282,7 +282,7 @@ A PR behind its base splits deterministically:
282
282
  | Condition | Action |
283
283
  |---|---|
284
284
  | Behind, no conflict | Update the branch through the forge API, re-enter the gate next tick |
285
- | Behind, real conflict | Write a rebase chore — brief: rebase onto base, resolve, push to the PR's branch — and re-enter the gate when it completes |
285
+ | Behind, real conflict | Write a rebase chore — resolve, push to the PR's branch, report `done` — then re-enter the gate, which re-requests stale review once per head |
286
286
 
287
287
  Rebase chores go through the **chore lane** (`~/.lobstah/chores/`, defined in
288
288
  the [design's queue contract](design.md#queue-contract)), never the primary queue. Same descriptor schema,
@@ -293,6 +293,12 @@ queue, and `lobstah ls` stays a list of things a human asked for.
293
293
  Chores report to no tracker. The merge loop consumes the chore's status file
294
294
  directly, holds its own PR-to-chore mapping, and bounds the attempt at one: a
295
295
  failed rebase comments on the PR, applies the `needs-human` label, and stops.
296
+ A rebase chore reports `done` after its push, even on a reviewed PR; the gate
297
+ re-requests stale review once per head. Older chores parked on review are
298
+ treated as finished by the gate and finish their own wait when the PR merges
299
+ or closes. They hold no chore slot while parked. A reported push that leaves
300
+ the original head unchanged and still conflicting fails the one attempt,
301
+ whether the report was `done` or a legacy review pause.
296
302
  A rebase chore's descriptor names its PR (`pr`): the runner pushes no branch
297
303
  and opens no PR for it. The brief tells the worker to push to the PR's head
298
304
  branch only and, on a non-fast-forward rejection, to fetch, rebase onto the
@@ -396,7 +396,7 @@ evidence).
396
396
  | descriptor `pr` | `{ url, headRefName?, headSha? }`: the existing PR a dispatch works on. A PR repair and a pickup rebase chore carry it. The runner pushes no branch and opens no PR for such a dispatch; its worker pushes to the PR's head branch. |
397
397
  | push rule | What a repair or rebase brief tells its worker: push only to the PR's head branch; on a non-fast-forward rejection, fetch, rebase the commits onto the moved head again, and push with `--force-with-lease` on the head just fetched, at most three times; a hook failure from a real test or type error is not retried; when it cannot push, report `failed "push rejected: <rejection text>; moved head <sha>"` and leave the PR as it was. That report marks the PR's repair `blocked` at the moved head and posts a `push-failed` notice. |
398
398
  | checks unknown | Without `Checks: read`, the check re-reads the PR without `statusCheckRollup`: the PR state is recorded, `checks.unknown` is `no permission`, and the check's output carries the permission `error`. `pr:ready` never stands on unknown checks. |
399
- | PR record | `~/.lobstah/prs/<owner>__<repo>__<n>.json` — the PR's latest observation keyed by the PR, not by a dispatch: the evidence `pr` object (with `title`, read on every check; a title change is not a state change) plus `key`, `repo` (`<owner>/<repo>`), `dispatches` (the ids whose watch observed it; empty for a human's or a culled PR), and `firstSeenAt` (the time of the first observation; written once, never rewritten). `firstSeenAt`, then the PR number, is the order of every PR list. A record from before `firstSeenAt` existed sorts by number at the earliest `firstSeenAt` in the set, and its next observation writes that time as its `firstSeenAt`. **Owner:** `packages/core/src/prs.ts` (`upsertPr`, `readPrs`); the one writer is the preset's observation path (`observePr`), on every observation, man-owned or dispatch-owned — a dispatch-owned one also stamps that dispatch's evidence, which stays the per-dispatch view. Tend's `pr:*` kinds and `stack-ready` aggregation and quiet stacked `pr:ready` items, the glass PRs tab and stacks, the merged/closed notice, and PR acks read records first and fall back to dispatch evidence only for a PR with no record yet. `cull` removes records merged or closed longer than its window, never open ones. |
399
+ | PR record | `~/.lobstah/prs/<owner>__<repo>__<n>.json` — the PR's latest observation keyed by the PR, not by a dispatch: the evidence `pr` object (with `title`, read on every check; a title change is not a state change) plus `key`, `repo` (`<owner>/<repo>`), `dispatches` (the ids whose watch observed it; empty for a human's or a culled PR), and `firstSeenAt` (the time of the first observation; written once, never rewritten). `firstSeenAt`, then the PR number, is the order of every PR list. A record from before `firstSeenAt` existed sorts by number at the earliest `firstSeenAt` in the set, and its next observation writes that time as its `firstSeenAt`. **Owner:** `packages/core/src/prs.ts` (`upsertPr`, `readPrs`); the one writer is the preset's observation path (`observePr`), on every observation, man-owned or dispatch-owned — a dispatch-owned one also stamps that dispatch's evidence, which stays the per-dispatch view. Tend's `pr:*` kinds and `stack-ready` aggregation (member PR attention is contained in one stack item), the glass PRs tab and stacks, the merged/closed notice, and PR acks read records first and fall back to dispatch evidence only for a PR with no record yet. `cull` removes records merged or closed longer than its window, never open ones. |
400
400
 
401
401
  Every event carries `headSha`. A dispatch-owned PR watch records work events,
402
402
  then the repair planner decides whether to follow up. One repair runs per PR
@@ -481,7 +481,7 @@ worktree is refused (the session lock); a stale one is adopted.
481
481
  | protected ref | `refs/lobstah/traps/<trapId>` in the trap's repository: the trap's last HEAD, written at sign-on and before stow, a ghost sweep, or `cull` removes the checkout. It is not a branch, so worktree removal and branch cleanup leave it and its commit in place. A missing checkout is recreated from it. A trap checkout whose revision cannot be written there is not removed by a ghost sweep or cull. |
482
482
  | trap request | A trap the human asked for from the glass's **+ New trap** button: a `trap-request` request in `requests/<id>.json` with a repo and a harness. It wakes the helm as a `trap-request` event; the helm starts it with `man throw --new --request <id>`, which takes the request's repo and harness and closes it. |
483
483
  | notice | The helm's attention channel for non-status events (`~/.lobstah/notices/`): reservations (`trap-starting`), reservations that did not start (`trap-start-failed`), trap requests from the glass (`trap-request`), sign-ons, a trap free to take work (`trap-available`), a batch throw settled (`trap-batch`), sign-offs, ghosts, defective enlistments, orphaned work, bounced messages, PRs merged or closed, watches held over the fork cap (`watch-held`), failing (`watch-failing`), and recovered (`watch-recovered`), free-space holds (`disk-held`, `disk-cleared`), worktrees released after their PR merged (`worktree-released`, one per cull pass), a repair or rebase that could not push to its PR's branch (`push-failed`), repairs stopped on a PR that made no progress (`repair-stopped`), and a human's answer to a decision (`decision-answer`; its ref is the request id in `~/.lobstah/requests/`). A trap leaves the registry only through a `trap-stowed` or `trap-ghosted` notice — the end-state is always explicit. Consumed by `man wait`/the park. A trap's start wakes once, with `trap-available` when it is listening; `trap-starting`, `trap-signed-on`, `trap-stowed`, `trap-listening` (older homes), a ghost of an idle trap, and a batch member's `trap-available` are quiet: consumed without waking anyone, shown in tend, the glass, and (sign-offs and idle ghosts) the digest's `traps` table. A batch throw wakes once, with `trap-batch` when every throw settled: counts, and the names that failed. tend always shows the recent tail. |
484
- | stack-ready notice | One wake for an all-ready PR stack, with the PRs in bottom-first merge order. Stacked per-PR ready items remain quiet; unchanged heads do not re-notify as the bottom PRs merge. |
484
+ | stack-ready notice | One wake for an all-ready PR stack, with the PRs in bottom-first merge order. One persistent item contains member readiness and human problems, without member lobsters; growth replaces its notice, and bottom-first merges keep its identity and ack without another wake. Its link follows the current top PR; legacy duplicates collapse silently. |
485
485
 
486
486
  Delivery routes by ownership, same as watches: a continuation for a chain
487
487
  claimed by a live trap is addressed back to that trap and stays sticky.
@@ -543,9 +543,9 @@ never until someone acknowledges it.
543
543
  | `pr:review` | An open PR has unresolved review questions, or requested changes that lobstah cannot repair, has exhausted, or is configured not to repair. | Every thread resolved and no changes requested, or merged / closed. |
544
544
  | `pr:checks` | An open PR has a failed latest check, and lobstah cannot repair it, has exhausted attempts, or is configured not to repair. | Green on the head, or merged / closed. |
545
545
  | `pr:conflict` | An open PR conflicts with its base, and lobstah cannot repair it, has exhausted attempts, or is configured not to repair. | The merge state leaves `DIRTY`, or merged / closed. |
546
- | `pr:ready` | An open, non-draft PR has no review condition, a mergeable state (`CLEAN`, `HAS_HOOKS`, or `UNSTABLE`), and no failed, pending, or unknown latest checks. It is approved or has at least one check, continuously on the same head for `readySettleSecs` (default 600; `0` is immediate). Attention reads re-evaluate expiry without requiring a new forge event. In a trunk-rooted same-repo stack of two or more non-fork PRs, each ready item stays visible but quiet; the stack wakes once. | Merged or closed, or the ready conditions stop holding; any not-ready observation or new head resets settling. |
547
- | `stack-ready` | Every open member of a trunk-rooted same-repo base/head chain meets the identical `pr:ready` rule, including settling. Enabled with `pr:ready` (or explicitly with `stack-ready`). One notice and walking item per ready epoch, naming PRs bottom to top in merge order. Tend, digest, and the existing glass group also show partial readiness. Existing `pr:` watches discover missing links at their normal cadence; `lobstah watch add pr:<owner>/<repo>#<n>` follows a stack from any member. | Any member becomes unready or gets a new head; all ready again wakes once. Bottom-first merges and retargeting do not wake again for unchanged surviving heads. |
548
- | `report` | A filed report (`report --report`, `man file`) has no ack for this filing. Opt-in. Key `report:<lane>:<uuid>` or `report:helm:<grounds>:<rid>`. | Not cleared: it stays listed until culled. `lobstah attention ack <key>` acks it, and a newer report in the same chain acks the older; an acked report no longer walks. |
546
+ | `pr:ready` | An open, non-draft PR has no review condition, a mergeable state (`CLEAN`, `HAS_HOOKS`, or `UNSTABLE`), and no failed, pending, or unknown latest checks. It is approved or has at least one check, continuously on the same head for `readySettleSecs` (default 600; `0` is immediate). Attention reads re-evaluate expiry without requiring a new forge event. In a trunk-rooted same-repo stack of two or more non-fork PRs, member attention appears only inside the one stack item; the stack wakes once. | Merged or closed, or the ready conditions stop holding; any not-ready observation or new head resets settling. |
547
+ | `stack-ready` | Every open member of a trunk-rooted same-repo base/head chain meets the identical `pr:ready` rule, including settling. Enabled with `pr:ready` (or explicitly with `stack-ready`). One persistent item per stack, born at the bottom PR and carried by surviving membership through growth, restacks and merges. Member ready/watch items and human problems are grouped inside it, not separate lobsters; Approval Gate failures never create an item. Links open the current top PR, with member links inside. Tend, digest, and the existing glass group also show partial readiness. Existing `pr:` watches discover missing links at their normal cadence; `lobstah watch add pr:<owner>/<repo>#<n>` follows a stack from any member. | Any member becomes unready or gets a new head; all ready again wakes once. Growth replaces the notice and wakes once when the larger stack is ready. Bottom-first merges keep the item and ack without a wake for unchanged surviving heads; a remaining lone PR returns to normal behavior. Legacy duplicates collapse without a wake. |
548
+ | `report` | A filed report (`report --report`, `man file`) has no ack for this filing. Opt-in. Key `report:<lane>:<uuid>` or `report:helm:<grounds>:<rid>`. | Not cleared: it stays listed until culled. Opening it on the glass acks it (`by: glass`, the first time kept; no wake, and a CLI or API read does not), `lobstah attention ack <key>` acks it, and a newer report in the same chain acks the older; an acked report no longer walks, and `man tend`'s `viewed` column shows when it was acked. Refiling it stands it again until it is opened again. |
549
549
 
550
550
  `pr:*` kinds read only the `pr:` watch's evidence — never a forge call —
551
551
  and carry `prUrl`, `number`, and the fields they derive from. Unconsumed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lobstah",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "Harness-agnostic, token-efficient supervision framework for coding agents",
5
5
  "license": "MIT",
6
6
  "author": "aequitas labs LLC",