| name | bootstrap |
| version | 0.11.0 |
| description | Bootstrap a NEW or EXISTING project folder for the praxis development lifecycle (PDLC), OR update an already-bootstrapped one after a plugin upgrade. Use when the user wants to set up praxisflux in a project, says "bootstrap this project for praxis/PDLC", "init the praxis lifecycle here", "wire this repo for grounding-wiki/spec-bridge/codebase-to-course", or asks how to get a folder ready for the plugin suite. Plants the always-on PDLC grounding (CLAUDE.md block), gitignores the .handoff/ transport, and handles the officially supported peer utilities — Backlog.md and GitHub Spec Kit — recommending installation when absent and offering opt-in (running their inits) when present. |
pdlc:bootstrap — stamp a project for the praxis development lifecycle
This skill turns any folder — brand-new and empty, or an existing codebase with history and
its own CLAUDE.md — into a PDLC project: one whose always-on context knows the
praxisflux loop, each plugin's role, the gates principle, and the .handoff/ transport. It
is the suite-level answer to "a plugin has no always-on slot": instead of bundling always-on
context, plant it.
Everything it plants rides inside <!-- pdlc:grounding BEGIN/END --> markers, so it composes
with an existing CLAUDE.md (appended, never clobbered) and can be refreshed wholesale on
update. The heavy lifting is deterministic — scripts/plant.mjs on the chassis
(lib/installer.mjs + lib/template.mjs) — this skill's job is the judgment around it.
What it does NOT do
- It does not run sibling skills or create their outputs. No
docs/wiki/ (that's
/grounding-wiki:wiki-build), no docs/course/ (codebase-to-course), no lessons
(educate has its own educate:start). Bootstrap sets the table and hands off.
- It does not copy
plant.mjs or any gate into the project — scripts live in the plugin
and are run as ${CLAUDE_PLUGIN_ROOT}/scripts/plant.mjs.
Precondition gate
- Pick the project root. Ask the user where (default: the current working directory).
Call it
<root>. It may be empty or an existing project — both are supported.
- Detect the mode.
<root>/.pdlc exists → update (re-bootstrap after a plugin
upgrade, or changing peer opt-ins; read the file — it records the previous choices).
Absent → fresh (even if the folder already has code and a CLAUDE.md).
- If
<root> is not a git repository, say so and continue — planting still works; the
.gitignore entry becomes meaningful once they git init.
Peer utilities — Backlog.md and Spec Kit
Backlog.md (task board) and GitHub Spec Kit (spec-driven development) are officially
supported peer utilities of the PDLC — spec-bridge exists to join them. Handle each:
- Detect:
command -v backlog and command -v specify.
- Absent → tell the user it is an officially supported peer and recommend installing:
- Backlog.md:
npm i -g backlog.md
- Spec Kit:
uv tool install specify-cli --from git+https://github.com/github/spec-kit.git
Offer to wait while they install (re-run detection afterwards); declining is fine — the
grounding is planted without that peer's block and opting in later is one re-run away.
Either way the outcome is deterministically traced, not left to this conversation:
plant.mjs records every known-but-not-opted-in peer in the .pdlc sentinel's
peersOmitted field and prints a one-line stderr notice per omitted peer naming its
stripped pdlc:peer:<name> block. Point the user at that trace as the durable record
of what the grounding does NOT carry.
- Present → ask whether to opt this project in (one question per peer; in update mode,
present the previous choice from
.pdlc as the default). Opting in means bootstrap does
the setup:
- Backlog.md: if
<root>/backlog/ already exists, skip (already initialized).
Otherwise run backlog init "<project name>" from <root>; it may prompt — accept
defaults unless the user directs otherwise.
- Spec Kit: if
<root>/.specify/ already exists, skip. Otherwise check
specify init --help and run its init for the current directory with the
claude assistant option (e.g. specify init --here --ai claude).
- Peer opt-ins decide which convention blocks the planted grounding carries — that wiring
happens in the plant step via
--peer flags.
Root-guard hook — opt-in enforcement (advanced)
A host that has adopted the root-read-only + worktree-only workflow doctrine (this repo
and promptworld both mandate it) can opt into the hardened root-guard PreToolUse hook:
a hard blocker that stops root-checkout commits outside the backlog/ board-sync carve-out,
plus rebase/force-push repo-wide (see pdlc/README.md for the full policy and the promptworld
divergence). It is the suite's first PreToolUse hook, so treat it like a peer opt-in, not
a default:
- Offer, do not assume. Ask whether to install it (default no on a fresh host; in
update mode, present the previous choice from
.pdlc's hooks array). A hard blocker
that forbids ordinary root commits is wrong for any host that has not adopted worktree
discipline — wiring it by default would break bootstrap's "safe to install anywhere"
property.
- On opt-in, pass
--hook root-guard to the plant step. That copies both
root-guard-hook.mjs and its scanner shell-scan.mjs into <root>/.claude/hooks/ and
merges two PreToolUse entries (Bash → pre-bash, Write|Edit|NotebookEdit → pre-write)
into <root>/.claude/settings.json, preserving any hooks already there. The choice is
recorded in .pdlc under hooks.
- There is no bypass flag by design. Tell the user emergencies go through them editing
the hook config in
.claude/settings.json, visibly.
Plant
- Preview first: run
node ${CLAUDE_PLUGIN_ROOT}/scripts/plant.mjs --root <root> [--name <name>] [--peer backlog] [--peer spec-kit] [--hook root-guard] --check
(one --peer per opt-in; --hook root-guard only if opted in above). The JSON report
says what would happen: created, appended
(existing CLAUDE.md gains the marked block at the end), replaced, unchanged, or
drifted.
- Project name. The block's heading never blindly trusts the folder name. The planter
resolves it (reported as
projectName): an explicit --name <name> wins > the name
.pdlc recorded at a previous plant > when <root> is a git worktree (.git is a
gitdir: file), the primary checkout's basename > basename(<root>) as the last
fallback. Pass --name whenever the folder name is not the project's name (scratch
dirs, CI checkouts). Doctrine: once planted, the name is sticky — a re-plant from a
differently-named checkout of the same project (a worktree, a renamed clone) reports
unchanged, never spuriously drifted; only --name changes the name, and that
change surfaces as honest drift handled per the next step.
drifted means the on-disk block differs from what this plugin version plants —
either a plugin upgrade or user edits inside the markers. Diff the block against the
rendered template, show the user what would change, and get explicit consent. Carry any
project-specific edits outside the markers (that's their supported home), then re-run
with --force. Never silently discard user text.
- Run the plant for real (same command without
--check, plus --force only after the
consent above). This writes the CLAUDE.md block, the .pdlc sentinel (version +
resolved name + peer choices), and gitignores .handoff/ — all idempotent.
- Plant the model-tier config and generate the implementer agent definitions — see
Model tiers below. This is a second planting step with its own files and its own
drift/consent semantics;
plant.mjs does not do it.
Model tiers — plant the config, generate the definitions
The planted block carries a ## Model tiers section: the posture (thinking is Opus/Fable-tier,
execution is Sonnet/Haiku-tier), a pointer to the config, and the rule that a tier's live pin
is the model: in .claude/agents/<tier>-implementer.md's frontmatter, not the dispatch-call
parameter. The ladder itself lives in .claude/model-tiers.json, and bootstrap plants both
halves:
- Plant the config — copy
${CLAUDE_PLUGIN_ROOT}/templates/model-tiers.json to
<root>/.claude/model-tiers.json only when absent. It is the operator's file: if one
already exists, leave it exactly as it is. Re-running bootstrap must never revert a tier
the host added or a model ID the host bumped.
- Generate the agent definitions —
node ${CLAUDE_PLUGIN_ROOT}/scripts/tiers.mjs --root <root> [--check]. Preview with
--check first (same convention as plant.mjs); it reports per tier created,
unchanged, replaced, or drifted. A drifted definition is one this generator did not
write, or one edited by hand — diff it, get consent, then re-run with --force, exactly as
you would for a drifted CLAUDE.md block. Never silently discard the operator's text.
Model IDs date fast, and the config is where they live, so keep them honest:
- Resolve, never author from memory. The standing source for current IDs is the
claude-api skill — consult it at plant time and reconcile the template's defaults
against what it reports before writing the config. Never type a model ID from memory
into model-tiers.json; a hallucinated ID becomes a pin that fails at dispatch.
- Resolve the host's ID FORM too, not just the ID. The
claude-api skill gives you the
bare API ID (claude-sonnet-5). That is what a plain install wants — but a host behind a
routing proxy may require an augmented form (e.g. cc/claude-sonnet-5[1m]) and reject
both the bare ID and the alias in agent-def frontmatter. Check the host before writing
the config: ANTHROPIC_DEFAULT_*_MODEL in ~/.claude/settings.json (and any
ANTHROPIC_BASE_URL pointing at a local proxy) shows the form this host actually speaks.
Write that form into model-tiers.json. Field case 2026-08-10: a 9router host rejected
claude-sonnet-5 and sonnet alike, and accepted cc/claude-sonnet-5[1m].
- Availability check = a real dispatch, not a table. A tier's ID is "available" only if
this harness accepts it as
model: in an agent definition and a dispatch to that agent
actually returns. Prove it once per host with a throwaway dispatch that asks the agent to
state its model, and compare what it reports against the config. An ID the harness rejects
is not available, whatever any table says — and a definition that dispatches successfully
on the wrong model is worse than one that fails loudly.
- Regenerating does not take effect until the session restarts. The agent registry is
read at session start: a newly generated tier is "not found" until then, and an edited one
keeps dispatching its old pin. Tell the user to restart before relying on a change.
- Fallback when the primary is unavailable. If the subscription does not surface a tier's
primary ID, use that tier's
fallback from the config and record which model actually
served — the 2026-07-31 operator ruling the sweep skill already carries. The per-tier
fallback slot exists for exactly this.
- The tier map is open. Do not treat the template's three tiers as the allowed set. A host
that wants a
fable tier, or a family that did not exist when this plugin shipped, adds a
key — the generator has no closed list to update.
Refresh path — two doors, don't confuse them:
- The planted doctrine (the posture and the mechanism) changes by re-running
pdlc:bootstrap: the block drifts → diff → consent → re-plant with --force, the same path
every other block change takes.
- A live pin (which model a tier resolves to, or which tiers exist at all) changes by
editing
.claude/model-tiers.json and re-running tiers.mjs — a plain tracked file
outside every marker, no drift, no --force. Superseding a model is a one-line config
edit plus a regenerate, not a re-plant. Do not reach for the agent definition itself: it
is generated, and hand-editing it decouples the pin from the config until the next --check
catches it.
Output gate
- Re-run with
--check: it must exit 0 (nothing left to change) and report
claudeMd: unchanged. Re-run tiers.mjs --root <root> --check too: it must exit 0 with
every tier unchanged — a nonzero exit means a definition is missing or its pin no longer
matches the config.
- Verify on disk — never claim success without looking:
<root>/CLAUDE.md contains the
pdlc:grounding markers (and each opted peer's pdlc:peer: block), the heading names
the project (not a worktree/scratch folder), <root>/.pdlc records the resolved
name and the right peers (each declined peer under peersOmitted), .gitignore
contains .handoff/. <root>/.claude/model-tiers.json exists and every tier it declares
has a generated <root>/.claude/agents/<tier>-implementer.md whose frontmatter model:
matches the config — read the two and compare, don't assume the generator ran. If the
root-guard hook was opted in, .pdlc's hooks lists root-guard, both
.claude/hooks/root-guard-hook.mjs and .claude/hooks/shell-scan.mjs exist, and
.claude/settings.json carries the two PreToolUse entries.
- Report exactly what was created, refreshed, skipped (e.g.
backlog init
skipped because backlog/ existed), and left untouched (everything outside the
markers; all user content).
Handing off
The project is now grounded; its CLAUDE.md governs from the next session. Tell the user
what the loop makes possible next — without doing any of it here:
- Ground the codebase:
/grounding-wiki:wiki-build → docs/wiki/ (do this first on an
existing codebase; everything downstream reads it).
- Spec-driven work (if Spec Kit opted in): author a spec with
specify, then put it on
the board with spec-bridge:link (if Backlog.md opted in) and spec-bridge:sync as you work.
- Teach it:
/codebase-to-course:codebase-to-course once a wiki exists.
- Re-run
pdlc:bootstrap any time — after plugin upgrades, or to change peer opt-ins.