Plumb line bootstrap
A plumb line finds true vertical by gravity alone. plumb-line is a provenance library and Claude Code plugin that does the same for code.
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.
What its author says it does
Copied from the file, not written here
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.
SKILL.md
10.9 KB, 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.