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

Choosing a template for a new forge: the spawn path takes a `template`, records it, and `GET /api/v1/forges/templates` + MCP `templates` list what is offered #7548

Merged opened this in answer to

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

Merged

A new forge can now be given a `template`, and the forge says which templates it offers. This is the data and API part of epic #7538; for now the choice is only recorded, and a `starter` child gets the same files as a `blank` one until #7535 adds the template's files.

shipped production is serving it deploy 9afb56b

opened this

A new forge can now be given a template, and the forge says which templates it offers. This is the data and API part of epic #7538; for now the choice is only recorded, and a starter child gets the same files as a blank one until #7535 adds the template's files.

What moved where

  • src/spawn/templates.ts (new) holds the list. There are two built-in templates: blank (the default, what a spawn makes today) and starter (the app /apps/new starts from, scaffoldFiles). Each has id, title, a one-sentence says and source, and the type already allows source: "repo" with repo: "owner/name" for #7536.
  • src/spawn/plan.ts checks template after size. An unknown id is a 422 on template with the message "template must be blank or starter". The field goes into the plan only when it is given and is not blank, so plans and plan hashes without a template are unchanged. Because the template is part of the plan, a retry with a different template is refused with 409 exists, the same as any other changed plan.
  • src/spawn/door.ts passes template through, which covers both POST /api/v1/forges and MCP spawn. src/actions/spawn.ts writes it to the ledger line and parent.json and shows it in the GET /api/v1/forges rows (7 lines). In all three places it appears only when not blank, so the existing tests that compare whole rows still pass unchanged.
  • src/http/api/forges.ts adds GET /api/v1/forges/templates, which needs no sign-in, has an ETag and is cached for 5 minutes. It shares one handler (publicTable) with /sizes; the two router.add lines stay literal because the OpenAPI coverage test reads them that way.
  • There is a new MCP tool, templates, with a one-sentence description, and a template argument on spawn. The new route is in the OpenAPI document, and docs/MCP.md has its row. docs/SPAWN.md has a new section, "Choosing a template".

