ai-app-factory

How it works

ai-app-factory turns an idea into a real, working app -- built and hosted using GitHub. Nothing gets built without a person approving it first: the project owner approves the initial plan, then the requester themselves approves or rejects each change as the app gets built.

The workflow

  1. 1

    Request a new app

    Describe what you want, submitted from your GitHub account.

  2. 2

    Approve it

    The project owner reviews the plan and approves it before anything gets built.

  3. 3

    AI agent builds it

    An AI coding agent works with GitHub to write the code and open pull requests.

  4. 4

    You approve or redirect each change

    The requester -- not the project owner -- approves or rejects each pull request on this site, and can comment @claude any time to give feedback or direct a change mid-build.

  5. 5

    Host it on GitHub

    The finished app is published and hosted using GitHub.

  6. 6

    Fork or clone it to take it further

    Fork on GitHub to keep it connected and optionally keep the pipeline running under your own account; clone it if you just want the code with no strings attached.

  7. 7

    Original archived after 7 days

    The project owner manually archives or deletes the original app about a week later.

Technical detail: the real commands

Curious what actually runs behind each step above? Every command below is sourced from a file that exists in this repo today, grouped by stage.

Triggering a run
gh issue edit <n> --add-label claude-go -R <owner>/<repo>

Human β€” Hands an issue to Lane B β€” matches the `issues: labeled` trigger in claude.yml.

Source: .github/workflows/claude.yml

@claude <comment> (posted on an issue or PR)

Human β€” a repo collaborator, or the project’s own recorded requester (parsed from README.md’s "Requested by" line, not just OWNER/MEMBER/COLLABORATOR as of 2026-08-18) β€” A comment containing "@claude" re-fires the workflow via its issue_comment trigger; the comment’s own text is read into the prompt (a real bug, fixed the same day it was found: the text used to be discarded, the comment only ever acted as a re-trigger signal), so this is also how a requester gives feedback or redirects work mid-build, not just approve/reject on something already drafted.

Source: .github/workflows/claude.yml

gh workflow run claude.yml -f issue_number=<n> -R <owner>/<repo>

Human β€” Manual workflow_dispatch run; overrides the lane:interactive / lane:manual exclusions.

Source: .github/workflows/claude.yml

Monitoring runs
gh run list -R <owner>/<repo>

Human β€” Lists recent claude.yml runs and their status.

Source: operational use β€” not embedded in a script

gh run view <run-id> -R <owner>/<repo>

Human β€” Inspects the logs/output of one run in detail.

Source: operational use β€” not embedded in a script

gh issue view <n> --repo <owner>/<repo> --json title,body,labels,milestone

Workflow (claude.yml) β€” Reads the target issue's labels (to resolve model + lane exclusions) and injects its title/body into Claude's prompt β€” a workflow_dispatch run has no event payload to fall back on.

Source: .github/workflows/claude.yml

gh pr list --repo <owner>/<repo> --search "linked:issue-<n>"

Workflow (journal-entry.sh) β€” Resolves the PR that references this issue, to record in the journal entry.

Source: .github/scripts/journal-entry.sh

Ask β†’ design doc
gh issue create --repo mmorrow24work/ai-app-factory --title "[new-project-ask] <name>" --body "<ask>"

Human, via the `/new` page (a pre-filled link to this exact GitHub issue form β€” no token, no account setup on the site itself) β€” M5 β€” draft-design-doc.yml triggers on issues: opened, filtered to the [new-project-ask] title prefix rather than a label (GitHub silently drops labels= on this URL for non-collaborators β€” found via a real end-to-end test on 2026-08-18). Opus drafts docs/proposals/<slug>.md and opens a "Design: <name>" PR against main for review; the issue author's GitHub login is stamped onto the doc and PR as the authenticated requester identity, then the intake issue is closed.

Source: .github/workflows/draft-design-doc.yml

Review β†’ provisioning plan
gh pr merge <n> --repo mmorrow24work/ai-app-factory

Human β€” approval gate #1 β€” Reviews/edits the drafted design doc in GitHub's normal PR review UI, then merges it. The merge (a push to main touching docs/proposals/*.md) is what fires generate-issues.yml next.

