From 7ccb6a2423e7129a40d21ca7d6c3b1a55529edda Mon Sep 17 00:00:00 2001 From: Invoker Bot Date: Sat, 12 Sep 2026 06:16:03 +0000 Subject: [PATCH 1/3] =?UTF-8?q?invoker:=20wf-1789193509008-2/fix-fenced-pa?= =?UTF-8?q?ragraph-hides-its-neighbours=20=E2=80=94=20Examine=20the=20pros?= =?UTF-8?q?e=20surviving=20after=20fenced=20blocks=20are=20removed,=20rath?= =?UTF-8?q?er=20than=20passing=20over=20the=20whole=20paragraph.=20Review?= =?UTF-8?q?=20claim:=20A=20paragraph=20holding=20a=20fenced=20block=20stil?= =?UTF-8?q?l=20has=20its=20non-fenced=20prose=20examined,=20and=20a=20sent?= =?UTF-8?q?ence=20that=20earned=20its=20silence=20honestly=20keeps=20it.?= =?UTF-8?q?=20Review=20lane:=20behavior=20Safety=20invariant:=20Every=20me?= =?UTF-8?q?ssage=20the=20detector=20is=20silent=20on=20today=20for=20a=20r?= =?UTF-8?q?eason=20other=20than=20the=20fenced-paragraph=20branch=20stays?= =?UTF-8?q?=20silent;=20the=20existing=20inline-code=20and=20file:line=20c?= =?UTF-8?q?learing=20keeps=20working=20unchanged.=20Effectiveness=20measur?= =?UTF-8?q?ement:=20`python3=20engine/hooks/diu-stop/tests/test=5Fhooks.py?= =?UTF-8?q?`=20exits=200,=20with=20a=20new=20positive=20case=20that=20exit?= =?UTF-8?q?s=20non-zero=20before=20this=20change,=20plus=20the=20negative?= =?UTF-8?q?=20case=20described=20below=20that=20must=20stay=20silent=20bot?= =?UTF-8?q?h=20before=20and=20after.=20Slice=20rationale:=20One=20conceptu?= =?UTF-8?q?al=20unit:=20which=20text=20inside=20a=20paragraph=20the=20dete?= =?UTF-8?q?ctor=20looks=20at.=20Architectural=20effect:=20None;=20no=20new?= =?UTF-8?q?=20module=20boundaries.=20The=20paragraph=20loop=20gains=20a=20?= =?UTF-8?q?normalization=20ahead=20of=20the=20existing=20per-paragraph=20b?= =?UTF-8?q?ranches.=20Goal:=20Stop=20a=20fenced=20block=20from=20buying=20?= =?UTF-8?q?silence=20for=20unrelated=20sentences=20sharing=20its=20paragra?= =?UTF-8?q?ph.=20Motivation:=20A=20false=20remedy=20claim=20reached=20an?= =?UTF-8?q?=20operator=20because=20it=20sat=20in=20the=20same=20paragraph?= =?UTF-8?q?=20as=20an=20unrelated=20fence.=20The=20operator=20acted=20on?= =?UTF-8?q?=20it=20and=20went=20looking=20for=20a=20package=20upgrade=20th?= =?UTF-8?q?at=20does=20not=20exist.=20Alternative=20considerations:=20Remo?= =?UTF-8?q?ving=20the=20fenced-paragraph=20branch=20outright=20was=20set?= =?UTF-8?q?=20aside=20--=20it=20would=20surface=20the=20fence=20body=20its?= =?UTF-8?q?elf=20as=20prose=20and=20fire=20on=20code.=20Splitting=20paragr?= =?UTF-8?q?aphs=20on=20fence=20boundaries=20was=20set=20aside=20as=20more?= =?UTF-8?q?=20disruptive=20to=20the=20surrounding=20offsets=20than=20remov?= =?UTF-8?q?ing=20fenced=20spans=20in=20place.=20Implementation=20details:?= =?UTF-8?q?=20Before=20the=20existing=20per-paragraph=20branches,=20remove?= =?UTF-8?q?=20fenced=20spans=20from=20the=20paragraph=20text=20and=20carry?= =?UTF-8?q?=20on=20with=20what=20remains.=20When=20nothing=20but=20whitesp?= =?UTF-8?q?ace=20remains,=20behave=20exactly=20as=20the=20current=20branch?= =?UTF-8?q?=20does.=20Non-goals:=20No=20vocabulary=20changes=20to=20the=20?= =?UTF-8?q?remedy=20or=20causal=20patterns=20--=20that=20is=20a=20separate?= =?UTF-8?q?=20slice=20depending=20on=20PR=20371.=20No=20change=20to=20firs?= =?UTF-8?q?t-hit-versus-every-hit=20behavior=20--=20that=20is=20PR=20433.?= =?UTF-8?q?=20No=20change=20to=20the=20stop=5Fhook=5Factive=20early=20retu?= =?UTF-8?q?rn=20--=20that=20is=20PR=20416.=20Layer:=20domain=20Feature=20s?= =?UTF-8?q?tate:=20active=20Files:=20engine/hooks/diu-stop/claude=5Fstop?= =?UTF-8?q?=5Fcheck.py,=20engine/hooks/diu-stop/tests/test=5Fhooks.py=20Ch?= =?UTF-8?q?ange=20types:=20-=20engine/hooks/diu-stop/claude=5Fstop=5Fcheck?= =?UTF-8?q?.py:=20modify=20-=20engine/hooks/diu-stop/tests/test=5Fhooks.py?= =?UTF-8?q?:=20modify=20Acceptance=20criteria:=20-=20`python3=20engine/hoo?= =?UTF-8?q?ks/diu-stop/tests/test=5Fhooks.py`=20exits=200=20after=20the=20?= =?UTF-8?q?change,=20with=20all=2073=20existing=20cases=20still=20passing.?= =?UTF-8?q?=20-=20A=20paragraph=20of=20the=20shape=20"prose,=20fenced=20bl?= =?UTF-8?q?ock,=20unproven=20remedy=20sentence"=20is=20flagged.=20-=20A=20?= =?UTF-8?q?paragraph=20whose=20claim=20sits=20beside=20a=20genuine=20file:?= =?UTF-8?q?line=20citation=20stays=20silent.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Solution: Examine the prose surviving after fenced blocks are removed, rather than passing over the whole paragraph. Review claim: A paragraph holding a fenced block still has its non-fenced prose examined, and a sentence that earned its silence honestly keeps it. Review lane: behavior Safety invariant: Every message the detector is silent on today for a reason other than the fenced-paragraph branch stays silent; the existing inline-code and file:line clearing keeps working unchanged. Effectiveness measurement: `python3 engine/hooks/diu-stop/tests/test_hooks.py` exits 0, with a new positive case that exits non-zero before this change, plus the negative case described below that must stay silent both before and after. Slice rationale: One conceptual unit: which text inside a paragraph the detector looks at. Architectural effect: None; no new module boundaries. The paragraph loop gains a normalization ahead of the existing per-paragraph branches. Goal: Stop a fenced block from buying silence for unrelated sentences sharing its paragraph. Motivation: A false remedy claim reached an operator because it sat in the same paragraph as an unrelated fence. The operator acted on it and went looking for a package upgrade that does not exist. Alternative considerations: Removing the fenced-paragraph branch outright was set aside -- it would surface the fence body itself as prose and fire on code. Splitting paragraphs on fence boundaries was set aside as more disruptive to the surrounding offsets than removing fenced spans in place. Implementation details: Before the existing per-paragraph branches, remove fenced spans from the paragraph text and carry on with what remains. When nothing but whitespace remains, behave exactly as the current branch does. Non-goals: No vocabulary changes to the remedy or causal patterns -- that is a separate slice depending on PR 371. No change to first-hit-versus-every-hit behavior -- that is PR 433. No change to the stop_hook_active early return -- that is PR 416. Layer: domain Feature state: active Files: engine/hooks/diu-stop/claude_stop_check.py, engine/hooks/diu-stop/tests/test_hooks.py Change types: - engine/hooks/diu-stop/claude_stop_check.py: modify - engine/hooks/diu-stop/tests/test_hooks.py: modify Acceptance criteria: - `python3 engine/hooks/diu-stop/tests/test_hooks.py` exits 0 after the change, with all 73 existing cases still passing. - A paragraph of the shape "prose, fenced block, unproven remedy sentence" is flagged. - A paragraph whose claim sits beside a genuine file:line citation stays silent. Invoker-Finalize-Id: 7fdeca09-72d7-4d0a-8d68-4cd443e73cac --- engine/hooks/diu-stop/claude_stop_check.py | 6 ++++++ engine/hooks/diu-stop/tests/test_hooks.py | 25 ++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/engine/hooks/diu-stop/claude_stop_check.py b/engine/hooks/diu-stop/claude_stop_check.py index 09b3d50..acb2443 100755 --- a/engine/hooks/diu-stop/claude_stop_check.py +++ b/engine/hooks/diu-stop/claude_stop_check.py @@ -67,6 +67,7 @@ FENCE_MARKER = "```" FENCED_BODY_RE = re.compile(r"```[^\n]*\n(.*?)```", re.DOTALL) INLINE_CODE_RE = re.compile(r"`([^`\n]+)`") +FILE_LINE_RE = re.compile(r"(? |\+\+\+ |--- |@@ |diff --git|commit [0-9a-f]{7,}|[0-9a-f]{7,10} )" r"|Traceback|^\s*at [\w.$<>]+ \(.*:\d+:\d+\)" @@ -142,10 +143,15 @@ def find_unverified_claim(message): truth check.""" fenced_output = any(OUTPUT_SHAPE_RE.search(body) for body in FENCED_BODY_RE.findall(message)) for para in re.split(r"\n\s*\n", message): + para = FENCED_BODY_RE.sub("", para) + if not para.strip(): + continue if FENCE_MARKER in para: continue if markers.excuses_paragraph(para): continue + if FILE_LINE_RE.search(para): + continue inline = INLINE_CODE_RE.findall(para) if inline and (fenced_output or any(OUTPUT_SHAPE_RE.search(code) for code in inline)): continue diff --git a/engine/hooks/diu-stop/tests/test_hooks.py b/engine/hooks/diu-stop/tests/test_hooks.py index d18ae41..ef25678 100644 --- a/engine/hooks/diu-stop/tests/test_hooks.py +++ b/engine/hooks/diu-stop/tests/test_hooks.py @@ -305,6 +305,31 @@ def test_unrelated_later_backtick_does_not_suppress_causal_claim(self): self.assertTrue(blocked) self.assertIn("unverified-shaped claim", err.lower()) + def test_fenced_block_does_not_silence_later_prose_in_same_paragraph(self): + message = ( + "I checked the local snippet.\n" + "```text\n" + "unrelated fenced content\n" + "```\n" + "This fixes it now." + ) + blocked, err = run_claude_check({"last_assistant_message": message}) + self.assertTrue(blocked) + self.assertIn("this fixes it", err.lower()) + + def test_file_line_citation_still_silences_claim_after_fence_normalization(self): + message = ( + "I checked the relevant snippet.\n" + "```ts\n" + "const guard = true;\n" + "```\n" + "The issue was the stale guard at file.ts:1276." + ) + self.assertIsNone(claude_stop_check.find_unverified_claim(message)) + blocked, err = run_claude_check({"last_assistant_message": message}) + self.assertFalse(blocked) + self.assertEqual(err, "") + def test_hedge_i_think_it_happened_without_evidence_is_flagged(self): message = "I think the deploy happened around 2am, so that's why the build is stale." blocked, err = run_claude_check({"last_assistant_message": message}) From 3464cac598242b1ccd27eb6419bdcf23f7eb7dd1 Mon Sep 17 00:00:00 2001 From: Invoker Bot Date: Sat, 12 Sep 2026 06:16:51 +0000 Subject: [PATCH 2/3] =?UTF-8?q?invoker:=20wf-1789193509008-2/verify-fenced?= =?UTF-8?q?-paragraph-hides-its-neighbours=20=E2=80=94=20Run=20the=20hook'?= =?UTF-8?q?s=20own=20suite=20as=20the=20shared=20repro=20for=20this=20slic?= =?UTF-8?q?e.=20Review=20claim:=20The=20suite=20exits=200=20only=20when=20?= =?UTF-8?q?fenced=20paragraphs=20have=20their=20surviving=20prose=20examin?= =?UTF-8?q?ed=20and=20honest=20citations=20still=20buy=20silence.=20Review?= =?UTF-8?q?=20lane:=20proof=20Safety=20invariant:=20The=20repro=20is=20ide?= =?UTF-8?q?ntical=20before=20and=20after=20the=20change;=20the=20new=20pos?= =?UTF-8?q?itive=20case=20exits=20non-zero=20before=20and=200=20after.=20E?= =?UTF-8?q?ffectiveness=20measurement:=20The=20exit=20status=20of=20the=20?= =?UTF-8?q?suite=20is=20the=20effectiveness=20signal=20for=20this=20slice.?= =?UTF-8?q?=20Slice=20rationale:=20One=20conceptual=20unit:=20the=20determ?= =?UTF-8?q?inistic=20repro=20only.=20Architectural=20effect:=20None.=20Goa?= =?UTF-8?q?l:=20Deterministically=20prove=20the=20change.=20Motivation:=20?= =?UTF-8?q?This=20branch=20shipped=20with=20no=20case=20covering=20a=20mix?= =?UTF-8?q?ed=20prose-and-fence=20paragraph,=20which=20is=20why=20it=20sur?= =?UTF-8?q?vived=20long=20enough=20to=20mislead=20an=20operator.=20Alterna?= =?UTF-8?q?tive=20considerations:=20Adding=20only=20the=20positive=20case?= =?UTF-8?q?=20was=20set=20aside=20--=20without=20the=20citation=20case,=20?= =?UTF-8?q?a=20change=20that=20floods=20honest=20messages=20would=20still?= =?UTF-8?q?=20exit=200.=20Implementation=20details:=20Execute=20the=20suit?= =?UTF-8?q?e=20as=20the=20terminal=20gate.=20Non-goals:=20No=20product=20e?= =?UTF-8?q?dits=20here.=20Layer:=20e2e=5Fregression=20Feature=20state:=20a?= =?UTF-8?q?ctive?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exit code: 0 Invoker-Finalize-Id: 88db3385-a0a6-46b0-8d2d-8091c0cac657 From 337dafce16c226204d00818001fd8fd23754da26 Mon Sep 17 00:00:00 2001 From: Invoker Bot Date: Sat, 12 Sep 2026 06:17:34 +0000 Subject: [PATCH 3/3] =?UTF-8?q?invoker:=20wf-1789193509008-2/scrub-handoff?= =?UTF-8?q?-artifacts=20=E2=80=94=20Terminal=20read-only=20gate=20confirmi?= =?UTF-8?q?ng=20no=20ephemeral=20handoff=20files=20were=20left=20behind.?= =?UTF-8?q?=20Review=20claim:=20The=20workflow=20leaves=20no=20ephemeral?= =?UTF-8?q?=20handoff=20files=20in=20the=20tree.=20Review=20lane:=20proof?= =?UTF-8?q?=20Safety=20invariant:=20Read-only=20--=20never=20deletes=20fil?= =?UTF-8?q?es,=20alters=20the=20index,=20or=20commits=20caller=20work.=20E?= =?UTF-8?q?ffectiveness=20measurement:=20A=20non-zero=20exit=20when=20ephe?= =?UTF-8?q?meral=20handoff=20files=20remain=20is=20the=20signal.=20Slice?= =?UTF-8?q?=20rationale:=20One=20conceptual=20unit:=20the=20hygiene=20gate?= =?UTF-8?q?.=20Architectural=20effect:=20None.=20Goal:=20Confirm=20no=20ep?= =?UTF-8?q?hemeral=20handoff=20files=20remain=20after=20every=20other=20ta?= =?UTF-8?q?sk=20finishes.=20Motivation:=20Ephemeral=20inter-task=20files?= =?UTF-8?q?=20leak=20into=20the=20diff=20and=20read=20as=20part=20of=20the?= =?UTF-8?q?=20change.=20Alternative=20considerations:=20Manual=20inspectio?= =?UTF-8?q?n=20was=20set=20aside=20as=20non-deterministic.=20Implementatio?= =?UTF-8?q?n=20details:=20Run=20scripts/scrub-handoff-artifacts.sh=20witho?= =?UTF-8?q?ut=20--apply.=20Non-goals:=20No=20deletion,=20no=20index=20chan?= =?UTF-8?q?ges,=20no=20commits.=20Layer:=20e2e=5Fregression=20Feature=20st?= =?UTF-8?q?ate:=20active?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exit code: 0 Invoker-Finalize-Id: 192f2887-89ae-4cfe-aa67-267352f8286b