How I Used agent-skills for Spec-Driven Development

This site is built out of Labs about PayPal data and Claude's API. This one is about the process that built the other five — including the part where I skipped a step and got caught.

Every entry in this project's BUILD_LOG.md was written to become a blog post eventually. This is that post. It covers adopting addyosmani/agent-skills — a Claude Code skill pack defining a lifecycle of /spec → /plan → /build → /test → /review → /ship — and running this entire portfolio site through it. Some of it went cleanly. One part didn't, and the fix for that is arguably the most useful thing here.

Two Installs, Not One

The methodology has two separable halves, and it's easy to install only one by accident. vercel-labs/skills is an agent-agnostic installer CLI — the package manager. addyosmani/agent-skills is the content it installs. Running the CLI alone gets you the 24 auto-activating skills, but not the slash commands:

# Skills CLI — installs the 24 auto-activating skills into
# .agents/skills/ (canonical) and .claude/skills/ (symlinked)
npx skills add addyosmani/agent-skills

# Plugin — adds the /spec /plan /build /test /review /ship
# slash commands and 4 specialist review personas on top
/plugin marketplace add addyosmani/agent-skills
/plugin install agent-skills@addy-agent-skills
/reload-plugins

The first command detected Claude Code non-interactively and installed all 24 skills without prompting — canonical copies in .agents/skills/, symlinked into .claude/skills/, with a per-skill Snyk/Socket-style security check running automatically on install (all 24 came back Safe or Low/Med risk). It also wrote a lockfile tracking exactly what was installed and from where:

{
  "version": 1,
  "skills": {
    "spec-driven-development": {
      "source": "addyosmani/agent-skills",
      "sourceType": "github",
      "skillPath": "skills/spec-driven-development/SKILL.md",
      "computedHash": "2dee9aa940633b7b5e0f45be34839ab31c0de98d482e16772c5cd350f4b91b4b"
    }
  }
}

What the CLI install doesn't give you is /spec, /plan, /build, and the rest — those slash commands, plus four specialist review personas (code-reviewer, security-auditor, test-engineer, web-performance-auditor), only show up after the second command: installing the actual Claude Code plugin. Skip that step and you've got skills quietly auto-activating by context with no way to explicitly drive the lifecycle — which looks like it's working, right up until you go looking for a command that isn't there.

Day 0, Before Any Code

The pack ships an adoption guide with two paths: retrofitting an existing codebase, or a greenfield path for starting from nothing. This project was an empty repo, so: greenfield. The guide's version of that path starts with a step it calls Day 0 — write the project's CLAUDE.md rules file before writing a single line of application code, so every later phase has something to be constrained by. Stack, commands, project structure, testing strategy, and — the part that mattered most later — a Boundaries section split into Always / Ask first / Never.

That ordering felt slightly backwards at first — rules for code that doesn't exist yet — but it's the same reasoning as writing a test before the implementation: the constraint is more honest when nothing exists yet to rationalize around it.

The Skills, In the Order They Actually Fired