Source: GitHub PR review UI β€” no script

gh workflow run generate-issues.yml -R mmorrow24work/ai-app-factory -f path=docs/proposals/<slug>.md

Workflow (generate-issues.yml) β€” auto-fired on merge; this is the manual re-run form β€” M6 β€” reads the merged design doc and opens a "Provision mmorrow24work/<slug>" issue in ai-app-factory with the exact commands to run next. Creates nothing else and touches no other repo.

Source: .github/workflows/generate-issues.yml

Provision the repo
scripts/factory-new.sh <type> <repo-name> --ask "<summary>" [--set KEY=VALUE ...]

Human β€” approval gate #2, own gh auth β€” M2 β€” scaffolds the repo from a template, runs `gh repo create`, applies templates/_shared/labels.json, and appends the project to projects.json (locally β€” does not push).

Source: scripts/factory-new.sh

scripts/factory-secrets.sh <repo-name>

Human β€” same gate as above β€” M2 β€” sets CLAUDE_CODE_OAUTH_TOKEN (from the local .env store) and a freshly minted, never-persisted GH_PAT (prompted interactively) as Actions secrets on the new repo.

Source: scripts/factory-secrets.sh

git add projects.json && git commit -m "Register mmorrow24work/<slug>" && git push

Human β€” same gate as above β€” Pushes the projects.json entry factory-new.sh wrote locally, so the dashboard picks up the new project.

Source: scripts/factory-new.sh writes projects.json; this command pushes it

Seed milestones & issues
gh workflow run seed-milestones.yml -R mmorrow24work/<repo-name>

Human β€” manual trigger, no inputs (Actions tab β†’ seed-milestones β†’ Run workflow works the same way) β€” M6 β€” fetches the approved design doc from ai-app-factory's public main over an unauthenticated raw.githubusercontent.com request, then creates one milestone per doc milestone and one or more claude-go-labeled issues per milestone, using the new repo's own secrets.

Source: templates/<type>/.github/workflows/seed-milestones.yml

gh api repos/<owner>/<repo>/milestones -f title=... -f description=...

Workflow (seed-milestones.yml) β€” Creates one GitHub milestone per "## Milestones" bullet in the design doc; skips any title that already exists.

Source: templates/<type>/.github/workflows/seed-milestones.yml

gh issue create --title ... --milestone "M<n>: <title>" --body ...

Workflow (seed-milestones.yml) β€” Files one or more issues per milestone with an acceptance-criteria checklist; applies claude-go only to issues concrete enough for the unattended pipeline.

Source: templates/<type>/.github/workflows/seed-milestones.yml

Review & decisions during the build
gh issue create --repo <owner>/<repo> --title "[review-approve] PR #<n>"

Human β€” the project’s own recorded requester, or the repo owner (via the "Pending decisions" section on that project’s dashboard page, a pre-filled link β€” or hand-constructed) β€” Implemented 2026-08-18. review-decision.yml triggers on issues: opened, filtered to a [review-approve]/[review-reject] title prefix (not a label β€” same labels=-is-dropped-for-non-collaborators reasoning as intake). Parses the referenced PR number from the title and merges (squash + delete branch) if authorized.

Source: templates/<type>/.github/workflows/review-decision.yml

gh issue create --repo <owner>/<repo> --title "[review-reject] PR #<n>"

Human β€” same authorization as above β€” Same mechanism, opposite action: closes the referenced PR without merging and deletes its branch.

Source: templates/<type>/.github/workflows/review-decision.yml

grep -oE '\[@[A-Za-z0-9-]+\]\(https://github\.com/[A-Za-z0-9-]+\)' README.md

Workflow (review-decision.yml and claude.yml’s Authorize step) β€” Recovers the project’s recorded requester login from its own README "Requested by" line β€” the only source of truth for who besides a collaborator is allowed to approve/reject a PR or direct changes via @claude. A repo predating that line (no match) falls through to collaborator/owner-only, not an error.

Source: templates/<type>/.github/workflows/review-decision.yml