[mine-blindspots hyperthrive corpus] TrailingProhibition misses bare 'not' after a strong delimiter #161

Closed
opened 2026-07-27 15:07:11 +00:00 by jared · 2 comments
Owner

Context

Cross-project filing from a mine-blindspots run against the hyperthrive-websites repo as external corpus. Bucket (a): "rule exists, logic gap" — tier-1 cop bug, not new rule.

Problem

hyperthrive-websites/context/workflow/video-pipeline.md:22:

**Guardrail:** half-day time-box. If ffmpeg stitching fights back, fall back to a plain manual video *per hot reply only* — not per prospect.

and hyperthrive-websites/docs/onboarding-tasks.md:9:

5. **Then** build the garage door template. Not before.

Both contain a bare, un-parenthesized trailing "not" clause functioning as a strong prohibition ("not per prospect", "Not before.") immediately after a CLAUSE_SPLIT delimiter (an em-dash, and a sentence-end period respectively). Neither is caught.

Detection

plugins/os-aidd-lint/lib/aidd_lint/cops/trailing_prohibition.rb:25-29:

PARENTHETICAL_NOT = /\(\s*not\b[^)]*\)/i

This only matches a bare "not" when it is wrapped in parentheses (per the comment: outside parens "not" is just as likely to be an ordinary sentence-lead, e.g. "Not green -> ..."). It never matches — not per prospect. (no parens, follows an em-dash) or Not before. (no parens, is itself the entire trailing sentence after a period-delimited clause split). STRONG_PROHIBITION (line 18) also doesn't match either, since "not" alone isn't in its word list.

Correction

Treat a bare "not" as a strong prohibition specifically when it is the first word of a clause immediately following an existing CLAUSE_SPLIT delimiter (semicolon, dash, or sentence end) — i.e., add "not" to (or alongside) STRONG_PROHIBITION's word set, but only for clauses produced by each_clause (post-split), not for lines scanned as a whole (which is what protects "Not green -> ..." as a sentence-lead, a case this fix doesn't touch since leads_with_prohibition? gates whole-line leads separately via LEADS_WITH_PROHIBITION, which is unaffected).

Pass/fail examples

  • Must fail (flag): ...fall back to a plain manual video per hot reply only — not per prospect.
  • Must fail (flag): Then build the garage door template. Not before.
  • Must not regress (no flag): Not green -> re-record. used as a bullet's own lead (already exempted by leads_with_prohibition?/LEADS_WITH_PROHIBITION, unaffected by this change since that check runs before any clause-split logic).

Provenance

/os-aidd-lint:mine-blindspots run, external corpus hyperthrive-websites, bucket (a), cop citation trailing_prohibition.rb:25-29 (PARENTHETICAL_NOT), evidence hyperthrive-websites/context/workflow/video-pipeline.md:22 and hyperthrive-websites/docs/onboarding-tasks.md:9. Verified independently by a second agent pass against the live cop source and the live corpus file. 2026-07-27.

Fix direction

Treat bare "not" as a strong-prohibition signal when it immediately follows an existing CLAUSE_SPLIT delimiter (i.e., it is the first word of a post-split clause), independent of whether it's inside parentheses.


Discoverer: hyperthrive-websites, session id unavailable, 2026-07-27. Filed from a mine-blindspots run against this repo as external corpus.

