nbp-forge
Health Warn
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 6 GitHub stars
Code Fail
- execSync — Synchronous shell command execution in src/hooks.mjs
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
Compose agent skills (SKILL.md / commands) from reusable bricks, with a drift-gate.
nbp-forge 🧱
Stop maintaining the same agent instructions in ten places. Write a step once, reuse it everywhere, and let a gate guarantee nothing ever drifts.
nbp-forge composes your AI-agent skills (SKILL.md / slash-command files) from reusable bricks. You edit the brick — every skill that uses it is current on the next build. A drift-gate makes it a guarantee, not a hope.
Built for and battle-tested in a real codebase where a single brick is reused by up to 7 skills. That's one edit instead of seven — and a copy-paste step that lived in 7 files collapses to one source of truth (~85% fewer copies). The bigger your skill library, the more it compounds.
The problem
Agent "skills" (the playbooks your agent follows) accumulate the same steps copy-pasted everywhere: set up a run folder, a closing checklist, a result contract. Fix one copy, forget the other six — they diverge silently, and nobody knows which is right. Reviewing means hunting every copy.
The fix
A skill becomes a recipe that points to bricks instead of copying them. A deterministic build assembles the final, self-contained file your agent reads. A gate blocks any output that drifts from its recipe.
forge/
├─ bricks/ reusable pieces (shared building blocks)
└─ recipes/ one per skill: local content + <!-- include: <brick> -->
↓ forge build
<out>/<skill>.md ← generated (carries a "GENERATED" banner); this is what the agent reads
- Portable. Output is a standard self-contained file — works with Claude Code, Codex, Cursor, etc. (no proprietary pointer syntax leaks into it).
- Deterministic. Same recipes + bricks → same output, every time.
- Drift-proof.
forge checkfails if any generated file was hand-edited or diverges from its recipe.
The golden rule
Variation between skills is a parameter the recipe passes — never a modified copy of the brick.
<!-- include: run-dir | skill=fix; flags=--prefix fix --track --> → {{skill}} / {{flags}} in the brick
Two skills, the same brick, different parameters. One source of truth.
See it in 60 seconds
One "set up a run folder" step, shared by two skills — written once, built into both, and the
drift-gate catching a hand-edit before it reaches anyone.
# 1) one brick, reused by both skills — bricks/run-dir.md
# "Create runs/{{skill}}/ and write progress there as you go."
# 2) two recipes include it with different params
# recipes/fix.md → <!-- include: run-dir | skill=fix -->
# recipes/feature.md → <!-- include: run-dir | skill=feature -->
$ npx nbp-forge build
✔ build: 2 file(s) generated. # both skills now carry the same step, parameterized
# 3) someone hand-edits a GENERATED file…
$ echo "rogue tweak" >> .claude/commands/fix.md
$ npx nbp-forge check
✗ check failed (1 drift, 0 orphans).
• drift: .claude/commands/fix.md is out of sync with its recipe # ← your CI fails right here
Fix it the right way — edit the brick, run build, and both skills update at once. That's
the whole idea: one source of truth, and a gate that makes it stick.
Try the runnable version now:
npx nbp-forge build --root examplesthennpx nbp-forge check --root examples.
Quick start
Installed from npm (zero runtime dependencies):
npx nbp-forge init # scaffold forge.config.json + dirs + a sample skill
npx nbp-forge build --root . # generate skills from recipes + bricks
npx nbp-forge check --root . # drift-gate: exit 1 if any output diverged from its recipe (CI, pre-commit)
npx nbp-forge help # all commands; `help <command>` for one
# or install once: npm i -g nbp-forge → nbp-forge build / nbp-forge check
From a clone of this repo, the CLI is node bin/cli.mjs <cmd>. A complete runnable project lives in examples/ — try npx nbp-forge build --root examples.
CLI-only. nbp-forge is a command-line tool, not a library: there is no public programmatic API and no TypeScript types are shipped. Drive it with the
forgecommands above (or yourpackage.jsonscripts / CI), not viaimport.
Pre-commit hook (optional)
Install a hook that runs the drift-gate and a basic secret scan before every commit:
npx nbp-forge install-hooks # in a project that depends on nbp-forge
npm run hooks:install # equivalent, from a clone of this repo
It's a thin shim that delegates to the versioned scripts/hooks/pre-commit bundled with the package — so the logic is reviewed in git and never drifts from what runs, whether you're in a clone or using nbp-forge as a dependency. The hook blocks a commit that (a) stages an env file (.env, .env.local, *.env — templates like *.example are allowed), (b) adds a token-shaped string (ghp_…, sk-…, npm_…, AWS keys, private-key headers), or (c) leaves a generated file out of sync with its recipe. The drift-gate runs forge:check if your package.json defines it, else npx nbp-forge check when the repo root is a forge project. Respects core.hooksPath. Bypass (discouraged) with git commit --no-verify.
More examples to copy into your own repo: a CI workflow that runs the drift-gate (examples/.github/workflows/forge-check.yml) and an optional Claude Code hook that stops the agent from hand-editing a generated skill (examples/claude-code/).
Full lifecycle (safe by default)
Skills are generated, so you never hand-edit the output. Manage them through the forge:
| Command | What it does |
|---|---|
forge init |
scaffold forge.config.json + dirs + a sample skill (idempotent; never overwrites) |
forge list |
show each skill → the bricks it uses, and per-brick ref-count (blast radius) |
forge new <skill> |
scaffold a new recipe |
forge import <file> |
onboard an existing SKILL.md/command as a recipe (verbatim; strips a prior GENERATED banner). --name overrides; --force overwrites. Run forge build after. |
forge rename <old> <new> |
rename a skill (regenerates, removes the stale output) |
forge remove <skill> |
soft-delete the recipe + the bricks only that skill owns; shared bricks stay (you're told which and why) |
forge restore <skill> |
bring a removed skill (and its bricks) back |
forge gc [--apply] |
find/archive orphan bricks (used by nobody) |
Removing a skill never deletes a shared brick. Ownership is decided by reference count: a brick used by exactly one skill is owned by it; a brick used by several belongs to none and is never touched. Removed items are soft-deleted to _archive/ (versioned, so forge restore — or plain git — gets them back). Set "deletePolicy": "hard" if you prefer permanent deletes.
Config — forge.config.json
{
"bricks": ".claude/forge/bricks",
"recipes": ".claude/forge/recipes",
"out": ".claude/commands",
"archive": ".claude/forge/_archive",
"deletePolicy": "soft",
"enforceGenerated": false,
"conformance": true
}
deletePolicy—soft(move to_archive/, recoverable) orhard(delete).enforceGenerated— whentrue,checkrequires every output file to have a recipe, forbidding hand-made/edited skills (forge-only guarantee).conformance— whentrue(default),build/checkalso validate a recipe's frontmatter against the agentskills SKILL.md standard (namelowercase-with-hyphens ≤64 chars; non-emptydescription≤1024) so a non-standard skill fails here, not when the agent platform rejects it. Only validates fields that are present; setfalseto disable.
Why this exists
The open agentskills standard defines the portable SKILL.md format — but has no composition/includes. Linters validate the spec, not content drift between a fragment and the skill. Prompt-templating tools (Jinja, LangChain) compose prompts, not skills. nbp-forge fills the gap: deterministic composition + a drift-gate on top of the standard. See SETUP.md for the #1 pitfall (tooling that tells you to edit the generated file) and SECURITY.md for the shared-brick blast radius.
License
MIT © Nikolas Padilha
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found