| name | saas-scaffolder |
| description | Materialize a full-stack SaaS project skeleton with Ulak OS governance pre-wired (Next.js + TypeScript + Supabase + optional payment + i18n + RLS + CI + tests + Traefik deploy). Generates a shippable starter that embeds 23 sector packs + 8 rule packs + 79 anti-patterns from commit 1. Use when starting a new SaaS product OR when /ulak-scaffold command dispatches. |
| context | fork |
| agent | autonomous-program-director |
| allowed-tools | Read Grep Glob Edit Write Bash |
SaaS Scaffolder — Greenfield Full-Stack Skeleton Generator
Goal
Produce a ship-ready full-stack SaaS starter directory where every Ulak OS governance discipline is enforced from day 1. The output must pass /ulak-director komple with zero critical findings on commit 1.
When to use
Dispatched from /ulak-scaffold command, or directly when greenfield SaaS creation is in scope.
Inputs (required — refuse to start without these)
product_name: "<kebab-case-name>"
product_domain: "<domain-slug>"
stack_frontend: "nextjs"
stack_backend: "supabase"
locale_primary: "tr"
locales_supported: ["tr"]
payment_provider: "none"
has_reseller_tier: false
has_admin_surface: true
has_mobile: false
hosting: "self-managed-vps"
output_path: "<relative or absolute>"
Outputs
A fully-populated target directory at output_path with the structure below, plus:
reports/current/scaffold-log.md — list of created files + their Ulak source templates
reports/current/scaffold-verdict.yaml — post-scaffold validation gate results
Target structure (Next.js + Supabase default)
<product_name>/
├── CLAUDE.md # imports Ulak OS core contract
├── AGENTS.md # Codex plain-text reading order (optional)
├──.gitignore # Ulak's full discipline block
├──.env.example # placeholders only
├── package.json # pnpm, Next 16, TypeScript 5 strict
├── pnpm-lock.yaml # generated on first install
├── tsconfig.json # strict: true, no any
├── next.config.ts
├── tailwind.config.ts # v4
├── postcss.config.mjs
├── middleware.ts # Supabase SSR session rehydration + auth helper entry
│
├── app/ # Next.js App Router
│ ├── (public)/ # landing, pricing, about, privacy, terms
│ ├── (auth)/ # login, register, reset
│ ├── (customer)/ # authenticated customer surface
│ ├── (admin)/admin/ # role-gated admin surface
│ ├── (partner)/partner/ # [if has_reseller_tier]
│ ├── api/
│ │ ├── public/ # unauthenticated; rate-limited
│ │ ├── customer/ # customer surface
│ │ ├── admin/ # admin surface
│ │ ├── partner/ # [if has_reseller_tier]
│ │ └── webhooks/ # payment / provider callbacks with signature verification
│ └── layout.tsx
│
├── components/ # feature-organized, not kitchen-sink
│ ├── ui/ # design system primitives
│ ├── auth/
│ ├── customer/
│ ├── admin/
│ └── shared/
│
├── lib/
│ ├── supabase/ # client, server, admin (with server-only guard)
│ ├── auth/ # single auth helper; used by every surface
│ ├── rate-limit.ts # Redis-backed (AP-01 avoidance)
│ ├── i18n/ # SSOT locale config per rule pack `localization-ssot`
│ └── logger.ts # structured JSON logs per observability-baseline
│
├── supabase/
│ ├── migrations/
│ │ ├── 00001_initial_schema.sql
│ │ ├── 00002_rls_policies.sql # templates for tenant isolation
│ │ └── 00003_seed_admin.sql
│ └── config.toml # for supabase CLI local dev
│
├── infrastructure/
│ ├── docker-compose.yml # dev-friendly defaults
│ ├── docker-compose.prod.yml # 127.0.0.1 binds + security_opt + cap_drop
│ ├── docker-compose.override.yml.example
│ ├── kale-kapisi.sh # VPS hardening per rule pack
│ ├── deploy.sh # webhook-triggered + health probe
│ ├── deploy-poll.sh # CI-independent fallback (architecture-currency)
│ └── nginx/
│ └── <product_name>.conf # reverse proxy with TLS
│
├── scripts/
│ ├── preflight.sh # pre-push parity (R-04)
│ ├── install-hooks.sh # installs.githooks/pre-push
│ ├── validate-imports.sh # from Ulak OS (symlink or copy)
│ └── seed-local-db.sh # local dev convenience
│
├── tests/
│ ├── unit/
│ │ └── lib.test.ts # at least one passing baseline test
│ ├── integration/
│ └── e2e/
│ └── landing.spec.ts # smoke E2E: / loads
│
├──.github/
│ ├── workflows/
│ │ ├── ci-validation.yml # copy of Ulak's; adjusted for target stack
│ │ ├── deploy.yml # webhook deploy + health probe poll
│ │ └── secret-scan.yml # gitleaks
│ ├── dependabot.yml
│ └── vendor-parity-exemptions.txt
│
├──.gitleaks.toml # config per Ulak's v2.1.4 discipline
├──.gitleaks.baseline # empty initially
│
└──.claude/
├── settings.json # safe-default allow + deny; hooks per Ulak
├── settings.local.example.json
└── rules/ # project-local rule-pack overrides (optional)
reports/
└── current/ # gitignored; populated by first director run
docs/
├── architecture/ # initial diagrams
├── governance/
│ └── pattern-import-ledger.yaml # seeded empty
├── i18n/
│ ├── tr.json
│ └── en.json
└── runbooks/
├── deploy.md
└── first-hours-on-vps.md
Process (step-by-step)
1. Validate inputs
Refuse to proceed if:
product_name is not kebab-case
product_name directory already exists at output_path
payment_provider requires configuration not in inputs
locales_supported is empty
2. Derive activations
Based on inputs, compute:
sector_packs_activated: [saas, <product_domain>, payment-integrated-saas?, reseller-enabled-saas?, multi-tenant-supabase?, vps-nginx-compose-topology?,...]
rule_packs_loaded: [typescript-nextjs, docker-compose, api-security, turkish-locale?, localization-ssot?, react-native-expo?, llm-streaming-context-aware?]
anti_patterns_relevant: [AP-01..AP-19 with auto-filter based on stack]
3. Create directory tree
Use mkdir -p for every directory in the target structure. Walk the tree in topo order (parents first).
4. Materialize files
For each file:
- Read the corresponding template from
templates/saas-starter/<path> (if exists)
- Substitute
{{product_name}}, {{product_domain}}, {{locale_primary}}, etc.
- Write to the target path
For files without a template, generate from sector pack + rule pack + anti-pattern guidance. The skill is a Claude-dispatched generator; the templates are starting points.
5. Seed governance artefacts
docs/governance/pattern-import-ledger.yaml — empty array
docs/i18n/tr.json + docs/i18n/en.json — minimal key set (login, logout, dashboard, settings)
- Supabase RLS policies — template for tenant_id-based isolation
.env.example — all env var names required by the activated packs
6. Initialize
cd <output_path>
git init
git add -A
git commit -m "feat: scaffold from Ulak OS v2.2.2 — <product_name>
Stack: Next.js + Supabase + <payment_provider>
Locales: <locales_supported>
Sector packs: <sector_packs_activated>
Rule packs: <rule_packs_loaded>
Generated by Ulak OS /ulak-scaffold."
7. Post-scaffold verification
Write reports/current/scaffold-verdict.yaml with:
scaffold_verdict:
product_name: "<name>"
files_created: <count>
validation:
structure_valid: true
no_real_secrets_in_tree: <grep check result>
rls_templates_present: <check>
ci_workflows_valid: <actionlint result or not-run>
pattern_import_ledger_seeded: true
next_steps:
- "cd <output_path>"
- "pnpm install"
- "cp.env.example.env.local && fill values"
- "pnpm dev"
- "./scripts/install-hooks.sh"
optional_followup:
- "Run /ulak-director komple in the new project for v0.1.0 baseline audit"
Rules
- No real secrets. If a template has a placeholder like
YOUR_STRIPE_KEY, leave it — do not attempt to detect the operator's keys and inject them.
- No silent deletion. If
output_path already exists with files, refuse (do not overwrite).
- Idempotency. Re-running with the same inputs in an already-scaffolded directory is a no-op (the skill detects and exits).
- Locale-aware templates. If
locale_primary: tr, generated user-facing strings in landing / auth pages are Turkish; English fallback in en.json.
- Product-surface-split discipline. Customer + admin + (optional) partner routes must live under different prefixes with different auth gates per
docs/governance/product-surface-split.md.
Anti-patterns the scaffolder actively prevents
By design, commit 1 of the generated project cannot contain:
- AP-11 Multi-layer auth bypass (single
lib/auth/index.ts helper; every surface imports it)
- AP-12 Fake rollback deploy (deploy.sh template has mandatory health probe + rollback path)
- AP-13
server-only installed but never imported (every server-role file starts with import 'server-only')
- AP-16
.env.local committed (gitignore pattern blocks it)
- AP-17 No database backup (scripts/backup-db.sh stub + reminder in runbook)
- AP-18 Static HMAC empty body (deploy webhook template signs full body + timestamp + nonce)
- AP-19 Root.env.local in monorepo (if has_mobile, per-app env files)
Integration
.claude/commands/ulak-scaffold.md — the command that dispatches this skill
docs/runtime/sector-packs.md §greenfield-saas-starter (SP-14) — the sector pack
templates/saas-starter/ — file templates
docs/runtime/output-profiles.md — GREENFIELD_BUILDER_PROFILE + PACK_GENERATION_PROFILE are compatible with this skill
.claude/agents/autonomous-program-director.md — the dispatching agent
Sector overlay orchestration (v2.2+)
Scaffolder skill v2.2 resolves inputs → overlay directories → merge order. The resolved file set is deterministic for any given input tuple.
Input → overlay directory mapping
| Input | Template directory | Applies when |
|---|
--sector <name> | templates/sectors/<name>/ | operator opts into a sector |
--hybrid-backend <kind> | templates/backends/<kind>/ (e.g., fastapi) | when a separate backend service is desired |
--has-mobile | templates/apps/mobile-expo/ | when an Expo app ships alongside web |
--has-bot <kind> | templates/apps/bot-<kind>/ (e.g., telegram) | when a bot ships |
--deploy <kind> | templates/sectors/container-k8s/ OR monorepo-root/infrastructure/ | deploy target selection |
--compliance <list> | templates/sectors/regulated-saas/ + any listed (gdpr, kvkk, soc2) | regulatory layer |
| base (always) | templates/saas-starter/ | every run writes this first |
Merge order (deterministic, v2.2)
- Monorepo root —
templates/monorepo-root/ if any overlay activates (package.json with pnpm-workspaces + turbo.json + pnpm-workspace.yaml + root .gitignore + README). Without overlays, base saas-starter runs in flat mode at <product_name>/ directly.
- Base web app —
templates/saas-starter/ → <product_name>/apps/web/ (monorepo mode) or <product_name>/ (flat mode)
- Hybrid backend —
templates/backends/<kind>/ → <product_name>/apps/api/
- Mobile app —
templates/apps/mobile-expo/ → <product_name>/apps/mobile/
- Bot app —
templates/apps/bot-<kind>/ → <product_name>/apps/bot/
- Shared packages —
templates/shared-packages/*/ → <product_name>/packages/{shared,ui,config}/
- Sector overlay —
templates/sectors/<sector>/ merged onto apps/web/ (pages, schemas, lib) with .override.template files replacing base paths. Each sector's supabase/migrations/00005_*.sql.template lands as an additional migration alongside base 00001-00004.
- Compliance overlay —
templates/sectors/regulated-saas/docs/compliance/ to <product_name>/docs/compliance/ (always docs-only; no code paths)
- Deploy target —
templates/monorepo-root/infrastructure/ (compose+traefik) OR templates/sectors/container-k8s/k8s/ (k8s) based on --deploy
- Scaffold log —
reports/current/scaffold-log.yaml per run
Override pattern
When a sector overlay's path matches a base template path, the overlay replaces the base if .override.template suffix is present:
templates/saas-starter/app/layout.tsx.template (base)
templates/sectors/ecommerce/app/layout.tsx.override.template (sector override)
Sector overrides are diffable — the merge log records which base files were superseded.
Compatibility matrix
| Sector | Pairs with (recommended) | Pairs with (allowed) | Refuses |
|---|
| payment-integrated-saas (SP-03 via B.2) | reseller, regulated | ecommerce, marketplace | — |
| ecommerce | payment-integrated-saas, ai-copilot | marketplace, multi-tenant | health-sensitive (schema overlap) |
| fintech | regulated-saas, payment-integrated-saas | enterprise-b2b | ecommerce |
| health-sensitive | regulated-saas | enterprise-b2b | ecommerce, marketplace |
| education | member-gated-community | ai-copilot, regulated-saas (COPPA) | fintech |
| enterprise-b2b | regulated-saas | media-content, ai-copilot | marketplace |
| media-content | ai-copilot, member-gated-community | education | fintech |
| marketplace | payment-integrated-saas | reseller | health-sensitive |
| ai-copilot | ai-relay-cost-control | media-content, education | — |
| pwa-desktop | any | any | container-k8s (deployment shape conflict) |
| admin-cms-hardening | any | any | — |
| container-k8s | self-hosted-supabase | any hybrid | pwa-desktop |
Scaffolder refuses conflicting combinations at Phase 0 with actionable error: "ecommerce + health-sensitive cannot coexist; separate into two apps (monorepo mode) or pick one".
Code enforcement (SEC-V22-H-15 fix, v2.2.1+): the compatibility matrix above is mirrored in docs/runtime/sectors-compat.yaml (machine-readable). The skill reads that file at Phase 0 and refuses the run with exit 65 (EX_DATAERR) when operator-selected sectors land on a refuses entry. The refusal message includes: (a) the reason (schema-overlap, FSM-clash, privacy-incompat, …), (b) an explicit "split into monorepo" suggestion, (c) a link to open an issue if the rule itself is wrong. Compatibility decisions are versioned in the YAML header for forward migration.
Sector-specific anti-patterns
Each sector overlay carries its own anti-pattern catalogue entries (trigger-enforced where possible). Examples shipped in v2.2:
- AP-EC-02 (oversell) — ecommerce
reserve_stock_on_order_item trigger
- AP-SSO-01 (IdP-trust without DB) — enterprise-b2b SSO libs
- AP-PHI-01 (unencrypted PHI) — health-sensitive envelope encryption
- AP-PWA-01 (cache poison) — pwa-desktop per-user scope hash
- AP-COM-01 (gated content in sitemap) — member-gated-community CHECK constraint
- AP-AI-01/02/03 (cost/token/fallback) — ai-relay-cost-control middleware
- AP-ADM-01/02 (impersonation audit + bulk cap) — admin-cms-hardening DB constraints
- AP-REG-01 (PHI in standard audit) — regulated-saas BEFORE INSERT trigger + separate PHI channel
- AP-MP-01/02/03 (commission / payout / dispute FSM) — marketplace triggers
- AP-SHS-01/02 (backup + kong rate limit) — self-hosted-supabase runbook + plugin enforcement
- AP-K8S-01/02 (resource limits + PDB) — container-k8s manifests + LimitRange
- AP-COPPA (under-13 consent) — education trigger
Determinism contract (v1.1+)
Every scaffolder run is a pure function of its input + the templates/saas-starter/ tree at the current commit. Two properties:
-
Template-first, synthesis-second. When a concrete .template file exists for a target path, the skill substitutes placeholders and writes the file verbatim. Synthesis (generating a file from sector-pack rules + rule-pack guidance) is only allowed when NO template exists for that path AND the sector pack explicitly marks the path as synthesizable: true. Every synthesized file records source: synthesized + the rules consulted in scaffold-log.yaml.
-
Idempotency. Re-running the scaffolder with the same input against an existing output directory:
- Detects every file already present
- Diffs against what the current template+input would produce
- Writes a
scaffold-rerun-report.md with: unchanged / template-updated-operator-untouched / operator-modified-ignored / missing (to be created)
- Never overwrites operator-modified files without explicit
--force flag
--force only restores to the template shape; operator losses are logged but not prevented
Verification — what a scaffolded project must pass (v1.1+)
After scaffolder completes, the output directory must satisfy:
pnpm install # zero errors, zero peer-dep warnings
pnpm lint # zero errors
pnpm typecheck # zero errors (tsc --noEmit)
pnpm test # all tests pass (Vitest baseline)
pnpm build # production build succeeds
pnpm dev # localhost:3000 responds (landing + login + dashboard)
pnpm preflight # full CI mirror passes
These are the post-scaffold contract. Template gaps that break any of them are v1.1+ blockers.
Template inventory reference (v1.1+)
The canonical file list lives at templates/saas-starter/. As of v1.1:
- Auth surfaces:
app/(auth)/{layout,login,register,forgot-password}/
- Customer surface:
app/(customer)/{layout,dashboard,settings}/
- Admin surface:
app/(admin)/{layout,page,users,audit-log}/
- Partner surface (gated):
app/(partner)/{layout,page,sub-users}/
- Marketing:
app/(marketing)/{about,pricing,blog,blog/[slug],changelog}/
- API routes:
app/api/{public/health,customer/me,admin/users,partner/sub-users,webhooks/stripe,webhooks/iyzico}/
- UI kit: 15 shadcn primitives under
components/ui/ + dashboard shells + theme provider
- Auth helper:
lib/auth/index.ts.template (single source of truth)
- Payments:
lib/payments/{index,stripe,iyzico}.ts.template (provider-agnostic)
- i18n:
docs/i18n/{tr,en}.json.template + lib/i18n/index.ts.template
- Utils:
lib/utils.ts.template (cn() + absoluteUrl() + slugify() + formatCurrency())
- DB: 4 migrations (initial schema + RLS + seed + payments)
- Infra:
infrastructure/{docker-compose*,nginx/*} + .github/workflows/{deploy,ci-validation,secret-scan} + dependabot
Total templates at v1.1: ~95 files. Scaffolder emits ≥ 85 of them for any single run depending on input gates.
Canonical footer
Authoritative as of Ulak OS v2.2.0. This skill is the primary "Ulak OS produces a SaaS" capability and defines the public-GA showcase promise. v1.1 closed UX-HI-03 via 85+ templates; v2.0 added hybrid stack support (FastAPI + Expo + Telegram + monorepo + Traefik); v2.2 added sector overlay orchestration for 14 sectors (education / fintech / ecommerce / marketplace / enterprise-b2b / media-content / health-sensitive / ai-copilot / pwa-desktop / admin-cms-hardening / ai-relay-cost-control / member-gated-community / self-hosted-supabase / regulated-saas / container-k8s). Total scaffolder template surface at v2.2: 175+ files across base + backends + apps + shared-packages + monorepo-root + 14 sectors.