Reference
CLI.
npx @friedbotstudio/create-baseline <target> materializes the baseline. The install, upgrade, and doctor subcommands each route to a branded interactive flow in a TTY and to plain output in CI. One pinned runtime dependency, @clack/prompts, supplies the prompt primitives; everything else is Node stdlib.
§ I
Invocation
Three shapes. Install takes a target; upgrade defaults to the current directory and runs against an existing install; doctor defaults to the current directory and reports drift.
# install or dry-run a target
npx @friedbotstudio/create-baseline <target> [--force] [--dry-run]
[--no-plantuml | --require-plantuml] [--with-npmrc]
# three-way upgrade against an installed target
npx @friedbotstudio/create-baseline upgrade [target] [--dry-run]
# read-only drift report against an installed target
npx @friedbotstudio/create-baseline doctor [target] [--strict] [--json]
# misc
npx @friedbotstudio/create-baseline --help
npx @friedbotstudio/create-baseline --version
--no-plantuml and --require-plantuml are mutually exclusive. Argv errors exit 2.
§ II
Modes
Three modes: a default fresh install, an opt-in --force overwrite, and the upgrade subcommand that reconciles an existing install against a newer baseline. The --merge flag was retired in this release; passing it now exits 2 with a stderr line pointing to create-baseline upgrade <target>.
| Mode | Behavior | Confirmation |
|---|---|---|
| fresh (default) |
Copies every baseline file into the target. Refuses with exit 1 if any of the four sentinel paths is already present (.claude, .claude/.baseline-manifest.json, CLAUDE.md, .mcp.json, docs/init/seed.md). On a clean target, .mcp.json takes the additive merge path so any user-added servers in a hand-written file are preserved. In a TTY, a branded intro / spinner / outro frames the install; in CI, plain text matches the prior output line-for-line. |
None. |
| --force | Overwrites every baseline file. Preserves .claude/project.json (it carries your /init-project tailoring; the install never touches it). Special-cases .mcp.json via the additive merge. |
Typed overwrite in an interactive TTY. Non-TTY without --dry-run exits 2. |
| upgrade | Three-way merge against .claude/.baseline-manifest.json. The per-file decision (see the action kinds below) refreshes untouched files, preserves customizations, and prunes files removed upstream that the user had not touched. In a TTY, each SKIP_CUSTOMIZED path becomes a keep-mine / take-theirs / abort prompt. In CI the prior --merge semantics apply unchanged: every action is reported, and exit 3 fires when any customisation cannot be reconciled. |
None in CI. In a TTY, an interactive prompt per conflict; abort or Ctrl+C exits 1 with the tree unchanged. |
| --dry-run | Combines with the install or with upgrade. Prints intended actions; writes nothing. The PlantUML jar fetch is also skipped. |
None. |
Under upgrade the per-file action is one of eight kinds:
| Action | Trigger |
|---|---|
| ADD | New in the template; absent in the target. |
| OVERWRITE | Target hash matches the old manifest (untouched since last install). Refreshed. |
| NOOP | Target hash matches the new manifest. Already current. |
| SKIP_CUSTOMIZED | Target hash differs from both old and new. User customized it. Preserved. Contributes to exit 3. |
| PRUNE | Removed from the new template; target hash matches the old manifest. Untouched stale file. Deleted. |
| PRUNE_SKIPPED_CUSTOMIZED | Removed from the new template; target hash differs from the old manifest. Customized stale file. Preserved. Contributes to exit 3. |
| NEVER_TOUCH_PRESERVE | Path is in NEVER_TOUCH (currently .claude/project.json only) and the target has it. Always preserved. |
| NEVER_TOUCH_ADD | Path is in NEVER_TOUCH and the target lacks it. Written from the template. |
| SPECIAL_MERGE | Path is in SPECIAL_MERGE (currently .mcp.json only). The additive merge runs (see below). |
§ III
Flags
Operational flags beyond the modes. Two control the PlantUML side-fetch; one materializes a security-hardened npm config.
| Flag | Effect |
|---|---|
| --dry-run | Print intended actions without writing. Skips the PlantUML fetch. Combines with any install mode. |
| --no-plantuml | Skip the jar download entirely. PlantUML rendering will fall back to either system plantuml (if on $PATH) or the plantuml-mcp-server hosted endpoint. |
| --require-plantuml | Treat jar fetch failure (network or sha256 mismatch) as fatal. Exit 4 instead of the default behavior of warning and continuing. |
| --with-npmrc | Materialize target/.npmrc with security-hardened defaults (ignore-scripts=true, min-release-age=7). Off by default; existing .npmrc preserved verbatim. |
| --help, -h | Print the usage block and exit 0. |
| --version | Print the package version and exit 0. |
§ IV
Exit codes
Five exit codes. Designed for CI scripts to branch on the structural cases.
| Code | Reason |
|---|---|
| 0 | Success. Or a clean doctor report. |
| 1 | User abort (install confirmation declined, or interactive upgrade aborted), sentinel conflict without --force or the upgrade subcommand, or doctor reports missing baseline files. |
| 2 | Argv error (including the retired --merge flag), mutually-exclusive flag combination, non-TTY where TTY is required, doctor finds no manifest at the target, or upgrade runs against a target without a baseline manifest. |
| 3 | upgrade produced a SKIP_CUSTOMIZED or PRUNE_SKIPPED_CUSTOMIZED action. The reconcile succeeded; you have customizations the upgrade could not auto-resolve. |
| 4 | --require-plantuml and the jar fetch failed. |
§ V
Doctor
A read-only drift check against an installed target's .claude/.baseline-manifest.json. Writes nothing. Returns a structured report.
$ npx @friedbotstudio/create-baseline doctor ./my-project
Baseline doctor — target: /Users/you/code/my-project
Manifest: version 1, installed 2026-05-11T14:00:00Z
matched: 160
customized: 0
missing: 0
added: 0
Four categories, surfaced in this order whenever non-zero:
| Category | Meaning |
|---|---|
| matched | File hash on disk equals the hash recorded in the manifest at install time. Clean. |
| customized | File hash differs from the manifest. The user (or /init-project, or any phase skill) has edited it. Informational only. |
| missing | File is in the manifest but absent from disk. Lossy drift. Triggers exit 1. |
| added | File is under .claude/ but not in the manifest. Likely from /init-project additions or your own customizations. Informational only. |
Doctor never modifies the target. To repair drift, run the upgrade subcommand (preserves customizations; interactive in a TTY) or --force (overwrites everything except .claude/project.json).
For CI parsers, add --json to emit the structured report on stdout instead of the human renderer. The schema preserves matched, customized, missing, added, plus strict, target, manifestVersion, generatedAt, tampered, and the exitCode. Exit codes are unchanged; --strict still escalates customizations to exit 1.
§ VI
.mcp.json merge semantics
The CLI special-cases .mcp.json on every install (fresh or merge). The behavior is baseline-refresh: servers the baseline owns get the template's current args; servers you added are preserved.
- Servers named in the template are baseline-canonical. Currently
context7,plantuml,playwright. The merge refreshes them from the template, so anupgradedelivers baseline arg and env updates (for example, a new flag on the playwright server). A user who customized a baseline-named server loses that customization on the next upgrade. Intentional customizations belong under a non-baseline name (for exampleplaywright-custom). - Servers absent from the template are user-added. Preserved byte-for-byte across merges.
- Top-level JSON keys outside
mcpServersare additive: template keys are added when missing; the target's existing keys win on collision. - If the target has no
.mcp.json, the template is written verbatim.
The implementation lives in src/cli/mcp.js. The decision was recorded in README.md under ".mcp.json merge semantics".
§ VII
Manifest
After every install, the CLI writes .claude/.baseline-manifest.json at the target: a sha256-keyed hash table of every file shipped, plus a version stamp and the install timestamp.
{
"manifest_version": 1,
"generated_at": "2026-05-11T14:00:00.000Z",
"files": {
"CLAUDE.md": "9a3f...",
".claude/hooks/track_guard.sh": "1c4b...",
".claude/skills/intake/SKILL.md": "f0d2...",
...
}
}
This file is the input to every subsequent upgrade and to doctor. Two design notes:
- Each
upgraderun replaces the manifest with a fresh one stamped at the new install time. The old manifest is the "old" side of the three-way reconcile; the new template is the "new" side; the target on disk is the lived side. - The manifest is recommended for commit to the project's git history. It is the audit trail for "what baseline version was this project on as of which commit". The baseline's
.gitignoredoes not exclude it.
§ VIII
PlantUML jar
The baseline's plantuml-syntax-guard and /spec-render skill need a PlantUML binary. The install side-fetches one when the system has no plantuml on $PATH.
| Property | Value |
|---|---|
| Pinned version | plantuml-asl-1.2026.2.jar |
| Pinned sha256 | c348f6a26d999f81fd05b5d49834bb70df9cf35fab0939c4edecb0909e64022b |
| Pinned size | 19 395 808 bytes (~19 MB) |
| Destination | .claude/bin/plantuml.jar (gitignored) |
| Write | Atomic. Writes to plantuml.jar.tmp.<pid>, then rename(2). |
| License | Apache 2.0. The LICENSE + NOTICE files ship in the npm tarball at .claude/bin/. |
The fetch is skipped when:
plantumlis already on the system$PATH.--no-plantumlis set.--dry-runis set.- The destination jar is already present with a matching sha256.
On network failure or sha mismatch, the install prints a warning and continues. Pass --require-plantuml to make the failure fatal (exit 4) so CI catches a wedged mirror.