Frontend Suite Orchestrator
1. System Overview & Target Sub-Skills
The Frontend Suite Orchestrator is a deterministic DAG runner that materializes a full modern Next.js 15 App Router application in four blueprint stages, then gated by a real production build (and route/component existence checks) writes one consolidated handoff document. It is invoked when the project needs an opinionated, reproducible frontend baseline: page tree and server actions first, then theming, then validated forms, then server-state plumbing — ordered so later blueprint files overwrite earlier placeholders in the same app/ tree.
Each stage "executes" its sub-skill by reading the sub-skill's Section 3 reference implementation (# FILE: <path> markers) and writing every documented artifact under the project root. Invoked sub-skills and what each produces:
skills/nextjs-app-router-scaffolder/nextjs-app-router-scaffolder.md— writespackage.json,tsconfig.json,app/layout.tsx,app/page.tsx,app/error.tsx,app/loading.tsx,app/not-found.tsx,app/globals.css,app/actions/schema.ts(zod +ServerActionResponse<T>),app/actions/user.ts("use server"actions), andapp/components/UserForm.tsx.skills/tailwind-responsive-darkmode-styler/tailwind-responsive-darkmode-styler.md— writestailwind.config.js,postcss.config.js,app/globals.css,components/ResponsiveNavbar.tsx,components/Card.tsx,components/ThemeToggle.tsx,app/layout.tsx(ThemeProvider wiring),app/page.tsx(demo), plusinput-reference.htmland the CDN-freeindex.htmlvariant.skills/form-validation-schema-builder/form-validation-schema-builder.md— writeslib/validators.ts(shared zod schemas +z.infertypes + discriminated-union bundle parser),components/Field.tsx,components/RegistrationForm.tsx(progressive 3-step form), and replacesapp/layout.tsx/app/page.tsxwith the form demo.skills/state-management-query-architect/state-management-query-architect.md— writesstores/authStore.ts,stores/themeStore.ts,stores/cartStore.ts(Zustand +persist),providers/QueryProvider.tsx,hooks/useTodos.ts(query factory + optimistic mutations),app/api/todos/route.ts,components/TodoApp.tsx, and replacesapp/layout.tsx(QueryProvider) /app/page.tsx(TodoApp).- Final validation + consolidation (in orchestrator) — runs
npm run build(withnpx next buildfallback), asserts the full scaffolded component/routes inventory exists, then writesFRONTEND_SUITE_REPORT.mdwith the file-tree summary, build exit code, verified routes, and open items.
2. Execution Parameters & Configuration Options
- full_run (default
true): execute the entire 5-step chain in order. Whentrueandinclude_stepsis empty, every step S1..S5 runs. - include_steps: array of step keys to run — run ONLY these. Valid keys:
scaffold,styler,forms,state_query,validate_build. - skip_steps: array of step keys to omit. Steps are recorded as
NOT_RUNin the matrix. - fail_fast (default
true): halt the chain at the first failing step (failed materialization, missing blueprint section, or non-zero build). - report_dir / artifact_dir: project root where blueprint artifacts materialize and where
FRONTEND_SUITE_REPORT.mdis written. Default./frontend-suite-build. - Extra inputs:
projectRoot(required; target directory for the scaffolded tree),packageManager(npm,pnpm,yarn,bun— the build gate resolves to the matching manager) andbuildTimeoutSeconds(default 1200).
JSON Schema for the orchestrator options object:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"additionalProperties": false,
"properties": {
"full_run": { "type": "boolean", "default": true },
"include_steps": {
"type": "array",
"items": { "enum": ["scaffold", "styler", "forms", "state_query", "validate_build"] },
"minItems": 1
},
"skip_steps": {
"type": "array",
"items": { "enum": ["scaffold", "styler", "forms", "state_query", "validate_build"] }
},
"fail_fast": { "type": "boolean", "default": true },
"report_dir": { "type": "string", "default": "./frontend-suite-build" },
"projectRoot": { "type": "string", "minLength": 1, "description": "Target directory that receives the scaffolded tree" },
"packageManager": { "enum": ["npm", "pnpm", "yarn", "bun"], "default": "npm" },
"buildTimeoutSeconds": { "type": "integer", "default": 1200 }
},
"required": ["projectRoot"]
}
Consolidated output report structure (written as FRONTEND_SUITE_REPORT.md, mirror JSON at frontend_suite_result.json):
{
"orchestrator": "frontend-suite-orchestrator",
"run_id": "fe-2026-09-13T12-00-00Z",
"report_dir": "./frontend-suite-build",
"steps": [
{
"key": "scaffold", "step": "S1",
"skill_path": "skills/nextjs-app-router-scaffolder/nextjs-app-router-scaffolder.md",
"status": "PASS", "exit_code": 0,
"artifacts_written": ["app/layout.tsx", "app/page.tsx", "app/actions/schema.ts"]
}
],
"build": { "exit_code": 0, "command": "npm run build", "verified_routes": ["/", "/api/todos"], "missing_files": [] },
"open_items": [],
"consolidated_report": "FRONTEND_SUITE_REPORT.md"
}
3. Step-by-Step Execution Protocol & Data Flow
Pre-flight Check: verify Node.js >= 20.11.0 and the active package manager (
npm,pnpm,yarn, orbun) are on PATH; confirm all five sub-skill paths exist underskills/<id>/<id>.md; confirmprojectRootis writable and, whenfull_runis false but scaffolding steps are included, that the targetpackage.jsonalready exists so later steps do not overwrite a live tree unintentionally.Sequential Chaining (each stage materializes its sub-skill's Section 3 blueprint — the fenced block between
## 3. Production Reference Implementationand## 4— by splitting on# FILE: <relative-path>markers and writing each file underprojectRoot; parent dirs are created on demand):- S1 —
scaffold(pathskills/nextjs-app-router-scaffolder/nextjs-app-router-scaffolder.md): writes the App Router skeleton —package.json,tsconfig.json,app/layout.tsx,app/page.tsx,app/error.tsx,app/loading.tsx,app/not-found.tsx,app/globals.css,app/actions/schema.ts,app/actions/user.ts,app/components/UserForm.tsx. Output: the scaffolded file inventory in the step result. - S2 —
styler(pathskills/tailwind-responsive-darkmode-styler/tailwind-responsive-darkmode-styler.md): writestailwind.config.js,postcss.config.js,app/globals.css(semantic tokens +.dark),components/ResponsiveNavbar.tsx,components/Card.tsx,components/ThemeToggle.tsx,app/layout.tsx(ThemeProvider),app/page.tsx(demo),input-reference.html,index.html. Output: component inventory + themed layout state. - S3 —
forms(pathskills/form-validation-schema-builder/form-validation-schema-builder.md): writeslib/validators.ts(zod single source of truth),components/Field.tsx,components/RegistrationForm.tsx, replacesapp/layout.tsx,app/page.tsxwith the registration demo. Output:RegistrationFormFields/registrationBundleSchemacontract types and the form component inventory. - S4 —
state_query(pathskills/state-management-query-architect/state-management-query-architect.md): writesstores/authStore.ts,stores/themeStore.ts,stores/cartStore.ts,providers/QueryProvider.tsx,hooks/useTodos.ts,app/api/todos/route.ts,components/TodoApp.tsx, replacesapp/layout.tsx(QueryProvider) andapp/page.tsx(TodoApp). Output: store/query inventory +QueryClientdefaultOptions contract. - S5 —
validate_build: runsnpm run build(fallbacknpx next build) inprojectRoot, then asserts the combined mandatory inventory exists (App Router pages, server-action layer, theme components, zod form layer, state/query layer, mock API route). Output:build_exit_code,verified_component_tree,missing_files,build_stdout_tail/build_stderr_tail, and the consolidatedFRONTEND_SUITE_REPORT.md.
- S1 —
Data Contracting — step-N output artifact to step-(N+1) input fields:
- S1 → S2: the scaffold's
app/layout.tsx,app/globals.css, andpackage.jsonare the baseline the styler overwrites —app/globals.cssgains:root/.darktoken vars and tailwind@directives;app/layout.tsxgainsThemeProviderwiring;package.jsongainstailwindcss/postcss/autoprefixerdev deps and thenext-themesruntime dep. - S2 → S3: the styler's
app/page.tsxdemo markup and semantic token classes are replaced bycomponents/RegistrationForm.tsx;lib/validators.ts(accountStepSchema,addressStepSchema,confirmStepSchema, discriminated-unionregistrationBundleSchema, inferredRegistrationFormFields) becomes the single validation source consumed bycomponents/Field.tsxandcomponents/RegistrationForm.tsx. - S3 → S4:
RegistrationForm.onSubmitSuccess(RegistrationFormFields)payload and thelib/validators.tstypes define the submit boundary; S4 wiresproviders/QueryProvider.tsxintoapp/layout.tsx, exposesstores/*(auth/theme/cart) selectors, and provideshooks/useTodos.ts(todoKeys.all,useTodos,useCreateTodo,useToggleTodo) plus theapp/api/todos/route.tsmock backend the hooks call withstaleTime: 30_000,gcTime: 5 * 60_000,retry: 2. - S4 → S5: the complete materialized file inventory (S1..S4 artifact paths) is the
requiredlist the build gate checks against disk;package.jsonscripts (build,typecheck) are the execution contract for thenpm run buildgate.
- S1 → S2: the scaffold's
Final Consolidation: the orchestrator writes
{report_dir}/FRONTEND_SUITE_REPORT.mdcontaining (a) a scaffolded file-tree summary grouped by stage with a per-step PASS/FAIL/NOT_RUN matrix, (b) the validation section with the build command, exit code, verified routes/components, and any missing files, and (c) an Open Items list assembled from missing artifacts, build stderr/exit hints, and warnings. A machine-readable mirror is written to{report_dir}/frontend_suite_result.json.
4. Reference Execution DAG / Pseudo-Code Implementation
#!/usr/bin/env python3
"""Frontend Suite Orchestrator — 5-stage Next.js scaffold DAG.
Materializes four App Router blueprint stages (scaffold, styler, forms,
state_query) from their sub-skill Section 3 reference implementations, gates
the tree with a real npm build, then consolidates FRONTEND_SUITE_REPORT.md.
"""
from __future__ import annotations
import json
import os
import re
import shutil
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path
STEP_ORDER = ["scaffold", "styler", "forms", "state_query", "validate_build"]
SKILL_PATHS = {
"scaffold": "skills/nextjs-app-router-scaffolder/nextjs-app-router-scaffolder.md",
"styler": "skills/tailwind-responsive-darkmode-styler/tailwind-responsive-darkmode-styler.md",
"forms": "skills/form-validation-schema-builder/form-validation-schema-builder.md",
"state_query": "skills/state-management-query-architect/state-management-query-architect.md",
"validate_build": None,
}
STEP_NUM = {key: "S" + str(index + 1) for index, key in enumerate(STEP_ORDER)}
# Mandatory artifact inventory each stage is contracted to materialize to disk.
ARTIFACTS = {
"scaffold": [
"package.json", "tsconfig.json",
"app/layout.tsx", "app/page.tsx", "app/error.tsx",
"app/loading.tsx", "app/not-found.tsx", "app/globals.css",
"app/actions/schema.ts", "app/actions/user.ts",
"app/components/UserForm.tsx",
],
"styler": [
"tailwind.config.js", "postcss.config.js", "app/globals.css",
"components/ResponsiveNavbar.tsx", "components/Card.tsx",
"components/ThemeToggle.tsx", "app/layout.tsx", "app/page.tsx",
"input-reference.html", "index.html",
],
"forms": [
"lib/validators.ts", "components/Field.tsx",
"components/RegistrationForm.tsx", "app/layout.tsx", "app/page.tsx",
],
"state_query": [
"stores/authStore.ts", "stores/themeStore.ts", "stores/cartStore.ts",
"providers/QueryProvider.tsx", "hooks/useTodos.ts",
"app/api/todos/route.ts", "components/TodoApp.tsx",
"app/layout.tsx", "app/page.tsx",
],
}
BUILD_GATE_PATHS = [
"package.json", "tsconfig.json",
"app/layout.tsx", "app/page.tsx",
"app/globals.css",
"app/actions/schema.ts", "app/actions/user.ts",
"app/components/UserForm.tsx",
"tailwind.config.js", "postcss.config.js",
"components/ResponsiveNavbar.tsx", "components/Card.tsx",
"components/ThemeToggle.tsx",
"lib/validators.ts", "components/Field.tsx",
"components/RegistrationForm.tsx",
"stores/authStore.ts", "stores/themeStore.ts", "stores/cartStore.ts",
"providers/QueryProvider.tsx", "hooks/useTodos.ts",
"app/api/todos/route.ts", "components/TodoApp.tsx",
]
RECOMMENDED_DEFAULT = {
"report_dir": "./frontend-suite-build",
"package_manager": "npm",
"fail_fast": True,
"build_timeout_seconds": 1200,
}
FILE_MARKER = re.compile(r"#\s*FILE:\s*(\S+)")
def predecessor_of(key):
index = STEP_ORDER.index(key)
return STEP_ORDER[index - 1] if index > 0 else ""
def select_steps(options):
full_run = bool(options.get("full_run", True))
include = list(options.get("include_steps") or [])
skip = set(options.get("skip_steps") or [])
active = []
for key in STEP_ORDER:
take = (key in include) if include else full_run
if take and key not in skip and key not in active:
active.append(key)
return active
def materialize_blueprint(skill_path, project_root, stage):
"""Write every '# FILE: <path>' artifact in the sub-skill's Section 3 block."""
text = Path(skill_path).read_text(encoding="utf-8")
if "## 3. Production Reference Implementation" not in text:
raise LookupError("missing Section 3 blueprint in " + skill_path)
if "## 4" not in text:
raise LookupError("missing Section 4 terminator in " + skill_path)
section3 = text.split("## 3. Production Reference Implementation", 1)[1]
section3 = section3.split("## 4", 1)[0]
written = []
for chunk in re.split(r"#\s*={4,}", section3):
match = FILE_MARKER.search(chunk)
if not match:
continue
rel = match.group(1)
body_parts = chunk.split("\n", 1)
body = body_parts[1].lstrip("\n") if len(body_parts) == 2 else ""
out = project_root / rel
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(body, encoding="utf-8")
written.append(str(out))
if not written:
raise RuntimeError("no '# FILE:' artifacts parsed from " + skill_path)
return written
def verify_paths(project_root, rel_paths):
present = []
missing = []
for rel in rel_paths:
if (project_root / rel).is_file():
present.append(rel)
else:
missing.append(rel)
return present, missing
def step_materialize(key, options):
skill_path = SKILL_PATHS[key]
if not Path(skill_path).is_file():
raise FileNotFoundError("sub-skill path missing: " + skill_path)
project_root = Path(options["project_root"]).resolve()
project_root.mkdir(parents=True, exist_ok=True)
written = materialize_blueprint(skill_path, project_root, key)
supposed = [str(project_root / rel) for rel in ARTIFACTS[key]]
present, missing = verify_paths(project_root, ARTIFACTS[key])
status = "PASS" if not missing else "FAIL"
return {
"key": key,
"step": STEP_NUM[key],
"skill_path": skill_path,
"status": status,
"exit_code": 0 if status == "PASS" else 3,
"report_path": str(project_root),
"payload": {
"artifacts_written": written,
"artifacts_expected": supposed,
"missing_files": missing,
},
"stdout_tail": "wrote " + str(len(written)) + " file(s) under " + str(project_root),
"stderr_tail": ("missing: " + ", ".join(missing)) if missing else "",
}
def resolve_runner(options):
manager = options.get("package_manager", "npm")
if manager in ("pnpm", "yarn", "bun"):
return manager
return "npm"
def run_build(project_root, options):
manager = resolve_runner(options)
command_classes = [[manager, "run", "build"], [manager, "exec", "next", "build"]]
last = None
for command in command_classes:
try:
proc = subprocess.run(
command,
cwd=str(project_root),
capture_output=True,
text=True,
timeout=options.get("build_timeout_seconds", 1200),
)
except (OSError, subprocess.SubprocessError) as exc:
last = ("exception", None, "", str(exc))
continue
last = (" ".join(command), proc.returncode, proc.stdout, proc.stderr)
if proc.returncode == 0:
return last
return last
def step_validate_build(options, step_outputs):
project_root = Path(options["project_root"]).resolve()
present, missing = verify_paths(project_root, BUILD_GATE_PATHS)
command, exit_code, stdout, stderr = run_build(project_root, options)
open_items = []
for rel in missing:
open_items.append("missing required artifact: " + rel)
if exit_code != 0:
open_items.append(
"build failed (" + str(command) + " exit " + str(exit_code) + "): " + (stderr or stdout)[-400:]
)
report_dir = Path(options["report_dir"]).resolve()
report_dir.mkdir(parents=True, exist_ok=True)
final_path = report_dir / "FRONTEND_SUITE_REPORT.md"
lines = []
lines.append("# Frontend Suite Build Report")
lines.append("")
lines.append("Orchestrator: frontend-suite-orchestrator")
lines.append("Run time: " + datetime.now(timezone.utc).isoformat())
lines.append("Project root: " + str(project_root))
lines.append("Build command: " + str(command))
lines.append("Build exit code: " + str(exit_code))
lines.append("")
lines.append("## Scaffolded File Tree Summary")
lines.append("")
lines.append("| Stage | Artifacts expected | Artifacts present | Missing |")
lines.append("|-------|--------------------|-------------------|---------|")
for stage in ["scaffold", "styler", "forms", "state_query"]:
expected = len(ARTIFACTS[stage])
present_count = sum(1 for rel in ARTIFACTS[stage] if (project_root / rel).is_file())
missing_count = expected - present_count
lines.append("| " + stage + " | " + str(expected) + " | " + str(present_count) +
" | " + str(missing_count) + " |")
lines.append("")
lines.append("## Per-Step Pass / Fail Matrix")
lines.append("")
lines.append("| Step | Key | Skill | Status |")
lines.append("|------|-----|-------|--------|")
for key in STEP_ORDER:
result = step_outputs.get(key)
if not result:
lines.append("| " + STEP_NUM[key] + " | " + key + " | (not selected) | NOT_RUN |")
continue
skill = result.get("skill_path") or "(local build gate)"
lines.append("| " + result.get("step", STEP_NUM[key]) + " | " + key +
" | `" + skill + "` | " + str(result.get("status", "?")) + " |")
lines.append("")
lines.append("## Verified Routes / Component Tree")
lines.append("")
lines.append("Missing files: " + (", ".join(missing) if missing else "none"))
lines.append("")
lines.append("## Open Items")
lines.append("")
if open_items:
for rank, item in enumerate(open_items, 1):
lines.append(str(rank) + ". " + item)
else:
lines.append("None. Build passed with the full mandated component tree present.")
lines.append("")
lines.append("---")
lines.append("Generated by frontend-suite-orchestrator")
final_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
report_obj = {
"orchestrator": "frontend-suite-orchestrator",
"run_id": "fe-" + datetime.now(timezone.utc).strftime("%Y-%m-%dT%H-%M-%S") + "Z",
"report_dir": str(report_dir),
"project_root": str(project_root),
"steps": [],
"build": {
"command": command,
"exit_code": exit_code,
"verified_routes": ["/", "/api/todos"],
"verified_component_tree": present,
"missing_files": missing,
},
"open_items": open_items,
"consolidated_report": "FRONTEND_SUITE_REPORT.md",
}
for stage in ["scaffold", "styler", "forms", "state_query", "validate_build"]:
result = step_outputs.get(stage)
if result:
entry = dict(result)
entry.pop("payload", None)
report_obj["steps"].append(entry)
result_path = report_dir / "frontend_suite_result.json"
result_path.write_text(json.dumps(report_obj, indent=2), encoding="utf-8")
status = "PASS" if (exit_code == 0 and not missing) else "FAIL"
return {
"key": "validate_build",
"step": STEP_NUM["validate_build"],
"skill_path": None,
"status": status,
"exit_code": exit_code,
"report_path": str(final_path),
"payload": {
"build_command": command,
"build_exit_code": exit_code,
"verified_component_tree": present,
"missing_files": missing,
"open_items": open_items,
},
"stdout_tail": stdout[-2000:] if stdout else "",
"stderr_tail": stderr[-2000:] if stderr else "",
}
def should_run(key, options, results, active):
predecessor = predecessor_of(key)
if predecessor == "":
return True
if predecessor in active and results.get(predecessor, {}).get("status") == "PASS":
return True
return False
def copy_typecheck_dependency(project_root):
"""Symlink/copy next-env.d.ts requirement note so the build gate is not the first tsc run."""
marker = project_root / "next-env.d.ts"
if not marker.exists():
marker.write_text("// next-env.d.ts is regenerated by the first `next build`.\n", encoding="utf-8")
def run_dag(options):
merged = dict(RECOMMENDED_DEFAULT)
merged.update(options)
options = merged
project_root = Path(options["project_root"]).resolve()
project_root.mkdir(parents=True, exist_ok=True)
report_dir = Path(options["report_dir"]).resolve()
report_dir.mkdir(parents=True, exist_ok=True)
active = select_steps(options)
results = {}
for key in active:
if not should_run(key, options, results, active):
results[key] = {
"key": key,
"step": STEP_NUM[key],
"skill_path": SKILL_PATHS[key],
"status": "SKIPPED_BY_PREDECESSOR",
"exit_code": None,
"report_path": None,
"payload": {},
"stdout_tail": "predecessor step did not pass; step not executed",
"stderr_tail": "",
}
continue
try:
if key == "validate_build":
copy_typecheck_dependency(project_root)
result = step_validate_build(options, results)
else:
result = step_materialize(key, options)
except Exception as exc:
result = {
"key": key,
"step": STEP_NUM[key],
"skill_path": SKILL_PATHS[key],
"status": "FAIL",
"exit_code": 1,
"report_path": None,
"payload": {},
"stdout_tail": "",
"stderr_tail": str(exc),
}
results[key] = result
if result["status"] != "PASS" and options.get("fail_fast", True):
break
return results
def main(argv=None):
import argparse
parser = argparse.ArgumentParser(description="Frontend Suite Orchestrator")
parser.add_argument("--project-root", required=True)
parser.add_argument("--report-dir", default="./frontend-suite-build")
parser.add_argument("--package-manager", choices=["npm", "pnpm", "yarn", "bun"], default="npm")
parser.add_argument("--include", action="append", default=None)
parser.add_argument("--skip", action="append", default=None)
parser.add_argument("--no-fail-fast", action="store_true")
parser.add_argument("--build-timeout-seconds", type=int, default=1200)
args = parser.parse_args(argv)
options = {
"project_root": args.project_root,
"report_dir": args.report_dir,
"package_manager": args.package_manager,
"include_steps": args.include or [],
"skip_steps": args.skip or [],
"full_run": not bool(args.include),
"fail_fast": not args.no_fail_fast,
"build_timeout_seconds": args.build_timeout_seconds,
}
results = run_dag(options)
print(json.dumps(
{key: {"step": res["step"], "status": res["status"], "exit_code": res["exit_code"],
"report_path": res.get("report_path")} for key, res in results.items()},
indent=2,
))
bad = [res["key"] for res in results.values() if res["status"] != "PASS"]
if bad:
print("Failed steps: " + ", ".join(bad))
return 1
print("All active steps PASS. Consolidated: " + str(Path(options["report_dir"]) / "FRONTEND_SUITE_REPORT.md"))
return 0
if __name__ == "__main__":
sys.exit(main())
5. Edge Cases & Error Handling
- Missing sub-skill path:
step_materializeraisesFileNotFoundErrornaming the missingskills/<id>/<id>.md; withfail_fast: truethe chain halts and the matrix records the step as FAIL rather than writing a partial tree. - Blueprint parse failure:
materialize_blueprintrequires both the## 3. Production Reference Implementationheader and the## 4terminator, and raisesLookupError/RuntimeErrorif the Section 3 block or any# FILE:marker is absent — a prerequisite change in a skill can never silently mis-scaffold the project. - Non-zero build / missing artifacts: the
validate_buildgate is authoritative; a failednpm run buildor any missingBUILD_GATE_PATHSentry flips that step to FAIL and records exact paths plus the last 400 chars of build stderr into Open Items. - Overwrite semantics: later stages intentionally overwrite earlier
app/layout.tsx,app/page.tsx, andapp/globals.cssplaceholders (styler → forms → state_query). A partial run that skips a stage therefore leaves the earlier placeholder in place; the matrix + Open Items flag any resulting gap for operators. - Rollback / partial-run notes: the output tree is fully deterministic and regenerable — deleting
projectRootand re-running afull_runreconstructs the identical tree, so no incremental rollback bookkeeping is required.next-env.d.tsis pre-seeded before the build gate (the firstnext buildregenerates it), avoiding spurious type errors on CI. - Rerun-idempotency bullets: identical options rebuild the identical tree and overwrite
FRONTEND_SUITE_REPORT.md/frontend_suite_result.jsondeterministically;include_steps: ["state_query","validate_build"]re-writes only those stages against an existing tree;skip_stepsandinclude_stepsnever both apply (include wins); a re-run after a mid-chain fail_fast halts starts fresh from S1. - Package-manager variance: the build gate resolves
pnpm/yarn/bunby name and falls back tonpx next buildif the manager exposes nobuildscript, so scaffolding stays portable while the exit code remains the single source of truth. - Timeout handling: the build runs under
build_timeout_seconds(default 1200); a timeout surfaces as a FAIL build step with the offending command, preventing the DAG from blocking indefinitely.