import type { BrandPresetSpec } from "../brand.js"; import type { ScaffoldInputs } from "../index.js"; import { type SubstitutionOptions } from "../substitute.js"; export interface ScaffoldFile { path: string; content: string; } export interface ScaffolderOptions extends SubstitutionOptions { } export declare const STANDARDS_BLOCK_MARKER = ""; export declare const AGENT_COORDINATION_BLOCK_MARKER = ""; /** Always-on, unlike the coordination marker — every project gets this one. */ export declare const CAPTURE_LEARNINGS_BLOCK_MARKER = ""; export declare const AGENTS_BODY_MARKER = ""; export declare function resolveBrandSpec(inputs: ScaffoldInputs): BrandPresetSpec; export declare function sub(template: string, inputs: ScaffoldInputs, opts: ScaffolderOptions | undefined, spec: BrandPresetSpec): string; /** WCAG 2.1 relative luminance of an `#rgb` / `#rrggbb` color. */ export declare function relativeLuminance(hex: string): number; /** WCAG 2.1 contrast ratio between two `#rgb` / `#rrggbb` colors. */ export declare function contrastRatio(a: string, b: string): number; /** Near-black ink, used wherever a light fill needs dark text. */ export declare const INK_DARK = "#1a1a1a"; /** Pure white ink, used wherever a dark fill needs light text. */ export declare const INK_LIGHT = "#ffffff"; /** * Pick the ink color that reads on `background`. * * This exists because the emitted CSS paints small UI glyphs ON a brand fill, * and brand primaries vary wildly across presets — `#82C92B` (Z2W green) and * `#72af43` (STF green) both want dark ink, while a dark custom primary wants * white. Hardcoding either one ships an unreadable glyph to half the brands, * and it is the kind of defect nobody reports because it looks *deliberate*. * Deciding it at scaffold time from the actual hex is deterministic, testable, * and costs the emitted project nothing at runtime. */ export declare function readableInkOn(background: string, minRatio?: number): string; /** Pure black. Only reached when the softened {@link INK_DARK} falls short. */ export declare const PURE_BLACK = "#000000"; /** Expand `#rgb` to a lowercase `#rrggbb`, so emitted CSS is comparable. */ export declare function normalizeHex(hex: string): string; /** * The nearest shade or tint of `color` that clears `minRatio` against * `surface`, or `color` itself when it already does. * * WHY THIS EXISTS, and it is a real defect this repo shipped rather than a * hypothetical. The emitted `globals.css` painted the input FOCUS RING in * `var(--brand-primary)`, on a white page. Measured against white: Z2W green * `#82C92B` is 2.03:1, STF green `#72af43` is 2.65:1, Z2W gold `#FFD700` is * 1.40:1 — all below the 3:1 that WCAG 2.1 SC 1.4.11 requires of the visual * information that identifies a component's STATE. So every scaffold built in * Zero2Webmaster's or Save The Frogs' own brand shipped a focus ring the * keyboard user can barely find, and focusing a field made its boundary LESS * visible than at rest (the resting `--field-border` is a measured 3.40:1). * * A brand color is the author's to choose, so the fix is not to override it — * it is to derive a same-hue variant for the one role that has a threshold, and * to leave `--brand-primary` alone for decorative fills. * * Steps in 1/200ths and takes whichever direction reaches the bar first, so the * result is the smallest visible departure from the author's color. Ties go to * the shade, which keeps more chroma than a tint does. */ export declare function accessibleOn(color: string, surface: string, minRatio: number): string; export declare function buildClaudeMd(inputs: ScaffoldInputs, opts: ScaffolderOptions | undefined, spec: BrandPresetSpec, perTypeNarrative: string): ScaffoldFile; export declare function commonReadme(inputs: ScaffoldInputs, opts: ScaffolderOptions | undefined, spec: BrandPresetSpec): ScaffoldFile; export declare function commonRoadmap(inputs: ScaffoldInputs, opts: ScaffolderOptions | undefined, spec: BrandPresetSpec): ScaffoldFile; export declare function commonStatus(inputs: ScaffoldInputs, opts: ScaffolderOptions | undefined, spec: BrandPresetSpec): ScaffoldFile; export declare function commonHandoff(inputs: ScaffoldInputs, opts: ScaffolderOptions | undefined, spec: BrandPresetSpec): ScaffoldFile; /** * Persist the verbatim instantiation brief into the repo at * directives/PROJECT_BRIEF.md. Returns null for the wizard / `--input` paths * that carry no brief text (nothing to persist). The body is the raw brief and * is intentionally NOT run through the placeholder substituter, so a brief that * happens to contain `{{token}}`-looking text is preserved exactly (only * CLAUDE.md is validated for leftover markers — see writer.ts). */ export declare function commonProjectBrief(inputs: ScaffoldInputs, opts: ScaffolderOptions | undefined, spec: BrandPresetSpec): ScaffoldFile | null; /** * Emit the short coordination-onboarding pointer — but ONLY for a project that * participates in the bulletin. Returns null otherwise (a non-participating * project has nothing to onboard onto). Approach (a): the file points at the * canonical primer in the bulletin repo rather than embedding it, so there is * one source of truth. See {@link COORDINATION_ONBOARDING_TEMPLATE}. */ export declare function commonCoordinationOnboarding(inputs: ScaffoldInputs, opts: ScaffolderOptions | undefined, spec: BrandPresetSpec): ScaffoldFile | null; export declare function commonTroubleshooting(inputs: ScaffoldInputs, opts: ScaffolderOptions | undefined, spec: BrandPresetSpec): ScaffoldFile; export declare function commonChangelog(inputs: ScaffoldInputs, opts: ScaffolderOptions | undefined, spec: BrandPresetSpec): ScaffoldFile; export declare function commonMarketing(inputs: ScaffoldInputs, opts: ScaffolderOptions | undefined, spec: BrandPresetSpec): ScaffoldFile; export declare function commonBackups(inputs: ScaffoldInputs, opts: ScaffolderOptions | undefined, spec: BrandPresetSpec): ScaffoldFile; export declare function commonVersion(): ScaffoldFile; export declare function commonCursorignore(): ScaffoldFile; /** * Tracked project config, NOT ignored — see CURSOR_INDEXING_IGNORE_TEMPLATE for * the 2026-08-12 ruling and why the previous gitignore position was * self-defeating. */ export declare function commonCursorIndexingIgnore(): ScaffoldFile; export declare function commonDirective(inputs: ScaffoldInputs, opts: ScaffolderOptions | undefined, spec: BrandPresetSpec): ScaffoldFile; /** * Per-type `.gitignore` body. Sources: skill's "Inline mini-templates" * section. Each variant is the verbatim block from there, plus the shared * `GITIGNORE_SECRET_SCRATCH_BLOCK` appended to every type (so `.tmp/` and * `.specstory/` are guaranteed present — see that constant's doc comment). */ export declare function gitignoreFor(projectType: ScaffoldInputs["projectType"]): ScaffoldFile; /** * Derive a gitignored local env file (`.env.local` for nextjs, `.env` for * python-service) from an already-substituted `.env.example` body. Keeps every * key and its inline doc comments, clears assigned values so the user fills * real ones, and swaps the example banner for a local-file banner. Emitted * alongside `.env.example` so a fresh clone has the local file ready to fill * rather than requiring a manual `cp .env.example .env.local` — closes the * z2w-agent-command-center 2026-06-01 Open question. */ export declare function localEnvFrom(exampleContent: string, filename: string, slug: string): string; /** * Common base files every scaffolded project gets — CLAUDE.md, README.md, * ROADMAP.md, STATUS.md, HANDOFF.md, TROUBLESHOOTING.md, CHANGELOG.md, * VERSION, .cursorignore, .gitignore, directives/system-architecture.md. * * Callers pass in the per-type CLAUDE.md narrative chunk; everything else is * generic across project types. Type-specific files (package.json, wrangler, * etc.) are appended by the per-type scaffolder. */ export declare function commonBaseFiles(inputs: ScaffoldInputs, opts: ScaffolderOptions | undefined, perTypeNarrative: string): ScaffoldFile[]; /** npm deps the mailer requires; merged into the scaffolded package.json. */ export declare const EMAIL_DEPS: Readonly>; /** * npm deps the emitted Sentry `dataCollection` guard requires (`hasObservability`). * * BOTH are declared deliberately, and the caret ranges must stay in the same * major.minor family: * * - `@sentry/nextjs` is the SDK the app actually runs. * - `@sentry/core` is where the `ResolvedDataCollection` TYPE lives. It is NOT * re-exported by `@sentry/nextjs` (measured on 10.69.0 — `import type * { ResolvedDataCollection } from "@sentry/nextjs"` is a TS2305), so the * shared module has to name `@sentry/core` directly. Relying on it as an * undeclared transitive would work until someone runs a stricter installer. * * `@sentry/nextjs@X` pins `@sentry/core` to EXACTLY `X`, so a matching caret * range on both dedupes to one copy and the type can never describe a different * SDK than the one running. The emitted test asserts that equality at run time * rather than trusting this comment. */ export declare const SENTRY_DEPS: Readonly>; /** * The Neon serverless driver, added when a card-accepting project also has * Postgres and therefore gets the shared-store rate limiter. * * The emitted limiter names this package directly rather than importing a * `lib/db` the scaffold does not create — the Next.js scaffold ships no database * layer at all, so a `@/lib/db` import would simply not compile. Bringing its own * thin client also keeps the limiter working whichever ORM the project adopts * later, since it never has to agree with one. */ export declare const NEON_DRIVER_DEP: Readonly>; /** * The env-var block appended to a scaffolded `.env.example` when the project * sends email. Both providers' vars are stubbed so switching is a one-line env * change with no code edit. */ export declare const EMAIL_ENV_BLOCK = "\n# Transactional / auth email\n\nEMAIL_PROVIDER=ses\nSES_REGION=\nSES_ACCESS_KEY_ID=\nSES_SECRET_ACCESS_KEY=\nAWS_SES_FROM=\n\n# Only read when EMAIL_PROVIDER=resend:\nRESEND_API_KEY=\nRESEND_FROM=\n\n# Wiring: lib/email/mailer.ts. Setup: directives/email-setup.md +\n# [[email-service-router]]. Until the four SES_ vars are set every send is a\n# logged no-op, so the app works and only delivery is deferred.\n#\n# EMAIL_PROVIDER \u2014 \"ses\" (default) or \"resend\". Switching is this one line; no\n# code change at any call site.\n#\n# SES_* \u2014 the custom prefix and the explicit credentials are deliberate. On\n# Vercel/Lambda the platform injects its own AWS_* vars, which would shadow the\n# default credential chain. SES_REGION MUST equal the region the sender domain\n# is verified in, or sends fail with \"Email address is not verified\".\n#\n# AWS_SES_FROM \u2014 a domain-verified identity can send from any local-part.\n# Format: \"Brand Name \". Never noreply@.\n#\n# RESEND_FROM \u2014 must be on a Resend-verified domain.\n"; /** * Build the email-wiring files for a scaffold that sends email. \`layout\` * selects the file paths + whether a vitest test is emitted: * - "nextjs" → \`lib/email/mailer.ts\` (imported via the \`@/*\` alias); no * test file. * - "node-service" → \`src/lib/email/mailer.ts\` + \`src/lib/email/mailer.test.ts\` * (vitest picks up \`src/**\/*.test.ts\`). * The directive is emitted for both. * * NOTE: the nextjs scaffold DOES ship a vitest runner as of v0.9.0 (it emits * \`test/form-standards.test.ts\`), so the historical reason for skipping the * mailer test there is gone. It stays skipped pending verification only: the * test body imports \`./mailer.js\` for NodeNext resolution, and the nextjs * tsconfig uses \`"moduleResolution": "Bundler"\`. Emit it once that import has * been run — under a REAL \`npm test\` in a scaffolded project, not asserted from * a template string. */ export declare function emailFiles(inputs: ScaffoldInputs, opts: ScaffolderOptions | undefined, spec: BrandPresetSpec, layout: "nextjs" | "node-service"): ScaffoldFile[];