read_version required --- at byte 0 while skill-size-check accepts leading blank lines, so a file one gate passed the other reported as unversioned, and an unversioned merge-base side let an unbumped change through. Match FRONTMATTER_RE, add case 39, and describe the main-tip check and fail-closed cases in the hook entry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
276 lines
16 KiB
YAML
276 lines
16 KiB
YAML
repos:
|
|
- repo: https://github.com/compilerla/conventional-pre-commit
|
|
rev: v2.4.0
|
|
hooks:
|
|
- id: conventional-pre-commit
|
|
stages: [commit-msg]
|
|
|
|
- repo: https://github.com/gitleaks/gitleaks
|
|
rev: v8.21.2
|
|
hooks:
|
|
- id: gitleaks
|
|
stages: ['pre-commit']
|
|
|
|
- repo: https://github.com/jumanjihouse/pre-commit-hooks
|
|
rev: 3.0.0
|
|
hooks:
|
|
- id: shellcheck
|
|
args: [--severity=warning]
|
|
stages: ['pre-commit']
|
|
|
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
rev: v4.5.0
|
|
hooks:
|
|
- id: end-of-file-fixer
|
|
stages: ['pre-commit']
|
|
- id: check-json
|
|
stages: ['pre-commit']
|
|
- id: pretty-format-json
|
|
stages: ['pre-commit']
|
|
args: [--autofix]
|
|
# Every generated manifest lives at a KNOWN path, so every alternative is
|
|
# root-anchored and spells that path out. This was five `(^|/)`
|
|
# any-depth alternatives plus one `^` root-only one -- a mixture with no
|
|
# rationale, under which a fixture or vendored tree containing
|
|
# `.../.claude-plugin/marketplace.json` would have been silently excluded
|
|
# from formatting while an equivalent
|
|
# `.../.agents/plugins/marketplace.json` would not. Only the one root
|
|
# marketplace manifest matches now; anything else is hand-authored and
|
|
# gets formatted. The twelve per-plugin `plugin.json` alternatives were
|
|
# dropped with the plugin manifests themselves when native
|
|
# `claude plugin install` support was removed (ADR-0024) -- apm probes
|
|
# `apm.yml` and never reached them. The `.agents/plugins/` and
|
|
# `.github/plugin/` marketplace mirrors went the same way, and their
|
|
# alternations went with them: `check-useless-excludes` fails on a
|
|
# pattern that matches no file.
|
|
#
|
|
# `.claude/settings.json` and its `.claude/apm-hooks.json` ownership
|
|
# sidecar are the last two alternations, and they are the only ones
|
|
# here for a reason other than "generated manifest":
|
|
# apm OWNS that file (ADR-0018, ADR-0019), and
|
|
# `apm audit --ci` replays the install into a scratch tree and diffs
|
|
# the result byte-for-byte. `pretty-format-json` sorts object keys
|
|
# unless `--no-sort-keys` is passed, while apm's hook integrator emits
|
|
# insertion order (`matcher` before `hooks`, `type` before `command`).
|
|
# Formatting the file therefore rewrites apm's output into a form apm
|
|
# would never produce, and the `apm-audit-ci` pre-push hook reports it
|
|
# as permanent drift on a file with no git diff -- exactly what
|
|
# happened when the SessionStart hook first landed in 2e395a4.
|
|
# Re-running `apm install` fixes the file; leaving it in scope here
|
|
# would re-break it on the very commit that carries the fix. The
|
|
# sidecar is committed so a fresh clone's install can claim the
|
|
# settings entry instead of duplicating it (ADR-0019, 2026-09-16
|
|
# correction), and it is apm output under the same byte-for-byte replay.
|
|
exclude: '^(\.claude-plugin/marketplace\.json|\.claude/(settings|apm-hooks)\.json)$'
|
|
- id: check-yaml
|
|
stages: ['pre-commit']
|
|
- id: trailing-whitespace
|
|
stages: ['pre-commit']
|
|
- id: check-merge-conflict
|
|
stages: ['pre-commit']
|
|
- id: detect-private-key
|
|
stages: ['pre-commit']
|
|
- id: check-toml
|
|
stages: ['pre-commit']
|
|
- id: check-ast
|
|
stages: ['pre-commit']
|
|
|
|
- repo: local
|
|
hooks:
|
|
- id: run-tests
|
|
name: Run test suite
|
|
description: Run all test-*.sh files and bats suite. --strict because a suite that exits 77 (SKIPPED) at pre-push means a documented dependency is missing on this machine, and pre-commit prints nothing for a passing hook -- without it the gate went green having verified 15 of 17 suites on a vale-less PATH, with the skip list swallowed. Ad-hoc `bash tests/run-tests.sh` still skips gracefully.
|
|
entry: bash tests/run-tests.sh --strict
|
|
language: system
|
|
stages: [pre-push]
|
|
pass_filenames: false
|
|
always_run: true
|
|
|
|
- id: check-executables-allow-sync
|
|
name: Check executables allow key sync
|
|
description: Verify root apm.yml's executables.allow key names kyberforge's actual version -- apm matches that key by exact "<package>#<version>" lookup, so a version bump on one side alone silently stops deploying kyberforge's hooks/ and bin/ and lets the apm install go stale (see ADR-0019)
|
|
entry: bash scripts/check-executables-allow-sync.sh
|
|
language: system
|
|
stages: [pre-push]
|
|
pass_filenames: false
|
|
always_run: true
|
|
|
|
- id: apm-audit-ci
|
|
name: apm audit --ci
|
|
description: Run apm's producer-side CI gate over the root manifest AND each of the six plugin packages. Verifies exactly two things per manifest -- apm.yml parses as a valid APM manifest (manifest-parse), and, if it declares dependencies, apm.lock.yaml exists and is consistent (lockfile-exists). It does NOT enforce an org policy and does NOT scan for hidden Unicode; see the comment below for why. Reference:plugins/kyberforge/.apm/skills/apm-workflow/references/audit.md
|
|
entry: bash -c 'for d in . plugins/*/; do (cd "$d" && apm audit --ci) || { echo "apm audit --ci failed in $d" >&2; exit 1; }; done'
|
|
language: system
|
|
stages: [pre-push]
|
|
pass_filenames: false
|
|
always_run: true
|
|
# The description above deliberately claims less than this hook's old one
|
|
# did ("lockfile/policy/hidden-content integrity"), because two of those
|
|
# three were never happening:
|
|
#
|
|
# * POLICY. `apm audit --ci` discovers an org policy from the git remote,
|
|
# and apm's discovery only understands github.com and Azure DevOps.
|
|
# This repo's remote is a self-hosted Gitea, so discovery resolves
|
|
# nothing and the run prints `No org policy found at unknown;
|
|
# enforcement skipped`. apm's own message suggests
|
|
# `policy.fetch_failure_default=block` in apm.yml "to fail closed" --
|
|
# that was tried on a scratch copy and REJECTED: it does not make the
|
|
# check meaningful, it makes it permanently red. `apm audit --ci` then
|
|
# exits 1 with `No org policy found at unknown
|
|
# (policy.fetch_failure_default=block)` on every push, because there is
|
|
# no org policy to find and no supported way for this remote to serve
|
|
# one. A gate that can never go green is not a gate. Revisit if this
|
|
# repo ever gains a policy source apm can actually reach.
|
|
# * HIDDEN CONTENT. The hidden-Unicode scan is plain `apm audit`, not
|
|
# `apm audit --ci` (the two are different modes, and --ci refuses to
|
|
# combine with --file/--strip/--dry-run/PACKAGE). Plain `apm audit`
|
|
# here reports `No apm.lock.yaml found -- nothing to scan` and exits 0,
|
|
# so adding it would buy a second vacuous check, not coverage.
|
|
#
|
|
# What IS left is worth keeping, and is now run against seven manifests
|
|
# instead of one. lockfile-exists is conditional -- it is vacuous while
|
|
# every apm.yml declares `dependencies: {apm: [], mcp: []}`, and it arms
|
|
# itself the moment one does not (verified: adding a git dependency to
|
|
# plugins/lint/apm.yml fails with `apm.yml declares dependencies but
|
|
# apm.lock.yaml is absent`). manifest-parse is unconditional and fires on
|
|
# any malformed manifest (verified: a dependency entry missing its
|
|
# git/path/registry field fails with `Cannot parse apm.yml`). Running the
|
|
# six plugin packages is what makes either reachable for them at all --
|
|
# the root-only invocation audits the marketplace manifest and nothing
|
|
# else. Costs ~0.5s per package, needs no network (checked under
|
|
# `unshare -rn`) -- consistent with every other pre-push hook: none of
|
|
# them need the network (see README.md's "Offline?" section).
|
|
|
|
- id: check-apm-agents-valid
|
|
name: Validate real APM agent files
|
|
description: Run factory-audit's validate.sh over every plugins/*/.apm/agents/*.agent.md file in this repo -- the artifacts it governs, not fixtures
|
|
entry: bash scripts/check-apm-agents-valid.sh
|
|
language: system
|
|
stages: [pre-push]
|
|
pass_filenames: false
|
|
always_run: true
|
|
# validate.sh was previously exercised only by check-scope-walkup-sync,
|
|
# and only against synthetic mktemp fixtures -- it had never run against
|
|
# the four agent files it governs. That is how ADR-0016 could be amended
|
|
# to bless a `disallowedTools` frontmatter field while validate.sh's
|
|
# allowlist still rejected it: the spec and its enforcer disagreed and
|
|
# every gate stayed green. The expected file set is derived from
|
|
# `git ls-files` (the pattern tests/run-bats.sh established) rather than
|
|
# a hardcoded count, and discovering zero files is an error, not a pass.
|
|
# Needs no network.
|
|
|
|
- id: apm-pack-check-clean
|
|
name: apm pack --check-clean
|
|
description: Release gate -- verify .claude-plugin/marketplace.json still matches what apm.yml + .apm/ would currently generate, and that per-package versions agree with the per_package versioning strategy. Closes issue #90's deferred item 3 (a check-clean-equivalent gate) using apm's own flag instead of custom drift logic.
|
|
entry: apm pack --check-versions --check-clean --dry-run
|
|
language: system
|
|
stages: [pre-push]
|
|
pass_filenames: false
|
|
always_run: true
|
|
|
|
# check-vale-style-sync was removed by ADR-0025. Only 6 of its 17
|
|
# assertions diffed skill-audit's Vale copy against agent-audit's; the
|
|
# merge into factory-audit leaves one copy, so those are moot. The other
|
|
# 11 moved into tests/test-vale-wrap.sh (case 0, cases 28-31, its
|
|
# Vale-absent skip, and case 32 for the one-plugin narrowing guard),
|
|
# which run-tests runs here at
|
|
# pre-push, so do not re-add the hook to restore coverage. Do not
|
|
# confuse its removal with check-scope-walkup-sync below, which survives:
|
|
# that one cross-checks four hand-ported walk-up implementations, only two
|
|
# of which lived in the audit pair.
|
|
|
|
- id: check-scope-walkup-sync
|
|
name: Check scope walk-up implementations agree
|
|
description: Behaviorally cross-check validate.sh, validate-provenance.sh, new-agent.sh, and new-skill.sh's independent $HOME/.git/apm.yml walk-up ports against each other
|
|
entry: bash scripts/check-scope-walkup-sync.sh
|
|
language: system
|
|
stages: [pre-push]
|
|
pass_filenames: false
|
|
always_run: true
|
|
|
|
- id: check-skill-version-bump
|
|
name: Check changed skills bump metadata.version
|
|
description: On every push, fail if a skill directory changed (tests/ excluded) since the merge-base with main without its SKILL.md metadata.version rising above both that merge-base's and main's tip's (ADR-0022)
|
|
entry: bash scripts/check-skill-version-bump.sh
|
|
language: system
|
|
stages: [pre-push]
|
|
pass_filenames: false
|
|
always_run: true
|
|
# "Changed" is measured from the merge-base with origin/main (falling
|
|
# back to main), not the remote branch tip: readers install from main.
|
|
# The version must also beat main's tip, so two branches making the same
|
|
# bump cannot both land. Fails closed when no main ref resolves, when
|
|
# there is no merge-base, or when only local main resolves and already
|
|
# contains the pushed commit. Merges through Gitea's merge button run no
|
|
# local hook, so they bypass this. See docs/spec/gates.md.
|
|
|
|
- id: validate-marketplace
|
|
name: Validate marketplace manifest
|
|
description: Run claude plugin validate --strict on the root marketplace manifest
|
|
entry: claude plugin validate --strict .claude-plugin/marketplace.json
|
|
language: system
|
|
stages: [pre-push]
|
|
pass_filenames: false
|
|
always_run: true
|
|
|
|
- id: skill-size-check
|
|
stages: ['pre-commit']
|
|
name: SKILL.md size and context-budget ceilings
|
|
description: Enforce agentskills.io's 500-line/2,770-whole-file-word spec ceilings AND ADR-0020's context budget -- description 250 chars SUGGESTION / 400 FAIL, body-only 600 words SUGGESTION / 900 FAIL, and every boundary-clause routing target resolving to a real skill or agent under plugins/*/.apm/
|
|
entry: scripts/skill-size-check.sh
|
|
language: script
|
|
files: '^plugins/[^/]+/\.apm/skills/[^/]+/SKILL\.md$'
|
|
pass_filenames: true
|
|
verbose: true
|
|
# verbose so the SUGGESTION tier is audible. ADR-0020 depends on it:
|
|
# "A ceiling does not produce an average ... The halving depends
|
|
# entirely on the 250-character SUGGESTION tier being visible and
|
|
# respected." pre-commit prints nothing at all for a passing hook, and
|
|
# a SUGGESTION deliberately does not fail, so without verbose every
|
|
# suggestion would be swallowed -- the exact invisibility ADR-0013
|
|
# records for Vale warnings. Costs nothing on a clean file: the script
|
|
# prints only findings.
|
|
|
|
- id: check-rtk-prefix
|
|
stages: ['pre-commit']
|
|
name: ADR-0023 rtk prefix on executable git commands
|
|
description: Enforce ADR-0023 clause 1 -- an executable, instructed git command in a shell code fence or a dispatch-table Run cell is written `rtk git`. Clauses 2 and 3 are not machine-decidable; a deliberately bare command opts out with the literal string ADR-0023 on its own line
|
|
entry: scripts/check-rtk-prefix.sh
|
|
language: script
|
|
files: '^plugins/[^/]+/\.apm/(skills/.*\.md|agents/.*\.agent\.md)$'
|
|
# README.md is excluded on purpose, not by oversight. A skill-directory
|
|
# README is consumer-facing prose that no agent ever loads, and the
|
|
# `git clone` lines in the six tests/README.md files are setup
|
|
# instructions for a third party who has no rtk installed. Prefixing
|
|
# those would be actively wrong -- see ADR-0023's consumer section.
|
|
exclude: '(^|/)README\.md$'
|
|
pass_filenames: true
|
|
|
|
- id: vale-audit-prefilter-skill
|
|
stages: ['pre-commit']
|
|
name: Vale audit prefilter (SKILL.md)
|
|
description: Run Vale against SKILL.md files as a deterministic prefilter for factory-audit's skill flow, via factory-audit's own bundled copy
|
|
entry: plugins/kyberforge/.apm/skills/factory-audit/scripts/vale-wrap.sh
|
|
language: script
|
|
files: '^plugins/[^/]+/\.apm/skills/[^/]+/SKILL\.md$'
|
|
pass_filenames: true
|
|
|
|
# Two hook IDs pointing at ONE vale-wrap.sh is deliberate, not leftover
|
|
# duplication. ADR-0014 measured a single hook entry silently scanning 0
|
|
# files of the other type, and ADR-0025 carried that finding across the
|
|
# merge: the two `files:` regexes are what keep the SKILL.md scope and the
|
|
# agent-file scope independently addressable. The script self-locates its
|
|
# config via ${BASH_SOURCE[0]}, so one copy serves both.
|
|
- id: vale-audit-prefilter-agent
|
|
stages: ['pre-commit']
|
|
name: Vale audit prefilter (agent files)
|
|
description: Run Vale against agent markdown files as a deterministic prefilter for factory-audit's agent flow, via factory-audit's own bundled copy
|
|
entry: plugins/kyberforge/.apm/skills/factory-audit/scripts/vale-wrap.sh
|
|
language: script
|
|
files: '^plugins/[^/]+/\.apm/agents/[^/]+\.agent\.md$'
|
|
pass_filenames: true
|
|
|
|
- repo: meta
|
|
hooks:
|
|
- id: check-hooks-apply
|
|
- id: check-useless-excludes
|