I built hugin-v0 because I needed a forcing function.


Not inspiration. I had plenty of that. I needed something that would hold Claude accountable the same way a senior engineer holds a team accountable—catch the lazy commit message before it lands, review every file touched at the end of a session without anyone asking, flag architectural decisions that should be documented but usually aren't.

So I built it. And building it taught me more about how Claude Code actually works than reading the docs ever did.

23 skills. 13 agents. 8 event hooks. 7 MCP server integrations. 4 output styles. This is what I learned making all of it.


The structure before the magic

A plugin is a directory. There's a plugin.json manifest at the root — it declares what the plugin provides. Below that, everything has a place:

/skills/        — reusable workflows, invoked by slash command or agent
/agents/        — specialized Claude personas with defined tools, models, memory
/hooks/         — event-driven scripts that fire at lifecycle moments
/output-styles/ — formats that change how Claude structures responses
/scripts/       — the runners, the logic, the automation glue

And settings.json, which does the most with the least:

{ "agent": "principal-engineer" }

One line. That activates the principal-engineer agent as the default for every main thread session. Claude doesn't start as a generalist. It starts as a principal engineer, with all the constraints and context that implies.

That single file is where the philosophy lives.


Hooks: automation in the seams

Hooks are the most underestimated primitive in the plugin system. They don't live in the prompt. They don't modify what Claude says. They fire around tool use and session events — in the seams — and that's exactly what makes them powerful.

Here's the actual hooks/hooks.json from hugin-v0:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/run-hook.cjs commitlint-enforcer.py"
          }
        ]
      },
      {
        "matcher": "Write|Edit|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/run-hook.cjs anti-pattern-guard.py"
          }
        ]
      }
    ],
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/run-hook.cjs adr-gate.py"
          }
        ]
      },
      {
        "hooks": [
          {
            "type": "command",
            "command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/run-hook.cjs task-context-injector.py"
          }
        ]
      },
      {
        "hooks": [
          {
            "type": "command",
            "command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/run-hook.cjs inject-adr-context.py"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/run-hook.cjs quality-gate-reminder.py"
          }
        ]
      },
      {
        "matcher": "Write|Edit|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PLUGIN_ROOT}/scripts/automatic-code-review-plugin.sh log"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PLUGIN_ROOT}/scripts/automatic-code-review-plugin.sh review"
          }
        ]
      }
    ]
  }
}

Walk through what this actually does.

PreToolUse on Bash intercepts every single bash command before it executes. commitlint-enforcer.py reads the command from stdin, checks whether it's a git commit call, and if so validates the message against Conventional Commits. Exit code 2 blocks the tool call entirely — the commit doesn't happen. Claude gets the reason back in context and should try again with a conformant message.

PreToolUse on Write|Edit|MultiEdit fires before Claude writes any file. anti-pattern-guard.py scans the incoming content for React 19 anti-patterns — the ones that look plausible but will fail at runtime. This isn't lint. Lint runs after. This runs before the write happens, which means Claude can correct it in the same pass.

UserPromptSubmit — no matcher on these. They fire on every single prompt, before Claude processes it. Three hooks chain here: adr-gate.py detects if your request involves an architectural decision that should be formally documented; task-context-injector.py reads your .tasks/ directory and injects active task context so Claude doesn't lose track; inject-adr-context.py pulls existing ADRs into the prompt so Claude operates with architectural memory baked in. You don't type any of this. It just happens.

PostToolUse on Write|Edit|MultiEdit fires after every file write. quality-gate-reminder.py surfaces a reminder to run typecheck and biome:check — because Claude will often write valid code, close the loop, and consider the work done. The reminder is a forcing function. automatic-code-review-plugin.sh log does something different: it collects the filenames of everything modified this session.

Stop — fires when Claude finishes responding and would otherwise hand control back to you. automatic-code-review-plugin.sh review takes that list of modified files and runs a semantic code review against every one of them before the session closes. Exit code 2 from a Stop hook blocks Claude from stopping. It feeds the reason back into context.

The result: you don't close a session anymore without a human-readable review of your changes. No extra prompt. No remembering to ask. The session ends with a review, or it doesn't end.


Agents: the right model for the right job

The agents/ directory has 13 files. Each one is a Markdown file with YAML frontmatter that defines its complete character. Here's principal-engineer.md:

