architecture — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited architecture (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
This skill is the specialized, authoritative description of how Blue Spec is structured: repository layout, build and distribution, how commands and templates are organized, the core/adapter boundary, and what Blue Spec scaffolds into a target project. Consult it before changing source, before adding an agent, and whenever a decision depends on the shape of the codebase.
The product mission and workflow philosophy live in CLAUDE.md. This skill covers the _how_, not the _why_.
src/.tsc --noEmit, run separately since esbuild skips type checks.npm test), Bun (bun run test:bun), and Deno (deno task test:deno).Top-level directories. Their internal structure is defined in the sections below.
| Directory | Purpose |
|---|---|
src/ | TypeScript source. Everything authored by hand lives here. |
src/types/ | The single home for every type declaration. No type is declared elsewhere. |
lib/ | Compiled JavaScript build output. Generated, not edited. This is what ships. |
spec/ | The Blue Spec core: the commands, templates, and sub-skills that ship. |
spec/commands/ | The /bluespec.* command definitions an agent reads to run each phase. |
spec/templates/ | The files a command fills in (the security artifacts produced per phase). |
spec/skills/ | Non-invocable sub-skills: on-demand knowledge the detect and verify phases load. |
test/ | Poku test suites, run against Node, Bun, and Deno. |
const. Use a function only when the this context strictly requires it.default export.left/right, not a/b, and index, not i).node:path, node:fs).node:fs/promises).type or interface is declared anywhere else in the codebase.interface only when a class is meant to implement it.satisfies. A plain as cast is allowed, but only when neither of those fits.src/ into self-contained JavaScript in lib/, so the published package carries no runtime node_modules for the end user.lib/ holds the shipped JavaScript. It is generated, never edited by hand.package.json bin field maps the blue-spec command to a file in lib/ (with a node shebang), so npx blue-spec ... runs the bundle directly.npx needs no dependency install on the user's machine.A phase of the workflow is a pair: a command and one or more templates.
spec/commands/, as markdown (.md) files with frontmatter, the format Claude Code consumes natively.spec/templates/.A phase command points to its template. The command carries the reasoning, the template carries the shape of the result. Splitting them keeps the "how to think" reusable and the "what to produce" consistent.
A command that produces no artifact has no template, and the type layer encodes that: TemplateKey is CommandKey minus repair, the one command that writes no template of its own. specialize is the exception that is not a phase yet still pairs with a template (specialize-template.md): its artifact is a sub-skill file under .bluespec/skills/, not a memory/ phase artifact.
spec/skills/ holds the built-in sub-skills: agent-agnostic knowledge modules that load only on demand. They are not commands. The detect and verify phases consume them inline: a phase lists the catalog with the skills hook (node ./.bluespec/hooks/skills.mjs, which prints each sub-skill as name: tags), then reads and follows the matching .bluespec/skills/<name>.md directly. A user can import one into any prompt with @.bluespec/skills/<name>.md. Adding a built-in is one .md plus one row in the baked catalog (src/hooks/skills/catalog.ts), no new command. A user grows the same set at runtime with /bluespec.specialize, which writes a sub-skill into .bluespec/skills/ and its row into .bluespec/skills.json. The skills hook reads that file and merges it over the baked catalog, the user's entry winning a name collision.
The codebase splits into two layers, and the split is what lets new agents be added later without rewriting the workflow.
Adapters are data, not code. Each supported agent is a single entry in the agent registry under src/providers/, declaring its key, display name, command format, and target directory. A factory turns each entry into a provider, and a format dispatcher under src/transform/ renders the same core command into that agent's packaging. Adding an agent is adding a row, not writing a module. The registry is the single source of truth for which agents are supported.
The command formats are:
<dir>/bluespec.<phase>/SKILL.md): a directory per command with name / description / argument-hint / user-invocable frontmatter. Used by Claude Code, Codex CLI, Antigravity, Cursor, Devin, Kimi, Lingma, RovoDev, Trae, and Mistral Vibe. Skill directories use each agent's current, project-scoped location (for example .claude/skills, .codex/skills, .agent/skills, .cursor/skills)..github/prompts/bluespec.<phase>.prompt.md): GitHub Copilot's prompt-file format, invoked as /bluespec.<phase>.<dir>/bluespec.<phase>.md): a single markdown file whose name becomes the command. Used by Amazon Q Developer, Amp, Auggie, IBM Bob, Cline, CodeBuddy, Continue, CoStrict, Crush, Factory Droid, iFlow, Junie, Kilo Code, Kiro CLI, opencode, Pi, Qoder, Qwen, Roo, SHAI, and Windsurf..forge/commands/bluespec.<phase>.md): a markdown command that swaps $ARGUMENTS for Forge's {{parameters}} placeholder.<dir>/bluespec.<phase>.toml): description plus a multi-line prompt, with $ARGUMENTS rendered as {{args}}. Used by Gemini CLI and Tabnine CLI..goose/recipes/bluespec.<phase>.yaml): a YAML recipe with the required version / title / description / instructions / prompt fields, with $ARGUMENTS rendered as {{ args }}.A future agent is one more row, leaving the core untouched. Keep adapters thin and the core neutral. Any logic that an adapter would duplicate belongs in the core instead.
Running Blue Spec in a user's project creates two things:
hooks/ (the compiled helper scripts the agent runs, see The tracking map) and skills/ (the non-invocable sub-skills the detect and verify phases load on demand, both the built-ins copied at init and any the user adds with /bluespec.specialize). Init also seeds skills.json, the runtime catalog of user sub-skills (empty until specialize writes to it), a sibling of tracking.json. It is committed alongside the user's code, so the security work is versioned and reviewable like any other part of the project..claude/skills/, GitHub Copilot from .github/prompts/, and opencode from .opencode/commands/. All hold the /bluespec.* commands the user invokes.The split mirrors the repository's own core/adapter boundary: .bluespec/ is the agent-agnostic state, and the agent's own directory is where the adapter places what that specific agent reads.
Each detect finding is one tracked item. The later phases carry that same item forward: it is a single entry that plan, harden, and verify re-report by name as they act on it, never a separate entry per phase. Its identity is its name (the finding's name, written identically as the section title in detect.md, plan.md, harden.md, and verify.md), and the map keys on that name and nothing else. The prose artifacts carry only that shared title and never a path. The file paths are the one volatile thing, and they live only in the tracking map. A rename or a moved path is corrected in one place, and every memory stays valid. The tracking map is tracking only: no prose, no note, no separate id, no phase.
.bluespec/tracking.json, a sibling of manifest.json and memory/. It is internal state, committed but never hand-edited and never shown to the user.{ name: 'blue-spec', entries: [{ name, paths }] }, where name is the identity and paths is string[] (charter has no items). There is no cross-entry link: the conveyor lives in the prose (which artifact carries the item's section), not in the map. Types live in the types module under src/types/./bluespec.repair, never as part of a phase. Track records each finding a phase reports and re-reports an existing item by name as a later phase writes, following a renamed path and never removing. By convention detect is where a finding is born and plan, harden, and verify re-report it, but the hook keys on the name alone and does not enforce the order. Untrack drops an item from the map: verify calls it at closure to stand a ✅ Risk closed finding down, after removing its section from the prose artifacts. Repair realigns the whole map against the artifacts and the code, and is the only act that surfaces what is gone. Without tracking there is nothing to repair, so track is what populates the map along the normal flow.src/core/ holds only what the hooks share: the map's I/O (load, serialize, write), the matcher and fold that drive registration, the removal that drives stand-down, and the payload guards (an { entries: [...] } guard that requires name plus a paths string array, and a { names: [...] } guard for untrack), each failing closed. The matcher finds the single map entry whose name equals the observed name, then overwrites paths in place when they changed (moved) or leaves it (unchanged), and a name with no match is new. Every hook consumes the same flat entry shape the file has.unresolved (orphan, or renamed-candidate when its paths match a reported entry under a new name), never removing it itself, and rejects an empty payload rather than accepting it. Each exposes a pure engine the tests exercise directly, plus the payload-driven function the CLI entry runs.src/hooks/, invoked as a node CLI from the project root with their input as a positional argument. Each entry holds no logic of its own: it imports its hook's pure function and ends with one call to the shared runner under src/cli/, which fires only when the file is the one being executed: importing an entry runs nothing, while running it reads the argument, prints the result, and exits non-zero on error. Passing the input as a process argument keeps it inert, so a value with quotes or backticks can never inject into the command. The build compiles each entry on its own into a self-contained artifact. A hook that grows past one file becomes a folder with an index.ts entry, and the build resolves the folder name as the artifact name. The init step copies the hooks into .bluespec/hooks/, so the user never installs Blue Spec: init populates everything the workflow needs, the hooks included. Hooks are read from lib/ (compiled JS), not spec/, since they ship as runnable code./bluespec.verify calls the untrack hook, at closure (its Step 8), to stand a ✅ Risk closed finding down after it has removed the finding's section from the four prose artifacts. This is why verify alone writes across the other phases' artifacts. Only the /bluespec.repair command calls the repair hook, to realign the whole map when something diverged. The phases never repair the tracking themselves: when one notices the tracking is inconsistent (a tracked path that no longer exists), it runs /bluespec.repair and continues. There is no user-facing track or untrack command, both are internal plumbing./bluespec.repair command reads the user's source only to learn a renamed file's new path, never to author or judge security. No hook edits the .bluespec/memory/*.md prose artifacts or injects IDs or anchors into them. The artifacts stay pure prose, and the one call no hook makes alone (fixed vs renamed) is returned for the agent or user to decide, never guessed.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.