Skip to content

chore: lights-on factory (phases 1–5) - #129

Merged
ShuhaoZQGG merged 7 commits into
devfrom
chore/lights-on-factory
Nov 7, 2026
Merged

ShuhaoZQGG merged 7 commits into
devfrom
chore/lights-on-factory

Conversation

@ShuhaoZQGG

@ShuhaoZQGG ShuhaoZQGG commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

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:

  1. A session runs from implemented to tested. CLAUDE.md gets a definition of done (covering Zoom-client specs ran in the real client) and standing approvals (dev deploy, starting Zoom, flag flips with restore, branch/PR work). zoom-up.sh launches Zoom and starts the test meeting; preflight and the skill treat that as Claude's step. A Stop hook refuses the first stop on a factory/ branch while covered files changed and no newer run.md names the spec.
  2. CI. There was none. ci.yml runs unit tests, the spec check, the oracle check, the build and Playwright on PRs into dev/master. The PR template is the completion report.
  3. Stations as skills, state in labels. /factory <n> routes; factory-plan, factory-build (oracle → implement → verify in fresh subagents), factory-zoom, factory-report, factory-issue. scripts/factory/state.mjs is the only reader of the label state machine, and trusts human gates only from config.json actors (public repo). Oracle tests are locked by a PreToolUse hook and checked in CI.
  4. Cloud triggers. factory-plan.yml and factory-build.yml fire on the human gates. Inert until FACTORY_ENABLED is true.
  5. Ideation. factory-ideate drafts idea issues 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

  • Hooks run on every Claude Code session in this repo: the Stop gate exits at once off factory/ branches, and the oracle guard only refuses listed files on a factory/<n> branch. Both let the action through if they error.
  • Cloud stations run only on gates added by an actor in .factory/config.json, and treat other issue text as data.
  • The meeting id for zoom-up.sh lives in the gitignored meeting.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.mjs now reads specs through scripts/lib/zoom-specs.mjs.
  • The Stop gate was smoke-tested on a throwaway factory/ branch (blocks, then passes on the second stop and with a newer run.md). state.mjs was run live against Simple Format import: words in the speaker name override the role in parentheses #90 and the queue.
  • CI: the first run of ci.yml passed unit tests, specs, the oracle check and the build, and failed installing Playwright (ubuntu-latest is now 26.04, which Playwright 1.58 can't install). With runners pinned to ubuntu-24.04 the whole job is green, Playwright included.
  • zoom-up.sh ran 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

Look here first

  • CLAUDE.md "Standing approvals": these are the things Claude will now do without asking.
  • .factory/config.json actors and riskHigh.
  • scripts/factory/stop-gate.mjs decide().

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
ShuhaoZQGG marked this pull request as ready for review November 7, 2026 21:37
@ShuhaoZQGG
ShuhaoZQGG merged commit 13ba779 into dev Nov 7, 2026
1 check passed
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.

1 participant