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.
npx @friedbotstudio/create-baseline. The same wordmark appears on --help and --version; the slim brand strip frames upgrade and the usage errors.§ 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 conflict; pick one or omit both. Command-line parse 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, dispatched by the per-file tier shipped in the new manifest. Tier BINARY_PROMPT surfaces each customized file as a keep-mine / take-theirs / abort prompt in a TTY. Tier MECHANICAL runs git merge-file --diff3 against a recovered BASE; clean merges land silently, conflicts surface with conflict markers (exit 4). Tier SEMANTIC stages the file at .claude/state/upgrade/<ts>/ for the /upgrade-project Claude Code skill to reconcile (exit 5). In CI every per-file action is reported with a user-facing label (add, update, kept yours, merged cleanly, …); exit 3 fires when any customisation cannot be auto-reconciled. |
None in CI. In a TTY, an interactive prompt per binary-prompt 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 twelve kinds. The internal kind is the enum surfaced in the JSON shape; the label is the user-facing string rendered in CLI output:
| Kind | Label | Trigger |
|---|---|---|
| ADD | add | New in the template; absent in the target. |
| OVERWRITE | update | Target hash matches the old manifest (untouched since last install). Refreshed. |
| NOOP | unchanged | Target hash matches the new manifest. Already current. |
| SKIP_CUSTOMIZED | kept yours | Target hash differs from both old and new. User customized it. Preserved. Contributes to exit 3. |
| PRUNE | removed (upstream) | Removed from the new template; target hash matches the old manifest. Untouched stale file. Deleted. |
| PRUNE_SKIPPED_CUSTOMIZED | kept yours (upstream removed) | Removed from the new template; target hash differs from the old manifest. Customized stale file. Preserved. Contributes to exit 3. |
| NEVER_TOUCH_PRESERVE | kept yours (never-touch) | Path is in NEVER_TOUCH and the target has it. Always preserved. Covers .claude/project.json, .claude/workflows.jsonl, and .claude/schemas/workflow-track.v1.json. |
| NEVER_TOUCH_ADD | add (never-touch) | Path is in NEVER_TOUCH and the target lacks it. Written from the template. |
| SPECIAL_MERGE | merged (.mcp.json deep-merge) | Path is in SPECIAL_MERGE (currently .mcp.json only). The additive merge runs (see below). |
| MECHANICAL_MERGE_CLEAN | merged cleanly | Tier-2 customized file. git merge-file --diff3 ran against the recovered BASE and produced no conflict hunks. The merged contents are now on disk. |
| MECHANICAL_MERGE_CONFLICTED | merged with conflicts — resolve manually | Tier-2 customized file. git merge-file wrote conflict markers into the target. Exit 4. |
| SEMANTIC_MERGE_STAGED | staged for /upgrade-project | Tier-3 customized file. The base/incoming/local triple is written to .claude/state/upgrade/<ts>/ for the /upgrade-project Claude Code skill to reconcile in main context. Exit 5. |
§ 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
Six 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 | Bad command line (including the retired --merge flag and conflicting flag combinations), prompted input needed but no terminal attached, doctor finds no manifest at the target, or upgrade runs against a target without a baseline manifest. |
| 3 | upgrade kept your customized files: at least one 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, or a tier-2 mechanical merge produced conflict markers (MECHANICAL_MERGE_CONFLICTED) you must resolve manually. |
| 5 | upgrade staged at least one tier-3 file for semantic merge (SEMANTIC_MERGE_STAGED). Open Claude Code and run /upgrade-project to reconcile. |
§ 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.mjs": "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.