Skip to content

Bundle the Builder Code starter as a core template layered over Chat - #6936

Merged
samijaber merged 13 commits into
mainfrom
claude/fusion-starter-template
Oct 8, 2026
Merged

samijaber merged 13 commits into
mainfrom
claude/fusion-starter-template

Conversation

@samijaber

@samijaber samijaber commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Why

BuilderIO/builder-agent-native-starter (what Builder Code clones) is templates/chat turned into a blank app by a patch: about 26 exact search/replace edits, an overlay and about 40 deletions, run on every sync. Each upstream wording change broke it: starter #409, #413, then #6872 (20 broken edits, fixed in starter #415). This replaces #6930. It keeps the starter in sync with Chat, and when a Chat change conflicts with the starter's, CI fails in that Chat PR instead of in a later starter sync.

Design: a patch layer over Chat

A bundled template can declare template-layer.json with { base, delete }. template materialize then:

  1. copies the base template (templates/chat)
  2. deletes the listed paths: the Chat shell, i18n, changelog, and the settings/extensions/team routes
  3. applies each <file>.patch, a unified diff applied with diff (jsdiff 9), to the base's <file>
  4. copies the layer's other files as new files
  5. merges the layer's partial package.json into Chat's as JSON

A whole layer file that would overwrite a regular base file is an error. The only file stored whole that replaces something is CLAUDE.md, which replaces Chat's symlink. Deletes, new files and patches must all resolve inside the scaffold, including through symlinks.

Keeping it in sync with Chat:

  • A Chat edit away from the patched lines applies automatically. Patches tolerate line offsets.
  • A Chat edit to the patched lines fails guard:template-layers. It runs in pnpm guards, so on every PR, and takes about a second with no install: it applies every bundled layer to its base in a temp dir. The Scaffold E2E install-plus-typecheck step also runs on Chat changes.
  • Fixing a broken patch: run pnpm template-layer rebase builder-code-starter --out .tmp/fs. It 3-way merges each patched file across the old base (origin/main) and the new base, leaving conflict markers only where both sides changed the same lines. Resolve those, then run pnpm template-layer diff builder-code-starter --from .tmp/fs to regenerate the patches. To edit the starter itself: pnpm template-layer expand builder-code-starter --out <dir>, edit, then diff.
  • AGENTS.md tells anyone editing Chat to run the guard and how to rebase, tracked by the new starter-patch-drift friction key.

