From purlin
Initializes a Purlin project for spec-driven development: creates directory structure, detects test frameworks, and scaffolds proof plugins.
How this skill is triggered — by the user, by Claude, or both
Slash command
/purlin:initThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Set up a project for spec-driven development. Creates `.purlin/`, `specs/`, detects the test framework, and scaffolds the proof plugin.
Set up a project for spec-driven development. Creates .purlin/, specs/, detects the test framework, and scaffolds the proof plugin.
purlin:init Full setup (all steps)
purlin:init --force Re-run full setup
purlin:init --add-plugin <source> Add a proof plugin
purlin:init --list-plugins List proof plugins
purlin:init --sync-audit-criteria Sync external audit criteria
purlin:init --audit-llm Change audit LLM (default/external)
purlin:init --pre-push Change pre-push mode (warn/strict)
purlin:init --report Toggle HTML dashboard report (on/off)
purlin:init --digest Change digest mode (auto/warn/off)
purlin:init --mcp Migrate legacy .mcp.json (MCP server is plugin-bundled)
Each --flag runs ONLY that step, not the full init.
git rev-parse --git-dir. If it fails, the project is not a git repository. Print: "Purlin requires git. Run 'git init' first." Stop. Do NOT proceed without git — proofs, receipts, manual stamps, drift detection, and the pre-push hook all depend on git..purlin/ exists and --force is not set: "Project already initialized. Use --force to re-initialize." Stop..purlin/ exists and --force is set: proceed, preserve existing config.json..purlin/
config.json # from templates/config.json
plugins/ # proof plugin installed here
specs/
_anchors/ # anchor specs go here
Read the Purlin framework version from ${CLAUDE_PLUGIN_ROOT}/VERSION and write it as the version field in config.json. This ensures the config always matches the installed framework version.
Config template fields (from templates/config.json):
| Field | Default | Description |
|---|---|---|
version | "0.9.0" | Purlin framework version |
test_framework | "auto" | Detected test framework(s) |
spec_dir | "specs" | Directory containing specs |
pre_push | "warn" | Pre-push hook mode (warn or strict) |
report | true | HTML dashboard report generation |
digest | "auto" | Digest generation mode (auto, warn, or off) |
Print DETECTING CODEBASE before scanning. Framework detection scans multiple files across the project and can take noticeable time — the user must see that work is happening:
DETECTING CODEBASE
Scanning project files for test frameworks...
Read references/supported_frameworks.md for the complete framework list, detection heuristics, and plugin file mappings. That file is the single source of truth — do NOT hardcode framework names here. The complete list spans BOTH the Built-in Plugins table and the Additional Plugins (manual setup) table — present every framework from both. Check project files for ALL matching frameworks using the detection columns in that reference.
Always present the framework selection list to the user, even when auto-detection succeeds. Build the list dynamically from references/supported_frameworks.md so every shipped plugin (built-in and manual-setup) is offered. Pre-select detected frameworks with [x], show undetected as [ ]. Always include other as the last option for custom plugins. This lets the user confirm, add, or remove frameworks before scaffolding.
When one or more frameworks are detected:
DETECTING CODEBASE
Scanning project files for test frameworks...
Test frameworks (detected frameworks are pre-selected):
[x] <detected framework> — <detection reason>
[ ] <other framework>
...
[ ] other
Confirm selection, or change? [enter to confirm]
When no framework is detected, do NOT silently default to shell. Show the list with nothing pre-selected:
DETECTING CODEBASE
Scanning project files for test frameworks...
No test framework detected.
Test frameworks (select one or more):
[ ] <framework>
...
[ ] other
Which framework(s)? You can select multiple, e.g.: pytest, jest
If the user selects "other", suggest purlin:init --add-plugin to install a custom proof plugin.
Write selected frameworks to .purlin/config.json under test_framework. For multiple frameworks, use a comma-separated list: "pytest,jest".
Copy ALL selected proof plugins from scripts/proof/ to .purlin/plugins/. Use the plugin file column in references/supported_frameworks.md to map each framework to its source file. If multiple frameworks were selected, scaffold ALL of them.
For a framework listed under Additional Plugins (manual setup) (e.g. xUnit), purlin:init does not auto-wire it — after copying its plugin file, print the framework's setup steps from its section in references/formats/proofs_format.md and direct the user to complete the wiring manually.
For pytest, also create or update conftest.py at the project root:
pytest_plugins = [".purlin.plugins.pytest_purlin"]
For jest, add reporter config to jest.config.js or package.json:
{
"reporters": ["default", ".purlin/plugins/jest_purlin.js"]
}
For vitest, add the TypeScript reporter to vitest.config.ts (Vitest loads .ts reporters natively via Vite — no Jest config):
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: { reporters: ['default', '.purlin/plugins/vitest_purlin.ts'] },
});
Ensure .gitignore contains:
# Purlin runtime (not committed)
.purlin/runtime/
.purlin/plugins/__pycache__/
.purlin/cache/
# Dashboard HTML (symlinked from framework)
/purlin-report.html
Note: .purlin/report-data.js is NOT gitignored — it is the project digest and should be committed. If upgrading from a prior version, remove any existing .purlin/report-data.js entry from .gitignore.
The HTML dashboard is enabled by default. Ask the user:
HTML dashboard report:
[on] Generate purlin-report.html — open in browser for live coverage (default)
[off] Disable dashboard report generation
If on (default): set "report": true in .purlin/config.json. Create a symlink at the project root: purlin-report.html -> ${CLAUDE_PLUGIN_ROOT}/scripts/report/purlin-report.html. This ensures the dashboard always reflects the latest Purlin version without manual copies. Print: Dashboard: purlin-report.html (open in browser after running purlin:status)
If off: set "report": false in .purlin/config.json. Do not copy the HTML file.
When called via purlin:init --report, ONLY this step runs. Read the current config, show the current setting, and ask to toggle:
Dashboard report is currently: on
[on] Keep enabled
[off] Disable
After changing, update "report" in .purlin/config.json. If turning on, copy the HTML file to project root. If turning off, do NOT delete an existing HTML file (the user may want to keep it).
The Purlin MCP server (sync_status, purlin_config, and drift tools) is bundled with the plugin: .claude-plugin/plugin.json declares it under mcpServers with ${CLAUDE_PLUGIN_ROOT}, which Claude Code resolves to the installed plugin path on every launch. It registers automatically wherever the plugin is enabled and tracks plugin updates. Do NOT create a purlin entry in the project's .mcp.json — a project-scope entry takes precedence over the plugin-provided server and pins a versioned cache path that silently goes stale on the next plugin update.
Legacy migration (pre-0.9.4 projects): If .mcp.json exists at the project root, read it as JSON. If it has a purlin key under mcpServers:
purlin key. Preserve ALL other server entries unchanged.mcpServers is now empty and the file contains nothing else, delete .mcp.json. Otherwise write the file back without the purlin entry.Removed legacy purlin entry from .mcp.json — the MCP server is now provided by the plugin. Run /reload-plugins (or restart the session) to pick it up.If .mcp.json has no purlin entry (or doesn't exist), print: MCP server: bundled with plugin (sync_status, purlin_config, drift).
When called via purlin:init --mcp, ONLY this step runs — use it to migrate an existing project after updating the plugin.
Project initialized for Purlin.
Created:
.purlin/config.json
.purlin/plugins/<proof_plugin>
specs/
specs/_anchors/
purlin-report.html (if report enabled)
.git/hooks/pre-push (if installed)
Test framework: <detected>
Proof plugin: .purlin/plugins/<name>
Dashboard: on (open purlin-report.html in browser)
Digest: auto (regenerated on every commit)
Next steps:
purlin:spec <topic> — create your first spec
purlin:status — see rule coverage
Install the Purlin pre-push hook so git push checks proof coverage before code reaches the remote.
The hook has two modes, set in .purlin/config.json under "pre_push":
"warn" (default) — blocks on FAILING proofs, warns on PARTIAL and UNTESTED coverage"strict" — blocks on anything not VERIFIED (requires verification receipt)Ask the user which mode they want:
Pre-push hook mode:
[warn] Block on FAILING, allow PASSING and PARTIAL (default)
[strict] Block on anything not VERIFIED (requires verification receipt)
Write the chosen mode to .purlin/config.json as "pre_push": "warn" or "pre_push": "strict".
When called via purlin:init --pre-push, ONLY the mode selection above runs (no hook installation). The hook install steps below only run during the full init flow.
$CLAUDE_PLUGIN_ROOT or the framework scripts directory)..git/hooks/pre-push already exists:
purlin): skip, print Pre-push hook already installed.Existing pre-push hook found — skipping Purlin hook install. To add manually, see scripts/hooks/pre-push.sh# Preferred: symlink (stays in sync with framework updates)
ln -s "$PURLIN_SCRIPTS/scripts/hooks/pre-push.sh" .git/hooks/pre-push
chmod +x .git/hooks/pre-push
If the symlink target is not resolvable (e.g., consumer project without local framework checkout), copy the file instead:
cp "$PURLIN_SCRIPTS/scripts/hooks/pre-push.sh" .git/hooks/pre-push
chmod +x .git/hooks/pre-push
Installed git pre-push hook (proof coverage check).Install the Purlin pre-commit hook so git commit automatically regenerates the project digest (coverage + drift data in .purlin/report-data.js). The digest is committed to the repo so non-engineer stakeholders (QA, PM, compliance) can access project status without running Purlin tools.
The digest has three modes, set in .purlin/config.json under "digest":
"auto" (default) — regenerate digest before every commit, auto-stage the file"warn" — warn if the digest is stale, don't regenerate or block"off" — disable the pre-commit hook entirelyAsk the user which mode they want:
Project digest (auto-generates coverage + drift data for stakeholders):
[auto] Regenerate on every commit — always up-to-date (default)
[warn] Warn if digest is stale, don't auto-regenerate
[off] Disable digest hook
NOTE: Digest generation runs coverage scan and drift only.
It NEVER triggers an audit — cached audit data is included.
Run purlin:audit separately when you want fresh audit scores.
Write the chosen mode to .purlin/config.json as "digest": "auto" (or "warn" or "off").
When called via purlin:init --digest, run the mode selection above AND the hook installation steps below. Also remove .purlin/report-data.js from .gitignore if present. This makes --digest a complete setup command for existing projects — the user runs one command and gets the full digest feature.
.git/hooks/pre-commit already exists:
purlin): skip, print Pre-commit hook already installed.Existing pre-commit hook found — skipping Purlin hook install. To add manually, see scripts/hooks/pre-commit.sh# Preferred: symlink (stays in sync with framework updates)
ln -s "$PURLIN_SCRIPTS/scripts/hooks/pre-commit.sh" .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
If the symlink target is not resolvable, copy the file instead:
cp "$PURLIN_SCRIPTS/scripts/hooks/pre-commit.sh" .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
Installed git pre-commit hook (project digest).Ask the user which audit criteria to use:
Audit criteria:
[default] Use Purlin's built-in audit criteria only
[additional] Add team-specific criteria from a git-hosted file
(appended to built-in — does not replace defaults)
If default: no config change needed — purlin:audit loads built-in criteria via load_criteria().
If additional: ask for the git URL and file path (e.g., [email protected]:acme/quality-standards.git#audit_criteria.md). Set audit_criteria and audit_criteria_pinned in .purlin/config.json:
{
"audit_criteria": "[email protected]:acme/quality-standards.git#audit_criteria.md",
"audit_criteria_pinned": "<current remote HEAD sha>"
}
Clone the repo to a temp directory, read the file at HEAD, save to .purlin/cache/additional_criteria.md, record the commit SHA as audit_criteria_pinned, then clean up. The load_criteria() function in static_checks.py reads this cached file and appends it to the built-in criteria.
Ask the user which LLM should perform proof audits:
Audit LLM:
[default] Claude audits (same model — fastest, independent context)
[external] Use a different LLM for cross-model auditing (experimental)
If default: no config change. The auditor runs in an independent context.
If external: ask for the CLI command:
Enter the command to call your external LLM.
Use {prompt} where the audit prompt should go.
Examples:
gemini -m pro -p "{prompt}"
openai chat -m gpt-4o "{prompt}"
ollama run llama3 "{prompt}"
Command:
After the user enters the command:
{prompt} with "Respond with exactly: PURLIN_AUDIT_OK" and run the command.PURLIN_AUDIT_OK..purlin/config.json:
{
"audit_llm": "gemini -m pro -p \"{prompt}\"",
"audit_llm_name": "Gemini Pro"
}
Print: Audit LLM configured: Gemini Pro ✓This step is also callable independently via purlin:init --audit-llm.
Commit per references/commit_conventions.md:
git commit -m "chore: initialize purlin project"
purlin:init --add-plugin <source>
Source can be:
./my_proof_plugin.py or /path/to/plugin.sh[email protected]:someone/purlin-go-proof.git or https://...Verify .purlin/plugins/ exists. If not, tell the user to run purlin:init first and stop.
If source is a local file path:
.purlin/plugins/Added proof plugin: .purlin/plugins/<filename>If source is a git URL:
git clone <url> /tmp/purlin-plugin-install*.py, *.js, *.sh, *.java in the repo root or a plugin/ directory).purlin/plugins/rm -rf /tmp/purlin-plugin-installAdded proof plugin: .purlin/plugins/<filename>Validate the plugin after copying:
| Language | Must contain |
|---|---|
Python (.py) | proofs and json |
JavaScript (.js) | proofs and JSON |
TypeScript (.ts) | proofs and JSON |
C header (.h) | purlin_proof function |
PHP (.php) | proofs and json_encode |
Shell (.sh) | purlin_proof function |
Java (.java) | proofs and Proof |
If validation fails, warn but still install:
⚠ This file doesn't look like a standard proof plugin.
It should read test markers and write .proofs-*.json files.
See references/formats/proofs_format.md for the schema.
Print next steps:
Plugin installed. To use it:
1. Add proof markers to your tests using the plugin's marker syntax
2. Run your tests — the plugin emits .proofs-*.json files
3. purlin:status shows coverage
purlin:init --list-plugins
List all files in .purlin/plugins/. For built-in plugins, look up the framework name from references/supported_frameworks.md (match the plugin filename to the "Plugin file" column). Label anything not in that reference as custom.
Installed proof plugins:
.purlin/plugins/pytest_purlin.py (Python/pytest)
.purlin/plugins/jest_purlin.js (JavaScript/Jest)
.purlin/plugins/my_go_plugin.py (custom)
If .purlin/plugins/ doesn't exist or is empty: No proof plugins installed. Run purlin:init to set up.
purlin:init --sync-audit-criteria
Syncs the additional team criteria file to the latest version.
Read .purlin/config.json. If audit_criteria is not set: "No external audit criteria configured. Using built-in defaults." Stop.
Parse the git URL and file path from audit_criteria (format: git@host:org/repo.git#path/to/file.md).
Clone the repo to a temp directory: git clone <url> /tmp/purlin-audit-criteria-sync
Get the current remote HEAD SHA: git rev-parse HEAD
Compare to audit_criteria_pinned in config:
"Audit criteria up to date." Clean up and stop..purlin/cache/additional_criteria.md, update audit_criteria_pinned in config to the new SHA, print "Audit criteria updated: <old SHA> → <new SHA>"Clean up: rm -rf /tmp/purlin-audit-criteria-sync
Provides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub rlabarca/purlin --plugin purlin