Installing 24 skills doesn't mean 24 skills got exercised evenly. Auto-activating skills fire when their context shows up, not on a schedule — so the honest way to describe them is in the order they actually appeared in BUILD_LOG.md, and what specifically was happening each time.

  1. spec-driven-development — first fired explicitly, invoked directly via /agent-skills:spec-driven-development right after the original plan file was discarded and restarted. The restart itself was in service of an explicit "always ask, never assume" instruction — worth a fresh set of clarifying questions rather than reusing partial answers from before. It wrote the site shell's spec first, and every spec after it.
  2. planning-and-task-breakdown — fired immediately after that first spec was committed, converting it into an actual build order:
    Wrote `docs/specs/2. site-shell-spec.md` (the six-area spec, converting the
    approved plan into the template: Objective, Tech Stack, Commands, Project
    Structure, Code Style, Testing Strategy, Boundaries, Success Criteria, Open
    Questions), then `tasks/plan.md` (component dependency order, risks,
    verification checkpoints) and `tasks/todo.md` (12 ordered, atomic tasks)
    per `planning-and-task-breakdown`. Committed together (`9b94a04`).
    Later labs, being one content page each rather than a multi- component shell, downsized this to a flat TaskCreate list instead of the two-file treatment — proportional to the work, not skipped.
  3. test-driven-development — fired for every piece with real logic behind it: the site shell's Sidebar/SidebarTree/Header and, later, CodeBlock, were all built test-first rather than test-after. The same habit is why a lint rule could later catch a bad useEffect+setStatemount pattern before it ever shipped — writing down what a piece of code must guarantee tends to surface that question before "does it look right on screen."
  4. source-driven-development — fired the moment framework specifics needed writing, and kept refiring anywhere trained knowledge could plausibly be stale. First trigger, scaffolding the app itself:
    This pulled **Next.js 16.2.10 / React 19.2.4** — newer than typical agent
    training data. The generated `AGENTS.md` explicitly warns conventions may
    differ and to check `node_modules/next/dist/docs/` before writing
    framework code.
    It refired adapting the second lab's Claude API content (checked against a separate claude-api skill before adapting — model ID and streaming pattern both confirmed still current), and again before the site-theme spec, verifying Tailwind v4's @custom-variant dark syntax and next/script's beforeInteractive placement rules against the actual installed package docs rather than a remembered API shape.
  5. code-review-and-quality — fired at the close of nearly every slice, named explicitly the first time on the site shell (below). It ran again after essentially every lab and every UI slice afterward — sometimes clean, sometimes not — and its verdict became the de facto gate before a branch merged.
  6. incremental-implementation — fired most visibly on the site-theme redesign, deliberately split into five slices (tokens, the manual toggle, CodeBlock theme-sync, sidebar accent, card badges), each independently linted, tested, and built before the next slice started, rather than landing all five changes on one branch at once.
  7. git-workflow-and-versioning — fired as a judgment call each time work started, not a blanket rule: real spec-first feature work got its own branch (a separate branch per lab, and again for the theme redesign), while a single, well-scoped bug fix went straight to main. Which one applied was decided explicitly per change.
  8. security-and-hardening — fired twice, in different forms: automatically, as a Snyk/Socket-style risk assessment run against all 24 skills the moment they were installed; and structurally, as a fixed axis on every code-review pass afterward, checking for secrets or unbounded user input in scope even when nothing else in the diff looked risky.
  9. documentation-and-adrs — fired continuously rather than at one point in the timeline. It's the discipline underneath a spec's Open Questions section getting revisited instead of just written once, and underneath BUILD_LOG.md itself — which is the only reason this page exists to write at all.

Not all 24 installed skills left a mark. ci-cd-and-automation stayed dormant — CLAUDE.mdlists CI setup explicitly under "Ask first," deferred rather than built. shipping-and-launch hasn't really fired either: Vercel deploy timing is still an open, undecided question as of this page being written. Installing a skill and actually exercising it are two different events, and it's worth being honest about which of the 24 actually did anything here.

Spec-First, Test-First, and a Bug That Wasn't Live Yet

The site shell — sidebar, header, tree navigation — got the full treatment: a formal spec, a task breakdown, then a test-first build in dependency order. A code review pass at the end of that slice (the code-review-and-quality skill, run explicitly) found one real issue in SidebarTree:

The auto-expand-if-active-descendant state was computed once via a
`useState` lazy initializer, so it never re-derived when the route changed
via client-side navigation. Since `Sidebar`/`SidebarTree` live in the root
layout and never remount between page navigations, a lab's parent branch
would stay collapsed if the active route changed by any means other than
clicking the tree itself — dormant today since `lib/labs.ts` is empty, but
a real bug waiting for the first lab.

