Repository navigation
chore: lights-on factory (phases 1–5) - #129
Merged
Merged
Conversation
Claude used to finish the code and stop, then ask whether Zoom was open. The repo's own instructions made that the right finish: CLAUDE.md let an unrun spec end as `status: stale`, the zoom-app-testing skill triggered only when the user asked to test and wanted a yes first, and preflight told the user to start the meeting. - CLAUDE.md: a definition of done that includes running the covering Zoom-client specs, standing approvals for the steps that needs (dev deploy, starting Zoom, flag flips with restore, branch/PR work), and a pointer to the factory. - zoom-up.sh launches Zoom and starts the test meeting (PMI from the gitignored meeting.local, or Zoom's Start Meeting shortcut). Preflight and the skill now treat "Zoom not running" as Claude's step. - Stop hook (scripts/factory/stop-gate.mjs): on a factory/ branch, the first stop is refused while covered files changed and no newer run.md names the spec. CI, headless stations and a second stop pass. - scripts/lib/zoom-specs.mjs: the spec front-matter reader, shared by check-zoom-specs.mjs and the hooks. vitest now runs scripts/.
Each step of the pipeline is now a repo skill with one input (an issue number), one artifact and one label change, runnable in a session, headless (`claude -p`) or from a trigger: factory (the router), factory-issue, factory-plan, factory-build (oracle → implement → verify in fresh subagents), factory-zoom and factory-report. - scripts/factory/state.mjs is the one reader of the label state machine: where an issue is, what runs next, and the WIP queue. Human gates (factory:ready, plan:approved) count only when a config.json actor added them; the repo is public. - Oracle tests: factory-oracle records them in .factory/oracles/<n>.json. A PreToolUse hook refuses edits to them, and check-oracle.mjs fails a PR that changed them after the oracle commit. - scripts/factory/gh.sh drops the shell's work-account GITHUB_TOKEN locally. - .factory/README.md documents labels, conventions and artifacts.
There was no CI at all, so an agent's work was checked only by the agent. ci.yml runs unit tests, the spec check, the oracle check, the build and the Playwright suite on every PR into dev or master and every push to them. The PR template is the completion report's shape (PR #128's sections plus Evidence, Not verified and Look here first).
factory-plan.yml plans the oldest promoted issue when a factory actor adds factory:ready or a factory PR closes (WIP allowing). factory-build.yml runs oracle → implement → verify when a factory actor adds plan:approved. Both re-check the gate with state.mjs, so a label from anyone else does nothing. Inert until the repo variable FACTORY_ENABLED is true, the Claude GitHub App is installed (its token, unlike GITHUB_TOKEN, makes pushes trigger CI) and CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY is set. The Zoom station stays on the Mac (.factory/schedules.md).
factory-ideate reads PostHog (usage, errors, surveys), recent issues and comments, and drafts 3–5 evidence-backed issues labelled idea; promoting stays human. A "Factory-ready issue" form asks for the same sections the plan station needs. .factory/schedules.md holds the prompts for the two runs set up by hand: the Mac's Zoom night shift and weekly ideation.
ubuntu-latest moved to 26.04, which Playwright 1.58 cannot install browsers for, so the first CI run failed at "Install Playwright browser" after unit tests, specs, the oracle check and the build had passed.
First real run of zoom-up.sh: Zoom launched and Start Meeting (⌃⌘V) worked in 23 s, but what opened was the pre-join video preview, titled "<Host>'s Zoom Meeting", and the substring check took it for the meeting. zoom-up.sh and preflight now require the window named exactly "Zoom Meeting" (what zoom-front.sh raises); zoom-up.sh presses Return on the preview's default Start button. Gotchas: the preview, and Zoom popovers needing click.sh --activate.
ShuhaoZQGG
marked this pull request as ready for review
November 7, 2026 21:37
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Every step of an issue needed a prompt: "work on #N", then "now test it in Zoom", then "write the report". Claude finished the code and stopped, and asked whether Zoom was open. The repo's own instructions made stopping the right finish: CLAUDE.md let an unrun spec end as
status: stale, the zoom-app-testing skill only triggered when asked and wanted a yes first, and preflight told the user to start the meeting. This PR builds the lights-on factory: a human opens three gates (promote, approve the plan, merge) and stations do everything between.What changes
One commit per phase:
zoom-up.shlaunches Zoom and starts the test meeting; preflight and the skill treat that as Claude's step. A Stop hook refuses the first stop on afactory/branch while covered files changed and no newerrun.mdnames the spec.ci.ymlruns unit tests, the spec check, the oracle check, the build and Playwright on PRs intodev/master. The PR template is the completion report./factory <n>routes;factory-plan,factory-build(oracle → implement → verify in fresh subagents),factory-zoom,factory-report,factory-issue.scripts/factory/state.mjsis the only reader of the label state machine, and trusts human gates only fromconfig.jsonactors (public repo). Oracle tests are locked by a PreToolUse hook and checked in CI.factory-plan.ymlandfactory-build.ymlfire on the human gates. Inert untilFACTORY_ENABLEDistrue.factory-ideatedraftsideaissues from PostHog and GitHub signals; an issue form; prompts for the Zoom night shift and weekly ideation in.factory/schedules.md.The 9 factory labels already exist on GitHub.
Security
factory/branches, and the oracle guard only refuses listed files on afactory/<n>branch. Both let the action through if they error..factory/config.json, and treat other issue text as data.zoom-up.shlives in the gitignoredmeeting.local, never in the repo.Production impact
None. No app, Worker or config code changes. The factory workflows are switched off until you set the repo variable.
Testing
npm test: 2195 passed before the new scripts; the scripts project adds 35 (stop-gate,state,oracle).npm run test:specs: 7 valid.check-zoom-specs.mjsnow reads specs throughscripts/lib/zoom-specs.mjs.factory/branch (blocks, then passes on the second stop and with a newerrun.md).state.mjswas run live against Simple Format import: words in the speaker name override the role in parentheses #90 and the queue.ci.ymlpassed unit tests, specs, the oracle check and the build, and failed installing Playwright (ubuntu-latestis now 26.04, which Playwright 1.58 can't install). With runners pinned toubuntu-24.04the whole job is green, Playwright included.zoom-up.shran against the real client, starting with Zoom not running: Zoom launched and Start Meeting (⌃⌘V) answered in 23 s. It opened the pre-join preview ("Shuhao Zhang's Zoom Meeting", with a Start button), which the substring check took for the meeting. Fixed in 71d6d7c: the meeting must be the window named exactly "Zoom Meeting", and the script presses Return on the preview. I closed the preview and quit Zoom afterwards.Not verified
zoom-up.shpressing Start on the preview (Return), and the PMI mode: neither has run yet. You were on camera at the time, so I didn't start a meeting. The exact "Zoom Meeting" window name comes fromzoom-front.shand no run log records it. Simplest fix: untick "Always show this preview when joining" in Zoom once.FACTORY_ENABLED, the Claude GitHub App and a secret.Look here first
CLAUDE.md"Standing approvals": these are the things Claude will now do without asking..factory/config.jsonactorsandriskHigh.scripts/factory/stop-gate.mjsdecide().