import { writeScaffold } from "./writer.js"; import type { AudienceGateReport } from "./writer.js"; import type { ScaffoldFile } from "./scaffolders/common.js"; import type { StandardDecision } from "./standards.js"; /** * This package's version, READ FROM `package.json` at load time rather than * written here by hand. * * πŸ”΄ It was a hand-maintained literal until 2026-09-19, and it had drifted to * `0.6.0` while the package was `0.31.0` β€” twenty-five minor versions, and it * was PUBLISHED that way, so `npx @zero2webmaster/starter-kit --version` * answered `0.6.0` for months. Nothing could have caught it: the constant was * internally consistent, every test that used it asserted against itself, and * the version-bump checklist in CLAUDE.md greps for the OLD version string β€” * which by then appeared nowhere near this line. * * Deriving it removes the class rather than the instance. `../package.json` * resolves correctly from both `src/` (vitest) and `dist/` (the built CLI and * the published tarball, where the layout is `package/dist/` + `package/package.json`). * `src/index.test.ts` additionally asserts the two agree, so a packaging change * that broke the path would fail loudly instead of silently reintroducing a * stale literal. */ export declare const version: string; export type ProjectType = "wp-plugin" | "cf-worker" | "cf-pages" | "nextjs" | "python-service" | "node-service" | "library" | "protocol-repo"; export type BrandPreset = "save-the-frogs" | "bansuri-bliss" | "zero2webmaster" | "custom"; /** * Who will READ the scaffolded project's agent files. * * `internal` β€” a Zero2Webmaster project. The full framework body is emitted: * the standards block with its rationale, the Skill Vault pointers, the * glossary, the capture-learnings routing, the framework-currency table. * * `external` β€” an outside customer, on their own GitHub account (the path * `project-creator` serves). Per **Kerry's 2026-08-14 ruling (Option A): * ship the artifact, not the doctrine.** The emitted CODE is unchanged β€” * that is the value the customer is buying β€” but the ~40 KB prose corpus * explaining which internal rulings produced it is NOT emitted. * * **This is deliberately NOT derived from `participateInBulletin`.** That flag * answers *"does this project post to the coordination bulletin"*, which is a * different fact from *"is the reader an outside customer"* β€” an internal * non-participating project would silently lose its own glossary. The * distinction was ruled on 2026-08-14 and the reasoning is in * `DECISION-BRIEF-2026-08-14.md`. * * Optional and defaults to `internal`, so every existing caller β€” the CI * `--input` path, the programmatic API, and every Z2W project scaffolded to * date β€” is byte-for-byte unchanged. */ export type Audience = "internal" | "external"; export type DbStrategy = "none" | "neon-postgres" | "supabase" | "sqlite" | "mysql-wp" | "other"; export declare const PROJECT_TYPES: readonly ProjectType[]; export declare const BRANDS: readonly BrandPreset[]; export declare const DB_STRATEGIES: readonly DbStrategy[]; /** * Follow-up details a user supplies when `brand` is `"custom"`. Carries the * four fields the confirmation / wizard flows ask for: brand name, the * capitalization rule that disambiguates correct from incorrect spellings, * and the two brand colors. Omitted (undefined) for the three named presets. */ export interface CustomBrandInputs { brandName: string; brandNameRule: string; primaryColor: string; accentColor: string; /** * Who owns the scaffolded project's work β€” the name that appears in the * emitted `README.md` copyright line and, for web types, the page footer. * * **Optional, and when omitted it falls back to `brandName` β€” NOT to * `DEFAULT_AUTHOR`.** Before v0.18.0 there was no way to supply this at all, * so `buildCustomBrandSpec()` hardcoded `Dr. Kerry Kriger` and every * outside-customer scaffold emitted `Β© 2026 Dr. Kerry Kriger` into the * CUSTOMER's own README and page footer β€” a false ownership claim on someone * else's repo, live on the `project-creator` path, found 2026-08-14. * * `custom` is the only brand an outside customer can express, so this field * is the one that matters most and was the one missing. */ author?: string; /** Optional URL for {@link author}. Omitted emits no author link. */ authorUrl?: string; } /** * Free-text sections lifted verbatim from a pasted project brief. Every field * is optional β€” only the sections the brief actually contained are populated. * Carried through to the scaffolder so the brief's planning content flows into * the generated ROADMAP.md + README.md rather than being discarded after * field inference (per [[instantiate-z2w-project]] SKILL Step 1a.4). Populated * automatically by the brief-first flow; absent for `--wizard`. The `--input` * path has no brief to parse, but a caller that already has the sections may * supply them in the input JSON (`parseScaffoldInputs` reads them) so an * `--input` scaffold still gets a concrete ROADMAP instead of a placeholder. */ export interface BriefSections { purpose?: string; keyApis?: string; dataSources?: string; expectedOutputs?: string; edgeCases?: string; roadmap?: string; futureImprovements?: string; } export interface ScaffoldInputs { slug: string; description: string; projectType: ProjectType; brand: BrandPreset; githubOrg: string; targetPath: string; dbStrategy: DbStrategy; hasWebUi: boolean; hasCron: boolean; participateInBulletin: boolean; /** * Who will read the emitted agent files β€” see {@link Audience}. Optional; * omitted reads as `"internal"`, which emits the full framework body exactly * as every release before v0.18.0 did. * * `"external"` implements Kerry's 2026-08-14 Option A ruling: the emitted * code is untouched, the doctrine is not emitted. */ audience?: Audience; /** * Whether this project sends transactional / auth email (magic links, * confirmations, notifications). When `true`, the `nextjs` and `node-service` * scaffolders emit a provider-router mailer (`sendEmail()` with a stable, * provider-agnostic signature) defaulting to Amazon SES with Resend as a * one-env-var swappable alternative (`EMAIL_PROVIDER=resend`), plus both * providers' env stubs and a `directives/email-setup.md` setup runbook. Per * the 2026-06-28 email-default decision (event-engine, on Kerry's steer): when * a stack already runs SES in production (STF / BB / Z2W), default to SES β€” * zero new vendor, no domain warmup, cheap at volume β€” and keep Resend as the * easier-DX alternative for non-developer clients. See [[email-service-router]]. * * Optional and defaults to `false` (no mailer emitted). Honored ONLY by the * `nextjs` and `node-service` types β€” WordPress uses FluentSMTP, Cloudflare * Workers use the Cloudflare Email Service ([[cloudflare-email-service]]), and * `python-service` is a different language, so those types ignore this flag. */ sendsEmail?: boolean; /** * Whether this project accepts user-generated content (UGC) β€” a community * platform, events app with public listings, marketplace, comments/forum, * profiles, direct messages, or any surface where one user's input is shown * to others. When `true`, the standards resolver inserts the "Moderation * baseline (user-generated content)" standard pointing at [[moderation-system]] * β€” the entity-agnostic reportβ†’queue model, reporting-as-a-safety-action, * enforcement-that-reuses-existing-gates, the fail-open AI-scan seam, the * public/anonymous-reporter variant, and the optional pre-publish gate β€” so a * new UGC app treats moderation as an at-instantiation decision (like the SEO * baseline for public web UIs) rather than a Phase-8 afterthought. Requested * by the event-engine agent on the bulletin (2026-07-08), which built the * second reference implementation of the pattern. * * Optional and defaults to `false` (no standard emitted). Skipped for the * `wp-plugin` type (WordPress moderates comments natively / via Akismet), and * for `library` / `protocol-repo` (no content surface). */ hasUserContent?: boolean; /** * Whether this project should wire runtime error / APM observability * (Sentry) at instantiation. When `true` AND the type is a Next.js / CF-Pages * web app, the standards resolver inserts the "Sentry runtime-error tracking" * standard pointing at [[sentry-runtime-errors]] β€” the `npx @sentry/wizard` * invocation, DSN-in-config posture, `SENTRY_AUTH_TOKEN` Production-only * scope, the tracing/replay feature-flag defaults, the smoke-route pattern, * and the 2025 Sentry-UI quirks β€” so a customer-facing app treats runtime * error capture as an at-instantiation decision (like Fathom for analytics and * Uptime Kuma for liveness) rather than a post-incident afterthought. Sentry * fills the gap uptime monitoring can't: a per-user 500 that `/health` never * sees. Requested by the videomigrator-dashboard agent on the bulletin * (2026-06-11) after wiring Sentry there; see [[sentry-runtime-errors]]. * * Optional. Omitted (undefined) reads as `false` in the standards resolver β€” * so the programmatic API and `--input` paths stay explicit/opt-in β€” while the * interactive brief-first and wizard flows apply a smart default of ON for * customer-facing Next.js / CF-Pages apps (a human can veto at the confirm * screen). Skipped for every type except `nextjs` / `cf-pages`: WordPress * gets error tracking via Admin Suite Diagnostics; CLI libraries, protocol * repos, Workers, and API-only services have no "individual user request * failed silently" surface. */ hasObservability?: boolean; /** * Whether this project has a PUBLIC SURFACE β€” pages meant for the general * public and therefore for search engines β€” as opposed to an internal * console, staff tool, or admin dashboard whose entire audience arrives * already knowing the URL. * * This is the question `app/robots.ts` actually needs answered, and it is NOT * the question the indexability gate asked before v0.27.0. That gate asked * *"is this a production deployment on a real custom domain?"* β€” and an * internal admin console satisfies **both** conditions while still being * wrong to index. The two facts come apart for every console this CLI emits. * Reported by `org-hq` on 2026-08-14 after serving `Allow: /` on a branded * public subdomain for a month. * * When `false`, the emitted `isIndexableDeployment()` returns `false` in every * environment, so `robots.txt` is `Disallow: /` permanently rather than * conditionally. The rest of the SEO baseline (metadata, canonical host, * sitemap, OG image, the alias redirect) is untouched β€” an internal tool still * wants a canonical domain and a working share card; it just does not want a * crawler. * * πŸ”΄ **Optional, and OMITTED READS AS `false` β€” this fails CLOSED**, which is * the opposite default from every other optional boolean here and is * deliberate. The two errors are not symmetric: * * - Wrongly non-indexable public app: one field, one redeploy, and the crawler * comes back. Nothing has escaped. * - Wrongly indexable internal console: admin URLs, staff names and internal * tooling are crawled and cached. Getting them back out is slow, awkward, * and the exposure has already happened. * * That asymmetry is the same argument the two-condition gate was built on in * the first place β€” fail toward the visible error ([[unknown-means-denied]]). * The wizard and brief-first flows ASK, so an interactive run never relies on * the default; the `--input` and programmatic paths must pass it explicitly. * Honored by the `nextjs` and `cf-pages` types, which are the two that emit a * robots document. */ hasPublicSurface?: boolean; /** * Whether the incoming `Host` header selects the tenant β€” a HOST-ROUTED * multi-tenant app, where each tenant has its own front door (its own custom * domain) served by one deployment. True for `org-hq`, `site-control`, * `z2w-member-match`; false for a single-tenant app that has exactly one * canonical domain. * * When `true`, the `nextjs` scaffolder changes two things: * * 1. **`next.config.mjs` omits the `*.vercel.app` β†’ custom-domain alias * redirect.** That redirect assumes ONE canonical destination, which is * precisely what a host-routed app does not have β€” redirecting an alias to a * fixed host drops visitors into some other tenant's app. * 2. **A `middleware.ts` host guard is emitted instead**, which returns **404** * for a production request on a known-non-canonical host. Per the 2026-07-30 * ruling (reached across `org-hq`, `site-control` and `z2w-member-match`): * **404 on an unmapped Host, never a redirect to a fixed one.** * * It also emits the `CANONICAL_HOSTS` allowlist stub in `.env.example`, since a * Host-routed app needs to know which hosts are legitimately its own. * * Optional and defaults to `false` (single-tenant β€” the alias redirect ships as * before). Honored only by the `nextjs` type; other types have no Host-based * routing surface. Deliberately opt-in on the `--input` / programmatic paths so * an existing caller's output is unchanged. */ hostRoutedTenancy?: boolean; /** * Whether this project will accept credit cards β€” a checkout, a donation * form, a save-a-card / update-a-card surface, a subscription signup. Any * project that will call `paymentIntents.create` or `setupIntents.create`, * or confirm either in the browser. * * When `true` AND the type is `nextjs`, the scaffolder emits the card-path * bot-protection shape rather than a bare endpoint: a Cloudflare Turnstile * verifier, an abuse policy sized for card entry, the browser widget, and an * emitted test that fails on the next unprotected card path. The standards * resolver also inserts the "Card-path bot protection" standard pointing at * [[stripe-elements-confirm-path]] Β§8. * * FOUR decisions are baked into what gets emitted, none of them cosmetic β€” * settled 2026-08-07 with `z2w-seller-suite` on the coordination bulletin, * against `z2w-crowdcommerce` v0.7.0 as the reference implementation: * * 1. **Invisible unless needed** (`appearance: "interaction-only"`). A * scaffold that emits a *visible* challenge by default is actively * harmful on a donation form β€” Kerry's explicit constraint. * 2. **Limits sized for card entry, and no global cap.** 30 per 10 minutes * per source, not the checkout's 5/60s: thirty people giving over one * wifi NAT at a fundraising dinner is a good night, not an attack. A * site-wide ceiling would convert going viral into an outage. * 3. **A five-valued verdict that fails OPEN.** A boolean silently collapses * "Cloudflare rejected this" into "we could not reach Cloudflare". This * is the deliberate *opposite* of [[webhook-fail-closed]], and the two get * conflated because they share the word "verify". * 4. **The enumeration grep ships with the standard.** A standard that says * "protect the card path" without saying how to *enumerate* card paths * passes review and misses a route β€” `z2w-seller-suite` found 2 of 3 card * paths bare in its own gateway by running exactly that grep. * * Kerry's directive, 2026-08-06: *"let's ensure that anywhere credit cards * are accepted in our system we have turnstile enabled"* β€” widened by him * the same day beyond any one plugin or project. * * Optional and defaults to `false` (nothing emitted). Honored only by the * `nextjs` type today; the standard itself is inserted for any type that * could plausibly take a card, so a Worker or WP plugin still gets told the * rule even though this CLI cannot emit its code. */ acceptsCards?: boolean; /** Present only when `brand === "custom"`. */ customBrand?: CustomBrandInputs; /** * Present when scaffolding from a pasted brief that had these sections (the * brief-first flow), or when an `--input` payload supplies them explicitly. */ briefSections?: BriefSections; /** * The verbatim project-instantiation brief/prompt this project was scaffolded * from. Captured automatically by the brief-first flow (the raw pasted brief); * may be supplied on the `--input` JSON. Written to the Airtable inventory * row's "Project Instantiation Prompt" column (Kerry's standing rule that * every inventory row carries its instantiation prompt). Absent for the * wizard path and `--input` payloads that omit it β€” in which case the agent * is reminded to add it to the row by hand. */ instantiationPrompt?: string; } /** * Options for the programmatic {@link scaffold} / {@link previewScaffold} entry * points. A deliberately small subset of {@link WriteScaffoldOptions} β€” the * interactive-only knobs (`promoteStandards`, `prompter`, `output`, * `showContents`) are omitted because a programmatic caller neither prompts nor * prints. Reach for `writeScaffold` directly if you need those. */ export interface ScaffoldOptions { /** Absolute target directory. Defaults to `inputs.targetPath`. */ targetPath?: string; /** * Pin the scaffold date as `YYYY-MM-DD` for deterministic output (tests, * reproducible builds). Defaults to today from the OS clock. */ date?: string; /** * Override the canonical Templates folder location. Defaults to the standard * local resolution (`~/Desktop/Zero2Webmaster/AI/Templates/`). A server * consumer bundles/mounts Templates and points this at it β€” the folder is * read server-side and never delivered to the client. */ templatesPath?: string; /** * Refuse to write into a non-empty directory. Defaults to `true`. Ignored by * {@link previewScaffold} (which never touches disk). */ refuseNonEmpty?: boolean; /** Layer the paid Pro SEO baseline extension (nextjs / cf-pages + hasWebUi only). */ withProSeo?: boolean; } /** * The result of a scaffold or preview: the composed file list, the standards * decision log, and the resolved absolute target path. For {@link scaffold} * these are the files that WERE written; for {@link previewScaffold} they are * the files that WOULD be written. */ export interface ScaffoldResult { files: ScaffoldFile[]; decisions: StandardDecision[]; targetPath: string; /** * What the audience gate did, present only for `audience: "external"`. * * **Check `danglingReferences` and show it to whoever asked for the * scaffold.** A non-empty list means the emitted `CLAUDE.md` still contains * prose pointing at sections the gate removed β€” the scaffold is usable, but * a human should decide what to do about the incoherence. The gate refuses to * rewrite those sentences itself, because a canonical body emitted with * sentences quietly altered still carries a fingerprint claiming it is * canonical. * * Added in v0.18.1 because v0.18.0 wrote this finding to the terminal only, * and {@link previewScaffold} discards its output stream by design β€” so the * one consumer that scaffolds for outside customers could not receive it. */ audienceGate?: AudienceGateReport; } /** * Scaffold a full project to disk and return the composed result. * * The high-level programmatic equivalent of `npx @zero2webmaster/starter-kit * init` for a caller that already has validated {@link ScaffoldInputs} (e.g. * from {@link parseScaffoldInputs} + {@link validateScaffoldInputs}). Delegates * to {@link writeScaffold}; the canonical AGENTS body + standards are read and * composed exactly as the CLI does. Throws `WriterError` on a non-absolute or * (by default) non-empty target, or if any template marker / `{{placeholder}}` * survives composition. */ export declare function scaffold(inputs: ScaffoldInputs, opts?: ScaffoldOptions): Promise; /** * Compose the exact same file list {@link scaffold} would write, but WITHOUT * touching the filesystem and without printing anything β€” the files are * returned as data. This is the entry point a web UI / server consumer wants: * show the user what they'll get (file tree, standards inserted) before they * commit to creating a repo. * * The only I/O is reading the Templates folder (needed to fill the AGENTS * body). `inputs.targetPath` (or `opts.targetPath`) must still be an absolute * path β€” it's used only to validate that every generated file resolves inside * it; nothing is created there. `refuseNonEmpty` is not consulted. */ export declare function previewScaffold(inputs: ScaffoldInputs, opts?: ScaffoldOptions): Promise; /** Outcome of {@link validateScaffoldInputs}: `ok` plus every error found. */ export interface ScaffoldInputValidation { ok: boolean; errors: string[]; } /** * Structurally validate a {@link ScaffoldInputs} without touching the * filesystem, collecting ALL problems (not just the first). Complements * {@link parseScaffoldInputs}, which checks primitive types + required-field * presence but deliberately does NOT check the slug regex, enum membership, or * that `targetPath` is absolute β€” this does, so a server can reject a bad form * submission with a complete error list before attempting a scaffold. * * Note: does not check that `targetPath` is empty/writable on disk β€” that's an * async filesystem concern the CLI's confirm flow owns, and `scaffold` enforces * it at write time via `refuseNonEmpty`. */ export declare function validateScaffoldInputs(inputs: ScaffoldInputs): ScaffoldInputValidation; export { writeScaffold }; export { parseScaffoldInputs, loadScaffoldInputs, WriterError } from "./writer.js"; export type { AudienceGateReport, WriteScaffoldOptions, WriteScaffoldResult, PromotionDecision, } from "./writer.js"; export { resolveStandards, applyStandardsBlock, fathomEnvVarFor } from "./standards.js"; export type { StandardKey, StandardDecision, StandardDecisionState, StandardsResolution, } from "./standards.js"; export { scaffoldByType } from "./scaffolders/index.js"; export type { ScaffoldFile, ScaffolderOptions } from "./scaffolders/common.js";