At the time this was found, lib/labs.ts was still empty — there was no lab yet to expose the bug in a browser. It only existed as a bug because the review looked at the logic itself, not at whether anything currently on screen was broken. It got fixed before the first Lab was ever built, which means it's a bug that, as far as any user is concerned, never happened.

The Process Gap: Two Labs Built Without Specs

Labs one and two — the PayPal transaction sync writeup and the Claude tool-use writeup — got built through direct clarifying questions and a flat task list. No spec file. Nobody decided that on purpose; it just didn't happen, twice in a row, until it was asked about directly:

Asked directly: "Are you creating specs or skipping that skill?" Checked
`docs/specs/` and `tasks/`: the site shell had a proper spec
(`2. site-shell-spec.md`) plus `tasks/plan.md`/`todo.md`, but **both
labs were built via `AskUserQuestion` clarification + a flat task list,
with no spec file at all** — a real inconsistency with the methodology this
project committed to, not a deliberate policy.

The fix wasn't just writing the two missing specs after the fact (though that happened too, marked explicitly as backfilled rather than pretending they'd existed all along). The fix that actually mattered was making it structurally harder to repeat: a new, permanent line in CLAUDE.md.

Every Lab gets its own spec at `docs/specs/N. lab-<slug>-spec.md` (six-area
template, matching `2. site-shell-spec.md`) **before** implementation
starts — this is a standing rule, not a per-lab judgment call. The first
two labs (`paypal-transaction-sync`, `claude-tool-use`) were built without
one and got specs backfilled retroactively after the gap was pointed out;
don't repeat that gap.

That paragraph is still in this repo's CLAUDE.md right now, and it names the exact two labs that skipped it. Every lab built after that point — including this one — has a spec committed before any page code exists. The interesting part isn't that a mistake happened; it's that the fix for it was a rule change, not just a one-off correction. A corrected instance of a mistake fixes that instance. A rule fixes the class.

Naming a Risk Before It's a Bug

Spec-first labs after that point got noticeably more proactive about calling out risk in advance rather than discovering it during implementation. The clearest example: one lab's source article included a code snippet with real template-literal interpolations that would themselves need to sit inside this page's owntemplate-literal snippet — the exact kind of nested-escaping problem that's easy to get wrong silently. That risk got written into the spec before a single line of the page existed, then verified two independent ways once built: a clean TypeScript compile, and a byte-for-byte diff of the rendered output against the source article. Naming the risk ahead of time didn't make it disappear — it made sure it got handled on purpose instead of found by accident.

What Review Actually Catches

The five-axis code review (correctness, readability, architecture, security, performance) run at the end of most slices kept finding things that worked in the sense of not crashing, while being wrong in a way that only shows up under specific conditions. A theme redesign review found a WCAG contrast failure measuring roughly 3.6:1 against a 4.5:1 requirement — visually fine, technically a failure. The same review found this:

`ThemeToggle`'s `useSyncExternalStore` `subscribe` was a permanent no-op —
correct for avoiding a hydration mismatch, but it silently meant a visitor
who never touched the toggle would no longer see the page follow a live OS
scheme change (e.g. a scheduled dark-mode switch), unlike the old
pure-media-query approach.

Fixing that regression surfaced a second one behind it: the naive fix would have taken a visitor who was passively following their OS's light/dark schedule and silently converted that into a permanently pinned preference the moment the OS flipped. Neither bug was visible by clicking around — both needed someone (or something) reading the actual subscription logic and asking what happens when the OS changes out from under it.

What This Actually Bought Me

None of the individual pieces here are exotic — writing a spec before building, reviewing a diff before merging, splitting a risky change into slices instead of one big branch. What the methodology added wasn't new ideas so much as a structure that made skipping them visible. Two labs skipped the spec step, and that was noticeable enough to get asked about directly instead of quietly becoming the norm. That's the actual value: not that mistakes stopped happening, but that when one did, it was legible enough to catch, name, and turn into a rule that outlives the mistake itself.