## Context Cross-project filing from a `mine-blindspots` run against the `hyperthrive-websites` repo as external corpus. Bucket (a): "rule exists, logic gap" — tier-1 cop bug, not new rule. ### Problem `hyperthrive-websites/context/workflow/video-pipeline.md:22`: ``` **Guardrail:** half-day time-box. If ffmpeg stitching fights back, fall back to a plain manual video *per hot reply only* — not per prospect. ``` and `hyperthrive-websites/docs/onboarding-tasks.md:9`: ``` 5. **Then** build the garage door template. Not before. ``` Both contain a bare, un-parenthesized trailing "not" clause functioning as a strong prohibition ("not per prospect", "Not before.") immediately after a `CLAUSE_SPLIT` delimiter (an em-dash, and a sentence-end period respectively). Neither is caught. ### Detection `plugins/os-aidd-lint/lib/aidd_lint/cops/trailing_prohibition.rb:25-29`: ```ruby PARENTHETICAL_NOT = /\(\s*not\b[^)]*\)/i ``` This only matches a bare "not" when it is wrapped in parentheses (per the comment: outside parens "not" is just as likely to be an ordinary sentence-lead, e.g. "Not green -> ..."). It never matches `— not per prospect.` (no parens, follows an em-dash) or `Not before.` (no parens, is itself the entire trailing sentence after a period-delimited clause split). `STRONG_PROHIBITION` (line 18) also doesn't match either, since "not" alone isn't in its word list. ### Correction Treat a bare "not" as a strong prohibition specifically when it is the first word of a clause immediately following an existing `CLAUSE_SPLIT` delimiter (semicolon, dash, or sentence end) — i.e., add "not" to (or alongside) `STRONG_PROHIBITION`'s word set, but only for clauses produced by `each_clause` (post-split), not for lines scanned as a whole (which is what protects "Not green -> ..." as a sentence-*lead*, a case this fix doesn't touch since `leads_with_prohibition?` gates whole-line leads separately via `LEADS_WITH_PROHIBITION`, which is unaffected). ### Pass/fail examples - **Must fail (flag):** `...fall back to a plain manual video per hot reply only — not per prospect.` - **Must fail (flag):** `Then build the garage door template. Not before.` - **Must not regress (no flag):** `Not green -> re-record.` used as a bullet's own lead (already exempted by `leads_with_prohibition?`/`LEADS_WITH_PROHIBITION`, unaffected by this change since that check runs before any clause-split logic). ### Provenance `/os-aidd-lint:mine-blindspots` run, external corpus `hyperthrive-websites`, bucket (a), cop citation `trailing_prohibition.rb:25-29` (`PARENTHETICAL_NOT`), evidence `hyperthrive-websites/context/workflow/video-pipeline.md:22` and `hyperthrive-websites/docs/onboarding-tasks.md:9`. Verified independently by a second agent pass against the live cop source and the live corpus file. 2026-07-27. ### Fix direction Treat bare "not" as a strong-prohibition signal when it immediately follows an existing `CLAUSE_SPLIT` delimiter (i.e., it is the first word of a post-split clause), independent of whether it's inside parentheses. -------- **Discoverer:** hyperthrive-websites, session id unavailable, 2026-07-27. Filed from a mine-blindspots run against this repo as external corpus.
Author
Owner

Classified OBVIOUS, but the filed fix direction contains a falsified premise — flagging it, because the correction it implies is what made this shippable.

The ticket claims Not green -> ... is protected by leads_with_prohibition?. It is not, in the shape that actually appears in the cop's own test fixture. In test_flags_a_prohibition_clause_trailing_after_a_semicolon the item is:

**Green gate.** Run the tests. Not green -> stop and report; never lint on red. Once

The bullet leads with **Green gate.**, so leads_with_prohibition? returns false. CLAUSE_SPLIT then yields Not green -> stop and report as the first clause, and trailing_strong_clause returns on first match. I applied the naive widening and confirmed the failure: the cop reports "Not green" instead of "never lint on red", breaking that test's assert_match. Editing that test to accommodate would have reversed the intent documented at PARENTHETICAL_NOT.

The narrowing that works: a bare "not" counts only when it is the item's final clause.

BARE_NOT = /\Anot\b/i
# + trailing_bare_not(text): last clause only, via the existing CLAUSE_SPLIT
  • -- not per prospect. — terminal → flagged.
  • Not before. — terminal → flagged.
  • Not green -> stop and report — followed by more clauses → still exempt, existing test untouched.

This also matches the cop's own name: the concern is a trailing prohibition.

Tests added: test_flags_a_bare_not_clause_after_an_em_dash, test_flags_a_standalone_trailing_not_sentence (both must-fail cases from the ticket), test_ignores_a_bare_not_clause_that_is_not_the_final_clause (pins the narrowing).

Suite: 290 runs, 699 assertions, 0 failures, 0 errors, 0 skips.

Corpus check (99 repo files, before/after): one new offense — CLAUDE.md:52, Hooks not yet registered in any marketplace manifest — not live in any session yet. Worth a reviewer's eye: that clause is a status statement rather than a prohibition, so it is the one debatable hit. Advisory severity, and I judged one soft hit acceptable against two real catches; say the word and I will tighten it.

Branch aidd-lint-trailing-prohibition-gaps, commit 9136cce. Not closing.

Classified **OBVIOUS**, but the filed fix direction contains a falsified premise — flagging it, because the correction it implies is what made this shippable. **The ticket claims** `Not green -> ...` is protected by `leads_with_prohibition?`. It is not, in the shape that actually appears in the cop's own test fixture. In `test_flags_a_prohibition_clause_trailing_after_a_semicolon` the item is: **Green gate.** Run the tests. Not green -> stop and report; never lint on red. Once The bullet leads with `**Green gate.**`, so `leads_with_prohibition?` returns false. CLAUSE_SPLIT then yields `Not green -> stop and report` as the *first* clause, and `trailing_strong_clause` returns on first match. I applied the naive widening and confirmed the failure: the cop reports "Not green" instead of "never lint on red", breaking that test's `assert_match`. Editing that test to accommodate would have reversed the intent documented at PARENTHETICAL_NOT. **The narrowing that works:** a bare "not" counts only when it is the item's *final* clause. BARE_NOT = /\Anot\b/i # + trailing_bare_not(text): last clause only, via the existing CLAUSE_SPLIT - `-- not per prospect.` — terminal → flagged. - `Not before.` — terminal → flagged. - `Not green -> stop and report` — followed by more clauses → still exempt, existing test untouched. This also matches the cop's own name: the concern is a *trailing* prohibition. **Tests added:** `test_flags_a_bare_not_clause_after_an_em_dash`, `test_flags_a_standalone_trailing_not_sentence` (both must-fail cases from the ticket), `test_ignores_a_bare_not_clause_that_is_not_the_final_clause` (pins the narrowing). **Suite:** 290 runs, 699 assertions, 0 failures, 0 errors, 0 skips. **Corpus check** (99 repo files, before/after): one new offense — CLAUDE.md:52, `Hooks not yet registered in any marketplace manifest — not live in any session yet.` Worth a reviewer's eye: that clause is a *status* statement rather than a prohibition, so it is the one debatable hit. Advisory severity, and I judged one soft hit acceptable against two real catches; say the word and I will tighten it. Branch `aidd-lint-trailing-prohibition-gaps`, commit 9136cce. Not closing.
Author
Owner

Follow-up, two items.

1. Owning an asymmetry. In the #159 memo I recommend against table scanning partly because the cop's message ("promote it to its own bullet") is unfollowable advice for the hits it would produce. That objection applies to this change's one soft hit too — CLAUDE.md:52 — not live in any session yet is a status clause, and "promote it to its own bullet" is equally unfollowable there. The difference is ratio, not kind: 1 soft hit out of 3 total here (2 genuine catches) versus 3–4 of 5 in #159. I judged that ratio shippable at advisory severity; if you disagree, the same tightening argument closes both.

2. Test fixture corrected (commit e9e2d84). The bare-"not" test used -- while the reported source line (video-pipeline.md:22) uses a real em-dash , so the em-dash path was measured but not pinned. Renamed to test_flags_a_bare_not_clause_after_either_dash_spelling and now covers both spellings CLAUSE_SPLIT accepts.

Also flagging for a human, unrelated to this issue: branch aidd-lint-trailing-prohibition-gaps now also carries commit bf02a35 ("Check leading-slash paths against the repo root (#162)"), which is not mine — a concurrent session was working in the same worktree and its commit landed on my branch. Someone should move it before merging.

Follow-up, two items. **1. Owning an asymmetry.** In the #159 memo I recommend against table scanning partly because the cop's message ("promote it to its own bullet") is unfollowable advice for the hits it would produce. That objection applies to this change's one soft hit too — CLAUDE.md:52 `— not live in any session yet` is a status clause, and "promote it to its own bullet" is equally unfollowable there. The difference is ratio, not kind: 1 soft hit out of 3 total here (2 genuine catches) versus 3–4 of 5 in #159. I judged that ratio shippable at advisory severity; if you disagree, the same tightening argument closes both. **2. Test fixture corrected** (commit e9e2d84). The bare-"not" test used `--` while the reported source line (video-pipeline.md:22) uses a real em-dash `—`, so the em-dash path was measured but not pinned. Renamed to `test_flags_a_bare_not_clause_after_either_dash_spelling` and now covers both spellings CLAUSE_SPLIT accepts. **Also flagging for a human, unrelated to this issue:** branch `aidd-lint-trailing-prohibition-gaps` now also carries commit bf02a35 ("Check leading-slash paths against the repo root (#162)"), which is not mine — a concurrent session was working in the same worktree and its commit landed on my branch. Someone should move it before merging.
jared closed this issue 2026-07-27 17:05:50 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jared/cc-os#161
No description provided.