* docs(specs): pre-commit mechanical gates umbrella design (D30-D39, closes-issue-context #206/#207/#208) Umbrella spec for the pre-commit framework rollout plus catalog-tool vetting (33 approved, 2 rejected, 1 needs triage) and a new sibling actions/ package for curated GitHub Actions. - D30: pre-commit config plugin-internal (preserves D15) - D31: umbrella + 7 sub-issues, each one PR - D32: forward-looking language coverage - D33: all 25+ candidates formally vetted in spec - D34: actions/ as sibling package to plugins/ - D35: pilot = trivy + reviewdog - D36: pre-commit.yml + retain shellcheck.yml - D37: fail_fast locally, stages:[manual] for slow hooks - D38: D15 reinforced - D39: spec stays thin; ADRs per slice Co-Authored-By: Claude <[email protected]> * docs(plans): A0 pre-commit core rollout implementation plan (12 tasks) A0 closes #206/#207/#208 in one PR. Covers: plugin-internal config with 6 repos (hygiene + ruff + biome + shellcheck-py + gitleaks + doublify), pre-commit/action@v3 CI workflow, CONTRIBUTING/README/ CHANGELOG updates, and the GitHub umbrella + sub-issue scaffolding for A1-A6 follow-up slices. Co-Authored-By: Claude <[email protected]> * fix(hooks): restore +x bit on install_git_hooks.sh Pre-existing test failure: test_install_sh_exists_and_executable expects user-executable bit. The file was checked in as 0644 (no execute bits). Restoring 0755 so the test passes. Discovered during A0 baseline check. Co-Authored-By: Claude <[email protected]> * feat(hooks): A0 pre-commit core config (hygiene + ruff + biome + shellcheck + gitleaks) Expands plugins/hooks/.pre-commit-config.yaml per spec D30 with: - pre-commit-hooks v6.0.0 (hygiene: trailing whitespace, EOF, merge, large files, YAML/TOML/JSON syntax) - ruff-pre-commit v0.6.9 (lint + format) - local heretek-fast-gate (already enforced by Layer 1) - biomejs/pre-commit v0.6.1 (JS/TS format) - shellcheck-py v0.10.0.1 (shell lint) - gitleaks v8.30.1 (secrets) - fail_fast: true at root per D37 - minimum_pre_commit_version: 3.5.0 - All remote repos SHA-pinned per D20 spirit Drops doublify/pre-commit-rust from A0 (unmaintained since 2020; fails D7 last_commit). Rust will be re-vetted in slice A3 alongside cargo-machete and fallow. Includes tests/test_precommit_config.py asserting config invariants (exists, valid YAML, fail_fast, SHA-pinned, allowlist, validate-config). Refs #206, #207, #208. Co-Authored-By: Claude <[email protected]> * chore(pyproject): add ruff config aligned with repo style Ignores E741 (single-letter variable name) which is common shorthand in this repo (ledger, loop, list). Per-file-ignores for: - tests/fixtures/** (intentional bad code that scanners must detect) - scripts/scanners/** (sys.path manipulation pattern requires late imports) Aligns ruff with the repo's existing conventions so the A0 pre-commit hook doesn't trip on cosmetic style issues. Auto-fixable findings still get auto-fixed; this just exempts the genuinely-ambiguous patterns. Refs #206, #207, #208. Co-Authored-By: Claude <[email protected]> * fix(ci): quote pip install --only-binary :all: to satisfy check-yaml PyYAML's safe_load treats ':all:' as a mapping-key indicator when it appears in an unquoted scalar value. Quote the affected values so check-yaml passes. Also normalises --only-binary=:all: to --only-binary :all: (space form also accepted by pip and avoids the colon-as-key confusion entirely in future maintainer edits). Refs #206, #207, #208. Co-Authored-By: Claude <[email protected]> * fix: noqa annotations for intentional ruff findings (A0 cleanup) Three files had ruff findings that are intentional code patterns: - scripts/lookup_gate.py:23 — late import for sys.path manipulation - tests/test_plugin_components.py:46 — unused 'item_kinds' documents the supported kind vocabulary for plugin components - tests/test_scanner_mcp.py:29 — '_digest' computed but only used indirectly by the surrounding block - tests/test_scanner_mcp.py:141 — late import for test fixture setup Refs #206, #207, #208. Co-Authored-By: Claude <[email protected]> * fix: auto-applied pre-commit fixes across 205 files (A0 cleanup) Mechanical sweep applied by: - end-of-file-fixer: 100+ files gained trailing newlines - ruff-format: 100+ Python files reformatted to project line length - biome-check: 50+ JSON files reformatted with tabs + compact arrays - shellcheck: passed on all `.sh` files (no edits needed) All fixes are purely cosmetic / whitespace. No semantic logic changed. Verified by passing full pytest (418 passed) + pre-commit run --all-files (14/14 hooks pass). Refs #206, #207, #208. Co-Authored-By: Claude <[email protected]> * test(hooks): assert install_git_hooks.sh has a real pre-commit config (A0) Regression guard: if the config ever goes missing again, this test fails loudly instead of letting install_git_hooks.sh silently install without a config. Co-Authored-By: Claude <[email protected]> * ci: add pre-commit workflow (closes #207) D36: enforce plugins/hooks/.pre-commit-config.yaml on PR + push-to-main + weekly Mon 06:00 UTC. D20: every action SHA-pinned. Read-only permissions. pre-commit/action@v3 has built-in caching. Action pins match existing workflow SHAs (validate.yml, shellcheck.yml): - actions/checkout@3d3c42e5... # v4.2.2 - actions/setup-python@5fda3b95... # v5.3.0 - pre-commit/action@2c7b3805... # v3.0.1 Co-Authored-By: Claude <[email protected]> * docs(contributing): add pre-commit framework section (closes #208) CONTRIBUTING.md gains a Pre-commit framework section between Tests and ShellCheck, documenting the plugin-internal config location, install commands, the --no-verify caveat, and the bundled /hooks:install-git-hooks path. plugins/hooks/README.md Layer 3 line expanded with the A0 tool list (hygiene, ruff, biome, shellcheck, gitleaks, fast gate) and the fail_fast root setting. CHANGELOG.md Unreleased section notes the A0 rollout referencing the umbrella spec. Co-Authored-By: Claude <[email protected]> * fix: regenerate marketplace.json + apply post-commit pre-commit fixes (A0 cleanup) Pre-commit hooks reformatted a few files after the previous commits (mostly the new pre-commit + workflow tests, plus marketplace.json got the biome formatting on its second pass). Regenerated marketplace.json from catalog.yaml. Co-Authored-By: Claude <[email protected]> * chore: regenerate marketplace.json + accept biome fixture reformat (A0 cleanup) biome-check reformatted three test fixtures (whitespace-only changes). marketplace.json regenerated from catalog.yaml. Co-Authored-By: Claude <[email protected]> * fix(hooks): exclude tests/fixtures/ from auto-fix hooks (A0) Auto-fix loops between ruff-format and biome-check were reformatting the same fast_gate fixtures back and forth on every pre-commit run. The fixtures are scanner test inputs that must stay byte-identical across runs — they're what's being SCANNED, not source code to format. Excludes: - ruff --fix and ruff-format: ^tests/fixtures/ - biome-check: ^tests/fixtures/ Closes the auto-fix loop. Verified: pre-commit run --all-files now passes cleanly in a single invocation with no further modifications. Co-Authored-By: Claude <[email protected]> * fix(hooks): scope auto-fix hooks away from test fixtures (A0) ruff and biome auto-fix hooks were reformatting the fast_gate test fixtures (bad_sample.*, good_sample.*) on every run, causing an auto-fix loop. These fixtures are inputs to scanner tests — they must stay byte-identical across runs. Scoped the hooks via files: + exclude: filters: - ruff --fix and ruff-format: file filter matches plugins/scripts/ test source files; excludes tests/fixtures/ - biome-check: file filter matches JSON/YAML/TS/JS/CSS; excludes tests/fixtures/ Pre-commit now passes cleanly and idempotently in a single invocation. Also restored the test fixtures that got mangled by earlier runs to their HEAD state via git checkout. Co-Authored-By: Claude <[email protected]> * fix(hooks): also exclude generated marketplace.json from biome-check The auto-fix loop was also reformatting .claude-plugin/marketplace.json on every pre-commit run. marketplace.json is generated by scripts/generate_marketplace.py, not hand-written — it doesn't need biome formatting. Restoring the original generator output is part of the commit discipline, not biome's job. exclude regex now also matches ^.claude-plugin/marketplace.json$. Verified idempotent: pre-commit run --all-files passes cleanly in a single invocation with no further modifications. Co-Authored-By: Claude <[email protected]> * fix(tests): address PR #217 review (remove unused vars, consolidate imports) Two GitHub code-quality bot comments flagged F841 (unused variable): - tests/test_plugin_components.py:47 - `_item_kinds` was assigned but never used. Removed the line entirely (it only documented an intent; nothing asserted on it). - tests/test_scanner_mcp.py:28 - `_digest` was assigned but never used. Removed the line entirely. Also consolidated the late import at the bottom of tests/test_scanner_mcp.py into the top import block (it was a duplicate 'scripts.scanners.mcp' import with different symbols, originally flagged E402 with a `# noqa` that ruff reported as unused). Merging preserves the symbols without needing the noqa. Pre-commit run --all-files: 14/14 hooks pass. pytest: 427/427 pass. Refs PR #217. Co-Authored-By: Claude <[email protected]> * fix: address PR #217 SonarCloud annotations (in-scope subset) Three of the 14 SonarCloud annotations on PR #217 are in scope for this PR; the other 11 (workflow version-pinning in validate.yml, security-scan.yml, smoke-test.yml, security-scan-pr.yml) are pre-existing project-wide patterns unrelated to the pre-commit framework rollout. Tracking the pre-existing ones in the umbrella comment. Fixes: 1. tests/test_precommit_workflow.py:35 — split composite assertion. `assert push_branches and 'main' in push_branches, ...` was a composite condition; SonarCloud flagged it because a failure of the first half (push_branches is None) short-circuits before the second half ("main" membership) is checked. Split into two separate assertions so each gets its own diagnostic message. 2. scripts/scanners/lsp.py:131 — merged implicit f-string concat. `f"..." f"..."` is implicit concatenation across a line wrap; the rule wants explicit single-string f-strings or explicit `+`. Merged into a single f-string. 3. scripts/issue_loop/merge.py:16 — same fix: merged two adjacent string literals into one. Verified: - 15 tests pass (5 + 8 + 2) - 427/427 full suite - 14/14 pre-commit hooks pass - Both files parse cleanly Refs PR #217. Co-Authored-By: Claude <[email protected]> * fix(ci): pin transitive deps via requirements-dev.lock.txt (closes S4502) Resolves the 11 SonarCloud python:S4502 'Using dependencies without locking resolved versions is security-sensitive' annotations on PR #217 (validate.yml:34, 35, 129, 154; smoke-test.yml:43, 64, 150; security-scan.yml:37, 38, 112; security-scan-pr.yml:30). Changes: 1. requirements-dev.txt — adds ruff==0.16.1 and ast-grep-cli==0.45.1 so the dev-deps lock file carries hashes for both. These were installed standalone in workflows with --only-binary=:all:, which SonarCloud flagged because there's no hash check on the wheel. 2. requirements-dev.lock.txt — new file, generated via `pip-compile --generate-hashes`. 360 lines with sha256 hashes for every transitive dep. 3. All 4 affected workflows switched from `pip install --only-binary :all: -r requirements-dev.txt` to `pip install --require-hashes -r requirements-dev.lock.txt`. Also consolidated the standalone ruff and ast-grep-cli installs into the lock (no separate install needed; ruff is now in the dev-deps set). Verified: - `pip install --require-hashes -r requirements-dev.lock.txt` in a fresh venv installs ruff 0.16.1 + ast-grep 0.45.1 + all prod deps with hash enforcement. - All 14 pre-commit hooks pass. - 427/427 tests pass. Refs PR #217, umbrella #210. Co-Authored-By: Claude <[email protected]> * fix(ci): add --only-binary :all: + pin pip upgrade (resolves S4502 + S2476) Resolves the second wave of SonarCloud annotations on PR #217: 1. **8x "Omitting --only-binary :all: can lead to setup script execution"** (S2476). Every pip install now combines --only-binary=:all: with --require-hashes so pip refuses sdists (no setup.py execution) AND verifies hashes. 2. **3x "Using dependencies without locking resolved versions"** on the `pip install --upgrade pip` lines (validate.yml:34, security-scan.yml:37, smoke-test.yml — same pattern). Fixed by adding pip==26.1.2 to requirements-dev.txt and regenerating the lock so pip installs via the hash-verified path: `python -m pip install --require-hashes --only-binary=:all: pip==26.1.2` Verified: - All 9 pip install lines now have --require-hashes + --only-binary=:all: - Lock file regenerated with --allow-unsafe to include pip with hashes - Fresh-venv install via lock file succeeds - 14/14 pre-commit hooks pass - 427/427 tests pass Refs PR #217, umbrella #210. Co-Authored-By: Claude <[email protected]> * fix(ci): tell pre-commit-action where to find the plugin-internal config (D30) The pre-commit workflow was failing with: `InvalidConfigError: .pre-commit-config.yaml is not a file` The action's default search path is the repo root, but per spec D30 the config is plugin-internal at plugins/hooks/.pre-commit-config.yaml (so the hooks plugin stays a self-contained installable unit per D15). Added `extra_args: --config=plugins/hooks/.pre-commit-config.yaml` so the action invokes pre-commit with the correct config path. Verified locally: `pre-commit run --all-files --config plugins/hooks/.pre-commit-config.yaml` passes all 14 hooks. Refs PR #217. Co-Authored-By: Claude <[email protected]> * fix(ci): remove --ignore-scripts from claude-code npm install The smoke test failed with: `Error: claude native binary not installed.` Root cause: the npm install used `--ignore-scripts`, which skipped the @anthropic-ai/claude-code postinstall that builds the platform-native binary. Without that binary, the `claude plugin marketplace add` step failed. Removed `--ignore-scripts` so the postinstall runs and the native binary gets installed. Verified locally: 14/14 pre-commit hooks pass, 464/464 tests pass. Refs PR #217. Co-Authored-By: Claude <[email protected]> * fix(tests): create smoke-test temp file inside REPO_ROOT so fast_gate can lint it The smoke test creates a temp file in /tmp/ via mktemp -d. fast_gate's _validate_file_path resolver rejects any path outside REPO_ROOT and fails-open (exit 0) — so the lint check was being skipped entirely on the 'bad' file, and the smoke test reported 'expected non-zero exit' against an actual exit of 0. Fix: derive REPO_ROOT via `git rev-parse --show-toplevel` and use it as the mktemp -d parent. Now the temp file lives inside the repo, fast_gate lints it, ruff flags the F401/E501 issues, fast_gate blocks with exit 2, and the smoke test reports OK. Verified locally: - bash tests/smoke/fast_gate_smoke.sh → 'fast_gate_smoke: OK' - pytest -q → 464 passed - 14/14 pre-commit hooks pass Refs PR #217. Co-Authored-By: Claude <[email protected]> * fix(ci): pin @anthropic-ai/claude-code to 2.1.227 (resolves S4502) Resolves SonarCloud S4502 "Using dependencies without locking resolved versions" on smoke-test.yml:64. Pinned to 2.1.227 (latest stable from anthropics/claude-code releases). Also documents in the workflow why we don't use --ignore-scripts: the @anthropic-ai/claude-code postinstall builds the native binary the runtime needs. The resulting S2648 security hotspot ("Omitting --ignore-scripts allows lifecycle scripts to run") is acknowledged in the workflow comment — the trade-off is necessary and the pinned version is from the official anthropics org. Verified locally: - 14/14 pre-commit hooks pass - 464/464 tests pass Refs PR #217. Co-Authored-By: Claude <[email protected]> * chore(sonar): suppress S2648 on smoke-test.yml (postinstall trade-off) Adds a project-level multicriteria exclusion for typescript:S2648 on smoke-test.yml. The smoke-test workflow installs @anthropic-ai/claude-code WITHOUT --ignore-scripts because the postinstall builds the native binary the runtime needs. The version is pinned (@anthropic-ai/[email protected]) from the official anthropics org; accepting the lifecycle-script risk is a deliberate trade-off, not an oversight. Mirrors the existing suppression pattern used for pythonsecurity:S8707 and pythonsecurity:S8705 on the trusted-maintainer CLI scripts. Verified: - 14/14 pre-commit hooks pass - 464/464 tests pass Refs PR #217. Co-Authored-By: Claude <[email protected]> --------- Co-authored-by: John Smith <[email protected]> Co-authored-by: Claude <[email protected]>
5.1 KiB
Contributing
Thanks for your interest in heretek. This document explains how to add a new item (skill, MCP, LSP, hook, agent, output-style) to the marketplace.
Quickstart
- Fork + clone the repo.
- Create a branch:
git checkout -b add-<plugin-or-item-name> - Add the item to
catalog/catalog.yamlunder the appropriate plugin'sitems[](or as a new plugin entry if it doesn't fit an existing one). - Write an ADR at
catalog/reviews/<plugin>-<item>.mdusingcatalog/reviews/0000-template.mdas the template. - Run the test suite:
pytest -q - Run the schema validator:
python scripts/validate.py - Open a PR.
D7 vetting bar
Every item must pass:
| Criterion | Bar |
|---|---|
| Stars | ≥ 500 |
| Last commit | ≤ 12 months ago |
| License | OSI-approved (MIT / Apache-2.0 / BSD / etc.) |
| Source-audit | Pass for any code-executing component (hooks, bin/, MCP servers, pre-commit hooks) — human review recorded in the ADR |
| Critical CVEs | None in last 24 months (GitHub Security Advisories, CVSS ≥ 9.0) |
Items that fail D7 go in catalog/rejected.md with the failing condition called out.
ADR template
Every item needs an ADR at catalog/reviews/<plugin>-<item>.md. Use catalog/reviews/0000-template.md as the starting point:
---
slug: <slug>
date: YYYY-MM-DD
status: pending
---
# <Item name>
## What
One-paragraph description of the item (repo, what it does, latest release).
## Why
Why this item is worth including in the heretek marketplace. What gap does it fill?
## Alternatives
What other tools cover the same need? Why this one?
## Verdict
- [ ] Approved
- [ ] Rejected
Reason: ...
## Target plugin
Which plugin will bundle this item? (`rust` / `python` / `js-ts` / `web-frontend` / `hooks` / `security` / `skills-pack` / `mcp-pack` / `lsp-pack` / `agents` / `output-styles`)
## Vetting checklist (D7)
- [ ] stars ≥ 500
- [ ] last_commit ≤ 12 months
- [ ] OSI-approved license
- [ ] source-audit pass (if code-executing)
- [ ] no critical CVEs in last 24 months
catalog.yaml entry shape
items:
- id: <item-slug>
kind: skill | mcp | lsp | hook | agent | output-style
upstream: <owner>/<repo>
sha: "<40-char-hex>" # pin to a specific commit
license: MIT
vetting:
status: approved
date: YYYY-MM-DD
stars: <int>
last_commit: YYYY-MM-DD
cve_scan: YYYY-MM-DD
review: reviews/<item-slug>.md
Plugin component declaration
Each plugin's .claude-plugin/plugin.json must declare its components:
{
"name": "<plugin>",
"displayName": "<Display Name>",
"description": "...",
"author": { "name": "Heretek-AI", "url": "https://github.com/Heretek-AI" },
"license": "MIT",
"<component>": "<path>"
}
Do NOT include a version field — D11 SHA-ride means we pin by sha instead.
Hooks are special. Only the hooks plugin may declare hooks in its components list. Per D15, the security plugin does NOT ship hooks even though it deals with security concerns. Hook-needs are routed to hooks.
Tests
Run before pushing:
python3 -m venv .venv
. .venv/bin/activate
pip install -r requirements-dev.txt
pytest -q
python scripts/validate.py
python scripts/generate_marketplace.py
git diff --exit-code .claude-plugin/marketplace.json
The CI workflow (.github/workflows/validate.yml) runs the same checks automatically.
Pre-commit framework
A pre-commit framework config lives at plugins/hooks/.pre-commit-config.yaml
and runs hygiene, Ruff, Biome, shellcheck, gitleaks, and the heretek fast gate
on every git commit and git push. To install it locally:
# Install the framework
pip install pre-commit
# Bind it to this repo (idempotent)
python3 -m pre_commit install --config plugins/hooks/.pre-commit-config.yaml
To run the full suite manually without committing:
python3 -m pre_commit run --all-files --config plugins/hooks/.pre-commit-config.yaml
If a hook fails, fix the issue (or use git commit --no-verify to bypass — but
this is discouraged; the CI workflow .github/workflows/pre-commit.yml
will block the PR anyway). The install is also bundled into the hooks plugin:
/plugin install hooks@heretek
/hooks:install-git-hooks
ShellCheck
CI enforces ShellCheck on every *.sh file. To run locally:
# Install (pick one):
# Ubuntu/Debian: sudo apt-get install shellcheck
# macOS: brew install shellcheck
shellcheck -x --severity=warning $(find . -name '*.sh' -not -path '*/.claude/worktrees/*' -not -path '*/node_modules/*')
The pre-commit hook (installed via plugins/hooks/install.sh) also runs ShellCheck on changed scripts before each commit.
Quarterly refresh
Once per quarter, a maintainer runs:
python scripts/refresh_pins.py --github-token $GH_TOKEN
This re-verifies every catalog entry against the D7 bar. Stale items are flagged for review; SHAs can be bumped with --update-shas.
Code of conduct
This project follows the Contributor Covenant. Be excellent to each other.