Skip to content

Latest commit

 

History

History
200 lines (152 loc) · 28.8 KB

File metadata and controls

200 lines (152 loc) · 28.8 KB

booping plugin — project guide

Claude Code plugin that grooms and executes plans across user projects. Plans live in the per-project vault — ~/Claude/{project}/ by default, or a repo-local directory via the .booping marker's vault_path: key; skills, agents, templates, and config live in this repo.

Status

Last updated: 2026-04-30.

Runtime template-rendering pipeline + build-time src/files/ pipeline are both live. Current state:

  • Build-time skills/agents: every committed skills/<name>/SKILL.md and agents/<name>.md is generated from src/files/<rel>.j2 via just build (which calls bin/booping build). Frontmatter values that vary across files (today: effort) live in src/config_files.yaml, a build-only config that is not project-overridable.
  • Runtime rendering: the body of each generated thin shell is a single !booping render src/templates/.../.md.j2`` line; bodies are rendered at skill-load time by bin/booping (Python uv project at `booping-python/`).
  • Project config override: ~/Claude/{project}/config.yaml deep-merges over src/config.yaml at render time (runtime only — does not affect config_files.yaml).
  • Single CLI: bin/booping with subcommands render, render-sprints, transition, frontmatter-update, vault-commit, build, debug-context, debug-template.
  • Static docs: docs/ is hand-authored from this point on (no build step). The single dynamic doc (plan_lifecycle_overview) is rendered at runtime via bin/booping render src/templates/docs/plan_lifecycle_overview.md.j2.

