baseline / docs
friedbotstudio/baseline

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.

One command, three modes — fresh, force, merge — converging on a single installed target. npx @friedbotstudio/create-baseline fresh default merge upgrade force overwrite one command · three modes · one target

§ 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>.

ModeBehaviorConfirmation
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:

ActionTrigger
ADDNew in the template; absent in the target.
OVERWRITETarget hash matches the old manifest (untouched since last install). Refreshed.
NOOPTarget hash matches the new manifest. Already current.
SKIP_CUSTOMIZEDTarget hash differs from both old and new. User customized it. Preserved. Contributes to exit 3.
PRUNERemoved from the new template; target hash matches the old manifest. Untouched stale file. Deleted.
PRUNE_SKIPPED_CUSTOMIZEDRemoved from the new template; target hash differs from the old manifest. Customized stale file. Preserved. Contributes to exit 3.
NEVER_TOUCH_PRESERVEPath is in NEVER_TOUCH (currently .claude/project.json only) and the target has it. Always preserved.
NEVER_TOUCH_ADDPath is in NEVER_TOUCH and the target lacks it. Written from the template.
SPECIAL_MERGEPath 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.

FlagEffect
--dry-runPrint intended actions without writing. Skips the PlantUML fetch. Combines with any install mode.
--no-plantumlSkip the jar download entirely. PlantUML rendering will fall back to either system plantuml (if on $PATH) or the plantuml-mcp-server hosted endpoint.
--require-plantumlTreat jar fetch failure (network or sha256 mismatch) as fatal. Exit 4 instead of the default behavior of warning and continuing.
--with-npmrcMaterialize target/.npmrc with security-hardened defaults (ignore-scripts=true, min-release-age=7). Off by default; existing .npmrc preserved verbatim.
--help, -hPrint the usage block and exit 0.
--versionPrint the package version and exit 0.

§ IV

Exit codes

Five exit codes. Designed for CI scripts to branch on the structural cases.

CodeReason
0Success. Or a clean doctor report.
1User abort (install confirmation declined, or interactive upgrade aborted), sentinel conflict without --force or the upgrade subcommand, or doctor reports missing baseline files.
2Argv 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.
3upgrade 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.

~/code · zsh
$ 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:

CategoryMeaning
matchedFile hash on disk equals the hash recorded in the manifest at install time. Clean.
customizedFile hash differs from the manifest. The user (or /init-project, or any phase skill) has edited it. Informational only.
missingFile is in the manifest but absent from disk. Lossy drift. Triggers exit 1.
addedFile 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 an upgrade delivers 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 example playwright-custom).
  • Servers absent from the template are user-added. Preserved byte-for-byte across merges.
  • Top-level JSON keys outside mcpServers are 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 upgrade run 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 .gitignore does 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.

PropertyValue
Pinned versionplantuml-asl-1.2026.2.jar
Pinned sha256c348f6a26d999f81fd05b5d49834bb70df9cf35fab0939c4edecb0909e64022b
Pinned size19 395 808 bytes (~19 MB)
Destination.claude/bin/plantuml.jar (gitignored)
WriteAtomic. Writes to plantuml.jar.tmp.<pid>, then rename(2).
LicenseApache 2.0. The LICENSE + NOTICE files ship in the npm tarball at .claude/bin/.

The fetch is skipped when:

  • plantuml is already on the system $PATH.
  • --no-plantuml is set.
  • --dry-run is 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.