BoringStack full-stack build loop (Phase C) + tooling alignment#83
Open
agjs wants to merge 43 commits into
Open
BoringStack full-stack build loop (Phase C) + tooling alignment#83agjs wants to merge 43 commits into
agjs wants to merge 43 commits into
Conversation
…tion, Phase B findings)
…ll-stack build loop against a scaffolded clone; type onEvent as Reporter
…idate, not docker-run) + use pinned bun-run-format not bunx-latest The single docker-run gate had no toolchain (tsc: not found — deps live in per-app volumes). Run the composed gate on the host after bun install, with DATABASE_URL pointed at the published localhost Postgres. And the generator formats via BoringStack's OWN pinned prettier (bun run format), never bunx-latest, which caused a union-formatting drift that failed the gate.
… typescript-eslint 8.62.1) + add dependabot BoringStack is the strict reference the harness gates against; keep tsforge's shared toolchain on par so a version drift never silently breaks a build (a bunx-latest prettier mismatch already caused a union-formatting false-fail). Reformatted 11 files under prettier 3.9.4. Added .github/dependabot.yml (weekly; dev-toolchain grouped so lint/format/type deps move as one unit) + github-actions updates.
…e) so generate:api syncs the OpenAPI client — fixes root 'OpenAPI drift' gate failure
…Case, clarify gate comment
…-web) Removes web-build evaluation system and all web: task references from self-harness: - Delete evaluate-web.ts and self-harness-web.test.ts - Remove runOneWeb(), webTimeoutMs option, and web: task dispatch from evaluate.ts - Remove web-corpus exports (runWebTaskOnce, WEB_SLOW_THRESHOLD, WEB_RUN_TIMEOUT_MS, IWebEvaluateOptions, IWebRunOutcome) from index.ts - Simplify split.ts: remove web: namespace validation logic (webSlugs parameter) - Update self-harness.ts: remove BENCHMARK_CATALOG import and webSlugs argument Keeps the rest of self-harness fully intact: Algorithm-1 loop, overlay, mining, proposer, campaign, all spec/code corpus functionality. All tests green: bun run typecheck, bunx eslint packages, bun test packages/core/tests/self-harness-*.test.ts (56 pass)
… to the spec corpus (auth/fix-regression proof; boringstack corpus is the future successor)
…the live-smoke 'fetch failed' race after resource hot-reload)
… retry (skip codegen if the resource exists — a feature can now be re-attempted to fix gate failures instead of crashing on 'directory already exists')
…he next attempt's prompt (IFeature.lastError) so the model fixes real errors instead of rebuilding blind; dedupe toCamelCase in refine-prompt
…rompt (canonical app.handle(Request) route-test, ApiErrors not throw, TS strict rules) — targets the Elysia-typing failures that stuck the live smoke
…d the dead UI-only web-build path: scaffoldWeb/buildStaged/web-gate/delegation) — Task 8 chunk C1
…e-eval, web-sweep) — Task 8 chunk C2
…ive REPL (keep boringstack/astro scaffold + normal session) — Task 8 chunk C3
…caffold_web/ui/routes tools Surgically remove the web-only staged-build machinery and its three scaffold tools: - Delete staged-build.ts and the two-phase build infrastructure (designBuild/implementBuild/buildStaged/generatePlan) - Remove scaffold_web/ui/routes tool definitions and dispatch handlers - Remove SetupWebFn type and setupWeb callback threading - Update session.ts: remove scaffoldWeb/scaffoldUi config, remove 4 public methods - Update agent.constants.ts: remove 3 tool names, specs, and definitions - Update execute-tool.ts: remove 3 handler entries - Update turn.ts, tool-context.ts, cli/repl.ts: remove setup callbacks - Clean up 3 test files (staged-build.test.ts, staged-gate.test.ts, scaffold-routes-idempotent.test.ts) - Remove or update 9 test cases that asserted scaffold-tool behavior All 4 validation gates pass: typecheck, eslint, e2e:pty, test suite (2206 pass)
…e from the core gate — Task 8 chunk C6
…lateral-deleted them; the tools survive as general research tools)
…er build BACKEND pull_conventions was gated behind flags.webTools(), so boringstack builds (TSFORGE_BORINGSTACK, not TSFORGE_WEB) never got the conventions tool — a real convergence bug. toolsFor now takes offerConventions (default false); a build backend that ships a convention library opts in via ISessionConfig.pullConventions. headless-build sets it true. Web being on no longer drags the tool in.
…th PULL The 'HOW TO WRITE THIS RIGHT (boringstack)' guide push fired on ANY gated build that tripped a mapped rule — so a plain CLI build tripping no-restricted-syntax got boringstack/web-flavored advice (DOM/query narrowing) labeled boringstack. Now gated on ILoopState.conventionsEnabled, set from the same ISessionConfig .pullConventions signal that offers the pull_conventions tool: push + pull activate together, a plain build gets neither. New test locks in the withheld case.
…before gate Two blockers found in a live build (Bookmark resource, stuck 0/3): 1. Test-helper catch-22 — new:resource generates the Drizzle table but nothing added it to tests/helpers/db.ts's schema re-export. The no-direct-db-in-tests rule forbids importing the table from the schema, but the sanctioned helper didn't expose it either, so ANY test referencing the table was trapped (the model diagnosed this exactly and had no legal fix). Adds a 4th deterministic wiring edit (wireTestHelperFile) — idempotent, existence-guarded. 2. Prettier thrashing — the model burned attempts guessing prettier's exact line-break formatting, which is deterministic and 100% auto-fixable. implement now runs 'bun run format' (pinned, never bunx-latest) over both apps after the model writes and before the gate — a dev gets format-on-save, so should the model. Best-effort; the gate stays the source of truth. Neither change relaxes the gate.
…ailures Every 'stuck 0/3' this session traced to the SAME harness bug, not model weakness: the gate ran BoringStack's whole api suite, and a pristine scaffold already fails 5 base tests (email-template render + validateEnv placeholder domain) that the model is FROZEN out of. So it was handed an impossible task — turn a gate green that was red on arrival, on files it can't edit — then stamped 'stuck'. Fix: capture the baseline failure set on the pristine scaffold before any model work, then grade each feature DIFFERENTIALLY — a red gate passes iff every failure is a pre-existing baseline failure (feature introduced none). Novel failures are the only feedback the model sees, so it never chases base defects. A red baseline is surfaced LOUDLY (the scaffold itself should be fixed). Empty parse on a non-zero exit stays failed (conservative). Not a gate relaxation: the full gate still runs on everything the model touches or newly breaks. extractFailures/novelFailures are pure + unit-tested; differential gate behavior covered in boringstack-build.test.ts.
…tier A stuck build's residual was 5 padding-line-between-statements lint errors — deterministic, auto-fixable by eslint --fix, but the auto-fix pass only ran prettier (which doesn't touch that rule class). Now runs 'bun run format' THEN 'bun run lint:fix' per app before the gate, so no auto-fixable lint costs the model an attempt. Genuine violations (no-non-null-assertion, missing audit) are correctly left for the model to fix.
…ck build path The UI-only Vite/React scaffold (scaffold_web/ui/routes, staged web gate, browser smoke) was removed; web apps are now built on a real BoringStack clone. Updated the Astro docs site + internal harness-subsystems.md to match: - delete scaffold/web.mdx + drop it from the sidebar/DocMap/Capabilities nav; repoint all links to /scaffold/boringstack/ - document the build loop (plan → generate → deterministic wiring → model fills domain → prettier+eslint --fix auto-fix → baseline-aware differential gate → freeze) in loop/greenfield.mdx + loop/gate-floor.mdx - drop removed tools from the agent tool table (add pull_conventions); rewrite the plan-mode staged-web section; remove dead flags (--web mode, TSFORGE_A11Y/SCREENSHOTS) - rewrite the web-scaffold subsystem section in harness-subsystems.md to the boringstack build subsystem Docs site builds clean (47 pages).
Wires the existing expert handoff (capabilities.expert = deepseek-4-pro) into the boringstack build loop — the rung above the per-attempt feedback the path already has. Adds an optional IGreenfieldDeps.rescue hook: run.ts calls it ONCE before parking a stuck feature and gives the result a final evaluation; green ticks the feature, anything else parks as before (generic greenfield leaves rescue unset — fully backward compatible, existing stuck tests unchanged). boringstackDeps.rescue hands the stuck file + its exact novel-failure errors to the expert via the existing resolveExpertAsk/resolveStuckFile/runExpertHandoff, then re-runs the auto-fix pass. Opt-in via TSFORGE_EXPERT_RESCUE (expert is a paid API); off/unconfigured → no-op. This is the automated form of 'a stronger hand steps in when the local model is stuck' — the escalation that would have given conv4/conv5's Elysia-typing feature a real shot before parking. Tests: run.ts escalation control flow (lands-green ticks / can't-help parks / applied-but-still-red parks once, no loop) + boringstack no-op when disabled.
The resource planner prompt invited 'entities, services, API endpoints' as separate items, so a bookmarks goal produced BOTH 'Bookmark' and 'BookmarkService' — a whole redundant generation cycle (BoringStack's new:resource already builds every layer from one entity). Two guards: - prompt rewritten to 'one entity = one resource; there is no separate XService/XApi/ XRoutes — the service and endpoints are part of the entity; list the smallest set' - dedupeLayerVariants() structurally drops a <Entity>Service/Api/Routes/... resource when the base entity is also present (belt-and-suspenders for when the model splits anyway); a genuinely standalone suffixed id with no base entity is kept This directly prevents the redundant cycle we watched waste ~15 min in build #5. Not a constraint on how the model builds — just stops asking for work already covered.
…line capture A pristine BoringStack scaffold fails its OWN gate on a fresh clone — not the model's fault. BoringStack's rename step rewrites identifiers repo-wide, which leaves two documented follow-ups undone: 'bun run regen' (resync OpenAPI client + ACL + doc catalogs → else OpenAPI-drift RED) and 'bun run format' (re-prettier lines whose width the rewrite changed → else format:check RED). The scaffold ran rename+setup but skipped these, so every build opened with a red baseline warning. driveBuild now runs regen + format after install, before baseline capture. Verified: a fresh scaffold that was RED on the full composed gate (api validate && ui validate && check) now exits 0 — OpenAPI drift ✓, ACL drift ✓, format ✓. Pairs with the BoringStack test-fixture fix (c2ab47e, separate repo). Best-effort: a residual defect still falls to the differential gate rather than aborting.
…gate Build #6 stuck at the JUDGE stage (gate green, but the service ignored description/ priority and close didn't set status) — yet the expert rescue no-op'd. It only looked for a file PATH in the failure, and a judge critique is prose with none, so resolveStuckFile found nothing. New rescueFileFor(): gate stuck → the file named in the errors (as before); judge stuck → fall back to the resource's <camel>.service.ts, where the domain logic the judge flags lives. So the expert now gets the service file + the judge's critique and can implement the missing fields / state transition — exactly the case where a stronger model should step in. Null only when neither resolves. Unit-tested (gate path, judge fallback, no-file).
Complements BoringStack's parameterized compose host ports: the scaffold now assigns a free host port to every binding (allocateHostPorts) and writes them to the clone's compose/.env, so a scaffolded project boots ALONGSIDE the dev stack (or another project) without colliding on 5432/7330/7331/… The compose project name is already per-project via rename-project.sh; this closes the ports half. The handoff reports the api/ui/postgres ports. allocatePort is injectable for deterministic tests; a repo tsforge didn't configure is unchanged (compose defaults to the upstream ports). Proven: a fresh scaffold writes free ports (e.g. postgres 52662 / api 52664 / ui 52665) into compose/.env.
… baseline The differential-gate feedback always appended '; N baseline failure(s) hidden' — confusing noise when the baseline is green (N=0). Now the suppression note only appears when there's actually something hidden.
…xing them Declaring params only to `_`-prefix and ignore them is dead weight when TS lets a stub/impl omit unused TRAILING params entirely. Removed them from the evaluate/gate/ browser/judge impls and the exec test fakes (async () => … satisfies the type). The `_`-params that remain elsewhere are LEADING/MIDDLE positionals that can't be omitted (a later param is used) — the one legitimate use of the convention.
…ale describe - capabilities: drop the removed 'vite (web)' from the scaffold blurb (wizard only offers boringstack/astro). - wizard (boringstack only): two optional steps — admin email (validated) + admin password (masked, min 12). Blank = skip (sign up in-app instead). - thread the creds answers → plan → applyScaffold, which writes SUPERUSER_EMAIL/ SUPERUSER_PASSWORD to compose/.env BEFORE boot (co-located with the port write), so the migrate task's db:seed creates the admin on first boot. Password is secret: on disk, redacted in the summary. - tests: creds land in compose/.env + password never in the summary; absent when not given.
Design for a product-first, interactive planning phase before the greenfield build loop: human describes the product (+ mockups via vision), a config-routed planner role (default deepseek-4-pro, swappable) PROPOSES the plan (domain model + feature slices + UI intent + verification contract), human approves once, then autonomous per-slice build carrying the approved plan as shared context. For greenfield BoringStack an approved plan is a HARD precondition (reuses solo_spec_gate). Reviewer role deferred.
TDD task-by-task plan on feat/boringstack-build-loop: planner capability role (T1), product-plan type + .specs I/O (T2), planner proposal (T3), approval gate (T4), refinePrompt context threading (T5), build precondition + build-from-slices (T6), interactive plan orchestration (T7), REPL/wizard/headless enforcement (T8).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The BoringStack full-stack build loop: for web apps, the harness scaffolds/drives BoringStack (Bun+Elysia API, Vite+React UI, Postgres/Drizzle) one resource at a time — the harness runs BoringStack's own generators + deterministic wiring, the model fills domain logic + tests, evaluated by BoringStack's own
validate/check. Supersedes the UI-only web scaffold that couldn't converge.New:
packages/core/src/loop/boringstack/{plan-resources,wire-resource,generate,gate,refine-prompt,build,exec,case}.ts(+ tests, 37 pass), driven by the existing greenfield engine (runGreenfield). Entry:TSFORGE_BORINGSTACK=1 bun packages/core/scripts/headless-build.ts "<goal>" react <clone>.Built via subagent-driven-development (fresh subagent per task + two-stage review + a final whole-branch review); all review findings fixed.
Proven
db:push+ UI feature +generate:api) passes BoringStack's ownvalidate/check—API_EXIT=0, UI_EXIT=0, ROOT_EXIT=0.bun install+ hostvalidate(not a throwaway container — deps live in per-app volumes), using BoringStack's pinned tooling (bun run format, neverbunx-latest — a version drift false-failed the gate).bun run validategreen.Also in this PR
.github/dependabot.yml(dev-toolchain grouped) — so a version drift never silently breaks a gated build.evaluate-web) and retargeted the campaign to the spec corpus (rest of self-harness intact).Pending (honest)
generate:apibecause the polluted test clone's api was unhealthy — a clean end-to-end proof needs a fresh, healthy clone.--web) — core-woven (~25 files); planned as a follow-up (seedocs/superpowers/plans/2026-07-12-boringstack-build-loop.mdTask 8).generate:apiapi-readiness poll; a resume wrapper for the single-invocation headless path.Do not merge without review — flagging the pending items above.