Skip to content

Needs attention: keep a list of what needs a person and notify on the Mac - #67

Merged
tomkaczocha merged 10 commits into
mainfrom
t3/factory-issue-58
Oct 16, 2026
Merged

tomkaczocha merged 10 commits into
mainfrom
t3/factory-issue-58

Conversation

@rawphp

@rawphp rawphp commented Oct 16, 2026 •

Copy link
Copy Markdown

Closes #58

Contract

  • done: An Attention service (apps/server/src/factory/attention.ts) keeps the open items (id, kind, repo, issue or none, run or none, message, first-seen) in <base>/factory/attention.json. Raised by: every attention event the pass writes, a run that goes stuck or needs-info, a machine config that fails to load, a pass that errors three times in a row. Cleared by: the issue being claimed again (a started claim, or one whose launch failed), a run-bound attention item whose run is no longer unfinished, the config loading, a pass that succeeds, the repo leaving the config. A new item posts one macOS notification through process.ts (osascript -e <fixed script> <title> <message>, on run argv), never again while it stays open, also across a restart. notify: false in the machine config keeps the list and posts nothing. A missing (exit 127) or hung (timed out) osascript logs once and stops trying. Attention.list() is a service method; FactoryLayer now provides Attention next to FactoryLoop.
  • out: Factory HTTP API for status, runs and control #27's API and Factory page in the T3 web app #28's page. Stop/abandon wiring: this fork has no stop or abandon path yet, so Attention.clear(matches) is the method those issues call. Click-to-open: not done. AppleScript's display notification carries no click action, so clicking the notification opens nothing for every item; the title names repo#issue (or factory) and the item holds the issue number for Factory page in the T3 web app #28's page. A click-to-open needs a different notifier (e.g. terminal-notifier -open), which the issue's osascript criterion rules out.
  • risk: none
  • test: vp test run apps/server/src/factory → pass (34 files, 1156 tests, in the gate run)

Settled by this run (not in the issue text)

  • A pass "errors" when its report has error: lines or it died or timed out; it "succeeds" when its report has 0 errors. The pass-errors item's message carries the last error line.
  • The consecutive-error count is in memory only; the open item itself persists across a restart.
  • stuck and needs-info items stay until the issue is claimed again; they are about finished runs, so the end-of-pass "run no longer unfinished" clear applies only to attention-kind items.