---
name: principal-engineer
description: Apply rigorous first-principles engineering analysis to any technical task...
model: opus
permissionMode: default
color: orange
tools:
  - Read
  - Grep
  - Glob
  - Bash
skills:
  - nodejs
  - react
memory: user
---

And automatic-code-reviewer.md:

---
name: automatic-code-reviewer
model: haiku
tools:
  - Read
  - Grep
  - Glob
disallowedTools:
  - Write
  - Edit
skills:
  - react
  - react-best-practices
  - nodejs
memory: project
color: purple
---

The frontmatter fields aren't cosmetic. Every one of them changes behavior.

model determines what Claude model runs that agent. Opus for the principal engineer — this is expensive on purpose. Deep first-principles analysis, architectural trade-off reasoning, the kind of work where you want the full model. Haiku for the automatic code reviewer — it's reading files and applying rules, not synthesizing novel analysis. Fast, cheap, and it doesn't need to be Opus to do the job.

tools vs disallowedTools — principal-engineer has an explicit allowlist: Read, Grep, Glob, Bash. It can read anything, search anything, run commands. The code reviewer has disallowedTools: [Write, Edit]. It can never write a file. This isn't advisory. It's enforced at the tool level. The reviewer can tell you what to change. It cannot make the change. That distinction matters for trust.

memory — principal-engineer uses memory: user. Its memory is stored in ~/.claude/agent-memory/principal-engineer/ and persists across every project. Everything it learns about your preferences, your architecture decisions, your patterns — it carries across repos. The code reviewer uses memory: project. Its knowledge lives in .claude/agent-memory/automatic-code-reviewer/ and can be committed to version control. Other engineers on the team get the same calibrated reviewer.

skills — when you list skills in an agent's frontmatter, that full skill content is injected at agent startup. Not made available on demand — loaded immediately, every session. The code reviewer starts every session with react, react-best-practices, and nodejs skill content in context.

The design philosophy underneath this: agents should be specialized, not general. A code reviewer that can't write files operates with different confidence than one that can. The constraints are part of the definition.

Hugin-v0 splits the 13 agents across three tiers. Opus handles deep analysis — principal-engineer, code-architecture-reviewer, plan-reviewer. Sonnet handles implementation work — auto-error-resolver, documentation-architect, refactor-planner, five others. Haiku handles fast scanning work — the code reviewer, task-check, web-research-specialist.

The tiers are a cost and latency decision, not just an aesthetic one. Running Opus for every automatic code review would be absurd. Running Haiku for a first-principles architectural analysis would produce superficial output. The tiering is intentional.


Skills: context with a call signature

Skills are the most visible part of a plugin — they're what you invoke, what create tangibly different outputs. But the piece that makes them more than just stored prompts is $ARGUMENTS.

Here's the frontmatter for figma-implement-design:

---
name: figma-implement-design
description: Translate Figma nodes into production-ready code...
argument-hint: 'Figma URL (https://figma.com/design/:fileKey/:fileName?node-id=1-2)'
user-invocable: true
context: fork
allowed-tools: figma__get_design_context, figma__get_screenshot, figma__get_assets, figma__get_variables
---

You call it like this:

/figma-implement-design https://figma.com/design/abc123/MyDesign?node-id=42-99

Inside the skill, $ARGUMENTS[0] resolves to that URL. The skill parses the file key and node ID, calls figma__get_design_context to pull the full component tree, fetches a screenshot, downloads assets, and translates the design into production code — matched against your project's conventions, not generic assumptions.

The context: fork field is what makes this work safely. It runs in a subagent, isolated from the main thread. The main session doesn't get polluted with the Figma MCP tool results. The subagent does the work and surfaces results. And allowed-tools restricts the fork to only the Figma MCP tools — it can't touch your filesystem during the design-read phase.

Compare that to create-tasks:

---
name: create-tasks
argument-hint: '<task-description-or-requirements>'
user-invocable: true
context: fork
---
/create-tasks Build a responsive product card component with dark mode support

The skill applies INVEST criteria — tasks should be Independent, Negotiable, Valuable, Estimable, Small, Testable. It uses Example Mapping to derive Given-When-Then acceptance criteria. It enforces vertical slices — never "add the database layer," always "user can save a preference and see it persist." Max one day per task. Output lands in .tasks/backlog/NNNN-short-title.md.

The argument-hint in the frontmatter is what surfaces in the Claude Code autocomplete when you start typing /create-tasks. That's not documentation. That's UX. You're defining the interface.


