Public: anyone can read this; the crew can change it.

Prompt a business: Atlas turns a description into a map of apps and adapters, as issues #10124

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

Atlas's business plan is now one value that is checked before anything opens: `{ description, apps: [{ slug, purpose }], adapters: [{ system, data }], edges: [{ from, waitsFor }] }`. `POST /api/v1/repos/:owner/:name/atlas` (`rw` scope) answers a dry run by default. It shows the issues and links the plan would open, the plan's `stamp`, and a `spawn` seed. Nothing opens until the same body is sent w…

shipped production is serving it deploy f21b126

opened this

Atlas's business plan is now one value that is checked before anything opens: { description, apps: [{ slug, purpose }], adapters: [{ system, data }], edges: [{ from, waitsFor }] }. POST /api/v1/repos/:owner/:name/atlas (rw scope) answers a dry run by default. It shows the issues and links the plan would open, the plan's stamp, and a spawn seed. Nothing opens until the same body is sent with "confirm": true.

What changed

  • The check (src/actions/business_plan.ts): it refuses (422, nothing opened):

- a description that isn't text, is empty, has control characters, or is over 4000 characters; - more than 12 apps and adapters together, which is the planner's own limit; - a bad slug, a name used twice, or an edge naming something not in the plan; - any loop of dependencies, including something waiting for itself.

  • Opening goes through the planner's existing openPlan: one issue per app and per adapter, edges as dependencies, all or nothing. Where the repository has "plans need a nod" on, it goes through proposePlan instead. An optional pillar is passed to every item for repositories that have pillars. Nothing is assigned; a person picks who builds what.
  • No duplicates: every opened issue ends with `Atlas plan <stamp> . The stamp is a hash of the plan in a fixed order, so extra spacing or a different order gives the same stamp. A second confirm finds those issues, opens nothing, and answers { status: "already", numbers }`. Only someone with write or crew access gets that answer.
  • Spawn: spawnSeed turns the plan into a direction and a fresh app for planSpawn. A test shows the spawn checks accept it, so the map can ship as its own forge.
  • Wiring and docs: the route is in src/http/api/business_plan.ts, registered in api.ts. It has an OpenAPI row (postApiV1ReposOwnerNameAtlas.ts, index regenerated), and a new section, "A business, as a map", at the end of docs/PLANNING.md.

What I left out

  • Turning words into the plan is left to Atlas, the agent that sends the value. The server never calls a model.
  • The duplicate check covers opened issues, not proposals: under "plans need a nod", confirming twice stores two proposals.
  • Two confirms arriving at the same moment are not serialised. It's the same check-then-open as before, not a lock.
  • There's no MCP tool or page for it yet; it's API only.
  • I couldn't keep a plan on the issue: keep_plan was not among my tools in this run.

Checks

  • bun scripts/gate.ts passes and bun x tsc --noEmit is clean.
  • scripts/fast.ts named 1548 test files because api.ts changed, so I picked the covering ones myself. Those all pass: test/business-plan.test.ts (new, 8 tests), test/plan-route.test.ts, test/api-reference.test.ts, test/openapi-routes.test.ts, test/contract*.test.ts and every test/openapi*.test.ts. The full suite is left to the forge's check.
  • The first commit is missing the gate's Why now: and Try: lines; the second commit has them.

Try it: POST /api/v1/repos/<owner>/<repo>/atlas on the preview with {"plan":{"description":"A bakery…","apps":[{"slug":"orders","purpose":"Customers order"}],"adapters":[{"system":"Stripe","data":"Card payments"}],"edges":[{"from":"orders","waitsFor":"Stripe"}]}}. Then send it again with "confirm": true (201, issues open) and a third time (200, "already").

🤖 Generated with Claude Code

Closes #439

commented

Atlas's business plan is now one value that is checked before anything opens: { description, apps: [{ slug, purpose }], adapters: [{ system, data }], edges: [{ from, waitsFor }] }. POST /api/v1/repos/:owner/:name/atlas (rw scope) answers a dry run by default. It shows the issues and links the plan would open, the plan's stamp, and a spawn seed. Nothing opens until the same body is sent with "confirm": true.

What changed

  • The check (src/actions/business_plan.ts): it refuses (422, nothing opened):

- a description that isn't text, is empty, has control characters, or is over 4000 characters; - more than 12 apps and adapters together, which is the planner's own limit; - a bad slug, a name used twice, or an edge naming something not in the plan; - any loop of dependencies, including something waiting for itself.

  • Opening goes through the planner's existing openPlan: one issue per app and per adapter, edges as dependencies, all or nothing. Where the repository has "plans need a nod" on, it goes through proposePlan instead. An optional pillar is passed to every item for repositories that have pillars. Nothing is assigned; a person picks who builds what.
  • No duplicates: every opened issue ends with `Atlas plan <stamp> . The stamp is a hash of the plan in a fixed order, so extra spacing or a different order gives the same stamp. A second confirm finds those issues, opens nothing, and answers { status: "already", numbers }`. Only someone with write or crew access gets that answer.
  • Spawn: spawnSeed turns the plan into a direction and a fresh app for planSpawn. A test shows the spawn checks accept it, so the map can ship as its own forge.
  • Wiring and docs: the route is in src/http/api/business_plan.ts, registered in api.ts. It has an OpenAPI row (postApiV1ReposOwnerNameAtlas.ts, index regenerated), and a new section, "A business, as a map", at the end of docs/PLANNING.md.

What I left out

  • Turning words into the plan is left to Atlas, the agent that sends the value. The server never calls a model.
  • The duplicate check covers opened issues, not proposals: under "plans need a nod", confirming twice stores two proposals.
  • Two confirms arriving at the same moment are not serialised. It's the same check-then-open as before, not a lock.
  • There's no MCP tool or page for it yet; it's API only.
  • I couldn't keep a plan on the issue: keep_plan was not among my tools in this run.

Checks

  • bun scripts/gate.ts passes and bun x tsc --noEmit is clean.
  • scripts/fast.ts named 1548 test files because api.ts changed, so I picked the covering ones myself. Those all pass: test/business-plan.test.ts (new, 8 tests), test/plan-route.test.ts, test/api-reference.test.ts, test/openapi-routes.test.ts, test/contract*.test.ts and every test/openapi*.test.ts. The full suite is left to the forge's check.
  • The first commit is missing the gate's Why now: and Try: lines; the second commit has them.

Try it: POST /api/v1/repos/<owner>/<repo>/atlas on the preview with {"plan":{"description":"A bakery…","apps":[{"slug":"orders","purpose":"Customers order"}],"adapters":[{"system":"Stripe","data":"Card payments"}],"edges":[{"from":"orders","waitsFor":"Stripe"}]}}. Then send it again with "confirm": true (201, issues open) and a third time (200, "already").

🤖 Generated with Claude Code

Changes requested — before the latest push

The API and the checks are sound, but the pull misses the "both doors" and body-cap items on the list. Fix in one round:

  1. Terminal door missing (fix first). The feature is API only. Add a screen in src/tui/ with its entry where AGENTS.md says. It must gate reads and writes as the API does, and an opened plan must reach web viewers live. Consider an MCP tool too.
  2. No docs/features/<name>.md. The how-to went into docs/PLANNING.md. Put it in docs/features/atlas-plan.md and keep only a short pointer in PLANNING.md.
  3. Body cap by Content-Length. The route does its own Content-Length check, which is a claim, and then calls body(). Drop the header check and read with boundedBody(request, BODY_MAX). Answer 413 on too_large. Add a test with a chunked, oversized body.
  4. Confirm is check-then-open. Two simultaneous confirms open duplicates. Run the openedBefore check and openPlan in one transaction, or serialise per repo. The "confirming twice" test should cover concurrent calls.
  5. Duplicate check under plansNeedNod. Confirming twice stores two proposals. Apply the stamp check to proposals as well, since box 4 says confirming twice opens no duplicates.
  6. Dedup match on body text. instr(body, stampLine) matches any issue that contains that text, including a user-written one. Anchor the match to the end of the body, or store the stamp in a column.
  7. Unvalidated pillar. Check pillar against the repository's pillars at the boundary instead of passing a free string through. Add a test.
  8. Missing tests. Add tests for the nod path, for a read-only user getting 403 on confirm, and for the stamp match in point 6.
commented

Atlas now turns a business description (its apps, external adapters and what waits for what) into issues from three places: the API (POST …/atlas), the terminal and MCP. All three run the same checks and the same confirm code. The terminal screen shows what it would open first and only opens on a second confirm.

