Bootstrap project
Set up a project's toolchain (linter, formatter, test framework) and verify it end-to-end when starting work on a greenfield or brownfield project. Invoke with /bootstrap-project. Say "skip setup" to bypass.From its SKILL.md
npx -y skills add cahenesy/throughline --skill bootstrap-projectAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 2 stars2 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
13.8 KB, as published. Nobody here has run it
Project setup
Run when starting work on a project. Determine whether it is greenfield (empty: no source files, fresh or absent package-manager init) or brownfield (existing code), then follow the matching path. If the user says "skip setup", stop and do nothing.
Step 0 — read the bootstrap marker (before any work)
Before detecting the language or touching anything, check whether this repo was already bootstrapped. Source the helpers from the plugin and read the committed repo marker (from the repo root):
# The short-circuit DEPENDS on these helpers: if a helper cannot be sourced
# (wrong/unset CLAUDE_PLUGIN_ROOT or GROK_PLUGIN_ROOT), tl_repo_marker_read
# becomes "command not found", $applied reads empty, and an unguarded block
# would fall through to a full re-bootstrap of an already-bootstrapped repo —
# silently bypassing FR-31. Fail loudly instead so the operator fixes the
# path and re-runs. Resolve the plugin tree first (tl_plugin_root is defined
# only after plugin-root.sh is sourced).
_tl_src="${CLAUDE_PLUGIN_ROOT:-${GROK_PLUGIN_ROOT:-}}"
# shellcheck disable=SC1091
. "${_tl_src}/scripts/lib/plugin-root.sh" || {
echo "bootstrap: could not source scripts/lib/plugin-root.sh from CLAUDE_PLUGIN_ROOT or GROK_PLUGIN_ROOT — cannot read the bootstrap marker to check for a prior run; fix the plugin path and re-run" >&2
return 1 2>/dev/null || exit 1
}
unset _tl_src
source "$(tl_plugin_root)/scripts/lib/repo-id.sh" || {
echo "bootstrap: could not source scripts/lib/repo-id.sh from $(tl_plugin_root) — cannot read the bootstrap marker to check for a prior run; fix the plugin path and re-run" >&2
return 1 2>/dev/null || exit 1
}
source "$(tl_plugin_root)/scripts/lib/markers.sh" || {
echo "bootstrap: could not source scripts/lib/markers.sh from $(tl_plugin_root) — cannot read the bootstrap marker to check for a prior run; fix the plugin path and re-run" >&2
return 1 2>/dev/null || exit 1
}
marker="$(tl_repo_marker_read)" # the marker JSON, or "{}" if absent/malformed
applied="$(printf '%s\n' "$marker" | sed -n 's/.*"plugin_version_applied"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1)"
language="$(printf '%s\n' "$marker" | sed -n 's/.*"language"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1)"
These reads use sed, not jq: FR-31's re-run short-circuit must work on
machines without jq (only the post-update hook's local notice is jq-gated).
The marker's string fields are written one-per-line by tl_repo_marker_write,
so a line-oriented sed extraction is exact.
If $applied is non-empty, this repo is already bootstrapped. Print exactly
one line:
already bootstrapped at <applied> (language: <language>)
then short-circuit: do NOT install anything, do NOT re-run the greenfield/brownfield flow, and do NOT rewrite the marker — it must stay byte-identical on a re-run. Re-apply ONLY the cheap, idempotent steps:
# Guard the source for the same reason as Step 0's reads above: an unguarded
# source under a misconfigured CLAUDE_PLUGIN_ROOT or GROK_PLUGIN_ROOT turns
# tl_gitignore_add_line into a "command not found" no-op, silently dropping
# the .gitignore re-apply.
_tl_src="${CLAUDE_PLUGIN_ROOT:-${GROK_PLUGIN_ROOT:-}}"
# shellcheck disable=SC1091
. "${_tl_src}/scripts/lib/plugin-root.sh" || {
echo "bootstrap: could not source scripts/lib/gitignore.sh from CLAUDE_PLUGIN_ROOT or GROK_PLUGIN_ROOT — cannot re-apply the .gitignore entry; fix the plugin path and re-run" >&2
return 1 2>/dev/null || exit 1
}
unset _tl_src
source "$(tl_plugin_root)/scripts/lib/gitignore.sh" || {
echo "bootstrap: could not source scripts/lib/gitignore.sh from $(tl_plugin_root) — cannot re-apply the .gitignore entry; fix the plugin path and re-run" >&2
return 1 2>/dev/null || exit 1
}
tl_gitignore_add_line "docs/tdd/.implement-logs/"
and re-create only the scaffold files (below) that are now missing — leave
existing ones untouched: docs/PRD.md (stub with section headers),
docs/adr/INDEX.md (empty index + header row + the "only accepted ADRs bind"
note), the docs/tdd/ directory, and docs/README.md (the
canonical-vs-transient note). Then stop.
If $applied is empty (no marker), or the marker is malformed
(tl_repo_marker_read returns {}), proceed with the normal flow below.
First: detect the primary language. If ambiguous, ask.
Default tooling (override only if the user objects)
| Language | Formatter + Linter | Test framework |
|---|---|---|
| JS/TS | prettier + eslint | vitest |
| Python | ruff (format + check) | pytest |
| Rust | rustfmt + clippy | built-in cargo test |
| Go | gofmt + golangci-lint | built-in go test |
Linting and formatting
A format-and-lint PostToolUse hook ships with this plugin and runs
automatically after edits when a linter is configured for the project —
it no-ops otherwise, so it never forces tooling onto a repo. Your job here is
to make sure a linter/formatter is actually configured, per these rules:
- Greenfield: install the default formatter + linter for the language and create minimal config files.
- Brownfield, tooling already configured: use what is there; do not swap it out.
- Brownfield, no linter/formatter: do NOT silently install one. Point out that the repo has no configured linter/formatter and ask whether to add the default before proceeding.
How the hook behaves: it formats the edited file, then runs the linter. On a lint failure it returns the error into the session so the fix happens at the root cause. The edit is already written to disk — the hook surfaces the failure for correction rather than reverting it.
Unit testing
- Greenfield: install the default test framework and write one trivial passing test, then run it to confirm the setup works end-to-end.
- Brownfield with existing tests: use the framework already in use. New code must ship with new tests; modified code must have its tests updated.
- Brownfield with no tests: stop and explicitly flag that the codebase has no unit tests. Ask whether to add a framework and backfill tests for existing code before proceeding. Do not silently introduce a framework.
/build-tdds gates every TDD's flip to implemented on the plugin's verify
gate, which runs the test suite + typecheck + project linter (eslint, ruff,
clippy -D warnings, golangci-lint). That gate needs a working test/typecheck
command (auto-detected, or set via CI_CHECKS_TEST_CMD / CI_CHECKS_TYPECHECK_CMD /
CI_CHECKS_LINT_CMD); a project with no test or typecheck cannot pass the gate
without CI_CHECKS_ALLOW_EMPTY=1 (lint alone is additive strictness, not
behavioral verification, so it does not satisfy the gate on its own).
Design-doc scaffold
Create the structure the PRD/TDD/ADR pipeline expects, if absent:
docs/PRD.md— a stub with the section headers (/prd-authorfills it).docs/adr/INDEX.md— an empty index with its header row and the note that onlyacceptedADRs are binding for new TDDs.docs/tdd/— empty directory for Technical Design Docs.docs/README.md— a short note on which docs are canonical, so neither you nor a teammate confuses throughline's records with superpowers' scratch:docs/PRD.md+docs/tdd/+docs/adr/are the canonical design-of-record; anything underdocs/superpowers/(specs/plans fromsuperpowers:brainstorming/writing-plans) is transient input that throughline ingests but never treats as authoritative or relocates. Leave any existingdocs/superpowers/content untouched.
Brownfield completion checklist
A brownfield run touches less than a greenfield one, but it still ends the same way. After you have resolved the linter/formatter and test-framework questions above and created any missing design-doc scaffold:
- Record the bootstrap markers +
.gitignoreentry — run the On completion — record the bootstrap markers step below. This is not greenfield-only: record the marker even when you installed nothing (so re-runs short-circuit at Step 0 and the reconcile hook can track the version). Set<steps-csv>to whatever you actually applied — often justscaffold, or an empty CSV if the repo already had everything. - Commit the marker and
.gitignorechange with your other edits (do notgit initan existing repo). - Report what you used, configured, and scaffolded.
Greenfield initialization checklist
When the project is empty:
- Detect or ask the primary language.
- Install + configure the default formatter and linter; create minimal config.
- Install the default test framework, then write and run one trivial passing test.
- Confirm the
format-and-linthook is active (it ships with this plugin). - Create the design-doc scaffold above.
- Initialize git on
main(the integration branch that phase-gate PRs merge into) — rungit initonly; do not make the initial commit yet. This must happen before the next step: the marker/.gitignorehelpers resolve the repo root viagit rev-parse --show-toplevel, which aborts on a repo that has not beengit init-ed. - Record the bootstrap markers +
.gitignoreentry — see On completion — record the bootstrap markers below. - Make the initial commit on
main, including the marker and.gitignorealongside the rest of the setup. - Report what was installed, configured, and scaffolded.
On completion — record the bootstrap markers
After any successful bootstrap run — greenfield or brownfield, and whether
or not it installed or changed anything — record both markers so re-runs
short-circuit at Step 0 and the post-update reconcile hook can track the
version. A brownfield repo that already had its tooling still completes a
bootstrap: record the marker with whatever repo_steps_applied were actually
performed (often just scaffold, sometimes an empty CSV). Run, from the repo
root:
# The marker steps are NOT optional, so a helper that cannot be sourced must
# abort loudly rather than turn the write calls below into silent
# "command not found" no-ops. Bail (with no marker written) if any source fails.
_tl_src="${CLAUDE_PLUGIN_ROOT:-${GROK_PLUGIN_ROOT:-}}"
# shellcheck disable=SC1091
. "${_tl_src}/scripts/lib/plugin-root.sh" || {
echo "bootstrap: could not source scripts/lib/plugin-root.sh from CLAUDE_PLUGIN_ROOT or GROK_PLUGIN_ROOT — markers NOT recorded; fix the plugin path and re-run" >&2
return 1 2>/dev/null || exit 1
}
unset _tl_src
for _lib in gitignore.sh repo-id.sh markers.sh; do
source "$(tl_plugin_root)/scripts/lib/$_lib" || {
echo "bootstrap: could not source scripts/lib/$_lib from $(tl_plugin_root) — markers NOT recorded; fix the plugin path and re-run" >&2
return 1 2>/dev/null || exit 1
}
done
ver="$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \
"$(tl_plugin_root)/.claude-plugin/plugin.json" | head -n1)"
# FR-32: ignore throughline's per-run artifacts (idempotent, byte-stable).
# Required step: if this write fails its return value must NOT be swallowed —
# otherwise the repo-marker write below would mark bootstrap "complete" while
# docs/tdd/.implement-logs/ stays un-ignored, failing TDD 0009 verification
# step 3 (git check-ignore). Abort loudly (no marker) instead.
tl_gitignore_add_line "docs/tdd/.implement-logs/" || {
echo "bootstrap: failed to add docs/tdd/.implement-logs/ to .gitignore — FR-32 requires this ignore entry; markers NOT recorded" >&2
return 1 2>/dev/null || exit 1
}
# Guard: a marker with an empty plugin_version_applied is silent corruption —
# Step 0 reads the field as empty and never short-circuits, so re-runs
# re-bootstrap forever. If the version could not be read, write NO marker and
# report it rather than poison the short-circuit.
if [ -z "$ver" ]; then
echo "bootstrap: could not read the plugin version from $(tl_plugin_root)/.claude-plugin/plugin.json — repo marker NOT written" >&2
else
# FR-31: committed repo marker. Replace <language> with the detected language
# and <steps-csv> with the subset of
# {scaffold,gitignore,linter_config,test_framework_config,git_init}
# you actually applied this run (comma-separated, no spaces).
# This marker is the source of truth for re-run short-circuiting: if its write
# fails, abort (non-zero, no local marker) rather than leave "local seen, repo
# absent" — that silently breaks idempotency.
tl_repo_marker_write "$ver" "<language>" "<steps-csv>" || {
echo "bootstrap: failed to write docs/.throughline-bootstrap.json — the repo marker is the source of truth for re-run short-circuiting; not recording local state" >&2
return 1 2>/dev/null || exit 1
}
# FR-33: per-developer local marker (records that deps were installed here).
# The '|| true' is required: when ${CLAUDE_PLUGIN_DATA} is unwritable the
# local write fails, but the committed repo marker above is the source of
# truth and bootstrap must still complete (see TDD 0009 failure modes).
tl_local_marker_write "$ver" deps_installed || true
fi
Commit docs/.throughline-bootstrap.json and the .gitignore change with the
rest of the bootstrap. The local marker lives outside the repo (under
${CLAUDE_PLUGIN_DATA}) and is never committed; if that path is unwritable,
tl_local_marker_write warns and returns non-zero — continue anyway, the
committed repo marker is the source of truth for re-run short-circuiting.
After this, the design-doc pipeline is: /prd-author (the what) →
/tdd-author (the how) → /adr-new (durable decisions), each in its own
fresh session.