Output styles: change the shape of reasoning

Four output styles. Switch them with /output-style <name>.

architecture-review — dependency mapping, component diagrams, systemic analysis. Use it when you need to reason about how something fits into the whole.

code-review — inline findings with severity, diff-block fix suggestions. Use it when you want line-level precision.

refactoring — before/after comparisons with risk-tagged migration steps. The risk tags matter. A refactor that can be paused halfway is different from one that can't.

documentation — API tables, progressive code examples. Useful for building understanding, not just capturing it.

These aren't formatting templates. They shift the kind of analysis Claude applies. The same question asked under architecture-review produces different output than the same question asked under code-review — not just in form, but in what Claude focuses on.


What this actually changed for me

Before hugin-v0, I was prompting. Describing what I wanted, managing context manually, asking Claude to remember things it wouldn't remember, correcting lazy commit messages after the fact, catching anti-patterns in review instead of before the write.

After: I'm routing.

The question shifted from "what do I need Claude to do right now" to "which primitive handles this category of work." An architectural question goes to principal-engineer. A quick scan of modified files goes to automatic-code-reviewer. A design implementation goes through figma-implement-design in a forked subagent. A new feature description becomes INVEST-compliant tasks via create-tasks.

The hooks handle the ambient stuff. I don't ask for commit message enforcement. I don't ask for end-of-session reviews. They happen because I configured them to happen, once, in hooks.json.

The compound effect is real. The Stop hook means every session ends with a review. The UserPromptSubmit hooks mean every prompt carries architectural context. The PreToolUse anti-pattern guard means React issues get caught before the write finishes. Stack those — and what changes is the floor of what Claude produces. The ceiling was always high. Raising the floor is harder and more valuable.


What this means for designers and product owners

This is where I think the implications go furthest, and where the tools are least understood.

A designer with hugin-v0 installed and a Figma API key can:

  1. Open a Figma file, copy the node URL
  2. Run /figma-implement-design [url]
  3. Get a production-ready React component — accessible, matched to project tokens, tested

No engineering handoff. No translation loss. No "that's not quite what I meant" in the PR review. The design intent goes directly to code through a tool that can read the actual source of truth — the Figma file — not a screenshot or a Slack message.

Then: /create-tasks Build a dark mode toggle for the header

You get INVEST-compliant tasks with Given-When-Then acceptance criteria, saved to .tasks/backlog/. A product owner with no engineering background can produce a task list that a developer can pick up and implement without clarification.

Then the principal-engineer agent reviews the plan before a line gets written.

Then the automatic-code-reviewer audits every file at the end of the session.

That's a loop a designer or PM can own. Not because AI did everything for them — because the expertise required to do the hard parts was encoded into the system they're working in. The Figma-to-code translation isn't magic — it's a seven-step workflow encoded into a skill, using the right MCP tools in sequence, with ultrathink applied at the translation step because that's where the reasoning has to be precise.

What building this made clear: the value isn't the model. The model is capable. The value is the framework around the model — the skills that encode best practices, the agents that apply the right constraints, the hooks that enforce consistency without anyone having to remember to ask.

Teams have always needed senior engineers not just for technical ability, but for judgment — the accumulated knowledge of what's right and why. Plugins are one attempt at making that judgment distributable. Not perfectly. Not completely. But enough to shift what a small team can own.


What I'd do differently

The hooks are powerful and fragile at the same time. The run-hook.cjs pattern — Node.js wrapper calling Python scripts — solves the Windows/Mac platform problem cleanly, but debugging failures in hook scripts is less visible than I'd like. Hook stderr goes somewhere. It's not always obvious where. I'd build better observability into the scripts before adding more of them.

The agent tier strategy is right, but the boundaries between Sonnet and Opus need more refinement. I have agents that probably warrant Opus for their full capability but run on Sonnet for cost — and you feel that difference in output quality on complex tasks.

The skill library is where I'd invest the most next. 23 skills sounds like a lot. It's not. There's a whole layer of project-lifecycle automation — from requirements to deployment verification — that I've only partially built out. The create-tasks skill handles one slice of that. There should be ten more.

The underlying insight I keep coming back to: the bottleneck in most teams isn't capability. It's consistency. Doing the right thing every time, not just when someone remembers to ask. Hooks, agents, skills — built well, they're consistency infrastructure. That's what I was really building.