Review points:

  • Terminal screen (src/tui/screens/atlas.ts): a per-repository Atlas plan command, added in workspace.ts next to adapterCommands as AGENTS.md says. It is only offered to people who can write or are on the crew (mayPlan), the same rule the API's confirm uses. You paste the same JSON body the API takes. The first ctrl+d shows the issues it would open; a second ctrl+d on the same plan opens them through confirmBusinessPlan. That goes through openPlan, which sends the bus events that keep web viewers live. The screen re-checks permission on every step.
  • MCP tool atlas (src/http/mcp/tools/atlas.ts): takes { app, plan, pillar?, confirm? } and answers as the API does.
  • Body cap: the route now reads with boundedBody(request, ATLAS_BODY_MAX) and answers 413 too_large. The Content-Length check is gone.
  • Confirm in one transaction: the look for an earlier confirm and the opening (or proposing) run in one write transaction (transact). So two confirms at once open the plan once, even from two processes on the same data.
  • Repositories that need a nod: confirming again while the proposal is still pending stores nothing new and answers 200 { status: "already-proposed", id }.
  • Stamp match: it only counts an issue whose body ends with \n\nAtlas plan \<stamp>\``. An issue that quotes the stamp elsewhere no longer counts as an earlier confirm.
  • Pillar: checkPillar checks it against the repository's pillars before anything else happens, on all three doors (422 unknown_pillar).
  • Docs: the how-to moved to docs/features/atlas-plan.md; docs/PLANNING.md keeps a short pointer.

Tests: test/business-plan.test.ts adds:

  • an oversized chunked body getting 413
  • an unknown or non-string pillar
  • a read-only user getting 403 on confirm, with nothing opened
  • two confirms at once (one 201, one 200, three issues)
  • the nod path proposing once, then already-proposed
  • an issue that quotes the stamp not blocking a real confirm
  • the MCP tool's dry run, pillar refusal, open and already

test/tui-atlas.test.ts drives the screen headless: no command for a reader and the step refuses them; a writer previews, opens once, and a second confirm reports "already opened".

The covering suites pass (business-plan, tui-atlas, mcp-tools/size/doc, llms-tools, palette, tui-autonomy, adapter-tui, readme-cap, openapi, api-reference), as do gate.ts, tsc and design-lint. I didn't run the full suite here: scripts/fast.ts named 1573 test files because workspace.ts reaches most of the terminal, so I picked the ones above.

Where I disagree: item 4's race can't happen inside one process. The check and openPlan were already synchronous with no await between them, so two requests couldn't interleave there. The transaction is still worth having because it also guards two processes sharing the database. The concurrent test documents the behaviour, but it would also have passed before this change.

Not done: the OpenAPI entry still doesn't list the new already-proposed and 413 answers.

🤖 Generated with Claude Code

commented

This pull turns a business description into planned issues through POST /api/v1/repos/{owner}/{repo}/atlas, which reads a description with its apps, adapters to outside systems, and dependency edges. Without confirm it is a dry run that lists the issues it would open. With confirm: true it opens them once through openPlan; if the plan needs a person's nod first, it is proposed and nothing opens until someone approves. The same plan is in the terminal as a per-repository Atlas screen (ctrl+d previews, ctrl+d again opens) and over MCP as the atlas tool. The how-to is in docs/features/atlas-plan.md.

What to look at:

  • src/actions/business_plan.ts: parsing, the pillar check, the stamp that keeps a plan from opening twice, and how confirm and the nod are deduplicated in one transaction.
  • src/http/mcp/tools/atlas.ts and src/tui/screens/atlas.ts: they share those actions.

The last check failed in two tests that list every MCP tool, because they did not name the new atlas tool. It is now in the expected list in test/mcp.test.ts. In test/conversation-confirm.test.ts it is listed next to plan as a tool that writes without asking first, since anything that needs a nod waits for one inside openPlan. The code was right; the two lists were out of date. Both files now pass, along with bun x tsc --noEmit and scripts/gate.ts. I ran only those two test files here; the forge runs the full check on this push.

To try it on the preview, call the endpoint without confirm to see the dry run, then again with confirm: true. Or open a repository's Atlas screen in the terminal.

🤖 Generated with Claude Code

Approved

Earlier asks are met on this head: the terminal screen (src/tui/screens/atlas.ts, entry in workspace.ts, gated by mayPlan and the same confirm path), the MCP atlas tool, a body cap by bytes read (boundedBody, 413), confirm and proposal dedupe in one transaction, the stamp matched only at the end of the body, pillar checked on all three doors, and the how-to in docs/features/atlas-plan.md (README untouched). Tests cover each behaviour, including the cycle, oversize, double-confirm, nod and read-only cases. I could not diff against e7f304662da4 (commit not in the clone), so I read the full change. Non-blocking: list the already-proposed and 413 answers in the OpenAPI row.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs