What you'll build with the Superpowers plugin
By the end you will have Superpowers installed and provably active, and a detector that names which skills actually fired in any headless Claude Code run. You will build one small feature twice: once by hand, once by handing it to the plugin's own test-first cycle, so you can see the difference rather than take my word for it. You will also have a token budget for what the plugin charges you every session, whether or not it does anything.
This assumes you use Claude Code regularly and know that skills, hooks, and subagents exist, but have never installed a plugin or opened a SKILL.md. Allow about 45 minutes.
Here is the finished detector, run against a transcript of a real Claude Code session:
hook SessionStart:startup injected 3472 bytesplugin superpowers 6.2.0 superpowers@claude-plugins-officialskill superpowers:brainstormingverdict SKILL FIREDAnd here is the same detector, same plugin, same machine, on a different request:
hook SessionStart:startup injected 3472 bytesplugin superpowers 6.2.0 superpowers@claude-plugins-officialskill none firedverdict LOADED BUT NO SKILL FIREDSuperpowers is a set of instructions asking a model to behave a certain way, and a model follows those instructions most of the time. Your plugin list cannot tell you which time you got.
Verified against Claude Code 2.1.228, superpowers 6.2.0 (superpowers@claude-plugins-official, user scope), git 2.51.0.windows.1, Python 3.13.9, and pytest 8.4.2 on 2026-08-12. Seventeen of the 58 code blocks were not machine-verified, because they make billable Claude API calls or read files that only such a call produces: the two preview outputs directly above, all four blocks of step 8, the Claude run in each of steps 6, 7 and 11 together with the command that reads what it produced and that command's output, and the post-disable check at the end of step 12 with its output. I ran those by hand, and every transcript and figure shown for them is a real capture from those runs. Everything else was executed or structurally checked on a clean rebuild.
Prerequisites
Level: intermediate. You should be comfortable running Claude Code from a terminal and reading JSON.
| Requirement | Pinned version | Why |
|---|---|---|
| Claude Code | 2.1.228 | The claude plugin subcommands and the shell hook key both need a recent build |
| Superpowers | 6.2.0 | Released late July 2026 |
| Git | 2.51.0 | Marketplaces are git repositories. On Windows, Git Bash is also what runs the plugin's hook |
| Python | 3.13.9 | For the detector and the worked example |
| pytest | 8.4.2 | For the test-first cycle |
Superpowers is MIT licensed and free. Running the steps that call Claude costs whatever your normal Claude Code usage costs. The Claude runs behind this tutorial cost me 2.63 USD in total, and two of them were me getting step 12 wrong.
Every shell block in this tutorial is POSIX. On Windows, run all of them in Git Bash, which ships with Git for Windows. PowerShell and cmd do not have wc, rm -rf, or < /dev/null, and several steps use them.
Windows readers have a second reason to care: Git for Windows is not optional here. Superpowers 6.2.0 runs its startup hook through Git Bash, and the fallback when it cannot find bash is to exit quietly. You get no error and no Superpowers. This is covered in detail in When it breaks.
On cost. Steps 6, 7, 8 and 11, and the last check in step 12, call the Claude API and cost money. If you would rather not spend it, skip them: nothing later in the tutorial reads run-fired.jsonl, run-skipped.jsonl, run-tdd.jsonl, run-disabled.jsonl, greet.py, or the design document, and the closing verification does not touch any of them. You will finish with a working detector, a tested feature, and a scoped-off plugin. What you will not have is live proof that a skill fired on your machine, which is the one thing steps 6 and 11 exist to give you.
Install pytest and confirm the whole toolchain in one command:
pip install "pytest==8.4.2"claude --version && git --version && python --version && python -m pytest --versionExpected output, with your own paths and patch versions:
2.1.228 (Claude Code)git version 2.51.0.windows.1Python 3.13.9pytest 8.4.2If claude --version prints nothing or errors, stop here and fix that first. Every later step shells out to it.
The mental model: how Superpowers makes a model change its behaviour
Superpowers is simpler and stranger than "a set of skills", and the difference decides what you can and cannot do with it.
A Claude Code plugin can ship skills, agents, hooks, MCP servers, and LSP servers. Superpowers 6.2.0 ships exactly two of those - 14 skills and one hook - and the hook is what makes the skills matter.
Normally a skill sits dormant. Claude Code shows the model a listing of every available skill name and a short description, and the model decides on its own whether to invoke one through the Skill tool. That listing is the mechanism behind skills as a production knowledge layer. That is a suggestion, and models routinely skip suggestions.
Superpowers changes the odds by adding a SessionStart hook, which is policy-as-code applied to an agent in its simplest form. Every time a session starts, clears, or compacts, that hook runs a shell script which reads one specific skill file, using-superpowers/SKILL.md, and prints it as JSON on standard output. Claude Code takes that text and injects it directly into the model's context, wrapped in an EXTREMELY_IMPORTANT tag. The injected text tells the model, in unusually forceful language, that it must check for an applicable skill before doing anything at all.
A hook injects a paragraph. The paragraph does the rest.
flowchart LR
A["Session starts"] --> B["SessionStart hook<br/>runs run-hook.cmd"]
B --> C["Reads using-superpowers<br/>SKILL.md from disk"]
C --> D["Prints JSON to stdout"]
D --> E["Claude Code injects it<br/>into model context"]
E --> F["Model reads:<br/>check skills first"]
F --> G["Model calls the<br/>Skill tool"]
G --> H["Full skill body loads<br/>on demand"]
style A fill:#95A5A6,color:#FFFFFF
style B fill:#4A90E2,color:#FFFFFF
style C fill:#4A90E2,color:#FFFFFF
style D fill:#4A90E2,color:#FFFFFF
style E fill:#7B68EE,color:#FFFFFF
style F fill:#7B68EE,color:#FFFFFF
style G fill:#6BCF7F,color:#2C2C2A
style H fill:#6BCF7F,color:#2C2C2A
The hook is the load-bearing part. If the hook does not run, the injection never happens, and the 14 skills go back to being dormant suggestions the model will mostly ignore. This is why a Windows box without Git Bash gets a plugin that reports itself as enabled and does nothing.
The skills load in two stages. Only the names and short descriptions are always in context. The full body of a skill - and subagent-driven-development alone is over 500 lines - loads only when the model invokes it. That two-stage design is why the always-on cost is small and the on-invoke cost is not, which you will measure yourself in step 3.
Step 1: Install Superpowers from the official marketplace
Goal. Get the plugin onto your machine from Anthropic's marketplace.
Why this step. Superpowers is distributed through two different marketplaces, and they are not interchangeable. Anthropic's claude-plugins-official is registered automatically the first time you start Claude Code interactively, so there is nothing to add. The author also runs obra/superpowers-marketplace, which you would have to register by hand. The trap in running both is covered at the end of this step.
Code. Run this from any directory:
claude plugin install superpowers@claude-plugins-officialRun it. The command clones the marketplace if needed and copies the plugin into your local cache. It does not need a project directory.
Expected output. The install prints a summary ending in either Plugin is now active. or Run /reload-plugins to activate. If you already have it, the command tells you so instead of reinstalling.
/reload-plugins is a slash command typed inside a session, not a shell command. If you get that second message, either start claude, type it at the prompt and exit, or just carry on: the next session you start picks the plugin up anyway.
What just happened. Claude Code copied the plugin into ~/.claude/plugins/cache/claude-plugins-official/superpowers/6.2.0/. It copies rather than symlinks, deliberately, so a marketplace cannot change code under a running install. The path carries the version, so several versions can sit side by side; superseded ones are removed automatically after 14 days.
Both marketplaces currently serve version 6.2.0, and they do not serve identical bytes. Anthropic's entry pins a raw commit SHA, 44c9b2d, which sits one commit ahead of the v6.2.0 tag that the author's marketplace points at. Because Claude Code reads the version from the plugin's own plugin.json before anything else, both report 6.2.0, and claude plugin update therefore compares 6.2.0 against 6.2.0 and does nothing. Auto-update also differs: it is on by default for official marketplaces and off for third-party ones. If you install from both, you end up with two same-named plugins from different sources, which is a mess to unpick. Choose one.
Step 2: Confirm the plugin actually loaded
Goal. Verify the install before you trust anything built on top of it.
Why this step. An install that reported success can still be inert, most obviously on Windows, where the hook can fail silently.
Code.
claude plugin listRun it. From any directory.
Expected output. Among your installed plugins you should see this block:
❯ superpowers@claude-plugins-official Version: 6.2.0 Scope: user Status: ✔ enabledWhat just happened. User scope is the default, so every project on this machine gets the plugin until you say otherwise.
This tells you the plugin is registered. It does not tell you the hook runs or that any skill fires. Those are steps 4 and 6.
Step 3: Measure what Superpowers costs your context window
Goal. Get real token numbers for what the plugin adds to every session and what each skill costs when it fires.
Why this step. You cannot decide which skills to keep from someone else's benchmark. The Hacker News thread on the Superpowers 6 announcement (item 48739459, 196 points) is where I went looking for numbers and found none. One commenter reports that it "burnt through all my max plan (before it could start making any changes)"; another that it was "burning too many tokens to do too little". Neither attaches a token count, and I did not find one anywhere else in the thread. Nobody has published the numbers for your machine either.
Code.
claude plugin details superpowersRun it. From any directory.
Expected output. The command prints an inventory and a token table. This is the real output from the machine this tutorial was written on:
superpowers 6.2.0 Core skills library for Claude Code: TDD, debugging, collaboration patterns, and proven techniques Source: superpowers@claude-plugins-officialComponent inventory Skills (14) brainstorming, dispatching-parallel-agents, executing-plans, finishing-a-development-branch, receiving-code-review, requesting-code-review, subagent-driven-development, systematic-debugging, test-driven-development, using-git-worktrees, using-superpowers, verification-before-completion, writing-plans, writing-skills Agents (0) Hooks (1) SessionStart (harness-only — no model context cost) MCP servers (0) LSP servers (0)Projected token cost Always-on: ~688 tok added to every sessionPer-component (rounded) component always-on on-invoke brainstorming ~70 ~3.6k dispatching-parallel-agents ~40 ~2.2k executing-plans ~40 ~800 finishing-a-development-branch ~40 ~2.5k receiving-code-review ~80 ~2.2k requesting-code-review ~40 ~1k subagent-driven-development ~40 ~10.3k systematic-debugging ~40 ~3.5k test-driven-development ~30 ~3.3k using-git-worktrees ~70 ~2.4k using-superpowers ~50 ~1.1k verification-before-completion ~80 ~1.2k writing-plans ~30 ~2.5k writing-skills ~40 ~9.7k On-invoke cost is paid each time a skill or agent fires. Token counts are estimates and may differ from actual usage.Your always-on figure may differ by a few tokens, because it is computed against your active model through the token-counting API and falls back to a character estimate if that call fails.
What just happened. You now have the shape of the cost, and it is lopsided. It is also one line item in a larger context budget for agent tooling. The standing charge is about 688 tokens on every session, which is negligible. The variable charge is not: subagent-driven-development costs about 10.3k tokens each time it fires, and writing-skills about 9.7k. Two of those in one session is roughly 20k tokens of instructions before any of your code is read.
Note the Hooks (1) line, which annotates the SessionStart hook as harness-only with no model context cost. That undercounts. The hook mechanism does cost the model nothing, true. But this hook's whole job is to inject text into the model's context, and the table does not charge that text to it. You measure the injection yourself in the next step.
Step 4: See exactly what the SessionStart hook injects
Goal. Run the hook by hand and read the text it puts into every one of your sessions.
Why this step. Everything the plugin does to your agent's behaviour starts with one blob of text that nothing ever prompts you to open. Two lines and no API call put it on your screen.
To be precise about what this proves: it shows the hook script works and what it contains. It cannot prove Claude Code actually runs that script, because you are invoking it with bash from inside Git Bash, where bash is by definition present. The Windows failure mode is Claude Code failing to find bash at all, and this test cannot reproduce it. Step 6 is where you see the hook fire inside a real session.
Code. The hook is a plain script. Point an environment variable at the plugin and run it. The $HOME form below resolves correctly on macOS, Linux, and Windows under Git Bash, so the same two lines work everywhere:
export CLAUDE_PLUGIN_ROOT="$HOME/.claude/plugins/cache/claude-plugins-official/superpowers/6.2.0"bash "$CLAUDE_PLUGIN_ROOT/hooks/session-start"The version is a directory name, so replace 6.2.0 with whatever claude plugin list reported in step 2. If the path does not exist, list what you actually have:
ls "$HOME/.claude/plugins/cache/claude-plugins-official/superpowers/"Run it. From any directory. The script only reads files and prints to standard output.
Expected output. A single JSON object. The first 500 characters look like this:
{ "hookSpecificOutput": { "hookEventName": "SessionStart", "additionalContext": "<EXTREMELY_IMPORTANT>\nYou have superpowers.\n\n**Below is the full content of your 'superpowers:using-superpowers' skill - your introduction to using skills. For all other skills, use the 'Skill' tool:**\n\n---\nname: using-superpowers\ndescription: Use when starting any conversation - establishes how to find and use skills, requiring skill invocation before ANY response including clarifying questions\n---Measure the whole thing:
bash "$CLAUDE_PLUGIN_ROOT/hooks/session-start" | wc -c3484What just happened. You just read the mechanism. That 3,484-byte JSON object is handed to Claude Code, which unwraps additionalContext and places it in the model's context before your first message. Inside it is the full text of the using-superpowers skill, including the line that does the actual work:
If you think there is even a 1% chance a skill might apply to what you are doing, you ABSOLUTELY MUST invoke the skill.
Now you know why the plugin behaves the way it does. There is no enforcement anywhere in this design. There is a strongly worded instruction, injected reliably, and a model that usually complies. Step 7 shows what happens when it does not.
The script also branches on which harness is running it, emitting additional_context for Cursor and hookSpecificOutput for Claude Code, because the two read different fields. If you see the Cursor shape, you set the wrong environment variable.
Step 5: Build a detector that proves which skills fired
Goal. Write a script that reads a Claude Code transcript and reports whether the hook ran, whether the plugin loaded, and which skills fired.
Why this step. Steps 2 and 4 proved the plugin is installed and the hook works in isolation. Neither tells you what happened during an actual request. Claude Code can emit a machine-readable transcript, and the events in it answer that question directly. Writing the detector once means you can check any future run in one command instead of reading a wall of JSON. It is the smallest useful piece of observability for a Claude Code session.
Create the project directory first:
mkdir booklog && cd booklog && git init -q && mkdir tests fixturesMake it a git repository now, because step 8 has Claude write and commit a design document into it.
Commands run from inside booklog up to step 10, and file paths are written with the booklog/ prefix so you always know where a file belongs. Step 11 is the one exception: it creates a sibling directory and works there, then hands you back at the start of step 12. Every step says where you are when it matters.
Code. Save this as booklog/sp_detect.py:
"""Report what Superpowers actually did during a headless Claude Code run.Reads a stream-json transcript (one JSON object per line) from a file or stdinand answers three questions the plugin list cannot: 1. Did the SessionStart hook run, and how much text did it inject? 2. Did Claude Code load the superpowers plugin, and at what version? 3. Which skills actually fired?Usage: python sp_detect.py fixtures/sample-stream.jsonl claude -p "..." --output-format stream-json --verbose < /dev/null | python sp_detect.py"""from __future__ import annotationsimport jsonimport sysfrom typing import Any, Iteratordef read_events(source: Iterator[str]) -> Iterator[dict[str, Any]]: """Yield one parsed event per non-blank line, skipping anything unparseable. A stream-json transcript can be truncated if the run is interrupted, so a bad final line is expected rather than exceptional. """ for line in source: line = line.strip() if not line: continue try: yield json.loads(line) except json.JSONDecodeError: continuedef summarize(events: Iterator[dict[str, Any]]) -> dict[str, Any]: """Collapse a transcript into the three facts we care about.""" report: dict[str, Any] = { "hook_name": None, "injected_bytes": 0, "plugin_version": None, "plugin_source": None, "plugin_errors": [], "skills": [], } for event in events: subtype = event.get("subtype") if subtype == "hook_response" and event.get("hook_event") != "PostToolUse": report["hook_name"] = event.get("hook_name") report["injected_bytes"] = len(event.get("output") or "") elif subtype == "init": for plugin in event.get("plugins") or []: if plugin.get("name") == "superpowers": report["plugin_version"] = plugin.get("version") report["plugin_source"] = plugin.get("source") report["plugin_errors"] = event.get("plugin_errors") or [] elif event.get("type") == "assistant": for block in event.get("message", {}).get("content", []): if block.get("type") == "tool_use" and block.get("name") == "Skill": skill = block.get("input", {}).get("skill") if skill and skill not in report["skills"]: report["skills"].append(skill) return reportdef render(report: dict[str, Any]) -> str: """Format the report as fixed-width lines, one fact per line.""" lines = [] if report["hook_name"]: lines.append( f"hook {report['hook_name']:<22} injected {report['injected_bytes']} bytes" ) else: lines.append("hook none no SessionStart hook ran") if report["plugin_version"]: lines.append( f"plugin superpowers {report['plugin_version']:<10} {report['plugin_source']}" ) else: lines.append("plugin superpowers not loaded") for error in report["plugin_errors"]: lines.append(f"error {error.get('plugin')}: {error.get('message')}") for skill in report["skills"]: lines.append(f"skill {skill}") if not report["skills"]: lines.append("skill none fired") fired = bool(report["skills"]) loaded = report["plugin_version"] is not None if fired: lines.append("verdict SKILL FIRED") elif loaded: lines.append("verdict LOADED BUT NO SKILL FIRED") else: lines.append("verdict SUPERPOWERS NOT ACTIVE") return "\n".join(lines)def main(argv: list[str]) -> int: if len(argv) > 1: with open(argv[1], encoding="utf-8") as handle: report = summarize(read_events(handle)) else: report = summarize(read_events(sys.stdin)) print(render(report)) return 0 if report["skills"] else 1if __name__ == "__main__": raise SystemExit(main(sys.argv))Test it against a fixture rather than a live call, so you can confirm the parser works before spending anything. Save this as booklog/fixtures/sample-stream.jsonl - a trimmed five-event excerpt from a real run. Each event must be exactly one line, five lines total, with no wrapping. The detector skips unparseable lines silently, so a wrapped line shows up as a missing fact rather than an error:
{"type":"system","subtype":"hook_started","hook_name":"SessionStart:startup","hook_event":"SessionStart"}{"type":"system","subtype":"hook_response","hook_name":"SessionStart:startup","output":"{\n \"hookSpecificOutput\": {\n \"hookEventName\": \"SessionStart\",\n \"additionalContext\": \"<EXTREMELY_IMPORTANT>\\nYou have superpowers.\\n\"\n }\n}"}{"type":"system","subtype":"init","plugins":[{"name":"superpowers","version":"6.2.0","source":"superpowers@claude-plugins-official"}]}{"type":"assistant","parent_tool_use_id":null,"message":{"content":[{"type":"tool_use","name":"Skill","input":{"skill":"superpowers:brainstorming","args":"Small tool to track books the user reads"}}]}}{"type":"result","subtype":"success","num_turns":4}Run it.
python sp_detect.py fixtures/sample-stream.jsonlExpected output.
hook SessionStart:startup injected 144 bytesplugin superpowers 6.2.0 superpowers@claude-plugins-officialskill superpowers:brainstormingverdict SKILL FIREDThe byte count is 144 because the fixture's hook output is truncated for readability. A real run reports the full size.
What just happened. You have a working detector and you proved it works without spending anything on the API. It keys off hook_response for the hook's output, init for the loaded plugins and any plugin_errors, and any assistant message carrying a tool_use block named Skill. Exit code 0 when a skill fired, 1 when none did.
Step 6: Watch a skill fire on a real request
Goal. Run Claude Code headlessly against a real request and confirm through the detector that Superpowers took over.
Why this step. Everything so far has been static inspection. This is the first step where a model reads the injected instruction and does something about it. If it does not fire here, on an open-ended greenfield request, it will not fire anywhere.
Code. Run this from inside the booklog directory:
claude -p "I want to build a small tool to track the books I read. Help me figure out what to build." \ --output-format stream-json --verbose \ --allowedTools "Read,Glob,Grep" \ < /dev/null > run-fired.jsonl-p runs a single prompt without opening a session and exits when it is done. --output-format stream-json with --verbose is what produces the machine-readable transcript. --allowedTools "Read,Glob,Grep" keeps the run read-only, so a demonstration cannot modify your files. And < /dev/null closes standard input, which you need in scripts.
Never add --bare to a command like this. That flag skips discovery of hooks, skills, and plugins entirely, so Superpowers would not load and the run would prove nothing. Anthropic's headless documentation recommends --bare for scripted calls generally and says it is expected to become the default for -p in a future release, so this will need watching.
Run it. The call takes about 40 seconds.
python sp_detect.py run-fired.jsonlExpected output.
hook SessionStart:startup injected 3472 bytesplugin superpowers 6.2.0 superpowers@claude-plugins-officialskill superpowers:brainstormingverdict SKILL FIREDIf your verdict says LOADED BUT NO SKILL FIRED instead, nothing is broken. That is the non-determinism step 7 is about, arriving early. Run the same command again; if it never fires across three attempts, the injection is not landing and step 4 is where to look.
What just happened. The model received an open-ended request, read the injected instruction, and invoked superpowers:brainstorming before writing anything. In the transcript it appears as an ordinary tool call:
{"type":"tool_use","name":"Skill","input":{"skill":"superpowers:brainstorming","args":"Small tool to track books the user reads"}}The brainstorming skill carries a hard gate instructing the model not to write any code until it has presented a design and you have approved it, so instead of guessing at a schema it starts asking questions one at a time.
The injected size reads 3,472 here against the 3,484 you measured in step 4. Both are correct: step 4 counted the raw bytes the script wrote to standard output, while this counts the JSON string as it arrives in the transcript event. They are two measurements of the same injection, not a discrepancy.
Step 7: Watch the same setup skip the skill entirely
Goal. Give the same installation a small, fully specified task and watch it decline to use the skill.
Why this step. Builder.io's trial and Schwartz's review both stop at the point where Superpowers works. Neither shows it declining. That omission matters, because the plugin's own instruction says a skill must be invoked if there is even a 1% chance it applies, and the brainstorming skill states its gate applies to every project regardless of perceived simplicity. Neither statement is reliably true in practice. If you believe them, you will build workflows on an assumption that quietly fails.
Code. The request needs a small, fully specified target, so give it one. Create booklog/greet.py:
import sysdef main() -> int: print(f"hello, {sys.argv[1] if len(sys.argv) > 1 else 'world'}") return 0if __name__ == "__main__": raise SystemExit(main())This file exists only to be the subject of the request. It is deliberately trivial: one function, one obvious place to add a flag. No engineer designs this before writing it.
Now the same plugin and the same directory, with a narrower request:
claude -p "Add a --json flag to greet.py so it can print its output as JSON." \ --output-format stream-json --verbose \ --allowedTools "Read,Glob,Grep" \ < /dev/null > run-skipped.jsonlRun it.
python sp_detect.py run-skipped.jsonlExpected output.
hook SessionStart:startup injected 3472 bytesplugin superpowers 6.2.0 superpowers@claude-plugins-officialskill none firedverdict LOADED BUT NO SKILL FIREDWhat just happened. The hook ran, the plugin loaded, the instruction was in context, and the model chose not to invoke any skill. It stated its reasoning in the transcript:
A tiny, fully-specified change - implementing directly rather than running a brainstorming pass on a one-flag addition.
That is a defensible engineering judgement. It is also a direct contradiction of the instruction the plugin injected, on a supported version pair. Issue #2051 describes the same thing: the model evaluates skill relevance at the start of a turn, not continuously.
Superpowers shifts probabilities. It does not enforce. Hence the exit code from your detector: 1 here, 0 in step 6. Any workflow that depends on a skill having fired should check.
On a one-flag change, skipping the skill is what you want anyway.
Step 8: Run the brainstorm-to-spec cycle in a real session
Goal. Take the full interactive path once: an open question, a design conversation, and a committed spec file.
Why this step. Steps 6 and 7 used headless mode because it produces a parseable transcript. But brainstorming is a conversation - it asks questions one at a time and requires your approval before proceeding - so headless mode can only ever capture its opening move. To see the workflow the plugin was built for, you have to sit in the session.
Code. Start Claude Code interactively in the booklog directory and give it the same open-ended request:
claudeThen type:
I want to build a small tool to track the books I read. Help me figure out what to build.Run it. Answer the questions as they come. Keep the scope small: a reading list with a title, an author, a year, and a way to print it as JSON is enough.
Expected output. Mine named the skill, read the repository, then asked exactly one question and stopped:
Using superpowers:brainstorming to turn this into a design before any code gets written.
What's the itch this scratches? Pick whichever is closest:
- A. Memory - "Did I already read this? When? What did I think of it?"
- B. Stats - "How many books did I read this year? What genres/authors dominate?"
- C. Queue - "What should I read next?"
- D. Notes - The books are the hook, but the real value is your highlights per book.
One question, then silence until I answered. It asked three in total: the itch, what "what I thought of it" should actually record, and where the read date comes from. Only then did it propose approaches, with the cost of each attached and one recommended outright:
B. Small package with an entry point (recommended) [...] After
pip install -e .you typebooklog add ...from anywhere - which matters, because a memory tool you have tocdto is a memory tool you stop using.
When I approved the design it wrote the document and committed it:
docs/superpowers/specs/2026-08-12-booklog-design.md157 lines, from a conversation of five turns. The date is the day you run it. The skill's terminal state is to invoke writing-plans, so expect an implementation plan offered next. Decline it and leave the session with /exit. You are going to write this feature by hand in steps 9 and 10, so that step 11 has something to compare against, and the design document stays in the repository as this step's artifact.
Your questions will be different, because the skill generates them from your answers. You have finished this step when a markdown file exists under docs/superpowers/specs/ and the model offers you an implementation plan:
ls docs/superpowers/specs/I captured this as a chain of resumed headless turns rather than in the terminal UI, because that is the only way to save the transcript. It is the same conversation either way. Punctuation in the two quotes above is normalised to this site's house style; nothing else is changed.
What just happened. You produced a reviewable artifact. The design document is ordinary markdown in your repository, so you can read it, edit it, and put it through code review like anything else. Schwartz's review makes the same point about the artifact rather than the process: the design doc is "a markdown file in your repository that you can read, comment on, and edit in your own editor." Builder.io's trial put a number on the same stage, at 424 lines of spec before a line of code.
It is also the part that costs the most, and the cost is not always proportionate. Issue #2079 describes this same pipeline producing 3,061 lines across 14 commits over roughly ten hours, for a change whose real logic was a single comparison. The reporter closed it themselves after finding #2063, a merged pull request that gives brainstorming a three-path router so the ceremony scales to the size of the job.
That router is not in the version you just installed. It merged on 7 August 2026, two weeks after 6.2.0 was tagged, and 6.2.0 is still the newest release as I write this. So the fix is real, it is coming, and you do not have it. Scoping this is your job today, and step 12 is the lever.
Step 9: Write the failing test first
Goal. Add a test for behaviour that does not exist yet, and watch it fail for the right reason.
Why this step. The test-driven-development skill states an iron law: no production code without a failing test first. I am following it here for a narrower reason than the skill's own. This whole tutorial is about refusing to take a claim on trust, and a test that has never gone red is a claim nobody has checked.
Code. Create booklog/booklog.py with the code the feature will extend:
"""A reading list you can query from the command line."""from __future__ import annotationsBOOKS = [ {"title": "The Soul of a New Machine", "author": "Tracy Kidder", "year": 1981}, {"title": "Thinking in Systems", "author": "Donella Meadows", "year": 2008},]def format_books(books: list[dict[str, object]]) -> str: """Render each book as one human-readable line.""" return "\n".join(f"{b['title']} by {b['author']} ({b['year']})" for b in books)Now create booklog/tests/test_booklog.py. The first test covers what already works; the second describes the feature you have not written:
import jsonfrom booklog import BOOKS, format_booksdef test_format_books_renders_one_line_per_book(): output = format_books(BOOKS) assert output.splitlines() == [ "The Soul of a New Machine by Tracy Kidder (1981)", "Thinking in Systems by Donella Meadows (2008)", ]def test_format_books_emits_json_when_asked(): output = format_books(BOOKS, as_json=True) assert json.loads(output) == BOOKSRun it. From the booklog directory:
python -m pytest -qUse python -m pytest, not a bare pytest. Only the module form puts the current directory on the import path, and a bare pytest here fails with ModuleNotFoundError: No module named 'booklog'.
Expected output.
.F [100%]================================== FAILURES ===================================___________________ test_format_books_emits_json_when_asked ___________________ def test_format_books_emits_json_when_asked():> output = format_books(BOOKS, as_json=True) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^E TypeError: format_books() got an unexpected keyword argument 'as_json'tests\test_booklog.py:15: TypeError=========================== short test summary info ===========================FAILED tests/test_booklog.py::test_format_books_emits_json_when_asked - TypeE...1 failed, 1 passed in 0.47sThe path separator in tests\test_booklog.py:15 is a backslash on Windows and a forward slash elsewhere, and the duration varies per run.
What just happened. One passed, one failed. Good. The failure is a TypeError about an unrecognised keyword argument, not an assertion error, so the test failed because the keyword really is not there yet.
Step 10: Make the test pass and verify the claim
Goal. Write the smallest change that turns the suite green, then confirm it at the command line rather than asserting it.
Why this step. The verification-before-completion skill has one rule: evidence before claims, always. I broke that rule writing this tutorial. An earlier draft of step 7 ran a command against a greet.py that no step created, because I had lifted the prompt from a different scratch directory and never walked the whole thing start to finish. The version you read has the file, because a verification pass caught the gap. I had read that step four times and did not.
Code. Replace booklog/booklog.py in full:
"""A reading list you can query from the command line."""from __future__ import annotationsimport argparseimport jsonBOOKS = [ {"title": "The Soul of a New Machine", "author": "Tracy Kidder", "year": 1981}, {"title": "Thinking in Systems", "author": "Donella Meadows", "year": 2008},]def format_books(books: list[dict[str, object]], as_json: bool = False) -> str: """Render the reading list, as JSON when as_json is set.""" if as_json: return json.dumps(books) return "\n".join(f"{b['title']} by {b['author']} ({b['year']})" for b in books)def main() -> int: parser = argparse.ArgumentParser(description="Print the reading list.") parser.add_argument("--json", action="store_true", help="print the list as JSON") args = parser.parse_args() print(format_books(BOOKS, as_json=args.json)) return 0if __name__ == "__main__": raise SystemExit(main())Run it. The suite first, then the tool both ways.
python -m pytest -qpython booklog.pypython booklog.py --jsonExpected output.
.. [100%]2 passed in 0.07sThe Soul of a New Machine by Tracy Kidder (1981)Thinking in Systems by Donella Meadows (2008)[{"title": "The Soul of a New Machine", "author": "Tracy Kidder", "year": 1981}, {"title": "Thinking in Systems", "author": "Donella Meadows", "year": 2008}]What just happened. The suite is green. You also ran the tool twice at the command line, so you know the feature works without taking pytest's word for it. format_books gained one optional parameter and the module gained an entry point. The first test is there to prove the old behaviour survived.
Step 11: Hand the same job to the plugin and compare
Goal. Give Claude the same feature request, let the test-driven-development skill drive it, and use your detector to confirm the skill actually ran.
Why this step. You just built that feature by hand, so you have a control. Without one there is no way to say what the plugin's test-first cycle adds over your own discipline, and "it felt more thorough" is not a measurement.
Code. From inside booklog, start a sibling directory holding the pre-feature state, so the comparison is fair. Everything else in this step runs in that new directory:
cd ..mkdir booklog-plugin && cd booklog-plugin && mkdir tests && git init -qCreate booklog-plugin/booklog.py with the pre-feature version from step 9:
"""A reading list you can query from the command line."""from __future__ import annotationsBOOKS = [ {"title": "The Soul of a New Machine", "author": "Tracy Kidder", "year": 1981}, {"title": "Thinking in Systems", "author": "Donella Meadows", "year": 2008},]def format_books(books: list[dict[str, object]]) -> str: """Render each book as one human-readable line.""" return "\n".join(f"{b['title']} by {b['author']} ({b['year']})" for b in books)and booklog-plugin/tests/test_booklog.py with only the test that already passes:
from booklog import BOOKS, format_booksdef test_format_books_renders_one_line_per_book(): output = format_books(BOOKS) assert output.splitlines() == [ "The Soul of a New Machine by Tracy Kidder (1981)", "Thinking in Systems by Donella Meadows (2008)", ]Run it. Commit the starting state, then hand the job over. This run has to write files, so its allowed-tools list is wider than the read-only runs in steps 6 and 7:
git add -A && git commit -qm "booklog: plain text output"claude -p "Add a --json flag to booklog.py so format_books can return the list as JSON. Do it test-first." \ --output-format stream-json --verbose \ --allowedTools "Read,Glob,Grep,Write,Edit,Bash" \ < /dev/null > run-tdd.jsonlpython ../booklog/sp_detect.py run-tdd.jsonlExpected output.
hook SessionStart:startup injected 3472 bytesplugin superpowers 6.2.0 superpowers@claude-plugins-officialskill superpowers:test-driven-developmentverdict SKILL FIREDtest-driven-development was the first tool call of the run, before a single file was read.
If your verdict says LOADED BUT NO SKILL FIRED, that is step 7 again, and it stings more here because you have already paid. The prompt says "test-first" but the model still decides. Re-run it, and if it still will not fire, name the skill in the prompt directly. I have not had to do that, so I cannot tell you how reliable it is.
Now open the transcript and count what it actually did:
python -m pytest -qcat tests/test_booklog.pyMine printed 4 passed, against the 2 passed you have in booklog. Yours may differ, because the skill decides how many tests to write.
What just happened. The skill drove a real red-green loop instead of writing the finished code in one pass. Mine ran pytest six times:
| Run | Result | |
|---|---|---|
| 1 | 1 passed | the starting state |
| 2 | 1 failed, 1 passed | red |
| 3 | 2 passed | green |
| 4 | 1 error | it broke something |
| 5 | 2 failed, 2 passed | red again |
| 6 | 4 passed | green |
Four tests, not two. It wrote the two you wrote by hand, then two more that drive main() through pytest's capsys fixture:
def test_main_with_json_flag_prints_json(capsys): main(["--json"]) assert json.loads(capsys.readouterr().out) == BOOKSTo make that callable it changed the signature to main(argv: list[str] | None = None) and passed argv to parse_args. That is the refactor that makes an entry point testable at all, and it is the gap in the version you wrote in step 10: your suite imports format_books directly and never touches main(), so you could delete the --json flag from the parser entirely and both your tests would still pass.
The run cost 0.78 USD across 24 turns, for one flag. I would pay that for the main() refactor and not for the flag, which is the same trade issue #2079 describes at a larger scale: the process does not scale its effort down to match a small job.
Step 12: Turn Superpowers off for one project, not globally
Goal. Disable the plugin for one project without affecting the rest of your machine, and confirm it is off.
Why this step. Step 8 showed the process pipeline can be wildly disproportionate on small work, and step 11 put a number on it: 0.78 USD and 24 turns for one flag. The three-path router in #2063 will address this, but it is not in 6.2.0, so on the version you have you scope it yourself. A repository where you make small, well-understood changes is exactly where the full brainstorm-to-plan cycle costs more than it returns.
Before the command that works, one that does not. The documented per-skill dial is skillOverrides in settings, which controls how a skill appears in the listing Claude Code shows the model. It is a reasonable first guess, and for Superpowers it does not do the job:
{ "_comment": "DOES NOT WORK - shown only to rule it out. See below.", "skillOverrides": { "brainstorming": "off" }}I tried this first. Twice - once with the bare key above, once with the namespaced superpowers:brainstorming form. brainstorming fired both times. The reason follows from the mental model. skillOverrides trims the skill listing, but Superpowers does not depend on the listing - the SessionStart hook injects instructions directly into context, and those instructions name the skills. Removing an entry from the menu does not remove the note telling the model to order it.
Code. Step 11 left you in booklog-plugin, so go back first. If you skipped step 11 you are already in booklog and the cd is a harmless no-op. This matters more than it looks: --scope local writes to whichever directory you are standing in, and it reports success either way, so running it from the wrong project gives you a confident message about a disable that did not happen where you wanted it.
cd ../booklogclaude plugin disable superpowers@claude-plugins-official --scope localRun it. From inside the project you want it off in.
Expected output.
✔ Successfully disabled plugin: superpowers (scope: local)This writes a small settings file. Check it:
cat .claude/settings.local.json{ "enabledPlugins": { "superpowers@claude-plugins-official": false }}What just happened. You turned the plugin off for this directory only. --scope local writes to .claude/settings.local.json, which is per-project and normally git-ignored, so your teammates are unaffected. Use --scope project and the setting lands in .claude/settings.json for the whole team instead.
Confirm it with the detector. This is what you built it for. Write to a new file rather than re-running step 6 verbatim, which would overwrite the transcript in which the skill fired:
claude -p "I want to build a small tool to track the books I read. Help me figure out what to build." \ --output-format stream-json --verbose \ --allowedTools "Read,Glob,Grep" \ < /dev/null > run-disabled.jsonlpython sp_detect.py run-disabled.jsonlhook none no SessionStart hook ranplugin superpowers not loadedskill none firedverdict SUPERPOWERS NOT ACTIVEThe hook itself no longer runs. You removed the injection at its source instead of suppressing its effects downstream. To switch it back on, swap disable for enable.
When it breaks
Every error string below comes from a reported issue or from the tool that prints it. The four most commonly hit have their own sections; the rest are in the table at the end.
SessionStart:startup hook error on Windows
The banner appears at the top of a session and the plugin does nothing afterwards. There are three distinct causes and they are worth telling apart, because two are fixed and one is cosmetic.
If the path in the error looks cut in half, reading SessionStart:startup hook error: 'C:\Users\UserName' is not recognized as an internal or external command, operable program or batch file., then your user profile path contains a parenthesis. cmd.exe treats ( as a grouping operator and splits the path there. Fixed in 6.2.0; on older versions, install to a path with no parentheses.
If you get a red banner but Superpowers demonstrably works, and it reads Failed with non-blocking status code: node:internal/modules/cjs/loader:1423, that value is a Node module path rather than an exit code, and the fault is in how the hook is spawned rather than in the hook itself. Cosmetic, and open at the time of writing. Confirm the injection still lands with step 6.
And if there is no error at all but nothing happens, that is the silent case from the Prerequisites: run-hook.cmd looks for Git Bash in two Program Files locations and then on PATH, and when it finds none it runs exit /b 0. Claude Code sees a clean exit with no output and reports nothing. Install Git for Windows.
Unexpected token 'session-start' in expression or statement
The full banner reads Unexpected token 'session-start' in expression or statement. followed by ParserError: UnexpectedToken. PowerShell parsed the hook command's leading quoted path as a string expression rather than a command. Superpowers 6.2.0 fixed this by declaring shell: "bash" in its hook manifest, which routes the call through Git Bash. If you are on 6.2.0 and still see it, the version you think you are running is not the version that is installed - check with claude plugin list.
Marketplace "claude-plugins-official" not found
Claude Code registers the official marketplace the first time you start it interactively, so a machine that has only ever run headless may never have done it. Add it by hand with claude plugin marketplace add anthropics/claude-plugins-official, then retry the install from step 1.
Git clone timed out after 120s
Marketplaces are git repositories, and 120 seconds is the default ceiling. On a slow or proxied network, raise it by setting CLAUDE_CODE_PLUGIN_GIT_TIMEOUT_MS=300000 in the environment before re-running the step 1 install.
Everything else
| Symptom | Verbatim error | Root cause | Fix |
|---|---|---|---|
| Reload reports no skills | 0 skills | The reload summary counts only a plugin's commands/ directory. Superpowers ships skills/ and no commands/ | Not a failure. Verify with claude plugin details superpowers, which reports all 14 |
| Skills stop matching after installing many plugins | (no error; skills silently stop triggering) | Anthropic's skills documentation puts the skill listing on a character budget of about 1% of the context window. When it overflows, descriptions are truncated starting with least-used skills, stripping the keywords needed to match | Run /doctor, check the Skills row in /context, and raise skillListingBudgetFraction or set unused skills to name-only |
| Headless run stalls for three seconds | Warning: no stdin data received in 3s, proceeding without it. If piping from a slow command, redirect stdin explicitly: < /dev/null to skip, or wait longer. | claude -p waits for piped input that never arrives | Append < /dev/null, as every headless command in this tutorial does |
| Skill fires but the tool call is refused | Claude requested permissions to write to ..., but you haven't granted it yet. | --allowedTools did not include the tool | Expected in steps 6 and 7, which are deliberately read-only. Add the tool when you want a run to make changes |
| Plugin skills never appear at all | (no error) | Corrupted plugin cache | rm -rf ~/.claude/plugins/cache, restart Claude Code, reinstall |
| Step 4 cannot find the hook | bash: /c/Users/you/.claude/plugins/cache/.../hooks/session-start: No such file or directory | The version in the path is a directory name, and yours is not 6.2.0 | ls "$HOME/.claude/plugins/cache/claude-plugins-official/superpowers/" and use the version you actually have |
wc, rm -rf, or < /dev/null not recognised | 'wc' is not recognized as an internal or external command | You are in PowerShell or cmd, not Git Bash | Open Git Bash and re-run. Every shell block here is POSIX |
Step 6 reports LOADED BUT NO SKILL FIRED | (no error; the detector is working correctly) | Skill invocation is probabilistic, which is step 7's whole subject | Re-run the same command. If it never fires in three attempts, the injection is not landing - go back to step 4 |
claude plugin details prints no token table | (inventory prints, cost section missing) | An older Claude Code build without the cost projection | Check claude --version; the table needs a recent 2.1.x |
The detector covers most of these already. plugin_errors appears in the init event only when something is wrong, so it prints an error line when Claude Code reports a plugin problem and stays silent otherwise.
How Superpowers' hook, skills, and your detector connect
This is the system you now have. Compare it against the mental model near the top: that one showed how the injection reaches the model, while this shows the loop you built around it, where the transcript feeds back into a check you control.
flowchart TD
subgraph plugin["Superpowers 6.2.0 (user scope)"]
HOOK["SessionStart hook<br/>3,484 bytes injected"]
SKILLS["14 skills<br/>~688 tok always-on"]
end
subgraph project["booklog/ project"]
SETTINGS[".claude/settings.local.json<br/>enabledPlugins toggle"]
DETECT["sp_detect.py"]
FIXTURE["fixtures/sample-stream.jsonl"]
CODE["booklog.py + tests/"]
end
CLI["claude -p --output-format stream-json"]
STREAM["run-fired.jsonl<br/>transcript"]
VERDICT{"verdict"}
PYTEST["pytest: 2 passed"]
HOOK --> CLI
SKILLS --> CLI
SETTINGS -->|"false disables the hook"| HOOK
CLI --> STREAM
STREAM --> DETECT
FIXTURE -->|"offline test"| DETECT
DETECT --> VERDICT
VERDICT -->|"exit 0"| FIRED["SKILL FIRED"]
VERDICT -->|"exit 1"| NOTFIRED["LOADED BUT NO SKILL FIRED"]
CODE --> PYTEST
style HOOK fill:#4A90E2,color:#FFFFFF
style SKILLS fill:#4A90E2,color:#FFFFFF
style SETTINGS fill:#FFD93D,color:#2C2C2A
style DETECT fill:#7B68EE,color:#FFFFFF
style FIXTURE fill:#98D8C8,color:#2C2C2A
style CODE fill:#98D8C8,color:#2C2C2A
style CLI fill:#95A5A6,color:#FFFFFF
style STREAM fill:#95A5A6,color:#FFFFFF
style VERDICT fill:#9B59B6,color:#FFFFFF
style FIRED fill:#6BCF7F,color:#2C2C2A
style NOTFIRED fill:#FFA07A,color:#2C2C2A
style PYTEST fill:#6BCF7F,color:#2C2C2A
The yellow node is the one worth remembering. The settings toggle acts on the hook, because the hook is where the behaviour originates. skillOverrides was the obvious downstream lever and it did nothing, for the reason the diagram shows: the listing is not the channel the instruction arrives through.
The complete detector script and file tree
Final file tree:
booklog/├── .claude/│ └── settings.local.json # step 12, disables the plugin here├── docs/superpowers/specs/│ └── YYYY-MM-DD-booklog-design.md # step 8, paid├── fixtures/│ └── sample-stream.jsonl # 5-event excerpt, for offline detector testing├── tests/│ └── test_booklog.py # 2 tests, both passing├── booklog.py # the reading list CLI├── greet.py # step 7, paid├── sp_detect.py # the skill-firing detector├── run-fired.jsonl # step 6, paid (skill fired)├── run-skipped.jsonl # step 7, paid (skill did not)└── run-disabled.jsonl # step 12, paid (plugin off)booklog-plugin/ # step 11, paid - the plugin-driven comparison├── tests/test_booklog.py # your 1 starting test, plus whatever the skill added├── booklog.py└── run-tdd.jsonlEverything marked "paid" comes from a step that calls the API. Skip those and the rest still works.
Running the steps also leaves __pycache__/ and .pytest_cache/ behind, which are generated and safe to ignore.
booklog/booklog.py and booklog/tests/test_booklog.py are listed in full in step 10 and step 9. sp_detect.py and the fixture are in step 5, greet.py is in step 7, and booklog-plugin's two starting files are in step 11. The only thing not printed here is whatever the skill wrote on your machine in step 11, which is the one file that is supposed to differ from mine.
To confirm the whole thing from a fresh shell, starting one directory above booklog:
cd booklogpython -m pytest -qpython booklog.py --jsonpython sp_detect.py fixtures/sample-stream.jsonlThat should print 2 passed, then the JSON list, then a SKILL FIRED verdict.
Related Claude Code patterns to pair with Superpowers
Wire the detector into CI. sp_detect.py already exits 0 when a skill fired and 1 when none did, so if you run Claude Code headlessly in a pipeline and depend on a particular skill firing, pipe the transcript through it and fail the job on a non-zero exit. That turns "we installed the plugin" into an assertion your build can check. I have not done this myself yet. Headless Claude Code works through what else changes when nobody is watching the session.
Measure the on-invoke cost you actually pay. Step 3 gives estimates. Count Skill invocations per run, multiply by the per-component figures, and compare against total_cost_usd in the result event.
Replace one skill with your own. Superpowers ships writing-skills for exactly this. If brainstorming is too heavy for your repository, the advisory-versus-enforcing trade-off is worth reading before you start, because a lighter project skill inherits the same weakness you saw in step 7: it can be skipped. Then write one that runs a shorter design pass, and use step 12 to disable the plugin wherever yours should win.
References
- Vincent, J. obra/superpowers (v6.2.0, MIT licensed). https://github.com/obra/superpowers
- Vincent, J. Superpowers release notes (pinned to the v6.2.0 tag). https://github.com/obra/superpowers/blob/v6.2.0/RELEASE-NOTES.md
- Vincent, J. Cross-platform polyglot hooks for Claude Code (pinned to the v6.2.0 tag). https://github.com/obra/superpowers/blob/v6.2.0/docs/windows/polyglot-hooks.md
- Vincent, J. (2025, October 9). Superpowers: How I'm using coding agents in October 2025. https://blog.fsck.com/2025/10/09/superpowers/
- Vincent, J. (2026, June 15). Superpowers 6. https://blog.fsck.com/2026/06/15/Superpowers-6/
- Anthropic. Superpowers plugin listing. https://claude.com/plugins/superpowers
- Anthropic. Discover and install prebuilt plugins through marketplaces. Claude Code documentation. https://code.claude.com/docs/en/discover-plugins
- Anthropic. Plugins reference. Claude Code documentation. https://code.claude.com/docs/en/plugins-reference
- Anthropic. Extend Claude with skills. Claude Code documentation. https://code.claude.com/docs/en/skills
- Anthropic. Run Claude Code programmatically. Claude Code documentation. https://code.claude.com/docs/en/headless
- obra/superpowers issue #1751 (2026-06-13). SessionStart run-hook.cmd command fails under PowerShell without call operator. https://github.com/obra/superpowers/issues/1751
- obra/superpowers issue #1918 (2026-07-04). Windows: SessionStart:startup hook fails when plugin path contains a parenthesis. https://github.com/obra/superpowers/issues/1918
- obra/superpowers issue #1554 (2026-05-15). SessionStart hook error on Windows: node:internal/modules/cjs/loader:1423 (non-blocking). https://github.com/obra/superpowers/issues/1554
- obra/superpowers issue #2051 (2026-07-29). using-superpowers "check any skill before every action" does not hold once a workflow step is underway. https://github.com/obra/superpowers/issues/2051
- obra/superpowers issue #2079 (2026-08-03). brainstorming: no off-ramp for small changes. Closed by the reporter as covered by #2063. https://github.com/obra/superpowers/issues/2079
- obra/superpowers pull request #2063 (merged 2026-08-07). brainstorming three-path router (spike / bounded / architectural). Merged after the v6.2.0 tag; ships in v6.3.0. https://github.com/obra/superpowers/pull/2063
- Hacker News. Superpowers 6 discussion thread (196 points). https://news.ycombinator.com/item?id=48739459
- Abrams, M. (2026, March 23). The Superpowers Plugin for Claude Code. Builder.io. https://www.builder.io/blog/claude-code-superpowers-plugin
- Schwartz, E. (2026, April 2). A Rave Review of Superpowers (for Claude Code). https://emschwartz.me/a-rave-review-of-superpowers-for-claude-code/
Related Articles
- Agent Skills Are Not Prompts. They Are Production Knowledge Infrastructure.
- Which Claude Code Layer Solves Your Problem? A Diagnostic Guide for AI Engineers
- Skills vs Hooks in Claude Code: Enforceability Is the Design Variable