Layout

  • booping-python/ — uv Python project containing the booping CLI (subcommands render, render-sprints, transition, frontmatter-update, vault-commit, build, debug-context, debug-template). Source under booping-python/src/booping/; tests under booping-python/tests/.
  • bin/booping — shell wrapper: resolves plugin root from its own location and exec's uv run --project booping-python booping "$@".
  • bin/booping-create-project — standalone uv inline script; scaffolds the vault directories + .booping marker. Defaults to ~/Claude/{project}/; with --local [dir] (default ./booping) scaffolds a repo-local vault and writes the .booping vault_path: key + a vault .gitignore. Out of scope for the runtime pipeline.
  • bin/booping-external-llm-call — standalone uv inline script; renders a Jinja2 prompt template from bin/llm-call-templates/ and sends it to Gemini. Out of scope for the runtime pipeline.
  • src/config.yaml — single source of truth for structured data rendered into skills at runtime (plan statuses + transitions, task types, per-skill agents / status / etc.). Loaded at render time by Context.assemble(). Project-overridable.
  • src/config_files.yamlbuild-only config consumed by bin/booping build. Carries frontmatter values that vary across src/files/**/*.j2 templates (today: just effort per skill / per agent). Not project-overridable.
  • src/files/<rel>.j2build-time templates for every committed skills/<name>/SKILL.md and agents/<name>.md. Mirrors the plugin root one-to-one. Each template is a thin shell (frontmatter + a single !booping render src/templates/...`` line); rendered by just build using `src/config_files.yaml`.
  • src/templates/skills/<name>.md.j2 — runtime skill templates. Rendered at skill-load time via !booping render ...`` in the generated thin shell.
  • src/templates/agents/<name>.md.j2 — runtime agent templates. Same shape. The developer agent's body lives in _partials/_developer_body.j2.
  • src/templates/docs/plan_lifecycle_overview.md.j2 — the single runtime-rendered doc (uses config.plan.statuses). Invoked from /chat via bin/booping render.
  • src/templates/_partials/_*.j2 — reusable fragments. Include via {% include %} (data-only) or import + macro (parameterized, e.g. _plan_transitions.j2). Parameterized partials receive call-site arguments via the kwargs namespace.
  • skills/<name>/SKILL.mdbuild artefact rendered from src/files/skills/<name>/SKILL.md.j2. Never hand-edit; edit the src/files/ template and run just build.
  • agents/<name>.mdbuild artefact rendered from src/files/agents/<name>.md.j2. Same rule.
  • docs/hand-authored static reference docs, lazy-loaded by skills via ${CLAUDE_PLUGIN_ROOT}/docs/<name>.md links. Includes docs/plan_templates/*.md, docs/review_templates/*.md, docs/template_plan_frontmatter.md, and docs/images/. No build step touches this directory.
  • documentation/end-user docs site source; per-page markdown consumed by MkDocs (mkdocs.yml at repo root). Built and deployed to the gh-pages branch by .github/workflows/docs.yml on push to main (published at https://A.github.io/claude-booping/). Also lazy-loaded by /help via ${CLAUDE_PLUGIN_ROOT}/documentation/<name>.md. Distinct from docs/ (which is plugin-internal lazy-load fragments owned by skills); documentation/ is the public reference site.
  • ~/Claude/{project}/config.yaml — optional per-project config override; deep-merges over src/config.yaml at render time (runtime only).
  • ~/.claude/agents/<id>.md — a project may register self-contained global agents as external workers (project-level user config, native to Claude Code; not maintained by the plugin). Wiring details: documentation/integrating-external-agents.md.

Information ownership

Every piece of information lives in exactly one of these owners. When you can't decide where something belongs, walk this list top-down.

src/config.yaml

Loaded at skill-load time by Context.assemble() — no build step. Per-project overrides live at ~/Claude/{project}/config.yaml and deep-merge over src/config.yaml (project keys win; missing keys fall through; lists replace wholesale). The architecture leaves room for a future global config (e.g. ~/Claude/config.yaml) by accepting an ordered override-paths list in Config.load() — adding a global tier is a one-line change when needed.

  • Where a plan can go (statuses, transitions, owner).
  • Boundaries of each move (gates — verifiable preconditions; when — human-readable trigger).
  • Artifacts produced in a state.
  • Side effects of a move (hooks per transition + plan.superstates.<name>.on_entry/on_exit boundary hooks + plan.hooks.post — frontmatter mutations, sprints.md re-render, vault commit), executed deterministically by booping transition.
  • Structured surfaces that other skills / CLIs also consume: task types, sprint scale, agent capabilities, branch conventions.
  • Delegation targets — which agents a skill may delegate to, declared as skills.<name>.agents.<id> entries (built-in workers plus any self-contained global agents the project registers). See documentation/integrating-external-agents.md.

Skill (src/templates/skills/<name>.md.j2)

Instructions, goal, rules, and judgement specific to one skill — the verbs and heuristics for clearing the gates that config defines.

  • Verbs and heuristics — how to produce the artifacts and clear the gates for the states this skill owns.
  • User interaction — what to ask, when, how to present, how to handle approval vs change requests vs cancellation.
  • Judgment calls the contract can't pre-specify.
  • Invariants the skill enforces across all its states ("Hard rules").

Shared template fragments (src/templates/_partials/, docs/)

Content shared across skills/agents, or extracted from a single skill body to keep it lean.

  • Cross-cutting guides included into multiple skills (git guide, plan transitions, available agents, project context).
  • Process fragments extracted from a single skill into its own partial (sprint planning, plan-frontmatter shape, task classification).
  • Bodies extracted from a single agent into a partial (_developer_body.j2 for the developer agent).
  • Lazy-loaded craft docs (docs/*.md) that a skill links into via [label](${CLAUDE_PLUGIN_ROOT}/docs/<name>.md) when the situation demands it.

Project vault (~/Claude/{project}/)

Out of framework scope — authored per-project, loaded by skills at runtime.

  • Per-skill / per-agent extensions (_booping/skill_<name>.md, _booping/agent_<name>.md).
  • Accumulated lessons (lessons/).
  • Project-local plan templates (plan_templates/*.md).
  • Project-local review templates (review_templates/*.md).

The attached repo's own CLAUDE.md is also loaded by skills (it sits in the repo, not the vault) — same role as the above: project conventions the framework reads but doesn't author.

Principles

  • Minimum useful context: show only information the skill needs to perform its job. Never bake in stale, speculative, or unrelated data.
  • Less prose, less drift: every extra sentence in a skill or partial adds tension between what's written and what the model does. Cut motivation, restated context, "you are the skill / you are responsible to" preambles, and any explanation the schema or a referenced doc already carries. The schema is the source of truth; prose decays.
  • Schema over prose: structured data lives in src/config.yaml. If a format or value is there, the skill body must not also describe it in prose — render from config.

Information hierarchy

When you write a skill, walk these questions top-down for every piece of information you're tempted to inline. Each step moves the cost out of the skill body and into a cheaper mechanism.

  1. Does the skill need this for its main route? Load it eagerly: inline a CLI fact via !command``, or include a static fragment as a partial.

    • Example: project name + path rendered into skill body via {% include "_partials/_project_context.j2" %}.
    • Example: the git guide included via the _git_guide.j2 partial.
  2. Does the skill need this only on a specific route, or only when a condition holds? Lazy-load with a [label](${CLAUDE_PLUGIN_ROOT}/docs/<name>.md) link; the model fetches it when the situation demands it.

    • Example: /groom links to per-task-type guidance; only the active type's guide (one of feature, bug, refactoring) is loaded per plan.
  3. Does the skill need the whole, or only one slice? Split into a partial.

    • Example: /develop only needs the plan frontmatter shape; it does not pull in the full plan-template body.
  4. Is this information already available to the skill? Remove the duplicate.

    • Example: plan transitions are rendered into each skill via _plan_transitions.j2. Skills must not restate the flow in their own prose.
  5. Is the value dynamic? Reference the source, never the literal.

    • Example: the status /learn queries to find plans to absorb is named in src/config.yaml; the skill renders from there rather than spelling the status name in prose.

Skill design

  • Wide-domain: skills must work across stacks (Django, Rust, Hugo, etc.). Project-specific concerns live in ~/Claude/{project}/_booping/skill_<name>.md, lessons/, and the project's own CLAUDE.md — never in the skill here.
  • Phases over flat sections: Preflight → High-level workflow → Phase 0..N.
  • Preflight becomes thinner in template-driven skills: items that were "Read partial X" now appear either as inlined partial content, as !command`` blocks, or as lazy [detailed guidance](${CLAUDE_PLUGIN_ROOT}/docs/…) links. Preflight is for the things that still require the model to act (e.g. read vault lessons, read `booping/skill.md`).
  • Research delegation: delegate heavy reads / summarization work to booping-researcher to protect the skill's context. The agent is for aggregating many sources into a summary, not for single-file spot checks — those stay in the skill.
  • Agent wiring: skills own all reads/writes against ~/Claude/{project}/. Agents touch only code in the attached repo and never scan the vault. Briefings carry the request, related files, and DoD — no lesson paths. Lesson context reaches agents via two baked-in channels: DoD + Verify pasted from the plan (folded in by /groom) and the shared extension at ~/Claude/{project}/_booping/agent_booping-<name>.md, injected at agent load time via tools.render('src/templates/_partials/_extra_instructions.j2', extra_instruction_key='agent_booping-<name>') in the agent template (owned by /learn). A skill (e.g. /develop) may also delegate to a self-contained global agent the project provides at ~/.claude/agents/<id>.md — invoked by bare name through the Agent tool (subagent_type="<id>"), the same path the built-in workers use. Such an agent is wired in via a plain skills.<name>.agents.<id> config block plus a _booping/skill_<name>.md extension that shapes its briefing. See documentation/integrating-external-agents.md.
  • Per-project quality checks: /develop runs the project's own lint / typecheck / test tooling once at Phase 4 (Final Verification), alongside plan-authored Verify commands. Command discovery order: repo CLAUDE.md_booping/skill_develop.md → inspection of package.json, pyproject.toml, Justfile, etc.
  • Local-vault branch offer: _project_context.j2 exposes is_local_vault (true when the resolved vault lives inside the repo working tree). On a local vault, /groom offers to create a branch for the plan before drafting it; the skill carries a Bash(git:*) allowance for that step. This is the intended end-state of the local-vault sprint (/install prompts for vault location, booping-create-project --local scaffolds it).

Config schema (src/config.yaml)

Top-level keys currently in use:

  • skills.<name>.agents.<agent-name>.good_for / .bad_for — delegation guidance rendered by _available_agents.j2. Currently used by /groom and /develop.
  • skills.<name>.agents.<agent-name>.internal (true on built-in entries) and per-skill skills.<name>.disable_internal_agents — when disable_internal_agents is set, _available_agents.j2 hides booping's built-in (internal: true) agents from that skill's table, leaving only the agents the project explicitly registered (e.g. a self-contained global external agent). See documentation/integrating-external-agents.md.
  • skills.<name>.status — the plan status this skill owns or reads. learn owns awaiting-learning; retro owns awaiting-retro. code-review is stateless (does not own a status) but reads skills.code-review.status (default awaiting-retro) for its argument-free plan picker. Rendered into skill bodies that gate on or query a single status.
  • git.branches — list of {branch, when} entries. branch is the literal prefix string (slash and format up to user, e.g. feat/); when is a list of short matches (plan type names like feature, or freeform descriptors). Rendered via _git_guide.j2 macro; consumed by /develop for branch selection.
  • plan.statuses.<key>desc, owner (skill), terminal (bool), optional artifacts (list of strings describing what the state produces), transitions (list of {to, skill, when, gates?, hooks?}). Filtered per-skill by _plan_transitions.j2 (macro shows only statuses a skill owns or has transitions out of, and only rows where skill == <current>). gates is a list of verifiable preconditions rendered into the Gates column. hooks is a list of machine-readable hook strings (e.g. frontmatter-update planned=@now, frontmatter-update commit=@head) the transition command runs when the edge fires; they replace the old prose on_exit strings. The partial summarises them into the On exit (auto) column.
  • plan.superstates.<name> — OR-superstate grouping over statuses, forming the lifecycle spine specificationplannedexecutingreviewterminal (specification = {backlog, in-spec}; planned = {awaiting-plan-review, ready-for-dev}; executing = {in-progress}; review = {awaiting-retro, awaiting-learning}; terminal = {done, fail, cancelled}). Phase-wide edges live on the superstate, not repeated per status: specification and planned both offer → cancelled; executing offers → fail; review offers → done (the skip-ahead path). Each carries states (the member statuses), optional transitions (edges inherited by every member), and on_entry / on_exit (boundary hook lists fired when a move crosses the superstate boundary). Edge resolution: a status's own edges ∪ its superstate's inherited edges, with the substate winning on to collision (e.g. awaiting-learning's gated → done shadows the review skip edge). Inherited edges render tagged *(via <superstate>)*. Resolved per booping.context.lifecycle.resolve_edges (edges) and resolve_hooks (boundary on_exit + edge hooks + boundary on_entry); the transition command and _plan_transitions.j2 both consume the resolution.
  • plan.hooks.post — list of hook names (render-sprints, vault-commit) the transition command runs after every edge's per-transition hooks, on every move. The hook vocabulary across both surfaces: frontmatter-update <key>=<val>... (set/interpolate frontmatter), render-sprints (re-render the vault sprints.md), vault-commit (stage + commit plan + sprints.md). frontmatter-update hooks appear per-edge and in superstate boundaries; render-sprints / vault-commit are the implicit automatic post hooks (suppressed from the rendered On exit (auto) column).
  • tasks — list of {type, description, doc_uri}. Rendered by _task_classification.j2 as bullets with a lazy-load link to doc_uri (relative from repo root).
  • sprintscale (list of {sp, meaning}), default_threshold_sp (SP total past which /groom should propose splitting — not a velocity), redecompose_threshold (tasks ≥ this SP must be re-decomposed), group_threshold (tasks ≤ this SP should be grouped into one agent briefing). Rendered by _sprint_planning.j2; redecompose_threshold / group_threshold are skipped when falsy. Threshold is rendered inline at skill-load time from config.sprint.default_threshold_sp.

Plan lifecycle

  • Statuses + transitions live in src/config.yaml (plan.statuses). The rendered skill body shows only that skill's slice. Terminal states: done, fail, cancelled.
  • Status transitions are executed via booping transition <to> <plan>, a deterministic hook-runner — not manual frontmatter edits or hand-run git. The LLM decides the edge and clears the judgment gates; the command applies every mechanical mutation (status set, date stamps, commit snapshot, sprints.md re-render, vault commit) atomically. The skill fires it when a trigger in its rendered transitions table matches an internal action.
  • Current flow: backlog → in-spec → awaiting-plan-review → ready-for-dev → in-progress → awaiting-retro → awaiting-learning → done, with cancelled / fail terminal branches and in-groom loopbacks (awaiting-plan-review → in-spec, in-spec → backlog).
    • backlog is for parked plans only (split stubs, user-filed ideas not yet in grooming). Active groom runs write directly to in-spec.
    • in-spec is where /groom does its work. awaiting-plan-review is the explicit user-approval gate. ready-for-dev is the queue /develop claims from. No backlog-shortcut into /develop.
  • Each transition carries when (trigger), optional gates (verifiable preconditions), and optional hooks (machine-readable side effects the transition command runs — e.g. frontmatter-update planned=@now). Superstates contribute boundary on_entry/on_exit hooks and inherited edges; plan.hooks.post adds the automatic render-sprints + vault-commit tail on every move. Each status carries optional artifacts (what the state produces).
  • Plans carry an optional commit: frontmatter field (repo HEAD at the time of the snapshot). Set on in-spec → awaiting-plan-review (groom finalises draft) and re-snapshotted on entry to the executing superstate (/develop intake, after the user confirmed the plan is still valid) — both via a frontmatter-update commit=@head hook the transition command runs. Legacy plans without commit: continue to load with commit: None.
  • After a transition, verify by re-reading the plan frontmatter to confirm status: matches the new state.
  • sprints.md is a snapshot rendered from context.plans via booping render-sprints (template src/templates/sprints.md.j2). Every booping transition move refreshes it (the automatic render-sprints post hook) and commits it alongside the plan (the vault-commit post hook), so it stays current across transitions. /chat also refreshes it on orient. Never hand-edit it.

Project vault layout (~/Claude/{project}/)

  • The vault lives at ~/Claude/{project}/ by default, or at a repo-local directory when the .booping marker carries an optional vault_path: key (relative paths resolve against the repo root; absolute paths and ~ expansion are honoured; absent → ~/Claude/{project}). booping-create-project --local [dir] (default ./booping) scaffolds the local variant and writes both vault_path: and a vault .gitignore.
  • The vault is Obsidian-ready: markdown files + YAML frontmatter that Obsidian renders as Properties. No proprietary database. Open the vault directory in Obsidian for graph view + backlinks across plans, retros, and lessons.
  • plans/{YYYYMMDD}-{kebab-title}.md — plan files; frontmatter per docs/template_plan_frontmatter.md. Sibling stubs set split_from: plans/... to point at the primary plan they were split from.
  • plan_templates/*.md — project-local plan templates. Each has frontmatter (name, description) + two top-level sections (# Plan Body, # Quality Checklist). Discovered alongside core templates by PlanTemplate.load_all(); can override a core template by sharing its name, or add entirely new ones.
  • review_templates/*.md — project-local code-review templates. Loaded by /code-review alongside core templates under docs/review_templates/; selected per-plan based on stack signals.
  • lessons/ — accumulated lessons; loaded by skills' Preflight.
  • notes/ — user notes (plan-review comments, code-review threads, ideas for next sprints). Not consumed by skills or agents — purely for the user's own reference.
  • _booping/skill_<name>.md — project-local extensions to wide-domain skills.
  • _booping/.booping.log — append-only invocation log. render / render-sprints write one line per call. Format: <iso8601-utc>: [<subcommand>] <detail>.

(Project conventions live in the attached repo's own CLAUDE.md, not in the vault.)

CLI

  • bin/booping render <template-path> [--output <path>] — render a Jinja2 template with full project context to stdout (or to a file with --output). Used at skill-load time via !booping render ...`` and to invoke the runtime plan_lifecycle_overview doc.
  • bin/booping render-sprints [--output <path>] — render <vault>/sprints.md from src/templates/sprints.md.j2. Default output is the resolved vault's sprints.md; --output PATH overrides; --output - writes to stdout.
  • bin/booping transition <to> <plan> [--also <path>...] — deterministic hook-runner: move a plan to status <to> and apply every mechanical mutation the resolved edge declares (status set, date stamps, commit snapshot, sprints.md re-render, vault commit) in one shot. The LLM decides the edge and clears the judgment gates; this command owns all the mechanics. --also <path>... stages extra vault artifacts (e.g. a retrospective file, a sibling stub) into the same transition commit.
  • bin/booping frontmatter-update <plan> [key=val ...] [--remove <key>...] — set or remove plan frontmatter keys. Values support @now (UTC yyyymmdd hh:mm), @today (yyyymmdd), and @head (repo HEAD sha) interpolation. --remove <key> (repeatable) drops keys; removals apply before key=val sets. The atomic primitive the frontmatter-update hook is built on.
  • bin/booping vault-commit <to-status> <plan> [--also <path>...] — path-scoped staging hook: stage and commit the plan file + sprints.md (plus any --also paths) in the vault git repo, with <to-status> in the commit message. The git side-effect the vault-commit post hook is built on.
  • bin/booping build — render every src/files/**/*.j2 to its plugin-root destination using src/config_files.yaml. Run after editing any src/files/<rel>.j2 or src/config_files.yaml.
  • bin/booping debug-context — dump the assembled Context as YAML for troubleshooting.
  • bin/booping debug-template <template-path> — render a template and append a debug context footer.
  • bin/booping-create-project <project-name> [--local [dir]] — scaffold the vault directories + .booping marker. By default the vault lands at ~/Claude/{project}/; --local [dir] (default ./booping) scaffolds a repo-local vault instead, writing a vault_path: key into .booping plus a vault .gitignore.
  • bin/booping-external-llm-call --prompt=<name> --context.<key>=<path>... [-- <free-text>] — render a Jinja2 prompt template from bin/llm-call-templates/<name>.md.j2 and send it to Gemini. Handles its own API-key check. Current templates: validate-plan.
  • just build — one-shot build via bin/booping build. just dev watches src/files/ + src/config_files.yaml and rebuilds on change (requires watchexec).
  • just lint, just typecheck, just test — run ruff, basedpyright, pytest against booping-python/.

Editing conventions

  • Edits to src/templates/skills/*.md.j2, src/templates/agents/*.md.j2, and src/templates/_partials/*.j2 are live — rendered at skill-load time, no rebuild needed.
  • Edits to src/files/**/*.j2 or src/config_files.yaml require just build to materialize into the on-disk skills/<name>/SKILL.md / agents/<name>.md. git diff -- skills/ agents/ after just build is the drift signal.
  • skills/<name>/SKILL.md and agents/<name>.md are build artefacts — never hand-edit. Edit src/files/<rel>.j2 (or src/config_files.yaml for shared frontmatter values) and run just build.
  • docs/*.md is hand-authored. No build step regenerates it.
  • documentation/*.md is hand-authored. Only mkdocs build consumes it, and the build runs in GitHub Actions on push to main (.github/workflows/docs.yml) — there is no local-build-on-commit step. Use just docs / just docs-serve for local preview.
  • Prefer extracting to a partial over inlining when prose grows past a paragraph or two.
  • Prefer moving structured data into src/config.yaml over prose in partials.
  • No comments that restate code. Only WHY for non-obvious bits.
  • Conventional commits with scope: feat(booping): ..., fix(install): ..., etc.
  • The plugin code itself stays stack-agnostic — no Python/Django/JS specifics inside skills.
  • README.md's Statuses section is hand-maintained narrative — revisit it whenever src/config.yaml plan.statuses changes (status name, description, terminal flag, or addition/removal).

Adding a new template-driven skill

Use src/templates/skills/chat.md.j2 as the reference.

  1. Decide what goes in config vs prose: structured values (per-skill agents, owned status, task-type / status / transition surfaces) belong in src/config.yaml; verbs, heuristics, and judgment calls go in the template.
  2. Author the template at src/templates/skills/<name>.md.j2. No frontmatter — the thin shell carries it. Standard wiring:
    • {% include "_partials/_project_context.j2" %} — loads project name/path at render time.
    • {{ available_agents.render("<name>") }} (import macro with with context) — renders agent delegation table from config.
    • {{ plan_transitions.render("<name>") }} (import macro with with context) — renders the transitions slice for this skill.
    • {{ tools.render('src/templates/_partials/_lessons.j2') }} — inlines live lessons.
    • {{ tools.render('src/templates/_partials/_extra_instructions.j2', extra_instruction_key='skill_<name>') }} — inlines project-local skill extension.
  3. Add the skill's agents block (and any status it owns) to src/config.yaml under skills.<name>. Skills with no structured surface still get an empty entry (<name>: {}) so _available_agents.j2 can resolve them.
  4. For any detail only some routes need, write it as docs/<name>.md and lazy-link from the skill body with [label](${CLAUDE_PLUGIN_ROOT}/docs/<name>.md) rather than inlining.
  5. Author src/files/skills/<name>/SKILL.md.j2 as a thin shell: copy frontmatter shape from a sibling (e.g. src/files/skills/chat/SKILL.md.j2); set allowed-tools (include Bash(booping:*) plus any non-booping shell calls the skill needs); use effort: {{ skills.<name>.effort }} and add the matching key to src/config_files.yaml under skills:; set the body to !booping render src/templates/skills/.md.j2``.
  6. Run just build to materialize skills/<name>/SKILL.md. Sanity-check by running bin/booping render src/templates/skills/<name>.md.j2 and inspecting the output.