kendex.ai

Marketplaces / vanillagreencom/kendex / growth-guards

growth-guards

Five repo growth guards beside size-ratchet — todo-ban, byte-ceiling, suppression-ban, conflict-markers, commit-msg — and the git hook shims that run them. Load to add, tune, or debug a check, the hooks, or GROWTH_GUARDS_* settings.

skill · automation · safety 83/100 (warn) · @ a7ca9af

Install in kendex: kendex add --skill growth-guards after subscribing to vanillagreencom/kendex.

Growth Guards

Problem with this skill? Run kendex report — it files to the owning repo automatically. Do not hand-file.

Five checks that stop quiet repo decay, one family beside size-ratchet, sharing its idiom and its exit contract.

.agents/skills/growth-guards/scripts/growth-guards              # batch: every enabled repo check
.agents/skills/growth-guards/scripts/growth-guards todo-ban     # one check by name, flags pass through
.agents/skills/growth-guards/scripts/install-git-hooks          # arm the git pre-commit/commit-msg shims
.agents/skills/growth-guards/scripts/install-git-hooks --check  # read-only: are the shims still armed?

Every check is also independently invocable as scripts/CHECK — wire CI at whichever grain fits.

The checks

CheckVerdict
todo-banAny work marker (the words TODO, FIXME, HACK, XXX in comment-marker shapes) in a tracked, non-excluded file fails. No baseline. Prose that quotes or names a marker word does not fire.
byte-ceilingA newly added tracked file over the ceiling (default 200 KB) fails. --staged (default) gates the staged diff, --base REF the additions since merge-base, --all sweeps every tracked file. Lockfiles are exempt built-in.
suppression-banBlanket lint suppressions fail flat: module-wide rust allow inner attributes, file-level ruff/flake8 noqa, the bare eslint-disable block form, bare or all nolint, biome's biome-ignore-all / unscoped biome-ignore-start / rule-less biome-ignore lint and group forms. Bare rust allow(dead_code)/allow(unused*) attributes are counted per file against a tighten-only baseline; --update lowers/removes rows, never adds or raises one. A per-line suppression naming its lint with a stated reason stays legal.
conflict-markersAn unresolved merge-conflict marker in a tracked, non-excluded file fails: the open/base/close trio (seven <, seven vertical bars, seven >) at column 0, each followed by a space or end of line. No baseline. Indented or quoted occurrences do not fire; the bare seven-equals separator is deliberately unmatched (a valid Markdown setext underline — a real conflict always carries the open and close markers).
commit-msgHeader must be type(scope)!: subject (scope and ! optional). Uppercase issue keys (fix(ABC-123)) and #-number scopes pass; git-generated messages (Merge/Revert/Reapply, fixup!/squash!/amend!) pass unchanged. Takes the message file or stdin.

Exit codes everywhere: 0 clean, 1 violations, 2 usage/config/collection error. The gates distinguish "measured and fine" from "could not measure": any failure to collect (an unreadable file, a git/grep execution failure) is a loud exit 2, never a silent pass. The batch dispatcher exits 2 if any check could not complete.

Scans read INDEX content (git grep --cached, staged blobs): what is staged is what gets committed, and a sparse checkout cannot hide a tracked file from a gate. An UNMERGED index cannot be scanned that way — git skips unmerged entries and spends no error status doing it — so a scan whose paths include one exits 2 naming them: finish or abort the merge, then re-run.

Git hooks

scripts/install-git-hooks [--repo PATH] writes real .git/hooks shims — pre-commit runs the chain (size-ratchet --staged and preflight --staged when the committing work tree or this install carries those skills — the work tree's copy wins, so a shared install in another checkout never decides which gates exist — a first commit skips preflight with a note, having no base to diff, and a size-ratchet that rejects --staged in its own first-line parser diagnostic is a repo-local replacement whose own wiring owns that gate: stated skip — any other failure blocks as usual — the batch over staged content, then the repo-root-relative executable named by GROWTH_GUARDS_PRE_COMMIT_LOCAL), commit-msg runs this family's message gate. They BLOCK on the family's exit contract, fail closed on a guard that could not run, and git commit --no-verify is the deliberate bypass. kendex add and kendex refresh run the installer, kendex remove growth-guards runs --uninstall first, and kendex check folds in --check's read-only verdict (0 armed — in .git/hooks, or in a core.hooksPath directory hand-wired to this skill's pre-commit and commit-msg; 1 drifted, absent, or dormant behind a core.hooksPath that redirects git away from the shims; 2 could not determine — an unreadable hooks directory, or a hand-wired hook whose shape this check does not recognize, is 2, never a pass and never a verdict). Repeat runs are no-ops and repairs; core.hooksPath is never set, existing hooks keep their content and their own exit status. Full behaviour, including what the installer refuses to touch: DEVELOPMENT.md.

Configuration

KeyDefaultMeaning
GROWTH_GUARDS_CHECKStodo-ban byte-ceiling suppression-ban conflict-markersBatch check list (commit-msg never batches).
GROWTH_GUARDS_TODO_EXCLUDEStools/todo-ban-excludestodo-ban exclusion list.
GROWTH_GUARDS_BYTE_CEILING_KB200Byte ceiling in KB.
GROWTH_GUARDS_BYTE_EXCLUDEStools/byte-ceiling-excludesbyte-ceiling exclusion list (declared asset trees).
GROWTH_GUARDS_SUPPRESSION_EXCLUDEStools/suppression-ban-excludessuppression-ban exclusion list.
GROWTH_GUARDS_SUPPRESSION_BASELINEtools/suppression-baseline.tsvBare-allow ratchet baseline.
GROWTH_GUARDS_CONFLICT_EXCLUDEStools/conflict-markers-excludesconflict-markers exclusion list.
GROWTH_GUARDS_COMMIT_TYPESbuild chore ci docs feat fix perf refactor revert style testAccepted commit types.
GROWTH_GUARDS_PRE_COMMIT_LOCAL(empty)Repo-root-relative executable the pre-commit shim runs last.

Resolution order for every key: explicit environment > .env.local > .kendex/settings.toml > the repo's committed kendex.settings.toml (flat KEY = "value" under [env]) > .env > built-in default. Only an ABSENT source is skipped: one that exists but is unusable is a config error (exit 2), never a fall-through. GROWTH_GUARDS_SETTINGS_FILE=/dev/null selects no settings source at all (.env.local, the settings file and .env are all skipped), leaving environment variables and the defaults.

Excludes formatpattern<TAB>reason per line (shell glob against the full repo-relative path; * crosses /); a pattern without a reason is a config error. Baseline formatpath<TAB>count, LC_ALL=C sorted, unique paths, positive counts.

Per-check consumer detail, seeding a first baseline, and CI wiring: README.md. Marker shapes, per-language suppression patterns, and the hook install and removal contract: DEVELOPMENT.md.