Files
claude/.claude/tasks/contracts/2026-09-27-floor-guard-1525.md
T

5.9 KiB

CONTRACT — floor-guard

  • date: 2026-09-27 | flow: feat (ad-hoc dispatch, /feat gates replayed by the orchestrator) | branch: feature/agent-skills-borrow
  • status: active

REQUEST (verbatim — IMMUTABLE)

Build lib/floor-guard.sh, a diff-scoped deterministic detector of a quietly weakened quality bar, adapted from addyosmani/agent-skills constraint-driven-development (floor guard) to this repo's gate model. Usage bash ~/.claude/lib/floor-guard.sh <base-ref> [-- <pathspec>...] over git diff <base-ref> (working tree included). Kinds: SUPPRESS (added checker silencing: @ts-ignore, @ts-expect-error without a trailing reason, eslint-disable*, # noqa, # type: ignore, nosemgrep, nosec, shellcheck disable), SKIP (added .skip(, .only(, xit(, xdescribe(, fit(, fdescribe(, it.todo(, @pytest.mark.skip, @unittest.skip, t.Skip( in test files), DELETED_TEST (deleted file whose path matches a test pattern), ASSERT_DROP (a test file whose assertion-line count decreases: expect(, assert, should, .toBe), STUB (added not implemented, NotImplementedError, empty catch block, except: pass), THRESHOLD_DOWN (a numeric value decreased on the same key in coverage/quality config files: jest.config*, vitest.config*, .nycrc*, codecov*, sonar-project.properties, lighthouserc*, CONSTRAINTS.md). Waiver: an added line carrying floor-guard: allow <reason> is printed as WAIVED and not counted. Output: one FLOOR <KIND> <file>:<line> <snippet> per finding, then FLOOR GUARD: clean (rc 0) or FLOOR GUARD: <n> finding(s), <m> waived (rc 2); rc 3 on usage error. Wire it as a mandatory verifier step (agents/verifier.md) and document it in lib/verify-secure-loop.md GATE 1; hermetic suite lib/tests/floor-guard.test.sh. User go 2026-09-27 ("ok pour les 4", case 2 item 2).

CLARIFICATIONS

  • Language: bash entry point; the diff parsing may live in an embedded python3 heredoc (precedent lib/tests/run-review-guards.sh). Functions <= 25 logic lines, 80-char lines.
  • File classes: test file = path contains test, spec, __tests__, or matches *.test.*, *.spec.*, *_test.go, *_test.py, test_*.py; config file = the names listed under THRESHOLD_DOWN. SKIP and ASSERT_DROP apply to test files only; SUPPRESS and STUB to any file; THRESHOLD_DOWN to config files only.
  • The guard's own pattern table contains the trigger strings: those source lines carry # floor-guard: allow pattern table so the guard stays clean on itself (this also exercises the waiver path for real).
  • Verifier step: bash ~/.claude/lib/floor-guard.sh <base> where base = the branch's gitflow base (develop; main for hotfix/release); rc 2 → verdict ECARTS listing each FLOOR line, unless the contract's CLARIFICATIONS explicitly authorize that exact weakening (quote it in the verdict).
  • Suite: throwaway repos (make test exports GIT_CONFIG_GLOBAL=/dev/null); one RED fixture per kind, one WAIVED fixture, one CLEAN fixture, each printed as PASS <KIND>; summary PASS=n FAIL=m like the other suites.
  • Self-detection: the suite file itself contains the trigger strings; the self-run criterion excludes it by pathspec.
  • Out of scope: a pre-commit hook, running it on the repo's own diff in make test, parsers beyond the regexes above.

ACCEPTANCE CRITERIA

  1. Suite green, every kind flip-tested. CHECK: out=$(make test suite=lib/tests/floor-guard.test.sh 2>&1); for k in SUPPRESS SKIP DELETED_TEST ASSERT_DROP STUB THRESHOLD_DOWN WAIVED CLEAN; do echo "$out" | grep -q "PASS $k" || { echo "missing PASS $k"; echo "$out" | tail -15; exit 1; }; done; echo "$out" | grep -qE "FAIL=[1-9]" && exit 1; echo KINDS_GREEN EXPECT: KINDS_GREEN EVIDENCE: MET exit=0 marker-found :: KINDS_GREEN
  2. Usage error is rc 3. CHECK: bash lib/floor-guard.sh >/dev/null 2>&1; [ $? -eq 3 ] && echo RC_USAGE EXPECT: RC_USAGE EVIDENCE: MET exit=0 marker-found :: RC_USAGE
  3. Self-run clean on this branch (suite file excluded by pathspec), waivers visible. CHECK: out=(bash lib/floor-guard.sh develop -- . ':!lib/tests/floor-guard.test.sh' 2>&1); rc=?; echo "$out" | tail -3; [ $rc -eq 0 ] && echo "$out" | grep -q WAIVED && echo SELF_CLEAN EXPECT: SELF_CLEAN EVIDENCE: MET exit=0 marker-found :: WAIVED STUB lib/floor-guard.sh:105 'not implemented', # floor-guard: allow pattern table WAIVED STUB lib/floor-guard.sh:106 'NotImplementedE…
  4. Verifier wired, loop documented. CHECK: grep -q "floor-guard.sh" agents/verifier.md && grep -q "floor-guard" lib/verify-secure-loop.md && echo WIRED EXPECT: WIRED EVIDENCE: MET exit=0 marker-found :: WIRED
  5. shellcheck and doctrine-citers clean. CHECK: shellcheck lib/floor-guard.sh lib/tests/floor-guard.test.sh && out=$(make test suite=lib/tests/doctrine-citers.test.sh 2>&1) && ! echo "$out" | grep -qE "FAIL=[1-9]" && echo LINT_OK EXPECT: LINT_OK EVIDENCE: MET exit=0 marker-found :: LINT_OK

FILE SCOPE

  • lib/floor-guard.sh (new), lib/tests/floor-guard.test.sh (new)
  • agents/verifier.md (one mandatory step), lib/verify-secure-loop.md (one paragraph under GATE 1)
  • CHANGELOG.md (Unreleased entry)

PLAN

  1. Script: arg parsing (base, optional -- pathspec), git diff --unified=0 <base> -- <pathspec> plus git diff --diff-filter=D --name-only <base> -- <pathspec>, rc contract, header comment stating WHY (BDR-100 class: deterministic floor under an LLM gate).
  2. Python parser on stdin: walk hunks, classify added lines by kind and file class, count assertion lines removed vs added per test file, compare numeric values on identical keys in config files (key: 80 → key: 60, JSON or YAML-ish), detect waivers.
  3. Output lines + summary + rc.
  4. Suite: helper mk_repo (git init -q, base commit with a test file holding 3 assertions, a vitest.config.ts with lines: 80, a source file), one fixture per kind → assert rc 2 and the FLOOR line; WAIVED → rc 0 and a WAIVED line; CLEAN → rc 0.
  5. verifier.md step + verify-secure-loop.md paragraph + CHANGELOG; run criteria 1-5.