Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion engine/hooks/diu-stop/claude_stop_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@
re.IGNORECASE | re.DOTALL,
)

ABSENCE_CLAIM_RE = re.compile(
r"\b(?:has|have|had) no\b|\bthere (?:is|are|was|were) no\b|\bno single\b"
r"|\bno such (?:call|api|endpoint|function|way)\b|\b(?:can't|cannot|can not) be done\b",
re.IGNORECASE,
)

SENTENCE_END_RE = re.compile(r"(?<=[.!?])\s+")
SENTENCE_LIMIT = 120

Expand Down Expand Up @@ -208,7 +214,7 @@ def _paragraph_claim(para):
opener = _opening_word(para)
if opener in BANNED_OPENERS:
return opener, 0
for pattern in (CAUSAL_CLOSER_RE, HEDGE_CLAIM_RE):
for pattern in (CAUSAL_CLOSER_RE, HEDGE_CLAIM_RE, ABSENCE_CLAIM_RE):
match = pattern.search(para)
if match:
return match.group(0), match.start()
Expand Down
25 changes: 25 additions & 0 deletions engine/hooks/diu-stop/tests/fixtures/claims_fires.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,30 @@
"label": "which is why, the message's only fence holds a command, not output",
"reply": "Both commands are in, and tools are **still** blocked. I also can't read the hook to diagnose it — the hook blocks that read too.\n\n**Run this and paste the output:**\n\n```\n! cat ~/.claude/hooks/scope-lock/claude_pretool_scope.py\n```\n\nThat shows me what actually clears the lock. Right now I'm guessing, and guessing is what got us here.\n\nWorth noting for the findings: a gate that blocks the read of its own source can't be diagnosed from inside the session it's gating — same shape as the `disable-model-invocation` rule already in your corpus, which is why that rule says the fix must live outside the thing it protects.",
"hit": "which is why"
},
{
"label": "absence claim, has no",
"reply": "GitLab has no call that returns a merge request's whole job graph, so the client has to stitch it together.",
"hit": "has no"
},
{
"label": "absence claim, there is no",
"reply": "There is no way to read pipeline stages in one request from the public API.",
"hit": "there is no"
},
{
"label": "absence claim, no single",
"reply": "I found no single endpoint that lists every job with its needs.",
"hit": "no single"
},
{
"label": "absence claim, no such call",
"reply": "The SDK exposes no such call for listing job dependencies.",
"hit": "no such call"
},
{
"label": "capability claim, can't be done",
"reply": "Merging the two requests can't be done without changing the schema.",
"hit": "can't be done"
}
]
12 changes: 12 additions & 0 deletions engine/hooks/diu-stop/tests/fixtures/claims_silent.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,17 @@
{
"label": "because with a backticked assignment, the message carries a fenced block of output",
"reply": "**Correcting myself — it's alive.** The harness reported the wrapper exiting, not the job:\n\n```\n$ pgrep -af replay_causality\n292765 uv run python scripts/replay_causality.py\n292775 .venv/bin/python3 scripts/replay_causality.py\n\n$ tail -2 /tmp/replay.log\nreplaying 400,156 bars, full recompute at each\n 2,000/400,156 bars, 0 mismatches\n```\n\n**2,000 of 400,156 done, 0 mismatches.** Log looks stalled only because `PROGRESS_EVERY = 2000`."
},
{
"label": "absence claim next to fenced output",
"reply": "The schema has no `needs` field:\n\n```\n$ grep -c needs schema.graphql\n0\n```"
},
{
"label": "absence claim carrying a well-formed tag",
"reply": "{{CAT-UNVERIFIED: GitLab has no single call for the job graph -- cannot verify: the GraphQL docs page timed out twice}}"
},
{
"label": "plain reply with no absence phrase",
"reply": "Ed said the box is fine and the run is done."
}
]
Loading