Scaffolder & Kits
WhiskerPlatform splits “the tooling a developer touches” into four packages with deliberately different lifecycles:
| Package | Binary | Lives in the consumer’s project as | Purpose |
|---|---|---|---|
@nike/whisker-cli |
thundercats |
Global install | Front-of-platform CLI — scaffolding, AI context, audits, updates |
@nike/create-whisker |
create-whisker |
Nothing (one-time generator); also imported by @nike/whisker-cli |
Writes the project skeleton |
@nike/whisker-module-kit |
whisker |
devDependency |
Per-module CLI for front-end modules |
@nike/whisker-service-kit |
whisker-service |
dependency (runtime) + devDependency (CLI) |
Runtime helpers + CLI for backend services |
Keeping them separate matters: the front-of-platform CLI can iterate on agent context without churning the kits, the scaffolder can change prompts without forcing every consumer to upgrade, and the kits can iterate without dragging scaffold-time concerns along for the ride.
What’s built today
Section titled “What’s built today”@nike/whisker-cli — front-of-platform CLI (Thundercats)
Section titled “@nike/whisker-cli — front-of-platform CLI (Thundercats)”The published package is @nike/whisker-cli; the binary is thundercats
(there is no tc alias yet — see Planned).
| Command | What it does |
|---|---|
thundercats create module|service [target] |
Wraps @nike/create-whisker’s scaffolder for that kind. |
thundercats create monorepo [target] |
Same, but for a pnpm-workspace.yaml root with one or more packages/* — module/service/lambda team-identity, compliance, and QMA prompts are collected once at the root and inherited by every package added in the same interactive session (only genuinely per-package fields, like a service’s AWS account numbers, prompt again). |
thundercats add module|service|lambda [target] |
Adds a new package to an existing monorepo (delegates to create-whisker’s runAddPackage). |
thundercats add iframe-consumer |
Wraps @nike/create-whisker’s iframe-conversion flow on an existing app. This one still chains thundercats init afterward — it’s a retrofit onto a pre-existing repo, not a fresh scaffold. |
thundercats init |
First-time .cursor/ scaffold for a repo create-whisker didn’t create (e.g. add iframe-consumer’s retrofit target, or a repo bootstrapped some other way) — writes the always-on core rule and the sidecar manifest tracking managed-file hashes. The @thundercats orchestrator skill and its specialist references install separately, once per machine, via thundercats install-skill. |
thundercats audit |
Read-only diagnostic. Exit 0 clean, 1 drift, 2 blocker. |
thundercats update |
Re-renders managed .cursor/ files (--rules / --agents surgical surfaces; --dry-run previews). For module-kind projects, also delegates to the project’s own locally installed @nike/whisker-module-kit (pnpm exec whisker upgrade, --write unless --dry-run) — see Module Kit → whisker upgrade below. Does not self-update the CLI itself first (see Planned). |
thundercats list-agent-skills |
Enumerates installed skills. |
thundercats upgrade |
Installs a newer global @nike/whisker-cli + @nike/create-whisker from Artifactory — the CLI’s own self-update path (unrelated to, and not to be confused with, the per-project whisker upgrade shipped by the module kit). |
thundercats add-skill and thundercats diff exist as command surfaces but
exit with a deferral notice — see Planned.
A team scaffolding a new module today runs one command and ends up with a
working project and a populated .cursor/:
thundercats create module my-module# scaffold runs, writing AGENTS.md + .cursor/rules/ itself → donecd my-module# open in any IDE → @thundercats in chat → the orchestrator routes to specialists@nike/create-whisker writes the agent-context files (AGENTS.md,
.cursor/rules/thundercats-core.mdc, the sidecar) directly as the last step
of every scaffold — module, service, lambda, and each package in a monorepo
— rather than thundercats create chaining a separate init call
afterward. That means a team scaffolding with bare create-whisker (no
@nike/whisker-cli installed at all) still gets agent context for free;
thundercats init is reserved for onboarding a repo create-whisker didn’t
create.
The old pnpm create whisker entry point still works for back-compat; it
shells into the same library code.
@nike/create-whisker — the scaffolder
Section titled “@nike/create-whisker — the scaffolder”Both a CLI (pnpm create whisker / npx @nike/create-whisker) and a
library — runScaffolder() and runAddIframeConsumer() are public exports
consumed by @nike/whisker-cli. Interactive, single-shot; re-running against
an existing target directory refuses to overwrite.
What it writes (module flow):
| Step | File(s) | Purpose |
|---|---|---|
| 1 | templates/module/** |
Base TypeScript + Vite + React app |
| 2 | src/moduleManifest.ts + module-federation.config.ts |
The module’s declared platform surface and federation contract |
| 3 | package.json (rewritten) |
Pinned kit + WCL versions |
| 4 | config/whisker.config.json |
Dev orchestration: ports, BFF URLs, host contract pin, auto-clone toggle |
| 5 | .husky/pre-commit + post-commit |
Wire git hooks to whisker pre-commit / post-commit |
| 6 | Jenkinsfile + config/quality-config.yaml |
Branch-aware CI pipeline + Nike QMA thresholds |
config/module-manifest.json is not generated at scaffold time — it’s
regenerated by whisker dev and CI from src/moduleManifest.ts, so it’s
never stale and never a hand-edit target.
add-iframe-consumer converts an existing app in place: detects the
package manager from the lockfile, writes src/moduleManifest.(ts|js) and
config/whisker.config.json (moduleType: "iframe-consumer"), patches
package.json#scripts.dev to shell into whisker dev, and scans for
*_ORIGINS arrays to patch idempotently. Prints a manual-step checklist for
anything it can’t safely auto-fix.
@nike/whisker-module-kit — the front-end CLI
Section titled “@nike/whisker-module-kit — the front-end CLI”A per-module devDependency exposing the whisker CLI plus the canonical
ModuleManifestSchema (Zod) and mergeManifestForUpsert. The Lambda
publisher imports the same schema and merge function — the kit and the
runtime ingest path can never disagree.
| Command | Pipeline |
|---|---|
whisker dev |
generate manifest → validate → whisker:prep on host → build module (VITE_IS_LOCAL=true) → start preview → rebuild host loader → build + start host → vite --watch + browser-sync proxying the host |
whisker dev:prod |
Same, against the prod BFF and prod registry |
whisker generate-manifest |
Executes src/moduleManifest.ts with Node 24’s native TS runtime and writes config/module-manifest.json |
whisker validate-manifest |
Runs ModuleManifestSchema.parse(); fails CI on schema violation |
whisker doctor |
Pre-flight checks — see Automation, devkit & E2E |
whisker pre-commit / post-commit |
Husky hooks — regenerate + validate on commit, update provenance after |
whisker update-check |
Fetches the host’s published contract and reports drift (read-only) |
whisker upgrade |
The kit’s own maintenance command — run after bumping @nike/whisker-module-kit. Dry-run by default; --write applies mechanical migrations (deprecated whisker.config.json field cleanup, hand-listed federation exposes entries collapsed into a resolveFederationExposes() spread) and bumps moduleKitVersion as a receipt. thundercats update shells out to this exact command for module projects. |
For iframe consumers, whisker dev runs a simpler pipeline: regenerate
manifest → whisker:prep on host → start the child app → start whisker-host
on 3001 with VITE_LOCAL_IFRAME_APPS pointing at the local child. No
browser-sync, no remote build.
ModuleManifestSchema is namespaced (identity / integration / access /
deployment / ui / capabilities) — see
Manifest schema reference for the full shape.
@nike/whisker-service-kit — the back-end runtime
Section titled “@nike/whisker-service-kit — the back-end runtime”A peer of the module kit for Node services. The runtime is feature-complete:
| Surface | What it removes from the consumer |
|---|---|
startService(opts?) |
Express bootstrap, file-based route discovery, helmet, CORS, error handling, request logging |
createAuthMiddleware |
Okta issuer allow-list, CORPORATE vs STORE token routing, typed req.principal |
fetchFromService<T>(url, opts) |
OSCAR S2S calling with scope validation, retry, timeout, trace propagation |
| Guards | requireGroup, requireMinStoreRole, requireUserType, requireAny, requireAll |
getLogger() |
ECS-shaped winston with active trace/span IDs |
whisker-service CLI |
setup, dev, doctor, build |
Not published to Artifactory yet — deliberately excluded from the first
pilot release (.changeset/config.json#ignore) because the pilot’s
dependency graph doesn’t reach it. thundercats create service scaffolds a
working template today, but until the package publishes, a freshly
scaffolded service can’t pnpm install outside this workspace.
Planned / proposed
Section titled “Planned / proposed”tcshort binary alias — decided but not shipped;package.json#binonly exposesthundercatstoday.- Self-update by default on
thundercats update— the original design hadupdateself-update the CLI before doing anything else. Today only the separatethundercats upgradecommand self-updates;updatedoesn’t call it first. thundercats add-skill/thundercats diff— Phase 2D. External-skill fetching (Tier 1/2/3), the centralized~/.thundercats/cache/, audience + quality gates, and promoted-rule drift all land together. See AI-first tooling.- Multi-repo scope (
--all/--repos/--root) — flags exist and fall back to single-repo with a notice; fan-out ships with Phase 2D. @nike/whisker-service-kitArtifactory publish — ships in the immediate follow-up release once the pilot publish path is validated.
How the packages relate
Section titled “How the packages relate”flowchart LR tc["@nike/whisker-cli<br/>(thundercats)"] -->|imports lib| cw["@nike/create-whisker"] tc -->|init/update .cursor/, retrofit + maintenance| repo["consumer repo"] tc -->|"'update' command delegates"| upgrade["whisker upgrade<br/>(module-kit codemods)"] cw -->|writes skeleton + .cursor/ in one pass| repo repo -->|devDependency| mk["@nike/whisker-module-kit"] repo -.dependency.-> sk["@nike/whisker-service-kit"] mk -->|ships| upgrade mk -->|exports| schema["ModuleManifestSchema (Zod)"] mk -->|exports| merge["mergeManifestForUpsert()"] schema -->|imported by| lambda["whisker-manifest-publisher Lambda"] merge -->|imported by| lambda
The schema being shipped from the kit and imported by the Lambda is the
single most important coupling in the platform — a manifest that passes
whisker validate-manifest locally is guaranteed to pass Lambda
validation, because there is exactly one definition.