Acceptance criteria

  • Attention service with id, kind, repo, issue, run, message, first-seen — attention.test.ts "raise stores the item and list returns it"; attention.ts
  • raised by every ledger attention event — pass.test.ts "an ATTENTION line raises an attention item whose message is the line"
  • raised by a run entering stuck / needs-info — pass.test.ts "a run that goes stuck raises a stuck item", "a run that goes needs-info raises a needs-info item"
  • raised by a machine config that fails to load — index.test.ts "an invalid machine config raises a config item and notifies once"
  • raised by a pass that errors three times in a row — attention.test.ts passEnded tests; index.test.ts "three failing passes raise pass-errors"
  • clear: run leaves stuck/needs-info (claimed again) — pass.test.ts "claiming an issue again clears every item of that issue and no other", "a claim whose launch fails clears the issue's earlier items and keeps the new one"
  • clear: run stopped or abandoned — attention.test.ts "clear by run removes the run's items and the file reflects it" (service method; wiring lands with the stop/abandon issues); pass.test.ts "a run-bound attention item whose run is no longer unfinished is cleared at the pass end"
  • clear: config loads — index.test.ts "a valid machine config clears the config item"
  • clear: a pass succeeds — attention.test.ts "a success clears pass-errors and resets the count"
  • clear: repo leaves the config — pass.test.ts "an item of a repo that is not in the machine config is cleared at the pass start; a machine item stays"
  • one notification per new item, names repo and issue or factory — attention.test.ts argv test (a/b#7, factory)
  • never notified again, including across a restart; items kept in attention.json — attention.test.ts "a second service over the same dir lists the item and raising it again posts nothing"; index.test.ts restart test
  • clicking opens the issue URL — not possible with osascript display notification; see out
  • osascript via process.ts, text as argv (on run argv), never in the source; quotes, backslashes and " & do shell script " — attention.test.ts "the text goes to osascript as argv and never into the script"
  • notify: false turns the notification off and keeps the list — config.test.ts notify tests; attention.test.ts "setNotify(false) posts nothing"; index.test.ts notify-false test
  • missing osascript logs once, does not fail the pass — attention.test.ts exit-127 test; timed-out test
  • Attention.list() is a service method — attention.ts; index.ts factoryRuntime merges the layer

Gate

  • factory/scripts/check.sh → exit 0 (.factory/gate-1.log: seam check, 34 test files / 1156 tests, t3 typecheck, @t3tools/web typecheck)

Models

  • maker: claude-sonnet-5-5 (host subagent)
  • checker: claude-opus-5-5 (host subagents: correctness, reliability, testing)

Review

  • cycles: 1
  • fixed: correctness-1 (a claim whose launch failed did not clear the issue's earlier items), reliability-1 (a hung osascript cost its timeout plus a 10 s grace on every new item; now 1 s grace and a timeout marks the notifier down), reliability-2 (a failed write left the temp file behind)
  • mutation pass on 85a781742: 12 mutants, 12 killed
  • leftovers (nits, not done): no test for the write-failure warning branch; the "no items list" and non-ENOENT read branches untested; one near-tautological assert.include(..., "a") in attention.test.ts; a no-match clear assertion that cannot distinguish an early return; the index.test.ts pass-errors loop leans on a 30 s bound

Notes for the factory

  • The worktree the factory launched was at e4bcce9f6, behind origin/main; it was fast-forwarded to ff23fa2fd at Isolate before any read (ancestor, clean tree).
  • No labels created: all seven factory:* labels already existed on the repo.
  • No upstream file changed; factory/SEAMS untouched. The boundary test passes (attention.ts imports only factory-internal paths and effect).
  • Red→green proven on the fix pair: the three tests 5ecaffef9 adds fail against b7c59a509's product code and pass at the tip. The makers reported red for the other three pairs.

Lessons

  • A lookup that cds elsewhere moves the session's shell. Symptom: the next command's relative paths fail with "No such file" and the harness reports the working directory is no longer a worktree. Cause: a one-off cd /tmp && gh api ... in a shell whose working directory persists between calls. Default: never cd out of the worktree, even for a read of another repo; prefix every command with cd <worktree> && and give other tools absolute paths.

  • A red check in a pnpm monorepo runs in a detached worktree with symlinked node_modules. Symptom: a detached worktree at a test: commit cannot run vitest (no modules), and vp i there costs minutes. Default: git worktree add --detach .factory/red <test sha>, git checkout <parent> -- <product files>, symlink the root, app and packages/*/node_modules from the main worktree, run the filtered test files, then git worktree remove --force and prune. Extends lesson 16 to JS.

  • In a fork, gh pr create without --repo opens the PR on the parent repository. Symptom: the draft lands on the upstream project (here pingdotgg/t3code) and has to be closed with an apology. Cause: gh resolves the default repo to a fork's parent when origin is a fork. Default: always pass --repo <owner/repo> (the issue's repo) and --head <branch> to gh pr create, and check the printed URL's owner before reporting FACTORY-DONE.

Commits

  • 10955d104 test: the machine config has a notify key — Make slice 1 (red)
  • 8b3ea13f9 feat: the machine config has a notify key — Make slice 1
  • 3dbdaffca test: the Attention service keeps open items and notifies once — Make slice 2 (red)
  • 6c39b22e3 feat: an Attention service keeps open items and notifies once — Make slice 2
  • a6f544d1b test: the pass raises and clears attention items — Make slice 3 (red)
  • 3fcce22ff feat: the pass raises and clears attention items — Make slice 3
  • 07100e7b7 test: the runtime raises config and pass-errors items and keeps them across a restart — Make slice 4 (red)
  • b7c59a509 feat: the factory runtime raises config and pass-errors items — Make slice 4
  • 5ecaffef9 test: a failed launch clears earlier items and a hung notifier stops notifying — Check cycle 1 (red)
  • 85a781742 fix: a failed launch clears earlier items and a hung notifier stops notifying — Check cycle 1

Files

  • apps/server/src/factory/attention.ts — the service: items, attention.json, osascript notifier, pass-error count
  • apps/server/src/factory/attention.test.ts — service tests (restart, argv form, notify off, 127, timeout, clears, passEnded)
  • apps/server/src/factory/pass.ts — raise sites (attention line, stuck, needs-info), clears (claimed again, repo left config, run no longer unfinished); PassWorld.attention
  • apps/server/src/factory/pass.test.ts — the Fake world's attention recorder and the raise/clear tests
  • apps/server/src/factory/index.ts — Attention layer merged into factoryRuntime; config item raise/clear; setNotify; passEnded on every pass exit
  • apps/server/src/factory/index.test.ts — config item, restart, pass-errors, notify-false tests
  • apps/server/src/factory/config.ts — machine key notify (optional boolean, default true)
  • apps/server/src/factory/config.test.ts — notify parse tests

rawphp added 10 commits October 16, 2026 16:23
The notify default, notify: false and a non-boolean notify are not handled yet, so
three config tests fail.

Issue: #58
notify turns the macOS notification of attention items on or off, on by default.

Issue: #58
attention.ts does not exist yet, so the whole file fails to load.

Issue: #58
Items are kept in attention.json, a new one posts one osascript notification with its
text in argv, and the third erroring pass in a row raises pass-errors.

Issue: #58
The fake world has an attention seam the pass does not use yet, so the six new tests fail.

Issue: #58
…across a restart

index.ts does not provide Attention or put it on the world yet, so these tests and the
tests that run a pass fail.

Issue: #58
Attention lines, stuck and needs-info runs raise items; a claim, a repo leaving the config
and a finished run clear them. The live world is wired in the next commit.

Issue: #58
The runtime provides Attention, raises a config item when the machine config does not load,
clears it when it does, applies notify, and reports how each pass ended.

Issue: #58
…notifying

Fails: a claim whose launch ends stuck leaves the issue's earlier stuck item
open beside the new one; a timed-out osascript is logged per item instead of
once and every later item runs it again; the osascript run carries no
termGraceMs.

Issue: #58
…otifying

A claim whose launch ends stuck now clears the issue's earlier items as a
started claim does. A timed-out osascript turns notifying off until restart
and is killed after a 1 s grace. A failed write or fsync of attention.json
no longer leaves its temp file behind.

Issue: #58
@rawphp
rawphp marked this pull request as ready for review October 16, 2026 06:57
@rawphp

rawphp commented Oct 16, 2026

Copy link
Copy Markdown
Author

Factory gate: passed (attempt 1, run by the factory in a fresh checkout of the pull request's head 85a7817 merged with the base branch tip ff23fa2)

Command Exit Time
factory/scripts/check.sh 0 38.8 s

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Needs attention: keep a list of what needs a person and notify on the Mac

2 participants