**Path:** `packages/create-zudo-doc/CLAUDE.md`

# create-zudo-doc

CLI scaffold tool for creating new zudo-doc documentation sites. Generates a project with configurable features, a Default light/dark color scheme, and i18n support.

> **Eject CLI contract:** the per-component `zudo-doc eject <component>` swizzle CLI + `.zudo-doc.json` provenance marker are specified in `docs/eject-contract.md` (C0 #2359; implemented by C1 #2362; Decision 5 revised by S4 #2373 — the `zudo-doc` bin now ships from `@takazudo/zudo-doc`, not `create-zudo-doc`, so it is reachable in generated projects).

## Architecture (minimal-scaffold, epic zudolab/zudo-doc#2651 Wave 6 #2660)

The generator emits the **locked ~18-file minimal manifest** — one config file
(`zfb.config.ts`, `zudoDoc({ ...only fields you chose })`) plus markdown
content plus a handful of unavoidable root files. Everything else (layout,
chrome, islands, default design-token custom properties, even the doc ROUTES themselves via
`packageOwnedRoutes`) ships from `@takazudo/zudo-doc` in `node_modules`.

1. Copy the minimal **base template** (`templates/base/`) — no injection
   anchors left; every file is either shipped verbatim or (`global.css`)
   patched by exactly one feature's `postProcess` hook.
2. **Generate** the ONE `zfb.config.ts` programmatically (`zfb-config-gen.ts`)
   — diff-from-defaults: only fields the user actually chose are emitted.
3. **Compose** selected features — copy feature files (only five features have
   a `templates/features/<name>/files/` directory: `claudeSkills`,
   `claudeSkillsWriting`, `i18n`, `tauri`, `tauriDev`) and run `postProcess`
   hooks for the handful of cases
   that need a small source patch (`docHistory` threads itself into the doc
   stub(s); `designTokenPanel` inserts the one conditional zdtp CSS import
   line; `tagGovernance` writes a tiny `src/config/` pair *inline* — it has no
   template directory — see each module's header comment for why).

Both base and i18n document stubs unconditionally consume
`virtual:zudo-doc-chrome-bindings`. The doc-history post-processor spreads the
configured object first and replaces only `DocHistory`, so it must never drop
primary slots, `headerRightComponents`, or `mdxExtras`. Presentational chrome
customization is config + one typed module; it does not require a route fork.

Most feature modules are now **pure settings-field emission**: the feature
only changes what `zfb-config-gen.ts` writes into `zudoDoc({...})`, because
the corresponding UI/behavior is entirely package-owned already (package-first
migration, epics #2321/#2344/#2356). `src/features/<name>.ts`'s header
comment says, per feature, exactly why there's nothing (or almost nothing)
left to inject or copy.

The `changelog` feature is handled directly in `scaffold.ts`. The optional
`--changelog-packages <a,b>` value (also available as the `changelogPackages`
preset/API array) implies `changelog`, seeds one nested page per validated slug,
and emits the Changelog header dropdown. `CreateOptions` and `PresetJson` must
remain in parity for this value as well as the other preset-only options.

## Key Files

| File | Role |
|------|------|
| `src/scaffold.ts` | Orchestrates the scaffold pipeline: copy base, seed content, generate `zfb.config.ts` + `package.json`, compose features |
| `src/compose.ts` | Composition engine: injection system (mostly unused now — see below), feature resolution |
| `src/features/*.ts` | Feature modules — settings-field emission via `zfb-config-gen.ts` + a handful of genuine file copies / `postProcess` patches. 20 module files, 20 `featureModules` keys in `index.ts` — one of which (`footer`) is a pseudo-feature triggered by `footerNavGroup`/`footerCopyright`/`footerTaglist`. `sidebarFilter` has no module (built into the package's sidebar tree); `skillSymlinker`, `claudeSkills`, `claudeSkillsWriting`, and `changelog` are handled directly in `scaffold.ts` |
| `src/zfb-config-gen.ts` | The SINGLE config generator — emits the one `zfb.config.ts` (`defineConfig(zudoDoc({...}))`), diff-from-defaults against a local mirror of `packages/zudo-doc/src/config.ts`'s `DEFAULT_SETTINGS`. Replaces the former `settings-gen.ts` + `zfb-config-gen.ts` two-file split — there is no more `src/config/settings.ts` in a fresh scaffold |
| `src/claude-md-gen.ts` | Generates the per-project `CLAUDE.md` for the scaffolded site, including the current zfb semantic-highlighting contract, chrome bindings, and binding-aware eject guidance |
| `src/preset.ts` | Resolves a JSON `--preset` file (or CLI flags) into `UserChoices` — unrelated to the package's own `@takazudo/zudo-doc/preset`, despite the similar name |
| `src/constants.ts` | Feature definitions, supported langs, header-right labels, and the current Default light/dark scheme pairing |
| `src/utils.ts` | Shared utilities (patchFile, patchDefaultLang, the deprecated legacy-locale compatibility helper) plus the destination seam: `validateProjectName` (locked package-name grammar), `splitDestination`/`normalizeDestination` (CLI destination path → directory + final segment), and `resolveTargetDir`/`destinationLabel` — the ONE resolver `scaffold.ts`, `api.ts`, and `index.ts` must all use rather than re-deriving `path.resolve(cwd, projectName)` (#4023) |
| `src/cli.ts` | CLI argument parsing (minimist). The first positional is `destination` (may be a path); `--name` is a separate bare package name that overrides the name derived from the destination's final segment. Keep them split — the locked project-name grammar (F4 #2013) must keep rejecting a path in `--name` |
| `src/api.ts` | Programmatic API (`createZudoDoc()`). `CreateOptions` must stay in sync with `PresetJson` (`preset.ts`) — a field added to one and not the other is a type-level parity gap (#2922). Shape validation for `headerRightItems`/`metaTags`/`changelogPackages` is shared via `preset.ts`'s exported validators — extend those, don't re-implement the allowlists here. |
| `src/prompts.ts` | Interactive prompts (@clack/prompts) |
| `src/index.ts` | Entry point |

### Template Directories

| Directory | Role |
|-----------|------|
| `templates/base/` | The static half of the locked ~18-file minimal manifest (barebone, EN-only): `pages/index.tsx` (1-line re-export), `pages/docs/[[...slug]].tsx` (self-contained doc stub), `src/styles/global.css` (~20-line `@import` chain + token-override slot), `tsconfig.json` (5-line extends form), `scripts/check-links.js` (source-first link/anchor validation), `scripts/setup-doc-skill.sh`, and four `public/` favicon files. `zfb.config.ts`/`package.json`/`CLAUDE.md`/`.gitignore`/`.npmrc`/`pnpm-workspace.yaml` are generated programmatically, not copied from here. |
| `templates/features/*/files/` | Feature-specific files copied when a feature is selected. Exactly five have a template directory: `claudeSkills` (skill copies, driven from `scaffold.ts`), `claudeSkillsWriting` (the zudo-doc-writing skill, also driven from `scaffold.ts`), `i18n` (locale doc stub), `tauri` and `tauriDev` (Rust shells). `tagGovernance` has **no** template directory — it writes one explicit tag vocabulary/config module inline in its `postProcess`; all audit/suggest behavior comes from package-owned bins. |

### Injection anchors — mostly retired

`src/compose.ts`'s `ANCHOR_FILES` is now an **empty array**. Every anchor
target the old system targeted — `pages/lib/_body-end-islands.tsx`,
`src/config/settings-types.ts`, `src/styles/global.css`'s `@slot:global-css:*`
comments — is gone from `templates/base/` (the package owns chrome/islands/
settings-types entirely now; `global.css` shrank to a fixed `@import` chain
with no anchors). The `Injection`/`applyInjections`/`cleanAnchors` machinery
in `compose.ts` is kept as infrastructure for a future feature that
genuinely needs it, not because anything uses it today — a few feature
modules (`docHistory`, `designTokenPanel`, `tagGovernance`) instead use a
plain `postProcess` hook that does a small, targeted string patch (see each
module's source).

## Testing

### Unit tests

```bash
pnpm test
```

Runs vitest tests in `src/__tests__/`.

### Generator CLI integration tests

Two Claude Code skills test the full scaffold-build-run cycle:

- `/l-generator-cli-tester <pattern>` — Test a single generation pattern
- `/l-run-generator-cli-whole-test` — Run all 11 patterns, fix bugs, verify everything

#### Test patterns

| Pattern | Description |
|---------|-------------|
| `barebone` | Everything OFF — minimal project |
| `search` | Only search enabled |
| `i18n` | Only i18n enabled |
| `sidebar-filter` | Only sidebar filter enabled |
| `claude-resources` | Only Claude Resources enabled |
| `codex-resources` | Only Codex Resources enabled |
| `design-token-panel` | Only design token panel enabled (uses `--design-token-panel` CLI flag) |
| `light-dark` | Light-dark color scheme mode |
| `lang-ja` | Japanese as default language |
| `all-features` | Everything ON |

Always rebuild the CLI before testing:

```bash
pnpm build
```

## Adding a New Feature

**Follow the "Feature Change Checklist" in the repo-root `CLAUDE.md`** — that is the single
canonical ordering, and it starts at `packages/zudo-doc/src/config.ts` (the ONE field census)
rather than at this package. This file deliberately keeps no second copy: the two orderings
used to be hand-maintained side by side and drifted (they disagreed about which features have
a `templates/features/` directory).

Generator-side touchpoints, for orientation only — the root checklist has the authoritative
order and the reasons: `src/constants.ts` (CLI flag), `src/features/<name>.ts` +
`src/features/index.ts` (module + registration), `src/zfb-config-gen.ts` (`DEFAULT_MIRROR` +
`buildDesiredConfig()` + `FIELD_ORDER`), `src/scaffold.ts` (deps), `src/__tests__/scaffold.test.ts`.

After changes, run `/l-update-generator` to verify no drift remains between the main project and the generator.