Choices

  • I added a new templates tool rather than a kind on sizes because the issue names the tool. test/mcp-size.test.ts passes with the ceilings unchanged.
  • The one existing test I changed is test/mcp.test.ts: it lists every tool by name, so I added templates to that list.
  • I left out the New forge page picker (#7534) and actually creating the files (#7535).

Testing

  • New test/spawn-template-api.test.ts with the five named tests. It also checks that a plan with no template, or with blank, is the same object and the same JSON as before templates existed.
  • bun x tsc --noEmit is clean. 82 test files covering spawn, MCP and OpenAPI pass (spawn-plan, openapi-oa-forges, openapi-coverage, mcp, mcp-size, mcp-docs among them). scripts/fast.ts named 1043 files, too many to run here, so I picked these myself.

Try it

  • curl $PREVIEW/api/v1/forges/templates
  • curl -X POST $PREVIEW/api/v1/forges -H 'content-type: application/json' -d '{"name":"kid","template":"starter"}' returns a plan with "template":"starter".
  • The same request with "template":"nope" returns a 422.

🤖 Generated with Claude Code

Closes #7533

commented

A new forge can now be given a template, and the forge says which templates it offers. This is the data and API part of epic #7538; for now the choice is only recorded, and a starter child gets the same files as a blank one until #7535 adds the template's files.

What moved where

  • src/spawn/templates.ts (new) holds the list. There are two built-in templates: blank (the default, what a spawn makes today) and starter (the app /apps/new starts from, scaffoldFiles). Each has id, title, a one-sentence says and source, and the type already allows source: "repo" with repo: "owner/name" for #7536.
  • src/spawn/plan.ts checks template after size. An unknown id is a 422 on template with the message "template must be blank or starter". The field goes into the plan only when it is given and is not blank, so plans and plan hashes without a template are unchanged. Because the template is part of the plan, a retry with a different template is refused with 409 exists, the same as any other changed plan.
  • src/spawn/door.ts passes template through, which covers both POST /api/v1/forges and MCP spawn. src/actions/spawn.ts writes it to the ledger line and parent.json and shows it in the GET /api/v1/forges rows (7 lines). In all three places it appears only when not blank, so the existing tests that compare whole rows still pass unchanged.
  • src/http/api/forges.ts adds GET /api/v1/forges/templates, which needs no sign-in, has an ETag and is cached for 5 minutes. It shares one handler (publicTable) with /sizes; the two router.add lines stay literal because the OpenAPI coverage test reads them that way.
  • There is a new MCP tool, templates, with a one-sentence description, and a template argument on spawn. The new route is in the OpenAPI document, and docs/MCP.md has its row. docs/SPAWN.md has a new section, "Choosing a template".

Choices

  • I added a new templates tool rather than a kind on sizes because the issue names the tool. test/mcp-size.test.ts passes with the ceilings unchanged.
  • The one existing test I changed is test/mcp.test.ts: it lists every tool by name, so I added templates to that list.
  • I left out the New forge page picker (#7534) and actually creating the files (#7535).

Testing

  • New test/spawn-template-api.test.ts with the five named tests. It also checks that a plan with no template, or with blank, is the same object and the same JSON as before templates existed.
  • bun x tsc --noEmit is clean. 82 test files covering spawn, MCP and OpenAPI pass (spawn-plan, openapi-oa-forges, openapi-coverage, mcp, mcp-size, mcp-docs among them). scripts/fast.ts named 1043 files, too many to run here, so I picked these myself.

Try it

  • curl $PREVIEW/api/v1/forges/templates
  • curl -X POST $PREVIEW/api/v1/forges -H 'content-type: application/json' -d '{"name":"kid","template":"starter"}' returns a plan with "template":"starter".
  • The same request with "template":"nope" returns a 422.

🤖 Generated with Claude Code

Approved

Clean. This is what #7533 asks for, and the preview confirms it: GET /api/v1/forges/templates answers 200 without sign-in, with an ETag, cache-control: public, max-age=300 and the full security header set. template:"nope" gets a 422 that names blank and starter. template:"starter" with no sign-in gives a dry-run plan that names the template.

Security: the new route and MCP tool are read-only and return static data. template goes through the existing spawn door, so the existing admin, rw-scope and same-origin checks and the body cap still apply. The id is checked against a closed list before it reaches the plan, the ledger or parent.json, so no user text reaches a path or a shell. There are no secrets in the diff.

Plan hashes: the template is left out when it is absent or blank, and the test proves the plan is the same object and the same JSON as before.

Tests: the five named tests are there and each would fail without the change. That includes the 409 when a made child is retried with a different template.

Both doors: the page picker and the terminal form are both scoped to #7534, so neither door offers the field before the other. docs/SPAWN.md and docs/MCP.md document it.

The publicTable refactor removes the duplication with /sizes and leaves no dead code behind.

commented

A new forge's spawn now takes an optional template, records it, and two endpoints list the templates on offer. This was already built and pushed in the branch's two commits; the new message on the issue is an automatic triage note, not a request for code. I changed nothing and pushed nothing. I re-ran bun x tsc --noEmit (clean) and test/spawn-template-api.test.ts (5 pass). I did not run the rest of the suite; the forge's check runs it on push.

  • Templates: src/spawn/templates.ts lists blank (the default) and starter (the scaffoldFiles app). Repo-backed templates are left as a seam (source: "repo", repo: "owner/name") for a later child.
  • Spawn: POST /api/v1/forges and the MCP spawn accept template. An unknown id is a 422 on template naming the valid ids. The plan carries the template only when it is given and not blank, so existing plan hashes don't change.
  • Recorded: the choice goes into the plan, the ledger line, parent.json, and the rows of GET /api/v1/forges. A retry with a different template is refused like a different modelKey.
  • Listing: GET /api/v1/forges/templates needs no sign-in, has an ETag, and is cached like /api/v1/forges/sizes. The MCP templates tool returns the same list.
  • Docs: OpenAPI, docs/MCP.md and docs/SPAWN.md ("Choosing a template") cover both.

The triage note says no pillar is named, so the issue waits behind the Later gate. Naming a pillar is for a person to do; I have not guessed one.

Try it with curl -XPOST $FORGE_URL/api/v1/forges -d '{"name":"x","template":"starter","dryRun":true}' and curl -i $FORGE_URL/api/v1/forges/templates.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs