Skip to content

fix(hooks): the decision gate judges every language, test file and header (ADR-1060 revision, no exception) - #555

Merged
cdeust merged 2 commits into
mainfrom
fix/adr-1060-decision-gate-no-exception
Sep 10, 2026
Merged

cdeust merged 2 commits into
mainfrom
fix/adr-1060-decision-gate-no-exception

Conversation

@cdeust

@cdeust cdeust commented Sep 10, 2026 •

Copy link
Copy Markdown
Owner

Summary

Implements the owner ruling of 2026-09-10 on ADR-1060 ("Révision de l'ADR-1060, pas d'exception"). On 2026-09-10 six lines of /// rationale were written into ai-architect-mcp-codebase/tests/rust_local_receiver_static_resolution.rs and mcp_server/hooks/decision_gate.py let them through, because each of its three exemptions sufficed alone: // languages were deferred "until measured", the path was under a test root, and the run sat before anything executable. All three exemptions are removed. The gate now judges every comment-marker language (#, // and /* */, --, ;), test files and header blocks alike. PROSE_RUN_LIMIT (8), grandfathering of blocks already in the file, the source: pointer line, fail-open on unreadable input and the explicit CORTEX_DECISION_GATE=off override are unchanged.

No issue is linked: the owner ruling is recorded in the canonical wiki page wiki/adr/cortex/1060-*.md (section "Revision 2026-09-10 — no exception"), with the read-only mirror docs/adr/ADR-1060-*.md regenerated through scripts/check_project_wiki.py --write.

Closes #N/A (owner ruling, no issue)

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would change existing behavior): a new file can no longer open with 8+ comment lines, and test files are judged; the ADR records this as the chosen cost
  • Refactor (no functional change; rules/coding-standards.md compliance)
  • Documentation only
  • Audit-finding closure (cite the finding ID)

What changed

  • mcp_server/hooks/_decision_gate_lex.py: one character-level scanner (_Scanner) for the //, -- and ; families that tracks quoted strings (single-line unless the language lets them span lines; backtick and Rust r#"..."# raw strings always), character literals (so a Rust lifetime 'a does not open a string) and block comments; every line a block comment crosses is a comment line, a line holding code is never one. Python stays on tokenize, the shell family keeps its heredoc skip. LANGUAGES is the single table of suffix, refusal marker and scanner, covering .py .sh .bash .zsh .rb .pl .r .toml .yaml .yml .cfg .ini .rs .js .jsx .ts .tsx .go .java .c .h .cpp .hpp .cc .swift .kt .kts .cs .scala .m .mm .dart .php .sql .lua .hs .el .clj .lisp.
  • mcp_server/hooks/decision_gate.py: is_test_path, _TEST_DIRS, _TEST_FILE_RE, _is_header_run and the header boundary are deleted; suffix lookup is case-insensitive; the refusal states that no header, test file or language is exempt. One narrowing on Python: a comment token after code on the same line (x = 1 # note) no longer counts as a comment line, which is what the scanner's docstring already claimed.
  • Tests: tests_py/hooks/test_decision_gate.py flips the former exemption tests to refusals and adds the grandfathered-header case; tests_py/hooks/test_decision_gate_languages.py pins the Rust tests/*.rs /// case (direct and through the process entry point), // in .ts/.go, /* */ in .java, a 10-line header on a new .py, // text and a block comment inside Rust string literals (not comments), the 7-line run, grandfathering in .go, source: lines, the override, every declared suffix, lifetimes, unterminated block comments and trailing comments.

Gate

The owner's simulation (a PreToolUse Edit event on stdin adding ten /// lines to tests/rust_local_receiver_static_resolution.rs, through python3 scripts/launcher.py mcp_server.hooks.decision_gate):

  • origin/main 8d71210f: exit=0 (allowed)
  • this branch: [decision-gate] BLOCKED: .../tests/rust_local_receiver_static_resolution.rs:3 would add 10 consecutive comment lines. ... No header, test file or language is exempt (ADR-1060, revision 2026-09-10). then ... leave only // source: ADR-NNNN here ..., exit=2

tests_py/hooks/test_decision_gate_languages.py on origin/main (detached worktree at 8d71210f, the module-level parametrize over lex.LANGUAGES stripped because it cannot collect there): 12 failed, 5 passed; the 5 that pass are the ALLOW-shaped invariants (string literals, the 7-line run, grandfathering, pointer lines). On the branch: 78 passed across both gate test files.

Measurement (header exemption removal)

Tracked tree at 8d71210f, every file whose suffix the gate now judges, scanned with the revised lexer. A header run is a run of 8+ comment lines preceded only by blank lines, comment lines, a shebang, a shell set line or the Python module docstring.

suffix tracked files header run 8+ (code / test) body run 8+ (code / test)
.py 1427 0 / 0 10 / 33
.sh 20 4 / 0 1 / 0
.yml 17 0 / 0 1 / 0
.sql 12 5 / 0 1 / 0
.yaml 1 0 / 0 0 / 0
.toml 1 0 / 0 1 / 0
.js 1 0 / 1 0 / 0
total 1479 9 / 1 14 / 33

Ten files carry a header run at or over the threshold (for example benchmarks/reproduce.sh, scripts/install-plugin.sh, scripts/phase_0_4_5_backfill.sql, tests_js/spatial_hash.test.js). All are grandfathered: editing them elsewhere is allowed. Nothing in the tree is blocked by the ruling; the number is recorded in the ADR for the owner to decide on.

Test plan

  • All existing tests pass: pytest tests_py/hooks/ = 465 passed (13 subtests).
  • New tests added for new behavior: 17 in test_decision_gate_languages.py plus the flipped and added cases in test_decision_gate.py (78 total across the two files).
  • Mutation survival check: removing any suffix from LANGUAGES fails test_every_declared_suffix_is_enforced; re-adding a test-path or header exemption fails the flipped tests; treating a string body as a comment fails the two Rust string tests; dropping the char-literal rule fails the lifetime test; counting trailing comments fails the trailing-comment test. Not caught: a scanner that mis-lexes a literal form no test uses (a C++ R"(...)" raw string, for instance); that fails open, never closed, and the ADR records it as a risk accepted.
  • Manual verification: the launcher simulation above, both sides. The committed entry-point test uses python -m mcp_server.hooks.decision_gate rather than scripts/launcher.py, because the launcher runs ensure_deps(<repo>/deps) first and pip-installs the base packages over the network when that directory is empty (which it is in CI); a unit test must not perform that install.

Gates as CI runs them (final state)

  • ruff check . and ruff format --check . (ruff 0.16.6 from requirements/lint.txt): clean
  • python scripts/check_craftsmanship.py: OK
  • python scripts/check_project_wiki.py: mirrors match
  • python scripts/check_doc_claims.py: OK
  • python scripts/check_no_deps_invariant.py: clean
  • python3 scripts/check_marketplace_pins.py: all pins current (untouched)
  • pyright per CONTRIBUTING.md § Reproducing the pyright gate locally (uv sync ... --group typecheck, .venv/bin/python -m pyright mcp_server/): exit 0

Audit notes

  • Engineering review: the launcher-driven test was found to depend on a network install and replaced by the module entry point (above). The Python trailing-comment narrowing was found while making the languages consistent and is recorded in the ADR.
  • Genius review: none run.
  • Outstanding deferred findings: none. Adjacent observation, not folded in: the tracked tree holds 47 files with a body run over the threshold (33 under test roots), all grandfathered; they are migration candidates the ADR already names.

Coding-standards compliance

  • §2.2 Layer dependency direction preserved (hooks import only hooks and stdlib).
  • §3.2 No any in production code.
  • §4.1 No file > 500 lines (repo cap 300: _decision_gate_lex.py 257, decision_gate.py 177).
  • §4.2 No function > 50 lines (repo cap 40, checked by the craftsmanship gate).
  • §4.4 No function with > 4 parameters.
  • §7 Local reasoning preserved.
  • §8 Every numeric constant carries a # source: annotation (PROSE_RUN_LIMIT, unchanged).
  • §9 No dead code, no TODOs.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LSTGMnuSuUXWs7ZBofW1AW

cdeust and others added 2 commits September 10, 2026 08:32
…ader

Owner ruling of 2026-09-10 on ADR-1060: no exception. Six lines of ///
rationale in a tests/*.rs file passed the gate because the language was
deferred "until measured", the path was a test root and the run sat before
anything executable. Each exemption sufficed alone.

- _decision_gate_lex.py: one character-level scanner for the //, /* */, --
  and ; families (string literals, Rust raw strings, backtick strings and
  block comments tracked), covering .rs .js .jsx .ts .tsx .go .java .c .h
  .cpp .hpp .cc .swift .kt .kts .cs .scala .m .mm .dart .php .sql .lua .hs
  .el .clj .lisp, plus the full # family (.py via tokenize, the shell
  family via the heredoc skip, .pl .r .toml .yaml .yml .cfg .ini). The
  suffix table and the scanner table are one table (LANGUAGES).
