@hobocode/thought-layer 0.2.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,7 +9,7 @@ import {
9
9
  aggregateConfidence, statusFromConfidence, gradeFromConfidence,
10
10
  checkDomains, registrarSearchUrl,
11
11
  computeProjection, fmtMoney,
12
- applyStateOp,
12
+ applyStateOp, runScaffold, runDeploy,
13
13
  type Assumptions, type StateOp,
14
14
  } from "../core/index.ts";
15
15
 
@@ -179,4 +179,58 @@ export default function (pi: ExtensionAPI) {
179
179
  return text(r.message, r.details);
180
180
  },
181
181
  });
182
+
183
+ // tl_scaffold: deterministically generate a self-contained, branded,
184
+ // SEO-complete static site (the instantly-deployable floor) from the spec +
185
+ // brand in the state file - no model needed. Writes the site to a publish dir
186
+ // plus a build.json manifest the deploy step consumes.
187
+ pi.registerTool({
188
+ name: "tl_scaffold",
189
+ label: "Thought Layer: scaffold",
190
+ description:
191
+ "Deterministically scaffold a self-contained, branded, SEO-complete static landing site (an instantly-deployable floor) from the spec + brand in the state file - no model call. " +
192
+ "Use it for the fastest path to something live, or as the floor when a model build is thin or fails. Writes the site (index.html + llms.txt/robots.txt/sitemap.xml/_redirects/netlify.toml/SEO.md) to the publish dir and a build.json manifest for the deploy step. " +
193
+ "The full product is built by the thought-layer-build skill; this is the guaranteed deployable baseline.",
194
+ parameters: Type.Object({
195
+ path: Type.Optional(Type.String({ description: "State file (or project dir) to read the spec + brand from. Defaults to ./.thought-layer/state.json; honors a named file." })),
196
+ outDir: Type.Optional(Type.String({ description: "Publish directory to write the site into. Defaults to ./dist." })),
197
+ domain: Type.Optional(Type.String({ description: "The site's real domain (e.g. https://acme.com) for canonical/OG/sitemap. Defaults to a placeholder you fill later." })),
198
+ founder: Type.Optional(Type.String({ description: "Founder name for the schema.org Person + footer. Optional." })),
199
+ }),
200
+ async execute(_id, params): Promise<ToolResult> {
201
+ const p = params as { path?: string; outDir?: string; domain?: string; founder?: string };
202
+ const r = runScaffold({ path: p.path, outDir: p.outDir, domain: p.domain, founderName: p.founder }, { builtAt: new Date().toISOString() });
203
+ return text(r.message, r.details);
204
+ },
205
+ });
206
+
207
+ // deploy: take the build output (read from build.json's publishDir) live to a
208
+ // user-owned URL. Two models, both keeping ownership with the user and nothing
209
+ // phoning a central account: a BYO Netlify token (NETLIFY_AUTH_TOKEN, deploys
210
+ // into their own account via the file-digest API) or, with no token, the
211
+ // Netlify CLI's own --allow-anonymous flow for a 1-hour claimable URL. The
212
+ // token is read ONLY from the environment (BYOK), never passed as a parameter.
213
+ pi.registerTool({
214
+ name: "deploy",
215
+ label: "Thought Layer: deploy",
216
+ description:
217
+ "Take the built site live to a user-owned URL. Reads build.json (publishDir/entry) next to the state file, then deploys to Netlify. " +
218
+ "Two models, both BYOK with no lock-in: with NETLIFY_AUTH_TOKEN set (read from the environment only, never a parameter) it deploys into the user's OWN account via the file-digest API (no zip); with no token it uses the Netlify CLI's --allow-anonymous flow for an instant live URL plus a one-hour claim link. " +
219
+ "Run the build first (thought-layer-build / tl_scaffold). Use dryRun:true to preview the file plan with no network call. Static-first: if build.json has hasBackend:true it warns that only the front end ships this way.",
220
+ parameters: Type.Object({
221
+ path: Type.Optional(Type.String({ description: "State file (or project dir) whose build.json to deploy. Defaults to ./.thought-layer/state.json; honors a named file." })),
222
+ dryRun: Type.Optional(Type.Boolean({ description: "Plan only: walk the publish dir and report the files + target, with no network call or CLI spawn." })),
223
+ anonymous: Type.Optional(Type.Boolean({ description: "Force the no-account path (Netlify CLI --allow-anonymous) even if a token is set. Default: token path when NETLIFY_AUTH_TOKEN is set, else anonymous." })),
224
+ siteName: Type.Optional(Type.String({ description: "Token path only: create the site under this name (a-z0-9-). Omit to let Netlify assign a random subdomain." })),
225
+ siteId: Type.Optional(Type.String({ description: "Token path only: re-deploy to an existing site id instead of creating a new one." })),
226
+ }),
227
+ async execute(_id, params): Promise<ToolResult> {
228
+ const p = params as { path?: string; dryRun?: boolean; anonymous?: boolean; siteName?: string; siteId?: string };
229
+ const r = await runDeploy(
230
+ { path: p.path, dryRun: p.dryRun, anonymous: p.anonymous, siteName: p.siteName, siteId: p.siteId },
231
+ { deployedAt: new Date().toISOString() },
232
+ );
233
+ return text(r.message, r.details);
234
+ },
235
+ });
182
236
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hobocode/thought-layer",
3
- "version": "0.2.2",
3
+ "version": "0.4.0",
4
4
  "description": "The Thought Layer: rigor for building. Validate an idea, grill it into a buildable spec, then build and deploy it, inside the agent you already use. BYOK, no telemetry.",
5
5
  "license": "MIT",
6
6
  "author": "Hobocode LLC <jerm@hobocode.net>",
@@ -64,6 +64,10 @@
64
64
  "core/stage-map.ts",
65
65
  "core/state-file.ts",
66
66
  "core/state-ops.ts",
67
+ "core/scaffold.ts",
68
+ "core/scaffold-io.ts",
69
+ "core/deploy.ts",
70
+ "core/deploy-io.ts",
67
71
  "dist",
68
72
  "README.md",
69
73
  "LICENSE"
@@ -0,0 +1,7 @@
1
+ Apply the **thought-layer-build** skill. Build the hardened PRD into a static-first, deploy-ready artifact - a self-contained site or a Vite/static build that yields a predictable `dist/`-style publish directory.
2
+
3
+ If there is no hardened PRD in the state file (`state.prd.markdown` + requirements), say so and point me to `/tl` (or `/tl-prd` then `/tl-grill`) rather than building from a bare idea - only proceed cold if I tell you to.
4
+
5
+ Honor the ubiquitous language (the glossary), R-ID traceability, the out-of-scope list, mobile+desktop, the brand if present, and the full SEO/discoverability layer. Default to static; escalate to a backend only when a requirement genuinely needs one, and flag loudly that the default deploy path is static. Verify the build runs, and leave `.thought-layer/build.json` + `DECISIONS.md` + `TRACEABILITY.md`.
6
+
7
+ Read the spec from the state file (default `.thought-layer/state.json`; honor `--path` / `THOUGHT_LAYER_STATE` if a named file is in use). For the fastest deployable floor, or if a full build is too much, run the **tl_scaffold** tool to write a branded, SEO-complete static landing site and the same `build.json` manifest.
@@ -0,0 +1,7 @@
1
+ Apply the **thought-layer-deploy** skill. Take the built site live to a URL I own, with no lock-in.
2
+
3
+ Read `.thought-layer/build.json` (next to the state file; honor `--path` / `THOUGHT_LAYER_STATE` if a named file is in use) for the publish directory and entry. If there is no `build.json`, say so and point me to `/tl-build` (or the `tl_scaffold` tool) rather than guessing - the build has to run first.
4
+
5
+ Default to a dry run first so I can see exactly which files would ship and where. Then deploy: if `NETLIFY_AUTH_TOKEN` is set, deploy into my own Netlify account (owned immediately); otherwise use the Netlify CLI's `--allow-anonymous` flow for an instant live URL plus a one-hour claim link. Read the token only from the environment, never ask me to paste it. If `build.json` says `hasBackend: true`, warn me clearly that this static deploy publishes only the front end.
6
+
7
+ After it is live, tell me the URL and (anonymous only) the claim link, and that a `.thought-layer/deploy.json` record was written.
@@ -0,0 +1,98 @@
1
+ ---
2
+ name: thought-layer-build
3
+ description: "Turn the hardened PRD into a static-first, deploy-ready artifact, built directly by this agent. Reads the build brief from the shared state file (PRD, glossary, R-ID requirements, brand, business context, open to-dos), then builds a self-contained site or a Vite/static build that yields a predictable publish directory (dist/), honoring ubiquitous language, R-ID traceability, the out-of-scope list, mobile+desktop, brand, and the full SEO/discoverability layer. Escalates to a backend only when the spec genuinely requires server-side, and flags loudly that the default deploy path is static. Verifies the build runs and writes a .thought-layer/build.json manifest for the deploy step. Run it after the grill has hardened the PRD; it is a standalone build step, not a validation stage."
4
+ ---
5
+
6
+ # Build it: the hardened PRD becomes a deploy-ready artifact
7
+
8
+ You are not generating a prompt to paste elsewhere. **You are the agent that builds.** The hardened PRD is your brief; build it now, static-first, and verify it actually runs. AI made building cheap, so the value is building the *right* thing, honestly, and shipping something real and ownable. One pass, then verify. This is a build step, not a second framework: no panel, no stages, no per-turn loop.
9
+
10
+ For the fastest possible deployable thing, or as a floor when a full build is too much, you can run the **tl_scaffold** tool first (or instead) - it deterministically writes a branded, SEO-complete static landing site you can ship immediately, then build the real product on top.
11
+
12
+ ## Precondition: a hardened PRD must exist
13
+
14
+ Read the state file first (next section). Then:
15
+ - **Required:** `state.prd` with a non-empty `markdown` and a non-empty `requirements` array.
16
+ - **Ideal:** `state.grill.done === true` (the PRD was actually hardened). If `prd` exists but the grill is absent or not done, build anyway and warn in one line: "Building from a drafted-but-not-grilled PRD - gaps the grill would have caught may surface; `/tl-grill` first would tighten it."
17
+ - **Refuse** only when there is no `prd.markdown` / no requirements at all: say so and point to `/tl` (the full framework) or at least `/tl-prd` then `/tl-grill`. Proceed cold only if the user explicitly says to, and record in DECISIONS.md that the spec was incomplete.
18
+
19
+ ## Read the brief from the state file
20
+
21
+ The spec lives in `.thought-layer/state.json` (or a named file). Honor the same selection as the rest of the kit: an explicit `--path` / tool `path` wins, then `THOUGHT_LAYER_STATE`, then the default. If the `tl_state` tool is available (Pi), `tl_state read`; otherwise `tl read --json` (the CLI). If neither a path nor the env is set, `tl list` (or `tl_state list`) first and **ask which idea to build** when several exist, then stick to that path for the whole session and write `build.json` next to it.
22
+
23
+ Assemble the brief from the state:
24
+ - `prd.markdown` - the full spec (your primary source of truth).
25
+ - `prd.glossary` `[{term, definition}]` - the ubiquitous language to enforce.
26
+ - `grill.requirements` if `grill.done`, else `prd.requirements` `[{id, category, text}]` - the R-IDs to build and trace.
27
+ - `prd.weakestAssumptions` - flag these as known gaps in DECISIONS.md.
28
+ - `brand` - the identity to apply (skip if absent; see ground rules).
29
+ - `bizModel.assumptions` / key `answers` - a one-line business context.
30
+ - `feedback` to-dos and any "Open validation to-dos" in the PRD - known gaps the founder set aside; build around them, do not treat them as blockers.
31
+
32
+ ## The ground rules (honor these exactly)
33
+
34
+ - **Ubiquitous language.** Use the glossary's terms verbatim for every entity, field, route, and UI label. Do not introduce synonyms.
35
+ - **Traceability.** Every requirement has an R-ID. Create `TRACEABILITY.md` mapping each R-ID to the file/component that implements it and how it is verified (a test, a manual check, or "deferred").
36
+ - **Out of scope is absolute.** Do not build past the spec's Out-of-Scope list, even if it looks easy.
37
+ - **Mobile and desktop both work.** Responsive by default; check both viewports.
38
+ - **Brand.** If `state.brand` is present, apply its colors, type, voice, and name throughout. If absent, pick a clean, neutral, accessible default and record the choice in DECISIONS.md.
39
+ - **Ask nothing, decide and record.** The spec is the answer; where it is genuinely silent, choose the simplest option consistent with the PRD and record it in `DECISIONS.md` rather than blocking.
40
+
41
+ ## Build static-first
42
+
43
+ Default to a self-contained static site or a Vite/Astro/static build whose output is one publish directory (`dist/`); set the manifest `hasBackend: false`. Pure HTML/CSS/JS where the interactivity allows; a bundler only when the spec warrants it.
44
+
45
+ **Escalate to a backend ONLY if a requirement genuinely needs one** - apply this three-question test to the R-IDs:
46
+ 1. Does it need a **secret** that cannot ship to the browser (a server-side API key, a payment secret)?
47
+ 2. Does it need **shared or persistent state across users** (a real database, server-stored accounts)?
48
+ 3. Does it need **trusted server-side enforcement** (something the client must not be allowed to fake)?
49
+
50
+ If all three are no, build **static, full stop.** localStorage, static data files, BYOK client-side AI calls, and third-party embeddable widgets do **not** count as a backend - many specs that sound like they need a server can ship a compelling static slice first.
51
+
52
+ **When a backend is genuinely required:** build the static parts anyway, set `hasBackend: true` + `backendNote` in the manifest, and **warn loudly** in chat: the default deploy publishes a static `dist/` to Netlify, so the server part will not deploy that way and needs serverless functions or a separate host. Build the static shell with a clear seam for the backend.
53
+
54
+ ## SEO and discoverability (build all of it, do not skip it)
55
+
56
+ This is the cheap moat. Either run **tl_scaffold** to lay these down for you, or build them by hand into the publish dir, and set each `build.json.seo.*` flag from the file you actually emitted:
57
+ - schema.org JSON-LD as one `@graph` (Organization + Person(founder) cross-referenced, WebSite, and the page type that matches the content); never a type that does not match visible content.
58
+ - `/llms.txt` (title, one-paragraph summary, links to the key pages, a short FAQ).
59
+ - `sitemap.xml`, `robots.txt` (allow search + AI crawlers, point to the sitemap), a canonical link, Open Graph + Twitter meta, and a 1200x630 social image.
60
+ - Semantic, accessible HTML: landmarks, ordered headings, alt text, labelled controls, a visible focus style.
61
+ - A `netlify.toml` (publish dir + SPA redirect) and a `SEO.md` documenting where each item lives and what to fill later (the sameAs links, the social image, the real domain).
62
+
63
+ ## Verify before you call it done
64
+
65
+ Do not declare victory - check:
66
+ 1. **Run the build.** For a bundler: `npm install` then the build command; confirm it exits clean (capture the command into `build.json.buildCommand`). For pure static: confirm the files exist.
67
+ 2. **Confirm the publish dir + entry load.** `dist/index.html` (or your entry) exists and is non-trivial. Where a preview or browser tool is available, load it and confirm it renders; otherwise inspect the built HTML for the expected title/nav/hero and that the mobile viewport meta is set.
68
+ 3. **R-ID coverage.** Walk TRACEABILITY.md: each R-ID is implemented (with a pointer) or explicitly deferred (with a reason). Put the counts in `build.json.requirements`.
69
+ 4. **SEO check.** Confirm the SEO files are actually in the publish dir; set `build.json.seo.*` from reality, not intent.
70
+ 5. **Report** what is built and what is deferred, plainly, in chat.
71
+
72
+ ## Honest about being model-built
73
+
74
+ If you cannot fully build it (the spec is thin, a requirement you genuinely cannot satisfy, time or tool limits), ship **the best static slice that loads** plus DECISIONS.md noting every gap and TRACEABILITY.md marking the unbuilt R-IDs deferred. A partial, honest, deployable artifact beats a complete-looking broken one. **Never fake a green check** - `verified.buildRan: false` is an acceptable, honest value. If even a slice is too much, run **tl_scaffold** to leave a real deployable landing page as the floor.
75
+
76
+ ## Leave a manifest and your decisions
77
+
78
+ Write three files with your own file tools (the manifest is NOT a `tl_state` artifact - it is a plain sidecar):
79
+ - `.thought-layer/build.json` - the deploy contract, co-located with the state file you read. Shape (fill what you can, never fake a field):
80
+
81
+ ```jsonc
82
+ { "app": "thought-layer", "kind": "build", "version": 1, "builtAt": "<ISO>",
83
+ "producer": "agent", "publishDir": "dist", "entry": "index.html",
84
+ "stack": "static|vite|astro|next-static|other", "hasBackend": false, "backendNote": null,
85
+ "buildCommand": null, "installCommand": null, "nodeVersion": "20",
86
+ "provenance": { "stateFile": "<the file you read>", "prdTs": <state.prd.ts>, "grillDone": <bool>, "fromSpeedrun": <bool> },
87
+ "requirements": { "total": 0, "built": 0, "deferred": 0, "deferredIds": [] },
88
+ "seo": { "jsonLd": true, "llmsTxt": true, "sitemap": true, "robots": true, "canonical": true, "openGraph": true, "socialImage": false, "semanticHtml": true, "seoDoc": true, "netlifyToml": true },
89
+ "artifacts": { "traceability": "TRACEABILITY.md", "decisions": "DECISIONS.md", "seo": "SEO.md" },
90
+ "verified": { "buildRan": true, "publishDirExists": true, "entryLoads": true, "notes": "..." } }
91
+ ```
92
+ `publishDir` + `entry` are load-bearing - the deploy step reads them. (The `tl_scaffold` tool writes this same manifest with `producer: "scaffold"`.)
93
+ - `DECISIONS.md` - every choice the spec did not pin down, one line each, with the reason.
94
+ - `TRACEABILITY.md` - the R-ID map. (`SEO.md` comes from the SEO step.)
95
+
96
+ ## Persisting
97
+
98
+ The build output and the three files live on disk; the portable `state.json` stays focused on validation and design. The only optional state touch is a best-effort cursor bump - `tl_state cursor` (or `tl cursor`) with `{ "backboneStage": 15, "phase": "built" }` - pure provenance; if the tool is absent or it fails, the build still succeeded. Tell the user where the artifact is (`<publishDir>`) and that the next step is the deploy - the **thought-layer-deploy** skill (`/tl-deploy`) or the `deploy` tool / `tl deploy` CLI, which reads `build.json` and takes it live to a URL they own.
@@ -0,0 +1,49 @@
1
+ ---
2
+ name: thought-layer-deploy
3
+ description: "Take the built site live to a user-owned URL with no lock-in, the last step after the build. Reads .thought-layer/build.json (the publish dir + entry) next to the state file, then deploys to Netlify by one of two BYOK models: with NETLIFY_AUTH_TOKEN set it deploys into the user's OWN account via the file-digest API (owned immediately, no claim), and with no token it uses the Netlify CLI's own --allow-anonymous flow for an instant live URL plus a one-hour claim link. Static-first: if build.json says hasBackend it warns that only the front end ships this way. Prefers the deploy tool (Pi) or the tl deploy CLI (any shell agent) so the deploy is one mechanical, honest step, never hand-rolled. Run it after thought-layer-build (or tl_scaffold) has produced build.json."
4
+ ---
5
+
6
+ # Deploy it: the build goes live to a URL you own
7
+
8
+ This is the last step of the loop: rigor -> spec -> build -> **a live, owned URL**. You are not writing a deploy script by hand; you run the kit's deploy tool, which reads the build manifest and takes the publish directory live. BYOK, nothing phones a central account, no lock-in.
9
+
10
+ ## Precondition: a build must exist
11
+
12
+ The deploy reads `.thought-layer/build.json` (written by the **thought-layer-build** skill or the **tl_scaffold** tool), co-located with the state file. Honor the same selection as the rest of the kit: an explicit `--path` / tool `path` wins, then `THOUGHT_LAYER_STATE`, then the default `.thought-layer/state.json`.
13
+
14
+ - **Required:** a `build.json` with a `publishDir` that exists on disk. If it is missing, **stop** and point the user at `/tl-build` (full build) or the `tl_scaffold` tool (an instant deployable landing floor). Do not invent a publish dir.
15
+ - `build.json.publishDir` + `entry` are load-bearing; `hasBackend` decides the static-only warning below.
16
+
17
+ ## How to run it
18
+
19
+ Use the tool, never a hand-written `curl`/`netlify` invocation:
20
+ - **Pi:** the `deploy` tool. Start with `deploy { dryRun: true }` to show the plan, then `deploy {}` to go live (add `anonymous: true` to force the no-account path, `siteId` to re-deploy to the same site).
21
+ - **Any shell agent (Claude Code, CI, a plain terminal):** `tl deploy` (via `npx -y @hobocode/thought-layer tl deploy`). Use `--dry-run` first, then `tl deploy`. Flags: `--anonymous`, `--name <slug>`, `--site <id>`, `--path <file>`.
22
+
23
+ Always **dry-run first** and show the user the file list and the target, then deploy.
24
+
25
+ ## The two models (both keep ownership with the user)
26
+
27
+ The tool picks automatically; explain which one ran.
28
+
29
+ - **BYO token (the default when `NETLIFY_AUTH_TOKEN` is set).** Deploys straight into the user's own Netlify account via the file-digest API (no zip, no extra dependency). The site is theirs from the first second - **no claim step**. Re-deploy to the same site with `--site <id>` (the id is in the deploy output and `deploy.json`). The token is read **only from the environment** - never ask the user to paste it into the chat, and never put it in a tool parameter or a file.
30
+ - **Anonymous (when no token is set).** Delegates to the Netlify CLI's `netlify deploy --allow-anonymous` (Netlify's own supported flow) for an instant live URL plus a **one-hour claim link** that transfers ownership to whatever account the user logs into. We never reverse-engineer that handshake. It needs a current Netlify CLI (`npm i -g netlify-cli@latest`; the flag shipped 2026-03); if the CLI is missing or too old, the tool says exactly what to do (set a token, update the CLI, or drag the publish dir onto https://app.netlify.com/drop).
31
+
32
+ If neither a token nor a usable CLI is available, relay the tool's guidance honestly instead of pretending it deployed.
33
+
34
+ ## Static-first honesty
35
+
36
+ The default deploy publishes a **static** publish directory. If `build.json.hasBackend` is `true`, the tool warns and you must repeat it plainly: only the front end goes live this way; the server part needs serverless functions or a separate host. Do not imply a backend is running when it is not.
37
+
38
+ ## After it is live
39
+
40
+ Report, plainly:
41
+ - the **live URL**, and (anonymous only) the **claim link** with the one-hour window called out;
42
+ - for the token path, that it is already owned by their account and how to re-deploy (`--site <id>`);
43
+ - that a `.thought-layer/deploy.json` record was written (URL, mode, site/deploy ids) next to `build.json`.
44
+
45
+ If the tool returned `ok: false`, do not claim success - surface its message (the next honest step) verbatim.
46
+
47
+ ## Persisting
48
+
49
+ The deploy is a real-world side effect, not validation state, so it lives in `deploy.json` on disk, not in the portable `state.json`. The only optional state touch is a best-effort cursor bump - `tl_state cursor` (or `tl cursor`) with `{ "phase": "deployed" }` - pure provenance; if it fails, the deploy still happened.
@@ -89,6 +89,8 @@ This is where "how will it actually be built" gets answered. Every implementatio
89
89
  14. **The PRD (draft).** Run the **thought-layer-prd** skill: compose a complete first-draft PRD — including a first-cut domain glossary and testable requirements — from the validated idea and the business model above.
90
90
  15. **The Grill.** Run the **thought-layer-grill** skill: grill that draft PRD. Challenge it against the domain one question at a time, sharpen the glossary, surface contradictions, unstated rules, and edge cases, and update the PRD inline until it is build-ready.
91
91
 
92
+ Once the grill has hardened the PRD, the spec is build-ready. Run the **thought-layer-build** skill (`/tl-build`) to turn it into a static-first, deploy-ready artifact (or the `tl_scaffold` tool for an instant deployable landing-page floor). This is the build step, not another validation stage.
93
+
92
94
  ## Supporting passes (run when relevant)
93
95
 
94
96
  Not strictly sequential; pull them in when they help: market research on the segment, a SWOT once the picture is full, and **thought-layer-naming** plus domain checks when the thing needs a name. These inform the stages above; they do not replace them.
@@ -49,6 +49,8 @@ The speedrun gets you all the way to a real spec, fast. The two design steps are
49
49
 
50
50
  What you get is a fast first draft built on a gut-check, not a validated spec. Re-running the grill under `/tl`, with the panels behind it, is what turns it into something to bet on.
51
51
 
52
+ With the PRD hardened you have a build-ready spec - run the **thought-layer-build** skill (`/tl-build`) to build it, or the `tl_scaffold` tool for an instant deployable landing page. (A speedrun spec is a gut-check; re-grill under `/tl` before you bet on it.)
53
+
52
54
  ## Persisting
53
55
 
54
56
  Write to the state file as you go, like the full framework (see the **thought-layer-framework** skill's "Saving and resuming"): answers via op `answer`, the bizModel / naming / brand / prd / grill via op `artifact`, and a cursor via op `cursor` after each stage so the file resumes and upgrades cleanly (use the backbone stage numbers - the spine maps to stages 1, 3, 4, 9, 10, then the PRD is 14 and the grill 15; set `phase` to `speedrun`). The default file is `.thought-layer/state.json`; to keep several ideas apart, pass `--path .thought-layer/<name>.json` (or the tool's `path`) on every op, or set `THOUGHT_LAYER_STATE`, and use `list` to see what is already there. **Do not write graded feedback** - the speedrun does not rank, so there are no panel verdicts to store and the answers persist ungraded.