create-nockta-repo
v0.2.1
Published
Creates new repositories or project targets by running official framework scaffolders, applying Nockta architecture overlays, and invoking inject-nockta-skills to render AI-tool adapter outputs.
Readme
create-nockta-repo
Scaffold a new project the Nockta way — one command that runs the official framework scaffolder, lays down Nockta's architecture conventions on top, and wires in your AI-tooling skill packs.
create-nockta-repo is a thin, deterministic orchestrator. It does not reimplement
create-next-app, create-vite, the Nest CLI, the Shopify CLI, or the Expo/React Native CLIs — it
drives them, then applies two Nockta-specific post-processing steps: an architecture overlay
(convention directories, template READMEs, a couple of curated file moves) and a hand-off to
inject-nockta-skills, which installs the AI-adapter skill
content (.claude/, AGENTS.md, etc.).
It is wizard-first (run it bare and it interviews you), monorepo-aware (drop a target into an existing workspace and it does the right thing), and multi-type aware (a Shopify theme that also ships a Vite/React frontend can pull both skill domains).
Who it's for: teams standing up new repos or new targets inside a monorepo who want every project to start from the same architecture baseline and the same AI-agent skill set, without hand-copying boilerplate or memorizing each upstream scaffolder's flags.
What it does
For a single invocation, in order (spec §12.1 / §12.2):
- Resolve the project path and repo type, read + validate the architecture preset, validate the
--adapterslist, and detect whether the target lands inside a monorepo. - Run the official scaffolder for the chosen type (e.g.
npx create-next-app@latest) into the resolved target directory. On failure it stops immediately — nothing downstream runs. - Apply the architecture overlay — create convention directories, copy in template files, and perform only the explicitly-declared file moves. Never overwrites an existing file; no rollback in the MVP (a failure reports exactly what was created before it stopped).
- Invoke
inject-nockta-skillsby spawning its CLI (npx inject-nockta-skills@latest ...) to render the AI adapters and skill packs. Never an npm dependency — see How skill installation works. - Write the repo profile —
<target>/.nockta/repo-profile.json, recording what was created.
Everything is deterministic (bundled overlay content, pinned upstream invocations) and every step has a dedicated exit code so a failure is never ambiguous.
No git commits, ever (decisions.md D11). create-nockta-repo never runs git commit — not even
when the upstream scaffolder initializes a git repo itself (create-next-app and create-expo-app
both do; there's no flag to suppress the latter). The architecture overlay and adapter outputs are
left uncommitted for you to review before committing. This is a hard rule, not a default you can
override with a flag.
Install & quick start
Status — not published yet.
package.jsoncarries"private": trueas a safety default; the owner removes it immediately before the first realnpm publish. The published invocation will benpx create-nockta-repo(the packagebiniscreate-nockta-repo). Until then, run it from a local build:cd create-nockta-repo pnpm install && pnpm build node dist/cli.js --help
Once published, the intended entry points:
# Interactive — the wizard interviews you (requires a real terminal)
npx create-nockta-repo
# Non-interactive — everything on the command line (requires --yes)
npx create-nockta-repo my-project --type next --yesRunning it bare (no project path or no --type) drops you into the wizard. Supplying a path
and --type and --yes (or --dry-run) runs straight through non-interactively. Anything in
between, on a real terminal, still opens the wizard with your partial flags pre-filled.
Requires Node ≥ 20 (
package.jsonengines).
$ create-nockta-repo create my-vite-app --type vite-react-ts --adapters claude --dry-run
# prints plan incl. spawn: npx inject-nockta-skills@latest install --type vite-react-ts --adapters claude --yes --json ; writes nothing
$ create-nockta-repo create my-vite-app --type vite-react-ts --adapters claude,cursor --yes
# runs real `npm create vite@latest`, applies architecture overlay (10 paths), then invokes inject:
# "AI skill injection succeeded (standalone): installed 130 files across 3 packs (common, razor, vite-react-ts) for adapters: claude, cursor"
# result: real Vite scaffold + .claude/skills (16) + .cursor/rules/* + .nockta/{repo-profile,skills-profile,generated-manifest}.json
# repo-profile.json: "skillsInjected": true, "skillsVersion": "0.1.0"
$ create-nockta-repo create my-app --type vite-react-ts --no-skills --dry-run
# "AI skill injection: --no-skills flag: inject-nockta-skills invocation skipped (spec §5.6)."Supported project types
Eight repo types (src/types/repo-type.ts — the RepoType union, mirrored from
inject-nockta-skills and guarded by a parity contract test). Each wraps one
official scaffolder and applies one Nockta overlay. Run create-nockta-repo list for this table live.
| --type | Wraps (official scaffolder) | Upstream invocation (conceptual) | Overlay adds |
|---|---|---|---|
| next | create-next-app | npx create-next-app@latest <path> | src/components/{ui,layout}, src/features/_template/{components,hooks} + README, src/lib/{env,http}, src/{types,config}; moves src/app/page.tsx → src/app/(public)/page.tsx (optional) |
| vite-react-ts | create-vite | npm create vite@latest <path> -- --template react-ts | Feature/components/lib convention dirs + _template README (frontend-shaped, minus the app-router move) |
| nest | @nestjs/cli | npx @nestjs/cli new <path> | src/modules/_template + README, src/common/{decorators,filters,guards,interceptors,pipes}, src/config |
| shopify-app | Shopify CLI (app) — interactive | shopify app init --path <path> | docs/nockta/ARCHITECTURE.md (structure is upstream-owned) |
| shopify-theme | Shopify CLI (theme) — interactive | shopify theme init <path> | docs/nockta/ARCHITECTURE.md |
| shopify-headless | @shopify/create-hydrogen — interactive, provisional | npm create @shopify/hydrogen@latest -- --path <path> | docs/nockta/ARCHITECTURE.md |
| react-native | @react-native-community/cli | npx @react-native-community/cli@latest init <Name> --directory <path> --skip-install --skip-git-init true | docs/nockta/ARCHITECTURE.md |
| expo | create-expo-app | npx create-expo-app@latest <path> --yes --no-install --template default@sdk-57 --no-agents-md | docs/nockta/ARCHITECTURE.md |
Notes traced to source (src/scaffolders/*.ts):
vite-react-tsbakes the--separator into its base args —npm createneeds it to forward flags tocreate-vite. Your passthrough args land after--template react-tsinside that same forwarded segment (don't add a second--).shopify-approutes the target path through the CLI's own--pathflag (an isolation choice in the registry, not the bareshopify app initthe spec shows). The three Shopify types andshopify-headlessrequire interactive stdio — the Shopify CLI prompts unless you supply enough flags via passthrough.shopify-headlessisprovisional: true— the exact upstream tooling (Hydrogen / Remix / custom) depends on the chosen preset and is not fixed. The current command shape targets@shopify/create-hydrogen(--pathis a named flag there, not positional).list --jsonmarks itprovisional. Do not treat its command shape as stable.react-nativederives the CLI's required positional<Name>(a bare identifier) from the target path's basename viaderiveReactNativeAppName()(PascalCased, letters/digits only, never starts with a digit — e.g.my-cool-app→MyCoolApp), while--directory <path>decouples the on-disk folder.npx react-native initis deprecated; this uses the community CLI.expopins--template default@sdk-57(barecreate-expo-app@latestcurrently scaffolds SDK 54 during the SDK-57 transition window) and passes--no-agents-mdso Nockta's ownAGENTS.md(theagentadapter) owns that space instead of Expo's thin stubs (decisions.md D24). Expo alwaysgit inits the new dir — there is no suppressing flag.
The wizard
Run create-nockta-repo bare (or create-nockta-repo wizard) on a real terminal. The wizard is
genesis-only (decisions.md D28/D29): it decides what gets scaffolded, not which AI skills get
installed. Steps, in order (src/wizard/):
- What do you want to create? (decisions.md D41, user-requested UX addition) — a genuinely bare
invocation (no positional, no
--type) on a real terminal asks this FIRST, before anything else below: "An app/repo" continues into steps 1-9 unchanged; "A monorepo root" hands off to the same dedicated wizardinit-monorepoalready runs (no duplicated prompting logic — the directory/flavor questions live in exactly one place). Skipped entirely (never asked) whenever you've already told the CLI what you mean: a project path,--type, or any other flag/subcommand that makes the invocation non-bare, and also whenever the current directory is already a detected monorepo root (detectMonorepoRoot()) — you're plainly adding a target there, so it drops straight into step 1 as it always did, narrating the detection exactly as before this question existed. - Project path — where to create the project. Validated with the same machinery as the
non-interactive path (rejects an existing target, an absolute path, or a
..escape), and loops on a bad path instead of aborting. - Standalone vs. monorepo target — auto-detected and narrated, not a real question. There is
no
--monorepo-targetflag anywhere; the filesystem decides (see Multi-type & monorepo). - Project type — the primary type, single-select, listed live from the scaffolder registry with friendly titles.
- Secondary skill domains (
--also) — an optional multi-select of every other repo type. Selecting none is a full no-op; selecting some unions extrainject-nockta-skillspacks (never a second scaffolder or overlay — decisions.md D22). - Package manager — asked only for
next/vite-react-ts/nest(recorded only, defaultnpm) — not currently wired into any scaffolder invocation or the repo profile. - Architecture preset — enumerated from the presets actually bundled under
packs/<type>/architecture/;standardis the default. Anonechoice maps to--no-arch. inject-nockta-skillsversion —latest(default), or a custom-entry option to type a specific version or dist-tag (decisions.md D14).- Confirm — a genesis-plan preview only: upstream scaffolder command, target path/monorepo
classification, secondary skill domains (if any), and the architecture summary — not the
resolved skill/adapter list. Instead it prints a note that adapters, skills, and Razor doctrine are
chosen next, in
inject-nockta-skills' own wizard. Decline here and nothing is created. - On confirm, the wizard hands off to the exact same
runCreateCommand()the flag-driven path uses — identical exit codes,--jsonshape, and safety guarantees. There is no second write path.
The create → inject interactive handoff (decisions.md D29). Because the wizard never collects
--yes, once the upstream scaffolder and architecture overlay succeed, create-nockta-repo launches
inject-nockta-skills's own interactive wizard as a child process, with the resolved repo type(s)
pre-filled and inherited stdio — npx inject-nockta-skills@<version> install --type <types> (or the
monorepo-target --target form), deliberately with no --adapters, --yes, or --json. You land
straight in inject's adapter → skill → Razor selection steps (see the sibling package's own README) with
the type step already answered; declining inject's own confirm just means skills weren't injected — it
does not undo the scaffold or the overlay. This replaced an earlier design (decisions.md D18) where
create's wizard fetched and displayed inject's resolved plan via install --dry-run --json instead of
handing over the terminal — that dry-run-preview approach is what --web mode below uses instead (a
non-interactive plan-fetch is exactly what a hosted page needs; a real terminal instead gets the fuller,
truly interactive inject wizard).
Non-interactive (--yes) runs never see any of this — they call inject headless
(--type <types> --adapters <list> --yes --json), exactly as documented under How skill installation
works.
Every partial flag you passed on the command line pre-fills its step and skips the prompt.
Web wizard mode (--web)
create-nockta-repo create --web (decisions.md D30) starts a local, token-secured HTTP server on
127.0.0.1 (random port) and serves a single self-contained page with two sections — "Project"
(create's own genesis fields: path, primary/secondary type, package manager, architecture, skills
version) and "Skills" below it (inject's adapter/skill/razor catalog, fetched reactively via a plain
CLI spawn — inject wizard --emit-schema --type <types> — never an npm dependency, same D4 boundary).
On submit, the page POSTs back once; the server runs the real create pipeline (scaffolder + overlay)
then inject headless (--yes --json), forwarding your web-collected skill choices as
--include-skills/--exclude-skills. Falls back to the terminal wizard (or --yes headless) when no
display is available; --cli forces the terminal path even if --web is also given; --no-open
serves and prints the URL without auto-launching a browser. Monorepo-target selection and the
Extras/claude-mem step are not yet wired into the web page (falls through to the existing create
pipeline / is simply unavailable there) — use the terminal wizard or flags for those.
Flags after
--webneed the--separator.npm create nockta-repoforwards flags to the CLI only after a literal--, so combine--webwith flags like this:npm create nockta-repo@latest -- --web --type next. Without the separator, npm swallows the flags and you get the bare wizard. (A globally installedcreate-nockta-repo --web --type nextneeds no separator — this only applies to thenpm create/npm initlauncher.)
Upstream scaffolder options in the browser (D36)
When you pick a project type, the Project section also shows an "Upstream scaffolder options" card
surfacing the choices the official scaffolder would otherwise prompt for — create-next-app's
TypeScript / Tailwind / ESLint / App Router / src/ / Turbopack / import-alias, nest new's package
manager / language / strict, Hydrogen's language / styling / markets, and the React Native package
manager. Defaults are pre-filled from the same schema the CLI --yes path uses, so the two can never
drift. Your answers flow into the submit payload and become explicit non-interactive flags — the web
submit spawns the upstream scaffolder with stdin detached, so it never prompts (or hangs) in the
launching terminal. Types whose contract pins every choice (Vite --template react-ts, Expo
--template default@sdk-57, the two option-less Shopify types) show no options card.
Types that can't run headless. shopify-app needs a browser login to your Shopify Partner
organization, which the wizard can't drive. Selecting it shows a "Finish in your terminal" warning
up front, and on submit the page hands you the exact command to run in your terminal rather than
hanging or pretending success.
Multi-type & monorepo
Multi-type (--also)
Scaffolding identity is singular — exactly one official scaffolder can own project genesis — but skill needs are a set. A Liquid theme that also uses Vite/React genuinely wants both domains' skills (decisions.md D22).
--type <primary>chooses the one genesis scaffolder and the one architecture overlay.--also <a,b>names secondary skill-domain types. They are validated, deduped against the primary (a duplicate is a warning, not an error), and forwarded toinject-nockta-skillsas a union — never a second scaffolder, never a second overlay.
npx create-nockta-repo store --type shopify-theme --also vite-react-ts --yesThe union [shopify-theme, vite-react-ts] (primary first) is what reaches inject: --type
shopify-theme,vite-react-ts for a standalone install, or --target <path>:shopify-theme+vite-react-ts
for a monorepo target (the two separators are inject's own — comma for --type, + inside the colon
form).
Monorepo targets
Monorepo detection runs at the current working directory only (no upward walk — the spec's own
example has you cd into the workspace root first). Signals (src/core/detect-monorepo-root.ts):
pnpm-workspace.yaml, turbo.json, nx.json, lerna.json, rush.json, or a package.json
workspaces field.
cd existing-monorepo
npx create-nockta-repo apps/web --type next --yes
npx create-nockta-repo apps/api --type nest --yesWhen the cwd is a monorepo root, the given path is created as a monorepo target. The difference
from a standalone create is where inject runs: for a monorepo target it spawns at the monorepo
root (so root adapters + .nockta/targets.json land there — inject-owned, decisions.md D5), while
create-nockta-repo's own <target>/.nockta/repo-profile.json still goes in the target's root, never
the monorepo root.
Creating a monorepo root
init-monorepo <dir> creates the ROOT of a new monorepo — workspace manifest, task config, and
empty apps//packages/ — and nothing else. It never scaffolds an app or package inside that
root; add targets afterward with the existing create command shown above.
npx create-nockta-repo init-monorepo my-platform --flavor turborepo --yes
cd my-platform
npx create-nockta-repo apps/web --type next --yesOmitting <dir>/--flavor/(--yes or --dry-run) on a real terminal drops you into a dedicated
interactive wizard instead of erroring (same "sufficient flags, else wizard, else a structured
no-TTY error" routing create uses); without a TTY it's a structured, non-hanging invalid-input
error. (Bug found by first interactive use, 2026-08-20: bare init-monorepo used to crash with
Commander's own "missing required argument 'dir'" before ever reaching the wizard — fixed; the
wizard's first question now asks for the directory, as always intended here.)
You don't need to know this subcommand exists to reach this same wizard: a bare create-nockta-repo
on a real terminal now asks "What do you want to create?" first (decisions.md D41)
and routes "A monorepo root" straight here.
| Flag | Meaning |
|---|---|
| --flavor <turborepo\|pnpm-workspaces> | Required. Which monorepo tooling flavor to scaffold. |
| --adapters <list> | Comma-separated AI adapters forwarded to inject-nockta-skills (default: claude,agent, post-implementation change 2026-08-19). |
| --dry-run | Print the plan without creating anything. |
| --yes | Required for non-interactive execution — --dry-run is exempt. |
| --json | (Global.) One compact JSON object: ok, status, dir, resolvedDir, flavor, rootFiles, skillsInjected, skills, metadata, warnings, errors. |
| --skills-version <v> | (Global, honored here too.) Pin the inject-nockta-skills version/dist-tag to spawn. |
Generated files, both flavors: package.json, pnpm-workspace.yaml, .gitignore, .npmrc,
README.md, apps/.gitkeep, packages/.gitkeep. The turborepo flavor additionally generates
turbo.json and adds a turbo devDependency.
Skill injection is not optional on this command (there is no --no-skills flag) — it always
runs inject-nockta-skills install --target . --common-only ... at the new root, installing only
the common skill pack + the standing-mode contract (AGENTS.md), no stack pack (the root has no
RepoType). See the "Multi-type & monorepo" section above for how to add a first target
afterward.
Version requirement. --common-only is only understood by inject-nockta-skills releases that
carry it — newer than the published 0.1.2. init-monorepo always spawns @latest by default (or
whatever --skills-version pins), so skill injection at this command's root fails against an
inject-nockta-skills install pinned to 0.1.2 or earlier; root files and the repo profile are
still written (skills-failure is isolated, no rollback — see the exit-code table just below).
Exit codes are its own map, not create's (only 0 and 2 share a meaning across both):
| Code | Name | When |
|---|---|---|
| 0 | success | Root files, skill injection, and the repo profile all completed. |
| 1 | root files failure | Writing the workspace root files failed. |
| 2 | invalid target/input | Bad <dir> (exists / absolute / .. escape), invalid --flavor, or invalid --adapters. |
| 4 | skills failure | inject-nockta-skills failed — root files already exist, only skill injection failed. |
| 5 | profile failure | Writing .nockta/repo-profile.json (kind: "monorepo-root") failed — root files + skills already succeeded. |
Code
3has no analog here (this command never runs an architecture-overlay step) and is deliberately left a permanent gap rather than repurposed, so the two exit-code maps stay comparable at a glance.
CLI reference
Every command and flag below is defined in src/cli.ts, src/commands/create.ts, and
src/commands/list.ts. create is the default command — create-nockta-repo my-project ... is
rewritten to create-nockta-repo create my-project ... before parsing.
Global options (accepted before or on any subcommand)
| Flag | Meaning |
|---|---|
| --json | Emit one compact machine-readable JSON line instead of human text (spec §5.9, decisions.md D13). |
| --skills-version <version\|dist-tag> | Pin the inject-nockta-skills version/dist-tag to spawn; defaults to @latest. |
| -V, --version | Print the CLI version (0.1.0). |
| -h, --help | Print help. |
create [projectNameOrPath] (default)
| Flag | Meaning |
|---|---|
| [projectNameOrPath] | Positional. Project name or path to create. Omit to trigger the wizard. |
| --type <repoType> | The primary repo type. One of the eight in Supported types. Required for a non-interactive run. |
| --also <types> | Comma-separated secondary skill-domain types, unioned with --type and forwarded to inject (decisions.md D22). Never a second scaffolder/overlay. |
| --arch <preset> | Architecture preset (default: standard). |
| --no-arch | Skip the architecture overlay entirely (spec §5.5). |
| --adapters <list> | Comma-separated AI adapters. One or more of claude, cursor, copilot, agent, antigravity. Default: claude,agent (post-implementation change, 2026-08-19 — fresh scaffolds ship the cross-tool AGENTS.md guidemap by default). |
| --no-skills | Skip the inject-nockta-skills step entirely (spec §5.6). |
| --skills-version <v> | (Global, honored here too.) Pin the inject version/dist-tag. |
| --web | Open a local browser page (Project + Skills sections) to set up the project (decisions.md D30). Falls back to the terminal wizard (or --yes headless) when no display is available. |
| --cli | Force the terminal path even if --web is also given (decisions.md D30). |
| --no-open | With --web: serve and print the URL but do not auto-launch a browser (decisions.md D30). |
| --dry-run | Print the full plan and stop. Writes nothing, runs no scaffolder. Exempt from the --yes requirement. |
| --yes | Required for non-interactive execution (spec §5.2, decisions.md D20). Without it (and without a TTY) the command exits invalid-input. |
| --force | Parsed and accepted, but currently a no-op — it is not read anywhere in the create flow. Documented here rather than describing behavior it doesn't have. |
| -- <args...> | Everything after a literal -- is forwarded verbatim to the upstream scaffolder. See below. |
Passthrough args. Flags after -- go straight to the official
scaffolder, appended after that scaffolder's base args:
npx create-nockta-repo apps/web --type next --yes -- --tailwind --eslint --src-dir --appbecomes trailing args on npx create-next-app@latest apps/web --tailwind --eslint --src-dir --app.
(For vite-react-ts, they land after --template react-ts inside the already-present -- segment —
don't add a second --.) Split is on whitespace; no shell-style quoting.
list
Lists the supported repo types and their upstream scaffolder commands, sourced from the registry.
| Flag | Meaning |
|---|---|
| --details | Reserved — not yet implemented (prints a notice). |
| --json | (Global.) One compact JSON object: { repoTypes: [...] }, each entry carrying repoType, displayName, upstreamTool, conceptualCommand, interactiveStdio, provisional, minNodeVersion, and an exampleCommand. |
wizard
Runs the interactive create wizard directly. Requires a real TTY — from a non-interactive process
it exits with a structured error rather than hanging on a prompt. Since it takes no
[projectNameOrPath]/--type of its own, it's unconditionally a "bare" invocation: on a real TTY it
also asks the "What do you want to create?" question first (decisions.md D41), with
the exact same choices/routing/smart-skip as running create-nockta-repo bare — "An app/repo"
continues into this same wizard; "A monorepo root" hands off to
init-monorepo; already standing in a detected monorepo root skips the
question and drops straight into step 1.
Non-interactive routing (src/commands/create-entry.ts)
- Sufficient = path and
--typeand (--yesor--dry-run) → runs straight through. - Insufficient + real TTY → the wizard, with whatever flags you gave pre-filled.
- Insufficient + no TTY → a structured, non-hanging invalid-input error (exit 2; one JSON line
under
--json).
Exit codes (src/commands/create.ts EXIT_CODE)
| Code | Name | When |
|---|---|---|
| 0 | success | Everything completed. |
| 1 | upstream failure | The official scaffolder failed — nothing downstream ran. |
| 2 | invalid target/input | Unknown --type/--arch/--adapters/--also, bad target dir (exists / absolute / .. escape), or insufficient flags with no TTY. |
| 3 | architecture failure | The overlay's manifest or apply step failed (reports what was already created; no rollback). |
| 4 | skills failure | inject-nockta-skills failed — project + overlay already exist, only skills failed. |
| 5 | profile failure | Writing .nockta/repo-profile.json failed — everything before it succeeded. |
Codes
3/4/5extend the spec §5.9 table (which stops at 4); each post-upstream step earns its own code rather than a catch-all, so callers can distinguish failure modes. Flagged in-source as a deliberate extension, not a spec-stated mapping.
How skill installation works
create-nockta-repo never imports inject-nockta-skills as a library and never lists it as an npm
dependency. It shells out to inject's own CLI as a child process (decisions.md D4, spec §8.1;
src/core/run-inject-skills.ts). This keeps the two packages independently publishable and versionable.
Default binary resolution:
npx inject-nockta-skills@latest install ...--skills-version <v> switches it to npx inject-nockta-skills@<v> install .... --no-skills skips
the step entirely.
Two invocation shapes:
# standalone — spawned INSIDE the created project directory
inject-nockta-skills install --type <type[,type...]> --adapters <list> --yes --json
# monorepo target — spawned AT the monorepo ROOT
inject-nockta-skills install --target <path>:<type[+type...]> --adapters <list> --yes --jsoninject's single-line --json result is parsed for the installed/skipped packs, rendered file count,
warnings, and profile paths. A nonzero exit, a signal, or unparseable output is a typed failure
carrying inject's exit code and a bounded stderr tail → exit code 4, with the honest partial state
reported (no rollback). The resolved inject version actually used is read back (from inject's own
--json data.version when present, else from the .nockta/skills-profile.json it wrote) and
recorded on the repo profile as skillsVersion — not the --skills-version flag echoed back.
Enum parity contract. The RepoType and AdapterType unions
are duplicated locally (not imported) from inject-nockta-skills (decisions.md D7). To stop them
drifting, test/enum-parity.contract.test.ts spawns the real built inject-nockta-skills list
--json and asserts its reported repoTypes/adapterTypes exactly match this package's local
REPO_TYPES/ADAPTER_TYPES. Adding a type to one package without the other breaks this test.
Architecture overlays
An overlay is a small, versioned pack of Nockta conventions applied after the official scaffolder
runs. Each lives at packs/<repo-type>/architecture/<preset>/ and is a declarative manifest
(arch.json) plus a files/ folder of template content (src/types/architecture.ts,
src/architecture/).
A manifest declares four things (only the first three are active):
directories— convention folders to create (e.g.src/features/_template/components).files— template files to copy from the pack'sfiles/into the project (e.g. a_template/README.mdexplaining the feature-folder convention).moves— relocate an upstream-generated file (e.g.src/app/page.tsx→src/app/(public)/page.tsx). Amovemay beoptional(a missing source is recorded as skipped, not an error); a missing non-optional source stops the apply with exit 3.deletes— present in the schema but disabled at read time (spec §13): a manifest with a non-emptydeletesarray is rejected.
The applier never overwrites an existing destination and never touches an unlisted file. Example — the
real packs/next/architecture/standard/arch.json creates eight convention directories, copies a
feature-template README and a .gitkeep, and optionally moves the App Router entry page into a
(public) route group. The three Shopify types plus react-native/expo ship deliberately minimal
overlays (a single docs/nockta/ARCHITECTURE.md) because their real structure is entirely
scaffolder-owned — Nockta doesn't fight it.
--arch <preset> selects a preset (standard is the only bundled preset today); --no-arch skips
overlays entirely and records architecture: null on the profile.
The repo profile
The final step writes <target>/.nockta/repo-profile.json (src/types/profile.ts) — always the
target's own root, never a monorepo root. Fields: tool, version (this CLI's own version),
repoTypes[] (primary first, then --also types), architecture, projectPath (as given),
isMonorepoTarget, officialScaffolder (name/command/args of the primary scaffolder only),
skillsInjected, skillsVersion, adapters, createdAt.
Relationship to inject-nockta-skills
Two separate, independently publishable packages with a clean boundary:
| | create-nockta-repo | inject-nockta-skills |
|---|---|---|
| Owns | Project genesis — running the official scaffolder + applying the architecture overlay | Skills — rendering AI adapters (.claude/, AGENTS.md, …) from skill packs |
| Writes | <target>/.nockta/repo-profile.json | .nockta/{skills-profile,targets,generated-manifest}.json, adapter outputs |
| Relationship | Spawns inject's CLI as a child process (never a dep) | Runs standalone too; create is just one caller |
inject-nockta-skills is the canonical semantic owner of the RepoType/AdapterType vocabulary;
create-nockta-repo mirrors it locally and is kept honest by the parity contract
test. You can run inject-nockta-skills install on an existing project at
any time — create-nockta-repo is only for standing up a new one.
Attribution & license
License. This package is Apache-2.0-licensed (see package.json "license" field, the root
LICENSE file, and the root NOTICE file).
Wraps and defers to these upstream tools, each under its own license: create-next-app,
create-vite, @nestjs/cli, the Shopify CLI, @shopify/create-hydrogen,
@react-native-community/cli, and create-expo-app.
Releasing
Maintainer-facing release process (tag push → CI publishes to npm with provenance, no tokens after
first release; note inject-nockta-skills must be published first) is documented in
RELEASING.md.
Contributing
Adding a new repo type
It is a cross-package change — both create-nockta-repo and inject-nockta-skills must gain the
type in the same pass (the parity contract test enforces it):
- Add the value to the
RepoTypeunion andREPO_TYPESarray insrc/types/repo-type.ts(same value, same position, in both packages — decisions.md D7). - Add a scaffolder module under
src/scaffolders/<type>.tsand register it insrc/scaffolders/registry.ts. TheRecord<RepoType, ScaffolderDefinition>annotation is itself the completeness check — the file won't typecheck until every type has an entry. Verify the real upstream command against primary sources before wiring execution. - Add an overlay pack:
packs/<type>/architecture/standard/arch.json+ afiles/folder. - On the
inject-nockta-skillsside, add the matching skill pack(s) and detection signals. - Run
pnpm test—enum-parity.contract.test.tsandstandard-overlays.test.ts(data-driven offREPO_TYPES) both need to stay green.
Local development
pnpm install # esbuild is allowlisted for postinstall (see package.json pnpm.onlyBuiltDependencies)
pnpm build # tsup
pnpm test # vitest — offline by default
pnpm typecheck # tsc --noEmit (pinned to TS 5.9.x — TS 7 breaks tsup's .d.ts bundling)
# prepublishOnly runs build && typecheck && test automatically on `npm publish`The network-touching real-scaffolder acceptance test is gated behind RUN_REAL_SCAFFOLDER_TESTS=1
and skipped by default.
Testing the create → inject handoff locally
This package always spawns inject-nockta-skills via npx inject-nockta-skills@latest (or
@<version> under --skills-version) — it never imports it as a dependency, so a plain
npm link inject-nockta-skills in this repo has no effect on what gets spawned; npx ...@latest
still resolves from the registry regardless. To exercise the real create→inject handoff against your
own local inject-nockta-skills build (e.g. before either package is published), point the
CREATE_NOCKTA_REPO_TEST_INJECT_BIN env var at its built dist/cli.js — run-inject-skills.ts spawns
node <that path> install ... instead of npx when it's set:
export CREATE_NOCKTA_REPO_TEST_INJECT_BIN=/path/to/inject-nockta-skills/dist/cli.js
node dist/cli.js create my-vite-app --type vite-react-ts --adapters claude --yesThis is also how test/create-skills.integration.test.ts and the real-scaffolder acceptance test
prove the full chain without a published inject-nockta-skills. (For testing inject-nockta-skills
standalone, its own README documents a separate npm link workflow — that one works because its CLI
is invoked directly, not spawned through a hardcoded @latest/@<version> npx call.)