- decision_gate.py: is_test_path, _TEST_DIRS, _TEST_FILE_RE, _is_header_run
  and the header boundary are deleted; the refusal states that no header,
  test file or language is exempt. PROSE_RUN_LIMIT, grandfathering, the
  source: pointer, fail-open and CORTEX_DECISION_GATE=off stay.
- Python: a comment token after code on the same line no longer counts as
  a comment line, as the scanner's docstring already claimed.
- ADR-1060 wiki page: revision section with the leak, what is removed,
  what stays and the tracked-tree measurement at 8d71210 (1479 files in
  judged suffixes, 10 with a header run of 8+, all grandfathered); mirror
  regenerated with scripts/check_project_wiki.py --write.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LSTGMnuSuUXWs7ZBofW1AW
@cdeust

cdeust commented Sep 10, 2026

Copy link
Copy Markdown
Owner Author

ZETETIC-REVIEW: APPROVE

Owner ruling implemented: ADR-1060 without exception. mcp_server/hooks/_decision_gate_lex.py lexes the // and /* / family (.rs .js .jsx .ts .tsx .go .java .c .h .cpp .hpp .cc .swift .kt .kts .cs .scala .m .mm .dart .php), -- (.sql .lua .hs), ; (.el .clj .lisp) and the # family, with string literals, raw and backtick strings, char literals and block comments tracked; decision_gate.py drops the test-path and header exemptions and their dead code, keeps PROSE_RUN_LIMIT 8, grandfathering, the source: pointer, fail-open and the explicit CORTEX_DECISION_GATE=off. The canonical page wiki/adr/cortex/1060-.md carries the revision (leak, three reasons, removed, kept, measurement) and the docs/adr mirror is regenerated through scripts/check_project_wiki.py --write. CHANGELOG cites #555.

Evidence on head 07059ab: all 24 CI checks green. The owner's own simulation (ten /// lines into tests/rust_local_receiver_static_resolution.rs through scripts/launcher.py) exits 0 on origin/main and exits 2 with the refusal on the branch; new tests 12 failed on origin/main, 78 passed on the branch across both gate files; ruff, craftsmanship, pyright (0 errors), doc-claims, project-wiki, no-deps and marketplace-pins gates all exit 0. Measurement: 10 tracked files carry a header run of 8+ lines, all grandfathered, nothing in the tree is blocked.

One narrowing recorded in the ADR: a Python comment after code on the same line no longer counts as a comment line, consistent with every other scanner. Merging; release 4.21.0 follows so installs receive the gate.

@cdeust
cdeust merged commit 88fbd74 into main Sep 10, 2026
28 checks passed
@cdeust
cdeust deleted the fix/adr-1060-decision-gate-no-exception branch September 10, 2026 06:44
@cdeust cdeust mentioned this pull request Sep 10, 2026
cdeust added a commit that referenced this pull request Sep 10, 2026
The decision gate judges every language, test file and header (ADR-1060
revision 2026-09-10, no exception, #555): // and /* */, --, ; and # comment
families, no test-path or header exemption, PROSE_RUN_LIMIT unchanged at 8,
grandfathering and the explicit override kept. Every version surface moves
to 4.21.0 (pyproject, plugin and marketplace manifests, server.json,
manifest.json, package.json and lock, gemini and codex manifests, badge,
uv.lock, README alt text) and the hypermnesia-mcp-viz pin moves to v3.2.0
(2482563172c0f02a1c022b82cb4e38299b7748b4, cortex-viz #156: the Wiki view
lists project wikis, where project_root ADRs actually live).


Claude-Session: https://claude.ai/code/session_01LSTGMnuSuUXWs7ZBofW1AW

Co-authored-by: Claude Fable 5.1 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant