Plumb line bootstrap
Use when setting up a project with the plumb-line discipline — interviews the builder to find their source-truth layer and layering, generates a domain-neutral ruleset, and installs parameterized enforcement (boundary check, test gate, pre-commit gate, branch guard) for the project's language. Ships no default layers and invents no answers.From its SKILL.md
npx -y skills add slopstopper/plumb-line --skill plumb-line-bootstrapAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 4 stars4 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
10.9 KB, ~2.6k tokens by cl100k_base, as published. Nobody here has run it
Bootstrap a project with plumb-line
Part of the four-skill flow: learn the discipline with plumb-line-method, set
the project up here, review changes with plumb-line-audit, and apply findings
with plumb-line-remediate.
REQUIRED READING FIRST: reference/portable-principles.md and
adapters/adapter-contract.md (plugin root).
If either file cannot be read, stop immediately and report which file is missing. Do not proceed from memory.
Two entries: full bootstrap vs. declaration-only (mid-audit)
This skill has two entry modes. Full bootstrap — the builder asked for
setup — runs every step below. Declaration-only — plumb-line-audit
invoked this skill because the project declares no architecture and the audit
needs that context before proceeding — runs a deliberately two-minute detour:
- Ask ONLY interview questions 1–3 (layers + direction; source-truth layer; composition root). The audit needs the declaration, not the ceremony.
- Write the ruleset from those answers (Step 3), marking every section the
interview did not reach as
planned— an un-asked question must not read as an answered one. - Skip Step 4, Step 4b, and Step 6's audit offer entirely: no enforcement install, no primitive offer, nothing added to the project beyond the ruleset file. Those belong to a full bootstrap run — name it as the follow-up in the report's TODOs.
- Emit the Step 5 report header with one extra line —
entry: declaration-only— then return the baton: the calling audit resumes with the freshly declared architecture (do not start a second audit).
The honesty constraint applies in both modes: if the builder cannot name a source-truth layer, stop and say so — in declaration-only mode that answer goes back to the audit, which then proceeds calibrated with the gap on record.
Step 1 — Detect language, pick the adapter
- Look for
package.json-> JS adapter (adapters/js). - Look for
pyproject.toml/setup.py/requirements.txt-> Python adapter (adapters/python). - If both or neither, ASK the builder which to use. Never guess silently.
Step 2 — Interview (one question at a time)
Ask the find-your-version prompts from the principles, in this order (ask one, wait for the answer, then proceed). Do NOT supply defaults; these answers are the builder's, not yours:
- Layers, top to bottom, and the one-way direction.
- The source-truth layer, and what must never leak into it.
- The one allowed exception (composition root), if any.
- What flows downstream (gets provenance + confidence).
- Where non-real (mock/fallback/cached) data is used.
- Which constants encode judgment calls (priors to lift to config).
- Public output shapes that need a versioned contract.
- For each key output, the inputs needed to reproduce it.
- Which derived outputs to freeze as a golden baseline.
- The phrasing for a valid null result in this domain.
HONESTY CONSTRAINT: if the builder cannot name a source-truth layer, stop and say so — that absence is the finding. Do not fabricate one.
Step 3 — Generate the ruleset
Fill reference/ruleset-template.md placeholders with the answers; write it to
the target repo as AGENTS.md (or append if one exists — never overwrite silently).
Step 4 — Install enforcement (from the chosen adapter)
- Copy the boundary config template, replacing layer placeholders with the builder's layers/direction. (JS: eslint zones; Python: import-linter layers.)
- Copy the two guard hook scripts (branch guard + pre-commit gate) into the
target repo's
.claude/guards/(or hooks dir). - Wire the pre-commit gate to the adapter's declared test command.
- Tell the builder exactly what was written and how to enable the hooks.
- Verify, don't assume. After installing, plant a deliberate upward import and confirm the boundary check errors; pipe a code path to the branch guard on the protected branch and confirm it blocks. An installed-but-inert guard is the failure mode to rule out.
JS boundary zones — get the direction right (easy to invert silently)
In import/no-restricted-paths, each zone reads: target = the layer doing
the importing; from = the layer it must NOT import. To forbid the bottom
layer importing upward, list one entry per forbidden (lower imports higher) pair:
zones: [
{
target: "./src/data",
from: "./src/ui",
message: "data must not import from ui",
},
{
target: "./src/data",
from: "./src/services",
message: "data must not import from services",
},
{
target: "./src/data",
from: "./src/engine",
message: "data must not import from engine",
},
// ...repeat for services (must not import ui), engine (must not import ui/services), etc.
];
A reversed { target: "./src/ui", from: "./src/data" } forbids the opposite
(ui importing data) and leaves the real upward leak unguarded — and nothing
errors, so the mistake is invisible. This is why the verify step above matters.
ESLint v9 uses flat config (eslint.config.mjs); the template is a .cjs
fragment. Load it from the flat config (import the fragment and spread its
rules) rather than expecting a legacy .eslintrc to be read.
Branch-guard allowlist forms
The docs allowlist matches an entry in exactly three forms — no other globbing:
- exact file:
README.md - directory (trailing slash):
docs/— everything under it - extension glob:
*.md— that extension at any depth
Empty entries are rejected. (Earlier versions matched files exactly only; a bare
*.md silently matched nothing — fixed, but still: prefer these three forms.)
Hook I/O contract (for wiring)
Each guard is a stdin/exit-code CLI: it reads { "filePath": "..." } on stdin,
the branch from PLUMBLINE_BRANCH, and config from PLUMBLINE_CFG (JSON), and
exits non-zero to block (per adapter-contract.md). It works directly as a git
hook. To wire it as a Claude Code PreToolUse hook, map the host's tool payload's
file path into the {filePath} stdin the guard expects — if the host payload
shape differs, add a one-line shim rather than assuming it matches.
Step 4b — Offer the runtime primitive (opt-in; never silent)
The interview's own answers say exactly where runtime provenance belongs: Q4
named what flows downstream (gets provenance + confidence), Q8 named the
lineage-bearing outputs. After enforcement is installed, make ONE explicit
offer — scaffold mark/derive from plumb-line-provenance at those exact
call sites — and act only on an explicit yes:
- Declined → the project is untouched. Record the offer as declined in the report and move on; no library, no marking, no new dependency appears.
- Accepted → ask which route the builder wants, then act on their answer —
don't default to one silently:
- Install the published package (
plumb-line-provenanceon npm / PyPI). Check it's importable first; if absent, tell the builder the one-line install and pause. - Vendor the bundled source instead (no npm/pip, no network install).
This plugin ships the same v2 primitive under its own payload at
.claude-plugin/bundled/primitives/js/(provenance.mjs,audit.mjs,marked.mjs,index.mjs) and.claude-plugin/bundled/primitives/python/(provenance.py,audit.py,marked.py,__init__.py) — copy those four files for the builder's language straight into the target repo (e.g. aprovenance/dir next to the source-truth layer named in the interview), unmodified. They carry a dual-import shim, so they work copied flat with nopackage.json/pyproject.tomlentry required. State plainly that this is a vendored copy the builder now owns and updates manually (no auto-sync) — the published-package route is what stays current automatically. This adds no mandatory step: a builder who never accepts never needs it.
- Install the published package (
When scaffolding, teach the pattern at the first site rather than carpeting all of them — the goal is a builder who can extend it, not a wrapped codebase:
- At a Q4 site: wrap the value's origin in
mark(value, { source, confidence })— the builder suppliessource/confidenceper site (their answers, not your defaults; the interview's honesty rule applies here too). - At a derivation:
derive([inputs], fn)(JS) / the Python equivalent — show that the output inheritsderivedFromMockand the weakest confidence automatically, and that no API exists to clear taint. - At a Q8 output: show
metaOf(x)/meta_of(x)exposing the lineage, andauditMeta/audit_metareturning[]when the envelope is consistent. - Add one failing-then-passing test that asserts the key output audits clean
(
auditMeta(metaOf(out)) === []/audit_meta(meta_of(out)) == []), and wire it into the test command the pre-commit gate already runs (Step 4) — so an unmarked or laundered return is caught before review, by the gate the builder just installed.
Show each file's diff as you scaffold; every remaining unscaffolded Q4/Q8 site
goes in the report as planned, so the coverage claim stays honest.
Step 5 — Report (audit format)
Open with the same required header block as the audit format (report-format: v3, scope, principles-revision, date, commit — see
skills/plumb-line-audit/SKILL.md). For a bootstrap run scope is the project
being wired, and add one line — adapter: <name> — recording the adapter used.
Bootstrap shares only the v3 header block; the glossary, findings table, and
coverage map are audit-specific and are not part of a bootstrap report.
Then list every file created/modified and any unanswered prompt left as a TODO
for the builder. Label anything not done as planned. (The adapter is recorded
in the header's adapter: line above.) Record the Step 4b outcome explicitly —
accepted (with the scaffolded sites and the planned remainder) or
declined — so the report says whether runtime provenance exists in this
project or was offered and turned down.
Step 6 — Hand the baton
The project is now declared and enforced; the natural next step is a first
audit against the freshly written ruleset. Offer it — "want me to run
plumb-line-audit now for a baseline read?" — and on a yes, invoke the skill
directly (via the host's skill mechanism) rather than telling the builder to
run it. If bootstrap was itself invoked mid-audit (the audit stops when no
architecture is declared and hands here for the interview), return the baton
instead: the calling audit resumes with the now-declared architecture — do not
start a second audit.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.