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
Check · Review · Land · Live done — Landed and live in production.
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
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) andstarter(the app/apps/newstarts from,scaffoldFiles). Each hasid,title, a one-sentencesaysandsource, and the type already allowssource: "repo"withrepo: "owner/name"for #7536.src/spawn/plan.tscheckstemplateaftersize. An unknown id is a 422 ontemplatewith the message "template must be blank or starter". The field goes into the plan only when it is given and is notblank, 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 409exists, the same as any other changed plan.src/spawn/door.tspassestemplatethrough, which covers bothPOST /api/v1/forgesand MCPspawn.src/actions/spawn.tswrites it to the ledger line andparent.jsonand shows it in theGET /api/v1/forgesrows (7 lines). In all three places it appears only when notblank, so the existing tests that compare whole rows still pass unchanged.src/http/api/forges.tsaddsGET /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 tworouter.addlines stay literal because the OpenAPI coverage test reads them that way.- There is a new MCP tool,
templates, with a one-sentence description, and atemplateargument onspawn. The new route is in the OpenAPI document, anddocs/MCP.mdhas its row.docs/SPAWN.mdhas a new section, "Choosing a template".
Choices
- I added a new
templatestool rather than akindonsizesbecause the issue names the tool.test/mcp-size.test.tspasses with the ceilings unchanged. - The one existing test I changed is
test/mcp.test.ts: it lists every tool by name, so I addedtemplatesto that list. - I left out the New forge page picker (#7534) and actually creating the files (#7535).
Testing
- New
test/spawn-template-api.test.tswith the five named tests. It also checks that a plan with no template, or withblank, is the same object and the same JSON as before templates existed. bun x tsc --noEmitis clean. 82 test files covering spawn, MCP and OpenAPI pass (spawn-plan,openapi-oa-forges,openapi-coverage,mcp,mcp-size,mcp-docsamong them).scripts/fast.tsnamed 1043 files, too many to run here, so I picked these myself.
Try it
curl $PREVIEW/api/v1/forges/templatescurl -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
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.
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.tslistsblank(the default) andstarter(thescaffoldFilesapp). Repo-backed templates are left as a seam (source: "repo",repo: "owner/name") for a later child. - Spawn:
POST /api/v1/forgesand the MCPspawnaccepttemplate. An unknown id is a 422 ontemplatenaming the valid ids. The plan carries the template only when it is given and notblank, so existing plan hashes don't change. - Recorded: the choice goes into the plan, the ledger line,
parent.json, and the rows ofGET /api/v1/forges. A retry with a different template is refused like a differentmodelKey. - Listing:
GET /api/v1/forges/templatesneeds no sign-in, has an ETag, and is cached like/api/v1/forges/sizes. The MCPtemplatestool returns the same list. - Docs: OpenAPI,
docs/MCP.mdanddocs/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.
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 astarterchild gets the same files as ablankone 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) andstarter(the app/apps/newstarts from,scaffoldFiles). Each hasid,title, a one-sentencesaysandsource, and the type already allowssource: "repo"withrepo: "owner/name"for #7536.src/spawn/plan.tscheckstemplateaftersize. An unknown id is a 422 ontemplatewith the message "template must be blank or starter". The field goes into the plan only when it is given and is notblank, 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 409exists, the same as any other changed plan.src/spawn/door.tspassestemplatethrough, which covers bothPOST /api/v1/forgesand MCPspawn.src/actions/spawn.tswrites it to the ledger line andparent.jsonand shows it in theGET /api/v1/forgesrows (7 lines). In all three places it appears only when notblank, so the existing tests that compare whole rows still pass unchanged.src/http/api/forges.tsaddsGET /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 tworouter.addlines stay literal because the OpenAPI coverage test reads them that way.templates, with a one-sentence description, and atemplateargument onspawn. The new route is in the OpenAPI document, anddocs/MCP.mdhas its row.docs/SPAWN.mdhas a new section, "Choosing a template".Choices
templatestool rather than akindonsizesbecause the issue names the tool.test/mcp-size.test.tspasses with the ceilings unchanged.test/mcp.test.ts: it lists every tool by name, so I addedtemplatesto that list.Testing
test/spawn-template-api.test.tswith the five named tests. It also checks that a plan with no template, or withblank, is the same object and the same JSON as before templates existed.bun x tsc --noEmitis clean. 82 test files covering spawn, MCP and OpenAPI pass (spawn-plan,openapi-oa-forges,openapi-coverage,mcp,mcp-size,mcp-docsamong them).scripts/fast.tsnamed 1043 files, too many to run here, so I picked these myself.Try it
curl $PREVIEW/api/v1/forges/templatescurl -X POST $PREVIEW/api/v1/forges -H 'content-type: application/json' -d '{"name":"kid","template":"starter"}'returns a plan with"template":"starter"."template":"nope"returns a 422.🤖 Generated with Claude Code
Closes #7533