builder-code-starter contents (35 files):

  • 20 patches: the 5 Chat skills it changes, AGENTS.md, DESIGN.md, DEVELOPING.md, README.md, _index.tsx (blank canvas), root.tsx, Layout.tsx, the navigation actions and hook, agent-native.config.ts, the manifest, the guarded migrate-production.ts, and the agent-chat/auth plugins. appId stays Chat's, so the scaffold rewrites it to the app's name, in standalone and workspace scaffolds alike.
  • 13 new files: 3 starter-only skills; the Drizzle files, including an empty drizzle/migrations/meta/_journal.json so the release db:migrate is a no-op on a fresh app; server/db.ts; the app-operation guard (unchanged from the starter's current one); config.ts with homePath: "/"; a small pnpm-workspace.yaml; and CLAUDE.md. drizzle.config.ts uses DATABASE_URL_UNPOOLED, then NETLIFY_DATABASE_URL_UNPOOLED, for the direct migration connection.
  • a partial package.json: name, description, dev without --open, db:generate/db:migrate, migrate:production chained with pnpm db:migrate, the Drizzle/Neon/dotenv deps, drizzle-orm 0.45.2, the pnpm 10.14 pin, and scaffold.template: "builder-code-starter"

Everything else is Chat's on every sync: config, UI, CSS, icons, server wiring, netlify.toml, specs, skill references, 13 whole skills, and dependency bumps.

Supporting changes:

  • Skills: a builder-code-starter policy (the Chat starter set plus 3 skills; turn-into-app, turn-into-skill, workspace-conventions and self-modifying-code are not in it). For a layered template, skills update scaffold builds the skill sources as Chat's skill folders plus the layer's patches, so it works from the published package.
  • Copy paths: create's local copy uses the same layer-aware copy. materialize --to <ref> fetches the layer from core's template folder and its base from templates/ at the same ref.
  • Mirror: push-starter-template.yml materializes --template builder-code-starter --name app and triggers on templates/chat/** and the layer.
  • guard-no-unscoped-queries: the table regex is anchored to the line start, so commented-out example tables no longer register as ownable.
  • Changeset: core patch. diff is added to core dependencies.

Verification

  • pnpm guards: all 88 checks pass. oxfmt is clean and core tsc --noEmit passes.
  • 226 core CLI tests pass, including:
    • template-layer.spec.ts: patch, add, delete and merge; a base edit away from the patch still applying; a base edit to the patched lines failing; a whole-file override being refused; a symlink replaced safely; deletes, new files and patches refused when they would escape the scaffold through .., an absolute path or a symlink; subtree-only application
    • a --to <ref> layer download test with a fake downloader
    • a workspace scaffold giving the starter's agent its own appId
  • Classifier tests: 43 passed.
  • Guard loop, tested end to end:
    1. I edited a patched import in Chat's root.tsx and the end of Chat's DEVELOPING.md.
    2. guard:template-layers failed and printed the fix command.
    3. rebase reported conflicts in exactly those two files.
    4. After resolving them, diff regenerated the patches and the guard passed.
    5. I reverted the test edits afterwards.
  • With local core: pnpm install, typecheck, build and agent-native doctor are clean, and pnpm db:migrate succeeds on a fresh tree.
  • skills update scaffold: the patched skills and Chat's skills are both restored correctly, 21 skills in total, and none of the 4 excluded skills appear.

Rollout

  1. Merge starter PR Drop the starter patch; template is the bundled builder-code-starter builder-agent-native-starter#416 (base template) and this PR.
  2. Wait for the next core release. The mirror pushes only once that version is on npm, and it's the first core whose skills update knows the builder-code-starter policy.
  3. The first sync opens a sync-conflict PR. This is expected: main holds the old patch output. Resolve it with
    git checkout origin/template -- $(git ls-tree -r --name-only origin/template | grep -v '^\.github/')
    This was verified in a clone of starter main (2866f5e): install, skills update, dedupe, typecheck, build and doctor pass, and the next template push merges with no conflicts.
  4. Existing Builder Code apps keep scaffold.template: "chat" and keep Chat's skills.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GM6TRjcTQFm6kUVaAeSgzh

BuilderIO/builder-agent-native-starter turned templates/chat into a blank
app with ~26 exact search/replace edits, an overlay, and a delete list on
every sync, so each upstream wording change broke the starter. The finished
tree now lives in core as packages/core/src/templates/fusion-starter and
the mirror materializes it directly.

- The template ships only the eight skills it changes. Its scaffold policy
  (sourceTemplate fusion-starter, additionalSourceTemplates chat) lets
  materialize fill in the other Chat starter skills and lets
  `skills update scaffold` keep the overrides; turn-into-app,
  turn-into-skill, workspace-conventions and self-modifying-code stay out.
- rewriteNetlifyToml leaves a template's own standalone netlify.toml alone,
  so the starter keeps `pnpm migrate:production && pnpm db:migrate`.
- push-starter-template.yml materializes fusion-starter as "app".
- Scaffold E2E materializes it against local core and runs install and
  typecheck when core changes; a PR step warns when a Chat skill that the
  starter overrides changes.
- guard-no-unscoped-queries no longer registers commented-out example
  tables as ownable.
@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Visual recap — generation failed

Recap authentication failed — the PLAN_RECAP_TOKEN secret may be expired or revoked. Re-mint it with npx -y @agent-native/core@latest reconnect <app-url> (or npx @agent-native/core@latest connect <app-url> for first-time setup) and update the repo secret.

@builder-io-integration builder-io-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Builder reviewed your changes and found 4 potential issues 🔴

Review Details

Code Review Summary

This PR packages the Fusion starter as a first-class Core scaffold, adds inherited skill handling, updates template publishing and CI coverage, and preserves standalone Netlify migration commands. The overall direction is sound: keeping the starter tree in Core removes brittle textual patching, while materialization and skills tests exercise important lifecycle behavior. This is a standard-risk change because it modifies scaffold/runtime behavior and release wiring.

Key findings

  • 🔴 HIGH — Fresh scaffold install fails: the package manifest uses catalog: dependency specifiers, but its bundled pnpm workspace config defines no catalog, so standalone installation cannot resolve dependencies.
  • 🟡 MEDIUM — App identity mismatch: provider requests remain scoped to chat, and agent chat remains scoped to app, after scaffolding under a different name; this can break per-app connection grants and scope chat state incorrectly.
  • 🟡 MEDIUM — Netlify migrations may use pooled DB URL: the build command does not map Netlify's unpooled URL to the variable read by Drizzle config.
  • 🟡 MEDIUM — Thread navigation targets a missing route: the starter accepts threadId and resolves it to /chat/:id, but ships no chat route.

🧪 Browser testing: Will run after this review (PR touches UI code).

Comment thread packages/core/src/templates/fusion-starter/package.json Outdated
Comment on lines +5 to +6
const runtime = createProviderApiRuntime({
appId: "chat",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Provider requests use Chat’s app authorization scope

This runtime (and the factory options at line 42) hardcodes appId: "chat", but the scaffold rewrites the agent-chat plugin to the destination app ID and does not rewrite this action. Provider connection grants are scoped by app ID, so a generated app may fail to use its own grants or resolve connections granted to Chat instead; derive both values from the generated app identity.

Fix in Builder

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not changing this here. actions/provider-api-request.ts now comes straight from templates/chat; the starter layer no longer carries its own copy. The hardcoded appId: "chat" is the same in every app scaffolded from Chat, so it's a Chat template issue, and fixing it in Chat would also fix the starter. Leaving it out of this PR to keep its scope to the starter.


Generated by Claude Code

Comment thread packages/core/src/templates/fusion-starter/server/plugins/agent-chat.ts Outdated
Comment thread packages/core/src/templates/fusion-starter/netlify.toml Outdated
The first version bundled the whole starter tree, so 46 non-skill files and
every dependency pin were frozen copies of Chat that would drift. A bundled
template can now declare a template-layer.json: materialize copies its base,
deletes the listed paths, replaces whole files and skill folders with the
layer's own, and merges the layer's package.json fields into the base's.

fusion-starter keeps only the 33 files it changes plus a partial
package.json, so Chat's config, UI primitives, icons, server wiring,
netlify.toml and dependency bumps reach the starter on every sync.
db:migrate runs through the migrate:production script, which removes the
netlify rewrite exception, and the inherited-skill fill-in is gone because
the base copy brings Chat's skills. The layer inherits Chat's workspace
overrides.

CI now also runs the starter scaffold step for templates/chat changes, the
mirror triggers on them, and the PR annotation warns about any Chat file or
skill the layer overrides.
@samijaber samijaber changed the title Bundle the Fusion starter as a core scaffold template Bundle the Fusion starter as a core template layered over Chat Oct 7, 2026
builder-io-integration[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor Author

Two checks were red on a32a060. Neither is caused by this PR:

  • Test-title production guard: main workflow runs scripts/guard-test-title-production.ts, which landed on main after this branch was cut. I merged main (7d06ba9), so the script is now present. The guard only applies to titles starting with test:, so it passes here.
  • Design E2E / Runtime budget: every gate passes, but the job exits 2 with "Could not measure: zoomCycle did not take effect". That's in Design's browser measurement (templates/design/scripts/runtime-budget.ts), which this PR doesn't touch; it runs here only because core changed. The same step failed the same way on unrelated Clips PR Clarify Clips Builder storage setup and retry guidance #6947 (run 37657900943), while other PRs passed it in the same hour. No fix for it exists yet. The push of 7d06ba9 re-runs it.

Generated by Claude Code

builder-io-integration[bot]

This comment was marked as outdated.

Whole-file overrides of Chat files still drifted: once the starter owned
Layout.tsx or DEVELOPING.md, Chat's later edits to them never arrived. The
layer now stores each change to a Chat file as a unified diff
(`<file>.patch`, applied with jsdiff). Chat edits away from the patched
lines flow through on every materialize; an edit to the patched lines makes
the materialize fail, so the Scaffold E2E step catches it in the Chat PR
that caused it. A layer file that would replace a Chat file wholesale is now
an error; only new files and replaced symlinks (CLAUDE.md) are stored whole.

`skills update scaffold` assembles a layer's skills the same way, Chat's
skill folders plus the layer's patches, so the identical reference files no
longer need copies. `pnpm template-layer expand|diff` turns a layer into an
editable tree and back. The advisory override annotation is gone: a stale
patch now fails the build instead.
builder-io-integration[bot]

This comment was marked as outdated.

…indings

guard:template-layers applies every bundled layer to its base in a temp dir (about a second, no install) so a templates/chat edit that breaks a fusion-starter patch fails in that PR. When it does, pnpm template-layer rebase three-way merges each patched file across the old and new base, then diff turns the resolved tree back into patches. AGENTS.md points Chat editors at both, under a new starter-patch-drift friction key.

Review fixes: ship an empty drizzle/migrations journal so the release migrate:production && db:migrate is a no-op on a fresh app; keep Chat's agent-chat appId in the patch so the scaffold rewrites it to the app's name; drizzle.config falls back to NETLIFY_DATABASE_URL_UNPOOLED for the direct migration connection; create's local copy goes through the same layer-aware copy as materialize, and materialize --to <ref> fetches a layer from core's template folder and its base from templates/ at the same ref.

Copy link
Copy Markdown
Contributor Author

Two more checks failed on 7d06ba9 (the main merge). Neither is caused by this PR, and both passed on a32a060:

  • Generate + run standalone Chat: a Playwright timeout waiting for "Approval continuation completed." in Chat's AgentKit approval smoke test (qa-standalone-chat-dev-smoke.ts:2982). This PR doesn't change how a non-layer template such as Chat is copied or scaffolded.
  • Content DB tests: one of 14 two-tab convergence tests got read ECONNRESET from Content's dev server (e2e/helpers.ts:60); the other 13 passed. This PR touches no Content code.

No fix exists for either yet. bda9cb3 re-runs both.


Generated by Claude Code

@samijaber
samijaber requested review from a team and sidmohanty11 and removed request for a team October 7, 2026 18:14
@builder-io-integration

Copy link
Copy Markdown
Contributor

⚠️ Review Agent ran into a problem and couldn't finish reviewing the latest commit.

We've been automatically notified and are looking into it. Push a new commit to re-trigger the review, or contact support@builder.io if this keeps happening.

Error ID: 3200040dbf2442f6808955769e560f6f

@samijaber
samijaber requested a review from BenGirone October 7, 2026 18:14
Comment thread package.json
"guard:beta-e2e-suite": "tsx scripts/guard-beta-e2e-suite.ts"
"guard:beta-e2e-suite": "tsx scripts/guard-beta-e2e-suite.ts",
"template-layer": "tsx scripts/template-layer.ts",
"guard:template-layers": "tsx scripts/template-layer.ts check"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a verification script that agents can use to verify that changes to Chat app don't break any of the diffs built on top of them

Comment thread AGENTS.md Outdated
Comment on lines +372 to +377
- The Fusion starter (`packages/core/src/templates/fusion-starter`) is patches
over `templates/chat`. After editing Chat, run `pnpm guard:template-layers`.
If a patch fails, run `pnpm template-layer rebase fusion-starter --out
.tmp/fs`, resolve conflicts keeping the starter's intent, then
`pnpm template-layer diff fusion-starter --from .tmp/fs`. Never delete a
failing patch.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instructions so agents know how to test their Chat changes vis-a-vis the fusion-starter overrides

builder-io-integration[bot]

This comment was marked as outdated.

The starter removes Chat's /chat/:threadId route, but its navigation hook still turned a navigate threadId into that URL and the navigate action still advertised the option. Both are gone, along with the inherited spec case that asserted it. Layer delete entries must now resolve inside the destination, so a stray ../ or absolute path in template-layer.json fails instead of deleting outside the scaffold.

Copy link
Copy Markdown
Contributor Author

CI failures on bdd8803:

  • Generate + run standalone Chat failed the same way a second time: a 30 s Playwright timeout waiting for "Approval continuation completed." (qa-standalone-chat-dev-smoke.ts:2982). A second failure counts as real, so I reproduced it locally: AGENT_NATIVE_CREATE_USE_LOCAL_CORE=1 pnpm qa:agentkit-chat-acceptance on this branch, with a fresh core build, passes, including "no-spend loopback → hello action → HITL → queue → rich reload". This PR doesn't change how Chat is copied or scaffolded: Chat isn't a layer, so copyTemplateTree falls straight through to copyDir. The job passed on a32a060 and started timing out only after merging main (df89804). PR feat: govern org service principals and export audit as OCSF #6895 on the same main was still running this job when I checked. 0755e74 re-runs it.
  • Fast tests lane-4: templates/content/shared/search-title-ranking.test.ts measured 54.5 ms against a 50 ms wall-clock limit on a shared runner. This PR doesn't touch Content. 0755e74 re-runs it.

Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

SSR cold-start smoke failed on bda9cb3 with "plan: FAILED — import took 3007ms (budget 3000ms)", 7 ms over Plan's budget. Clips (927 ms) and Assets (840 ms) passed in the same run. This PR doesn't touch Plan's server bundle: its only new dependency, diff, is imported only by the CLI's template-layer.ts. The check passed on a32a060, and 0755e74 re-runs it.


Generated by Claude Code

builder-io-integration[bot]

This comment was marked as outdated.

The lexical containment check let a delete entry under an in-destination symlink to an outside directory reach that directory, because rmSync follows the symlinked parent. The real path of the nearest existing parent must now also stay inside the real destination.
builder-io-integration[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor Author

SSR cold-start smoke: Plan failed at 3008 ms against its 3000 ms budget, on e64aef3 and 3007 ms on bda9cb3. This PR didn't cause it. I built Plan for Netlify locally from this branch and from its origin/main merge base, then compared them:

  • Statically imported closure of main.mjs: identical on both builds, at 415 files and 11,682,732 bytes. The new template-layer and diff code only lands in a CLI chunk loaded lazily from create/skills, so it's outside that closure.
  • Lockfile: the only change is a specifier for diff@9.0.0, which was already resolved. No other version changed.
  • NETLIFY=true node scripts/ssr-boot-smoke.mjs plan, 3 runs each on the same machine: 3785–4087 ms on main, against 3537–3718 ms on this branch.

Plan's import time sits right at the budget on main itself, so the 1–8 ms overage here is runner variance. No fix exists yet to port into this PR.


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

Generate + run standalone Chat: failed on 1fc3a49. This PR didn't cause it.

  • Same failure on an unrelated PR: it ends with three Unexpected loopback prompt: "" errors and an empty approvalActionResults, after the accept-agentkit-release … wait for my approval turn. Track LLM and Slides generation timing #6953 (run 37664517368, job 112940876781) fails with exactly the same signature, from qa-standalone-chat-dev-smoke.ts:3966.
  • Untouched code: the job scaffolds plain chat, which has no template layer. 1fc3a49 only changed template-layer.ts and the fusion-starter branch of workspacifyApp, and neither runs for that scaffold.
  • Intermittent here too: the same job passed on e64aef3.

No fix exists yet to port. I'm re-running the failed job once.


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

Content DB tests: failed on 1fc3a49, but this PR didn't cause it. One Playwright case failed: templates/content/e2e/two-tab-convergence.spec.ts:437, "Keep Mine retains a peer title adopted while an editor recovery draft is pending". The other 13 two-tab cases passed. At line 558 the journal snapshot still held the first peer title (Peer title first …) instead of the final one. That's a beta-cadence timing race in Content's editor recovery.

This PR doesn't touch templates/content or any runtime code Content loads. It only changes CLI scaffolding (template-layer.ts, create.ts, template-sync.ts, skills.ts, workspacify.ts) and the bundled fusion-starter template. No fix exists yet to port. Once the run finishes I'll re-run the failed jobs once, along with the SSR smoke and standalone Chat failures explained above.


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

Design canvas interaction acceptance: failed on 1fc3a49, but this PR didn't cause it. Two Design editor Playwright cases couldn't find their elements:

  • e2e/inspector-styles.spec.ts:609, the Lato Medium font search case
  • e2e/position-alignment.spec.ts:484, Frame-relative position through Groups

After that, the "Run focused Design regression cases" step hit its 10-minute timeout. The same job also failed on #6974 (job 112940307814), an unrelated PR. This PR doesn't touch templates/design or any runtime code it loads. No fix exists yet to port. It gets the same single re-run as the other failed jobs once this run finishes.


Generated by Claude Code

@builder-io-integration builder-io-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Builder reviewed your changes and found 3 potential issues 🔴

Review Details

Code Review Summary

This PR packages Fusion as a Chat-derived template layer, adds patch drift checks and materialization support, and adapts the generated app shell, starter guidance, migration setup, and workspace identity. The layering approach is sound overall: unified patches preserve upstream changes outside edited regions, while the guard and materialization tests make drift visible. I classified this as high risk because template application writes filesystem content and the PR changes generated app authorization/identity behavior.

Key findings

  • 🔴 HIGH — A patch can follow a symlink at its final target and write outside the scaffold destination.
  • 🟡 MEDIUM — Any tool-search call bypasses the app-operation guard, even when unrelated.
  • 🟡 MEDIUM — Common domain operations such as archiving are not recognized by the guard.
  • Lower priority: the generated PWA manifest retains the generic App name.

Two prior findings were verified fixed and their threads resolved: symlinked-ancestor writes and workspace Fusion agent-chat identity. The existing provider authorization-scope finding remains open and was not reposted.

🧪 Browser testing: Skipped — the browser-test-planner agent is unavailable; the dev server is running.

Comment thread packages/core/src/cli/template-layer.ts

const requestText = context.requestText ?? latestUserText(context.messages);
if (!looksLikeDomainOperationRequest(requestText)) return null;
if (calledToolSearch(context.toolCalls)) return null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Unrelated tool searches bypass the app-operation guard

This returns as soon as any tool-search call appears, without checking that it searched for the requested operation or found no matching action. An unrelated search therefore lets a domain-operation request proceed to a source-code handoff without the guard's intended action check.

Fix in Builder

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not changing this in this PR. server/agent/app-operation-guard.ts is byte-identical to the guard the starter already ships on builder-agent-native-starter main; this PR moves the file into Agent-Native without changing its behavior. It's a best-effort nudge, not an authorization boundary: it retries once with expandToolSurface, and a tool-search call counts as the agent having looked for an action. Requiring the search to match the request would be a separate behavior change to the starter's agent, so it belongs in its own PR.


Generated by Claude Code

} from "@agent-native/core/server";

const DOMAIN_OPERATION_PATTERN =
/\b(create|add|make|build|update|edit|delete|remove|list|show|open|save|publish|submit|send|export|import)\b/i;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Common domain operations bypass the app-operation guard

The operation matcher omits common verbs such as archive, restore, and complete. Requests using those verbs can receive a source-code handoff without triggering this guard; include the operations the starter expects to route through app actions or use a less partial intent check.

Fix in Builder

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not changing this in this PR, for the same reason as the thread above. The verb list is the starter's existing guard, carried over byte for byte. Adding archive/restore/complete would change which requests trigger a retry for every starter app. That's worth doing as its own tuning change, so this PR stays a move with no behavior change.


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

Slides caret navigation in macOS: failed on 394af2d during pnpm install, before any test ran. It's not this PR's. canvas@3.2.3 couldn't build its native module on the macOS runner (Package 'pangocairo' not found, gyp ERR! configure error). This PR's lockfile change is only the diff specifier, and canvas is untouched. I'll re-run the job once when this run finishes.


Generated by Claude Code

builder-io-integration[bot]

This comment was marked as outdated.

@BenGirone BenGirone left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

But I would want to see an end to end experiment before we merge this. Things I'd rather see than believe:

  • This results in the same or similar enough template being generated.
  • Results in the builder code harness are the same or better.

Seeing as every builder-code harness project uses this template, I don't think its too big of an ask for a full demo as a sanity check validation. Could permanently mess up some projects if there's something off

Comment thread .changeset/bundled-fusion-starter-template.md Outdated
@samijaber samijaber changed the title Bundle the Fusion starter as a core template layered over Chat Bundle the Builder Code starter as a core template layered over Chat Oct 7, 2026
Scaffold guidance only recognized catalog templates, so builder-code-starter apps lost templateRef, templateSource, coreVersion and shape. A layer now counts as first-party through its base, matching what starter main records today.

Copy link
Copy Markdown
Contributor Author

@BenGirone Here's part 1 of the end-to-end check: is the generated template the same or similar enough?

I ran the mirror's command (template materialize --template builder-code-starter --name app) and simulated the first sync onto starter main (2866f5e): take the template side, then skills update scaffold --project. Then I diffed the result against today's main. 20 paths differ, excluding .github and the lockfile. All main-only files stay, including builder.config.json, scripts/maybe-migrate.mjs and the 7 extra skills on main.

Difference Why
package.json scaffold.template becomes builder-code-starter. migrate:production now runs db:migrate itself, and netlify.toml drops its separate call, so the effective command is the same. The duplicate drizzle-orm key collapses to the 0.45.2 that already won, and drizzle-kit/postgres move position. The description changes.
app/lib/app-config.ts, DEVELOPING.md main still says chat/Chat because the old patch missed them. The new output says app/App.
actions/navigate.ts + spec, app/hooks/use-navigation-state.ts The dead threadId → /chat/:id route is removed, since the starter deletes that route.
drizzle/schema.ts, crud-action-example.ts, server/db.ts (all comment-only examples), drizzle.config.ts The examples now use PostgreSQL builders and owner-scoped sharing, and migrations fall back to NETLIFY_DATABASE_URL_UNPOOLED.
root.tsx, migrate-production.ts, pnpm-workspace.yaml Chat's current content: a guard pragma comment, import order, and key order with the same entries.
build-an-app skill Its edit-points-chat.md reference is removed, and one line names the starter.
learnings.md, server/plugins/agent-native-email-branding.ts (new) Standard files every scaffold gets.

This check found one real regression, now fixed in 78a6c8d. Bundled layer templates weren't treated as first-party, so the starter's package.json lost the templateRef/templateSource/coreVersion/shape provenance that main records today, which template sync and upgrades read. It's covered by the materialize test now.

Part 2 is the Builder Code harness run. I'm pushing the post-sync tree to a throwaway temp-sami-experiment branch of the starter repo, so a local ai-services can clone it and we can record a clip.


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

The experiment branch for the end-to-end clip is ready: BuilderIO/builder-agent-native-starter@temp-sami-experiment, commit cecf601.

It's starter main (2866f5e) after the first sync from this PR's head 78a6c8d. The steps were: materialize builder-code-starter, overlay it onto main, then skills update scaffold --project, which keeps 21 managed skills and leaves main's 7 extra skills in place, as a real merge would, since template never had them. On the branch, pnpm install, pnpm typecheck and pnpm build all pass, and the lockfile is unchanged. git diff --stat origin/main...temp-sami-experiment shows 17 files, +136/−126: the same set as the table above, plus the deleted edit-points-chat.md reference.

To record the Builder Code clip, point a local ai-services at this branch instead of main. It's a throwaway branch with no PR; delete it after the demo.


Generated by Claude Code

@builder-io-integration builder-io-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Builder reviewed your changes and found 2 potential issues 🟡

Review Details

Incremental Code Review Summary

This update renames the bundled layered scaffold from Fusion to Builder Code and updates its template paths, workflow labels, tests, skill policy, and metadata. The layer-materialization architecture and the previously added symlink-target containment check remain intact. I classified this as high risk because template application mutates generated filesystem trees and the PR includes authorization-sensitive behavior.

New findings

  • 🟡 MEDIUM — Workspace materialization deletes the Builder Code starter’s local internationalization skill and does not restore it.
  • 🟡 MEDIUM — The unscoped-query guard’s anchored regex misses valid exported table declarations later on a line.
  • Lower priority: template-layer check derives its path from encoded URL.pathname, failing for checkout paths with spaces or non-ASCII characters.

The open provider-scope and two app-operation-guard findings remain unchanged and were not reposted. A prior symlink patch-target issue is resolved. Some parallel review agents timed out; findings from the completed agents were reviewed and aggregated.

🧪 Browser testing: Skipped — the browser-test-planner agent is unavailable; this PR includes UI changes.

Comment thread packages/core/src/cli/workspacify.ts
/export\s+const\s+([a-zA-Z_$][\w$]*)\s*=\s*(?:[a-zA-Z_$][\w$]*Table|table)\s*\(\s*"([^"]+)"\s*,\s*\{/gm;
// Anchored to the line start so a commented-out example table in a
// template does not register its name as ownable repo-wide.
/^[ \t]*export\s+const\s+([a-zA-Z_$][\w$]*)\s*=\s*(?:[a-zA-Z_$][\w$]*Table|table)\s*\(\s*"([^"]+)"\s*,\s*\{/gm;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 The table guard misses inline exported declarations

Anchoring the regex at the start of a line avoids comment examples, but it also misses valid declarations after another statement on that line, such as const x = 1; export const notes = pgTable("notes", { ...ownableColumns() }). The table then is not treated as ownable and later query-scope checks cannot catch unscoped access; strip comments before matching or parse the source and cover inline exports in a test.

Fix in Builder

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not changing this. The anchor exists so commented-out example tables (// export const notes = …) stop registering as ownable. Without it, the starter's own drizzle/schema.ts example trips the guard. The case it misses is a second statement on the same line as an export const … = pgTable(…). oxfmt, which every app here runs, always splits those onto separate lines, so no formatted source reaches that shape. Stripping comments or parsing the file would add a parser to a line-based guard to cover input the formatter never produces.


Generated by Claude Code

…he layer script path

Workspace linking pruned shared framework skills unless a template was factory, so a starter app added to a workspace lost its internationalization guidance. Template-local skills now come from one map. template-layer check resolves its directory with fileURLToPath so checkouts under paths with spaces work.
builder-io-integration[bot]

This comment was marked as outdated.

@BenGirone

Copy link
Copy Markdown
Contributor

@BenGirone Here's part 1 of the end-to-end check: is the generated template the same or similar enough?

Very re-assuring! I see stuff that looks like bloat (email-branding), but we can deal with that later in the initiative. Excited to see it get cloned and used by the harness!

The published core strips spec/test files from its bundled Chat, and npm drops symlinks, so the starter's navigate.spec.ts patch had no target there and materializing from npm threw. The layer now deletes that spec instead of patching it. template-layer check also applies every layer to a package-shaped copy of its base (no spec/test files or symlinks), using the same filter the prepack sync applies, so a patch the published package cannot satisfy fails in CI.

@builder-io-integration builder-io-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Builder reviewed your changes and has a few items to flag 🟡

Review Details

Code Review Summary

This incremental update addresses the published-template materialization failure: actions/navigate.spec.ts is now a delete entry rather than a patch target, and guard:template-layers checks the layer against a package-shaped base with spec/test files and symlinks removed. I verified the prior high-severity finding is fixed and resolved its review thread. The new packaged-base check is a useful regression guard alongside the existing symlink containment checks.

Existing open findings

  • Provider requests still inherit Chat’s authorization appId; this is the same issue already covered by the open provider-scope thread, so it is not reposted.
  • The app-operation guard still exempts any tool-search call and its operation regex still omits common domain verbs.
  • The anchored ownable-table regex still misses an exported declaration following another statement on the same line; the new test only exercises a declaration at line start.

I did not accept the template-catalog report as a new issue: the normal create/add-app route is intentionally not the supported entry point for this starter; direct materialization is the documented path. Risk remains High because this PR changes filesystem-mutating template materialization. No new actionable issues from this update are submitted; existing open comments remain visible. 🧪 Browser testing: Skipped — browser-test-planner is unavailable in this environment.

@samijaber
samijaber merged commit d9ab440 into main Oct 8, 2026
69 of 77 checks passed
@samijaber
samijaber deleted the claude/fusion-starter-template branch October 8, 2026 12:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants