From 465315d902ed49956264347554542a5525c54f8a Mon Sep 17 00:00:00 2001 From: Invoker Bot Date: Sun, 27 Sep 2026 00:17:35 +0000 Subject: [PATCH 01/17] =?UTF-8?q?invoker:=20wf-1789407151357-66/implement-?= =?UTF-8?q?hook-unverified-tag-ledger=20=E2=80=94=20Put=20the=20unverified?= =?UTF-8?q?-tag-ledger=20hook=20onto=20the=20shared=20hook=20code.=20Revie?= =?UTF-8?q?w=20claim:=20This=20hook=20reports=20findings=20to=20the=20shar?= =?UTF-8?q?ed=20hook=20code,=20which=20applies=20its=20registry=20mode=20a?= =?UTF-8?q?nd=20writes=20event=20rows.=20It=20keeps=20mode=20stop.=20Revie?= =?UTF-8?q?w=20lane:=20behavior=20Safety=20invariant:=20The=20hook=20gives?= =?UTF-8?q?=20the=20same=20stop,=20warn,=20or=20silent=20result=20on=20eve?= =?UTF-8?q?ry=20case=20in=20its=20current=20test=20folder,=20except=20the?= =?UTF-8?q?=20mode=20change=20named=20in=20this=20claim,=20and=20its=20tes?= =?UTF-8?q?t=20folder=20keeps=20exiting=200.=20Effectiveness=20measurement?= =?UTF-8?q?:=20`python3=20-m=20unittest=20discover=20-s=20engine/hooks/unv?= =?UTF-8?q?erified-tag-ledger/tests`=20exits=200,=20and=20the=20new=20mode?= =?UTF-8?q?-override=20case=20fails=20before=20this=20change.=20Slice=20ra?= =?UTF-8?q?tionale:=20One=20hook=20per=20workflow,=20as=20the=20user=20ask?= =?UTF-8?q?ed,=20so=20each=20migration=20is=20reviewed=20on=20its=20own.?= =?UTF-8?q?=20Architectural=20effect:=20The=20unverified-tag-ledger=20entr?= =?UTF-8?q?y=20scripts=20become=20thin=20calls=20into=20the=20shared=20run?= =?UTF-8?q?time;=20its=20detection=20returns=20findings.=20Goal:=20Stops?= =?UTF-8?q?=20when=20unchecked=20claims=20pile=20up.=20Keep=20that=20behav?= =?UTF-8?q?ior=20while=20its=20mode=20moves=20into=20the=20registry.=20Mot?= =?UTF-8?q?ivation:=20Mode=20and=20output=20shape=20live=20inside=20each?= =?UTF-8?q?=20hook=20today;=20the=20shared=20code=20makes=20a=20mode=20cha?= =?UTF-8?q?nge=20a=20one-line=20registry=20edit.=20Alternative=20considera?= =?UTF-8?q?tions:=20Migrating=20several=20hooks=20per=20workflow=20was=20s?= =?UTF-8?q?et=20aside=20because=20the=20user=20asked=20for=20one=20hook=20?= =?UTF-8?q?per=20workflow.=20Implementation=20details:=20Turn=20this=20hoo?= =?UTF-8?q?k's=20detection=20into=20detect(event)=20returning=20Finding=20?= =?UTF-8?q?objects=20with=20stable=20rule=20ids,=20and=20make=20each=20har?= =?UTF-8?q?ness=20entry=20script=20call=20run=5Fhook=20from=20engine/hooks?= =?UTF-8?q?/=5Fsdk/runtime.py.=20It=20keeps=20mode=20stop.=20Non-goals:=20?= =?UTF-8?q?No=20change=20to=20what=20the=20hook=20detects.=20No=20other=20?= =?UTF-8?q?hook=20changes.=20Layer:=20domain=20Feature=20state:=20active?= =?UTF-8?q?=20Files:=20engine/hooks/unverified-tag-ledger/claude=5Fprompt?= =?UTF-8?q?=5Freminder.py,=20engine/hooks/unverified-tag-ledger/claude=5Fs?= =?UTF-8?q?top=5Fcheck.py,=20engine/hooks/unverified-tag-ledger/detect.py,?= =?UTF-8?q?=20engine/hooks/unverified-tag-ledger/install=5Fclaude=5Fhook.p?= =?UTF-8?q?y,=20engine/hooks/unverified-tag-ledger/tests/test=5Fhooks=5Fsd?= =?UTF-8?q?k=5Fmode.py=20Change=20types:=20-=20engine/hooks/unverified-tag?= =?UTF-8?q?-ledger/claude=5Fprompt=5Freminder.py:=20modify=20-=20engine/ho?= =?UTF-8?q?oks/unverified-tag-ledger/claude=5Fstop=5Fcheck.py:=20modify=20?= =?UTF-8?q?-=20engine/hooks/unverified-tag-ledger/detect.py:=20modify=20-?= =?UTF-8?q?=20engine/hooks/unverified-tag-ledger/install=5Fclaude=5Fhook.p?= =?UTF-8?q?y:=20modify=20-=20engine/hooks/unverified-tag-ledger/tests/test?= =?UTF-8?q?=5Fhooks=5Fsdk=5Fmode.py:=20create=20Acceptance=20criteria:=20-?= =?UTF-8?q?=20`python3=20-m=20unittest=20discover=20-s=20engine/hooks/unve?= =?UTF-8?q?rified-tag-ledger/tests`=20exits=200.=20-=20With=20CATSTACK=5FH?= =?UTF-8?q?OOK=5FMODE=5FUNVERIFIED=5FTAG=5FLEDGER=20set=20to=20warn,=20a?= =?UTF-8?q?=20case=20that=20stops=20today=20produces=20a=20warning=20inste?= =?UTF-8?q?ad,=20proving=20the=20registry=20mode=20drives=20the=20response?= =?UTF-8?q?.=20-=20Each=20finding=20writes=20one=20event=20row=20with=20th?= =?UTF-8?q?e=20hook's=20rule=5Fid.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exit code: 0 Invoker-Finalize-Id: 48e433fc-e1d3-4b76-94e5-ffd3eb3fb67c From f31826d2a1f96b6734d3b55c8048176462834825 Mon Sep 17 00:00:00 2001 From: Invoker Bot Date: Sun, 27 Sep 2026 00:28:31 +0000 Subject: [PATCH 02/17] =?UTF-8?q?invoker:=20wf-1789407151357-66/verify-hoo?= =?UTF-8?q?k-unverified-tag-ledger=20=E2=80=94=20Run=20the=20deterministic?= =?UTF-8?q?=20proof=20for=20put=20the=20unverified-tag-ledger=20hook=20ont?= =?UTF-8?q?o=20the=20shared=20hook=20code.=20Review=20claim:=20The=20proof?= =?UTF-8?q?=20exits=200=20only=20when=20put=20the=20unverified-tag-ledger?= =?UTF-8?q?=20hook=20onto=20the=20shared=20hook=20code=20holds.=20Review?= =?UTF-8?q?=20lane:=20proof=20Safety=20invariant:=20Proof=20only;=20it=20c?= =?UTF-8?q?hanges=20no=20product=20behavior.=20Effectiveness=20measurement?= =?UTF-8?q?:=20The=20exit=20status=20of=20`python3=20-m=20unittest=20disco?= =?UTF-8?q?ver=20-s=20engine/hooks/unverified-tag-ledger/tests`=20is=20the?= =?UTF-8?q?=20signal=20for=20this=20slice.=20Slice=20rationale:=20One=20pr?= =?UTF-8?q?oof=20unit=20for=20this=20workflow.=20Architectural=20effect:?= =?UTF-8?q?=20None;=20verification=20only.=20Goal:=20Prove=20put=20the=20u?= =?UTF-8?q?nverified-tag-ledger=20hook=20onto=20the=20shared=20hook=20code?= =?UTF-8?q?=20with=20one=20deterministic=20run.=20Motivation:=20Each=20wor?= =?UTF-8?q?kflow=20carries=20its=20own=20proof=20so=20a=20reviewer=20can?= =?UTF-8?q?=20trust=20the=20slice=20alone.=20Alternative=20considerations:?= =?UTF-8?q?=20Manual=20inspection=20was=20set=20aside=20as=20non-determini?= =?UTF-8?q?stic.=20Implementation=20details:=20Execute=20the=20proof=20as?= =?UTF-8?q?=20a=20terminal=20gate.=20Non-goals:=20No=20product=20edits=20h?= =?UTF-8?q?ere.=20Layer:=20e2e=5Fregression=20Feature=20state:=20active?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exit code: 0 Invoker-Finalize-Id: d33259c1-d2cd-4a03-9752-f1898bf787ec From 53422e4848e0400cae0cbc110e8bfc772814ce8a Mon Sep 17 00:00:00 2001 From: Invoker Bot Date: Sun, 27 Sep 2026 00:29:36 +0000 Subject: [PATCH 03/17] =?UTF-8?q?invoker:=20wf-1789407151357-66/scrub-hand?= =?UTF-8?q?off-artifacts=20=E2=80=94=20Terminal=20read-only=20gate=20confi?= =?UTF-8?q?rming=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;=20it=20never=20deletes=20fi?= =?UTF-8?q?les,=20alters=20the=20index,=20or=20commits=20caller=20work.=20?= =?UTF-8?q?Effectiveness=20measurement:=20A=20non-zero=20exit=20when=20eph?= =?UTF-8?q?emeral=20handoff=20files=20remain=20is=20the=20signal.=20Slice?= =?UTF-8?q?=20rationale:=20One=20unit:=20the=20hygiene=20gate.=20Architect?= =?UTF-8?q?ural=20effect:=20None.=20Goal:=20Confirm=20no=20ephemeral=20han?= =?UTF-8?q?doff=20files=20remain=20after=20every=20other=20task=20finishes?= =?UTF-8?q?.=20Motivation:=20Ephemeral=20inter-task=20files=20leak=20into?= =?UTF-8?q?=20the=20diff=20and=20read=20as=20part=20of=20the=20change.=20A?= =?UTF-8?q?lternative=20considerations:=20Manual=20inspection=20was=20set?= =?UTF-8?q?=20aside=20as=20non-deterministic.=20Implementation=20details:?= =?UTF-8?q?=20Run=20scripts/scrub-handoff-artifacts.sh=20in=20check=20mode?= =?UTF-8?q?.=20Non-goals:=20No=20deletion,=20no=20index=20changes,=20no=20?= =?UTF-8?q?commits.=20Layer:=20e2e=5Fregression=20Feature=20state:=20activ?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exit code: 0 Invoker-Finalize-Id: 88a9a276-8784-4563-bf65-7aeaa4174240 From bb94e67a1801395a0079afa0d764568d802bcffd Mon Sep 17 00:00:00 2001 From: Invoker Bot Date: Sun, 27 Sep 2026 00:32:33 +0000 Subject: [PATCH 04/17] =?UTF-8?q?invoker:=20wf-1789407155931-67/implement-?= =?UTF-8?q?hook-verdict-flip-watch=20=E2=80=94=20Put=20the=20verdict-flip-?= =?UTF-8?q?watch=20hook=20onto=20the=20shared=20hook=20code.=20Review=20cl?= =?UTF-8?q?aim:=20This=20hook=20reports=20findings=20to=20the=20shared=20h?= =?UTF-8?q?ook=20code,=20which=20applies=20its=20registry=20mode=20and=20w?= =?UTF-8?q?rites=20event=20rows.=20It=20keeps=20mode=20warn.=20Review=20la?= =?UTF-8?q?ne:=20behavior=20Safety=20invariant:=20The=20hook=20gives=20the?= =?UTF-8?q?=20same=20stop,=20warn,=20or=20silent=20result=20on=20every=20c?= =?UTF-8?q?ase=20in=20its=20current=20test=20folder,=20except=20the=20mode?= =?UTF-8?q?=20change=20named=20in=20this=20claim,=20and=20its=20test=20fol?= =?UTF-8?q?der=20keeps=20exiting=200.=20Effectiveness=20measurement:=20`py?= =?UTF-8?q?thon3=20-m=20unittest=20discover=20-s=20engine/hooks/verdict-fl?= =?UTF-8?q?ip-watch/tests`=20exits=200,=20and=20the=20new=20mode-override?= =?UTF-8?q?=20case=20fails=20before=20this=20change.=20Slice=20rationale:?= =?UTF-8?q?=20One=20hook=20per=20workflow,=20as=20the=20user=20asked,=20so?= =?UTF-8?q?=20each=20migration=20is=20reviewed=20on=20its=20own.=20Archite?= =?UTF-8?q?ctural=20effect:=20The=20verdict-flip-watch=20entry=20scripts?= =?UTF-8?q?=20become=20thin=20calls=20into=20the=20shared=20runtime;=20its?= =?UTF-8?q?=20detection=20returns=20findings.=20Goal:=20Notes=20a=20check?= =?UTF-8?q?=20that=20passed=20earlier=20and=20failed=20later.=20Keep=20tha?= =?UTF-8?q?t=20behavior=20while=20its=20mode=20moves=20into=20the=20regist?= =?UTF-8?q?ry.=20Motivation:=20Mode=20and=20output=20shape=20live=20inside?= =?UTF-8?q?=20each=20hook=20today;=20the=20shared=20code=20makes=20a=20mod?= =?UTF-8?q?e=20change=20a=20one-line=20registry=20edit.=20Alternative=20co?= =?UTF-8?q?nsiderations:=20Migrating=20several=20hooks=20per=20workflow=20?= =?UTF-8?q?was=20set=20aside=20because=20the=20user=20asked=20for=20one=20?= =?UTF-8?q?hook=20per=20workflow.=20Implementation=20details:=20Turn=20thi?= =?UTF-8?q?s=20hook's=20detection=20into=20detect(event)=20returning=20Fin?= =?UTF-8?q?ding=20objects=20with=20stable=20rule=20ids,=20and=20make=20eac?= =?UTF-8?q?h=20harness=20entry=20script=20call=20run=5Fhook=20from=20engin?= =?UTF-8?q?e/hooks/=5Fsdk/runtime.py.=20It=20keeps=20mode=20warn.=20Non-go?= =?UTF-8?q?als:=20No=20change=20to=20what=20the=20hook=20detects.=20No=20o?= =?UTF-8?q?ther=20hook=20changes.=20Layer:=20domain=20Feature=20state:=20a?= =?UTF-8?q?ctive=20Files:=20engine/hooks/verdict-flip-watch/claude=5Fstop?= =?UTF-8?q?=5Fcheck.py,=20engine/hooks/verdict-flip-watch/detect.py,=20eng?= =?UTF-8?q?ine/hooks/verdict-flip-watch/install=5Fclaude=5Fhook.py,=20engi?= =?UTF-8?q?ne/hooks/verdict-flip-watch/tests/test=5Fhooks=5Fsdk=5Fmode.py?= =?UTF-8?q?=20Change=20types:=20-=20engine/hooks/verdict-flip-watch/claude?= =?UTF-8?q?=5Fstop=5Fcheck.py:=20modify=20-=20engine/hooks/verdict-flip-wa?= =?UTF-8?q?tch/detect.py:=20modify=20-=20engine/hooks/verdict-flip-watch/i?= =?UTF-8?q?nstall=5Fclaude=5Fhook.py:=20modify=20-=20engine/hooks/verdict-?= =?UTF-8?q?flip-watch/tests/test=5Fhooks=5Fsdk=5Fmode.py:=20create=20Accep?= =?UTF-8?q?tance=20criteria:=20-=20`python3=20-m=20unittest=20discover=20-?= =?UTF-8?q?s=20engine/hooks/verdict-flip-watch/tests`=20exits=200.=20-=20W?= =?UTF-8?q?ith=20CATSTACK=5FHOOK=5FMODE=5FVERDICT=5FFLIP=5FWATCH=20set=20t?= =?UTF-8?q?o=20warn,=20a=20case=20that=20stops=20today=20produces=20a=20wa?= =?UTF-8?q?rning=20instead,=20proving=20the=20registry=20mode=20drives=20t?= =?UTF-8?q?he=20response.=20-=20Each=20finding=20writes=20one=20event=20ro?= =?UTF-8?q?w=20with=20the=20hook's=20rule=5Fid.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exit code: 0 Invoker-Finalize-Id: 5c05c0bc-f01b-490e-a0ef-a9903bd7be6f From 55a721d8ea71e717851620155c3993e55d49111d Mon Sep 17 00:00:00 2001 From: Invoker Bot Date: Sun, 27 Sep 2026 00:33:35 +0000 Subject: [PATCH 05/17] =?UTF-8?q?invoker:=20wf-1789407155931-67/verify-hoo?= =?UTF-8?q?k-verdict-flip-watch=20=E2=80=94=20Run=20the=20deterministic=20?= =?UTF-8?q?proof=20for=20put=20the=20verdict-flip-watch=20hook=20onto=20th?= =?UTF-8?q?e=20shared=20hook=20code.=20Review=20claim:=20The=20proof=20exi?= =?UTF-8?q?ts=200=20only=20when=20put=20the=20verdict-flip-watch=20hook=20?= =?UTF-8?q?onto=20the=20shared=20hook=20code=20holds.=20Review=20lane:=20p?= =?UTF-8?q?roof=20Safety=20invariant:=20Proof=20only;=20it=20changes=20no?= =?UTF-8?q?=20product=20behavior.=20Effectiveness=20measurement:=20The=20e?= =?UTF-8?q?xit=20status=20of=20`python3=20-m=20unittest=20discover=20-s=20?= =?UTF-8?q?engine/hooks/verdict-flip-watch/tests`=20is=20the=20signal=20fo?= =?UTF-8?q?r=20this=20slice.=20Slice=20rationale:=20One=20proof=20unit=20f?= =?UTF-8?q?or=20this=20workflow.=20Architectural=20effect:=20None;=20verif?= =?UTF-8?q?ication=20only.=20Goal:=20Prove=20put=20the=20verdict-flip-watc?= =?UTF-8?q?h=20hook=20onto=20the=20shared=20hook=20code=20with=20one=20det?= =?UTF-8?q?erministic=20run.=20Motivation:=20Each=20workflow=20carries=20i?= =?UTF-8?q?ts=20own=20proof=20so=20a=20reviewer=20can=20trust=20the=20slic?= =?UTF-8?q?e=20alone.=20Alternative=20considerations:=20Manual=20inspectio?= =?UTF-8?q?n=20was=20set=20aside=20as=20non-deterministic.=20Implementatio?= =?UTF-8?q?n=20details:=20Execute=20the=20proof=20as=20a=20terminal=20gate?= =?UTF-8?q?.=20Non-goals:=20No=20product=20edits=20here.=20Layer:=20e2e=5F?= =?UTF-8?q?regression=20Feature=20state:=20active?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exit code: 0 Invoker-Finalize-Id: 426060cc-ee4e-4853-a0ed-01c4ee43290c From 175457a33586a6635f2ba7cafcc0153d76771d1a Mon Sep 17 00:00:00 2001 From: Invoker Bot Date: Sun, 27 Sep 2026 00:34:37 +0000 Subject: [PATCH 06/17] =?UTF-8?q?invoker:=20wf-1789407155931-67/scrub-hand?= =?UTF-8?q?off-artifacts=20=E2=80=94=20Terminal=20read-only=20gate=20confi?= =?UTF-8?q?rming=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;=20it=20never=20deletes=20fi?= =?UTF-8?q?les,=20alters=20the=20index,=20or=20commits=20caller=20work.=20?= =?UTF-8?q?Effectiveness=20measurement:=20A=20non-zero=20exit=20when=20eph?= =?UTF-8?q?emeral=20handoff=20files=20remain=20is=20the=20signal.=20Slice?= =?UTF-8?q?=20rationale:=20One=20unit:=20the=20hygiene=20gate.=20Architect?= =?UTF-8?q?ural=20effect:=20None.=20Goal:=20Confirm=20no=20ephemeral=20han?= =?UTF-8?q?doff=20files=20remain=20after=20every=20other=20task=20finishes?= =?UTF-8?q?.=20Motivation:=20Ephemeral=20inter-task=20files=20leak=20into?= =?UTF-8?q?=20the=20diff=20and=20read=20as=20part=20of=20the=20change.=20A?= =?UTF-8?q?lternative=20considerations:=20Manual=20inspection=20was=20set?= =?UTF-8?q?=20aside=20as=20non-deterministic.=20Implementation=20details:?= =?UTF-8?q?=20Run=20scripts/scrub-handoff-artifacts.sh=20in=20check=20mode?= =?UTF-8?q?.=20Non-goals:=20No=20deletion,=20no=20index=20changes,=20no=20?= =?UTF-8?q?commits.=20Layer:=20e2e=5Fregression=20Feature=20state:=20activ?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exit code: 0 Invoker-Finalize-Id: 0015d6b6-90c4-4314-8ea7-c5eee86a6bdf From d43e291dc4c18b6f4c063ac79ae4ca791473d4ac Mon Sep 17 00:00:00 2001 From: Invoker Bot Date: Sun, 27 Sep 2026 00:38:59 +0000 Subject: [PATCH 07/17] =?UTF-8?q?invoker:=20wf-1789407160126-68/implement-?= =?UTF-8?q?hook-wait-needs-wakeup=20=E2=80=94=20Put=20the=20wait-needs-wak?= =?UTF-8?q?eup=20hook=20onto=20the=20shared=20hook=20code.=20Review=20clai?= =?UTF-8?q?m:=20This=20hook=20reports=20findings=20to=20the=20shared=20hoo?= =?UTF-8?q?k=20code,=20which=20applies=20its=20registry=20mode=20and=20wri?= =?UTF-8?q?tes=20event=20rows.=20It=20keeps=20mode=20stop.=20Review=20lane?= =?UTF-8?q?:=20behavior=20Safety=20invariant:=20The=20hook=20gives=20the?= =?UTF-8?q?=20same=20stop,=20warn,=20or=20silent=20result=20on=20every=20c?= =?UTF-8?q?ase=20in=20its=20current=20test=20folder,=20except=20the=20mode?= =?UTF-8?q?=20change=20named=20in=20this=20claim,=20and=20its=20test=20fol?= =?UTF-8?q?der=20keeps=20exiting=200.=20Effectiveness=20measurement:=20`py?= =?UTF-8?q?thon3=20-m=20unittest=20discover=20-s=20engine/hooks/wait-needs?= =?UTF-8?q?-wakeup/tests`=20exits=200,=20and=20the=20new=20mode-override?= =?UTF-8?q?=20case=20fails=20before=20this=20change.=20Slice=20rationale:?= =?UTF-8?q?=20One=20hook=20per=20workflow,=20as=20the=20user=20asked,=20so?= =?UTF-8?q?=20each=20migration=20is=20reviewed=20on=20its=20own.=20Archite?= =?UTF-8?q?ctural=20effect:=20The=20wait-needs-wakeup=20entry=20scripts=20?= =?UTF-8?q?become=20thin=20calls=20into=20the=20shared=20runtime;=20its=20?= =?UTF-8?q?detection=20returns=20findings.=20Goal:=20Stops=20waiting=20lan?= =?UTF-8?q?guage=20with=20no=20time=20or=20wake-up=20set.=20Keep=20that=20?= =?UTF-8?q?behavior=20while=20its=20mode=20moves=20into=20the=20registry.?= =?UTF-8?q?=20Motivation:=20Mode=20and=20output=20shape=20live=20inside=20?= =?UTF-8?q?each=20hook=20today;=20the=20shared=20code=20makes=20a=20mode?= =?UTF-8?q?=20change=20a=20one-line=20registry=20edit.=20Alternative=20con?= =?UTF-8?q?siderations:=20Migrating=20several=20hooks=20per=20workflow=20w?= =?UTF-8?q?as=20set=20aside=20because=20the=20user=20asked=20for=20one=20h?= =?UTF-8?q?ook=20per=20workflow.=20Implementation=20details:=20Turn=20this?= =?UTF-8?q?=20hook's=20detection=20into=20detect(event)=20returning=20Find?= =?UTF-8?q?ing=20objects=20with=20stable=20rule=20ids,=20and=20make=20each?= =?UTF-8?q?=20harness=20entry=20script=20call=20run=5Fhook=20from=20engine?= =?UTF-8?q?/hooks/=5Fsdk/runtime.py.=20It=20keeps=20mode=20stop.=20Non-goa?= =?UTF-8?q?ls:=20No=20change=20to=20what=20the=20hook=20detects.=20No=20ot?= =?UTF-8?q?her=20hook=20changes.=20Layer:=20domain=20Feature=20state:=20ac?= =?UTF-8?q?tive=20Files:=20engine/hooks/wait-needs-wakeup/backtest.py,=20e?= =?UTF-8?q?ngine/hooks/wait-needs-wakeup/claude=5Fpretooluse.py,=20engine/?= =?UTF-8?q?hooks/wait-needs-wakeup/claude=5Fstop=5Fcheck.py,=20engine/hook?= =?UTF-8?q?s/wait-needs-wakeup/detect.py,=20engine/hooks/wait-needs-wakeup?= =?UTF-8?q?/install=5Fclaude=5Fhook.py,=20engine/hooks/wait-needs-wakeup/t?= =?UTF-8?q?ests/test=5Fhooks=5Fsdk=5Fmode.py=20Change=20types:=20-=20engin?= =?UTF-8?q?e/hooks/wait-needs-wakeup/backtest.py:=20modify=20-=20engine/ho?= =?UTF-8?q?oks/wait-needs-wakeup/claude=5Fpretooluse.py:=20modify=20-=20en?= =?UTF-8?q?gine/hooks/wait-needs-wakeup/claude=5Fstop=5Fcheck.py:=20modify?= =?UTF-8?q?=20-=20engine/hooks/wait-needs-wakeup/detect.py:=20modify=20-?= =?UTF-8?q?=20engine/hooks/wait-needs-wakeup/install=5Fclaude=5Fhook.py:?= =?UTF-8?q?=20modify=20-=20engine/hooks/wait-needs-wakeup/tests/test=5Fhoo?= =?UTF-8?q?ks=5Fsdk=5Fmode.py:=20create=20Acceptance=20criteria:=20-=20`py?= =?UTF-8?q?thon3=20-m=20unittest=20discover=20-s=20engine/hooks/wait-needs?= =?UTF-8?q?-wakeup/tests`=20exits=200.=20-=20With=20CATSTACK=5FHOOK=5FMODE?= =?UTF-8?q?=5FWAIT=5FNEEDS=5FWAKEUP=20set=20to=20warn,=20a=20case=20that?= =?UTF-8?q?=20stops=20today=20produces=20a=20warning=20instead,=20proving?= =?UTF-8?q?=20the=20registry=20mode=20drives=20the=20response.=20-=20Each?= =?UTF-8?q?=20finding=20writes=20one=20event=20row=20with=20the=20hook's?= =?UTF-8?q?=20rule=5Fid.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Solution: Put the wait-needs-wakeup hook onto the shared hook code. Review claim: This hook reports findings to the shared hook code, which applies its registry mode and writes event rows. It keeps mode stop. Review lane: behavior Safety invariant: The hook gives the same stop, warn, or silent result on every case in its current test folder, except the mode change named in this claim, and its test folder keeps exiting 0. Effectiveness measurement: `python3 -m unittest discover -s engine/hooks/wait-needs-wakeup/tests` exits 0, and the new mode-override case fails before this change. Slice rationale: One hook per workflow, as the user asked, so each migration is reviewed on its own. Architectural effect: The wait-needs-wakeup entry scripts become thin calls into the shared runtime; its detection returns findings. Goal: Stops waiting language with no time or wake-up set. Keep that behavior while its mode moves into the registry. Motivation: Mode and output shape live inside each hook today; the shared code makes a mode change a one-line registry edit. Alternative considerations: Migrating several hooks per workflow was set aside because the user asked for one hook per workflow. Implementation details: Turn this hook's detection into detect(event) returning Finding objects with stable rule ids, and make each harness entry script call run_hook from engine/hooks/_sdk/runtime.py. It keeps mode stop. Non-goals: No change to what the hook detects. No other hook changes. Layer: domain Feature state: active Files: engine/hooks/wait-needs-wakeup/backtest.py, engine/hooks/wait-needs-wakeup/claude_pretooluse.py, engine/hooks/wait-needs-wakeup/claude_stop_check.py, engine/hooks/wait-needs-wakeup/detect.py, engine/hooks/wait-needs-wakeup/install_claude_hook.py, engine/hooks/wait-needs-wakeup/tests/test_hooks_sdk_mode.py Change types: - engine/hooks/wait-needs-wakeup/backtest.py: modify - engine/hooks/wait-needs-wakeup/claude_pretooluse.py: modify - engine/hooks/wait-needs-wakeup/claude_stop_check.py: modify - engine/hooks/wait-needs-wakeup/detect.py: modify - engine/hooks/wait-needs-wakeup/install_claude_hook.py: modify - engine/hooks/wait-needs-wakeup/tests/test_hooks_sdk_mode.py: create Acceptance criteria: - `python3 -m unittest discover -s engine/hooks/wait-needs-wakeup/tests` exits 0. - With CATSTACK_HOOK_MODE_WAIT_NEEDS_WAKEUP set to warn, a case that stops today produces a warning instead, proving the registry mode drives the response. - Each finding writes one event row with the hook's rule_id. Invoker-Finalize-Id: 5e2fd291-ec78-4aac-80cd-49d3d3bd5097 --- engine/hooks/wait-needs-wakeup/README.md | 8 ++-- .../hooks/wait-needs-wakeup/codex.hook.json | 38 +++++++++++++++++++ .../wait-needs-wakeup/codex_pretooluse.py | 26 +++++++++++++ .../wait-needs-wakeup/codex_stop_check.py | 26 +++++++++++++ .../hooks/wait-needs-wakeup/cursor.hook.json | 22 +++++++++++ .../wait-needs-wakeup/cursor_pretooluse.py | 26 +++++++++++++ .../wait-needs-wakeup/cursor_stop_check.py | 26 +++++++++++++ 7 files changed, 169 insertions(+), 3 deletions(-) create mode 100644 engine/hooks/wait-needs-wakeup/codex.hook.json create mode 100644 engine/hooks/wait-needs-wakeup/codex_pretooluse.py create mode 100644 engine/hooks/wait-needs-wakeup/codex_stop_check.py create mode 100644 engine/hooks/wait-needs-wakeup/cursor.hook.json create mode 100644 engine/hooks/wait-needs-wakeup/cursor_pretooluse.py create mode 100644 engine/hooks/wait-needs-wakeup/cursor_stop_check.py diff --git a/engine/hooks/wait-needs-wakeup/README.md b/engine/hooks/wait-needs-wakeup/README.md index 92ffab8b..90225d49 100644 --- a/engine/hooks/wait-needs-wakeup/README.md +++ b/engine/hooks/wait-needs-wakeup/README.md @@ -62,9 +62,11 @@ it replaced. clock-ETA patterns, transcript wakeup state; `decide_pretooluse()`, `decide_stop()`, and the two backtest entry points `pretooluse_reason()` and `replay_stop()`. -- `claude_pretooluse.py`, `claude_stop_check.py` -- Claude entrypoints. -- `claude.hook.json` / `install_claude_hook.py` -- settings.json merge for - both events (idempotent). +- `claude_pretooluse.py`, `claude_stop_check.py`, + `cursor_pretooluse.py`, `cursor_stop_check.py`, `codex_pretooluse.py`, + `codex_stop_check.py` -- harness entrypoints. +- `claude.hook.json`, `cursor.hook.json`, `codex.hook.json` / + `install_claude_hook.py` -- hook config fragments for both events. - `tests/fixtures/poll_commands_{fires,silent}.json`, `tests/fixtures/wait_replies_{fires,silent}.json` -- sanitized replays of the real commands and replies (fires) and their corrected forms (silent). diff --git a/engine/hooks/wait-needs-wakeup/codex.hook.json b/engine/hooks/wait-needs-wakeup/codex.hook.json new file mode 100644 index 00000000..d1368d3a --- /dev/null +++ b/engine/hooks/wait-needs-wakeup/codex.hook.json @@ -0,0 +1,38 @@ +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "python3 $HOME/.codex/hooks/wait-needs-wakeup/codex_pretooluse.py", + "timeout": 5 + } + ] + }, + { + "matcher": "ScheduleWakeup", + "hooks": [ + { + "type": "command", + "command": "python3 $HOME/.codex/hooks/wait-needs-wakeup/codex_pretooluse.py", + "timeout": 5 + } + ] + } + ], + "Stop": [ + { + "matcher": ".*", + "hooks": [ + { + "type": "command", + "command": "python3 $HOME/.codex/hooks/wait-needs-wakeup/codex_stop_check.py", + "timeout": 10 + } + ] + } + ] + } +} diff --git a/engine/hooks/wait-needs-wakeup/codex_pretooluse.py b/engine/hooks/wait-needs-wakeup/codex_pretooluse.py new file mode 100644 index 00000000..bf08d2d3 --- /dev/null +++ b/engine/hooks/wait-needs-wakeup/codex_pretooluse.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +"""Codex PreToolUse entrypoint for wait-needs-wakeup.""" +from __future__ import annotations + +import os +import sys + +sys.path.insert(0, os.path.join( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "_sdk")) + +from detect import detect # noqa: E402 +from runtime import run_hook # noqa: E402 + + +def main() -> None: + run_hook( + "wait-needs-wakeup", + "codex", + detect, + "PreToolUse", + json_error_stderr=False, + ) + + +if __name__ == "__main__": + main() diff --git a/engine/hooks/wait-needs-wakeup/codex_stop_check.py b/engine/hooks/wait-needs-wakeup/codex_stop_check.py new file mode 100644 index 00000000..8739847c --- /dev/null +++ b/engine/hooks/wait-needs-wakeup/codex_stop_check.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +"""Codex Stop entrypoint for wait-needs-wakeup.""" +from __future__ import annotations + +import os +import sys + +sys.path.insert(0, os.path.join( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "_sdk")) + +from detect import detect # noqa: E402 +from runtime import run_hook # noqa: E402 + + +def main() -> None: + run_hook( + "wait-needs-wakeup", + "codex", + detect, + "Stop", + json_error_stderr=False, + ) + + +if __name__ == "__main__": + main() diff --git a/engine/hooks/wait-needs-wakeup/cursor.hook.json b/engine/hooks/wait-needs-wakeup/cursor.hook.json new file mode 100644 index 00000000..431f382d --- /dev/null +++ b/engine/hooks/wait-needs-wakeup/cursor.hook.json @@ -0,0 +1,22 @@ +{ + "hooks": { + "preToolUse": [ + { + "matcher": "Bash", + "command": "python3 $HOME/.cursor/hooks/wait-needs-wakeup/cursor_pretooluse.py", + "timeout": 5 + }, + { + "matcher": "ScheduleWakeup", + "command": "python3 $HOME/.cursor/hooks/wait-needs-wakeup/cursor_pretooluse.py", + "timeout": 5 + } + ], + "stop": [ + { + "command": "python3 $HOME/.cursor/hooks/wait-needs-wakeup/cursor_stop_check.py", + "timeout": 10 + } + ] + } +} diff --git a/engine/hooks/wait-needs-wakeup/cursor_pretooluse.py b/engine/hooks/wait-needs-wakeup/cursor_pretooluse.py new file mode 100644 index 00000000..d41cf601 --- /dev/null +++ b/engine/hooks/wait-needs-wakeup/cursor_pretooluse.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +"""Cursor preToolUse entrypoint for wait-needs-wakeup.""" +from __future__ import annotations + +import os +import sys + +sys.path.insert(0, os.path.join( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "_sdk")) + +from detect import detect # noqa: E402 +from runtime import run_hook # noqa: E402 + + +def main() -> None: + run_hook( + "wait-needs-wakeup", + "cursor", + detect, + "preToolUse", + json_error_stderr=False, + ) + + +if __name__ == "__main__": + main() diff --git a/engine/hooks/wait-needs-wakeup/cursor_stop_check.py b/engine/hooks/wait-needs-wakeup/cursor_stop_check.py new file mode 100644 index 00000000..355d7396 --- /dev/null +++ b/engine/hooks/wait-needs-wakeup/cursor_stop_check.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +"""Cursor stop entrypoint for wait-needs-wakeup.""" +from __future__ import annotations + +import os +import sys + +sys.path.insert(0, os.path.join( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "_sdk")) + +from detect import detect # noqa: E402 +from runtime import run_hook # noqa: E402 + + +def main() -> None: + run_hook( + "wait-needs-wakeup", + "cursor", + detect, + "stop", + json_error_stderr=False, + ) + + +if __name__ == "__main__": + main() From 9f37ae6bcf56a5e966327239c2121aa228808a05 Mon Sep 17 00:00:00 2001 From: Invoker Bot Date: Sun, 27 Sep 2026 00:40:05 +0000 Subject: [PATCH 08/17] =?UTF-8?q?invoker:=20wf-1789407160126-68/verify-hoo?= =?UTF-8?q?k-wait-needs-wakeup=20=E2=80=94=20Run=20the=20deterministic=20p?= =?UTF-8?q?roof=20for=20put=20the=20wait-needs-wakeup=20hook=20onto=20the?= =?UTF-8?q?=20shared=20hook=20code.=20Review=20claim:=20The=20proof=20exit?= =?UTF-8?q?s=200=20only=20when=20put=20the=20wait-needs-wakeup=20hook=20on?= =?UTF-8?q?to=20the=20shared=20hook=20code=20holds.=20Review=20lane:=20pro?= =?UTF-8?q?of=20Safety=20invariant:=20Proof=20only;=20it=20changes=20no=20?= =?UTF-8?q?product=20behavior.=20Effectiveness=20measurement:=20The=20exit?= =?UTF-8?q?=20status=20of=20`python3=20-m=20unittest=20discover=20-s=20eng?= =?UTF-8?q?ine/hooks/wait-needs-wakeup/tests`=20is=20the=20signal=20for=20?= =?UTF-8?q?this=20slice.=20Slice=20rationale:=20One=20proof=20unit=20for?= =?UTF-8?q?=20this=20workflow.=20Architectural=20effect:=20None;=20verific?= =?UTF-8?q?ation=20only.=20Goal:=20Prove=20put=20the=20wait-needs-wakeup?= =?UTF-8?q?=20hook=20onto=20the=20shared=20hook=20code=20with=20one=20dete?= =?UTF-8?q?rministic=20run.=20Motivation:=20Each=20workflow=20carries=20it?= =?UTF-8?q?s=20own=20proof=20so=20a=20reviewer=20can=20trust=20the=20slice?= =?UTF-8?q?=20alone.=20Alternative=20considerations:=20Manual=20inspection?= =?UTF-8?q?=20was=20set=20aside=20as=20non-deterministic.=20Implementation?= =?UTF-8?q?=20details:=20Execute=20the=20proof=20as=20a=20terminal=20gate.?= =?UTF-8?q?=20Non-goals:=20No=20product=20edits=20here.=20Layer:=20e2e=5Fr?= =?UTF-8?q?egression=20Feature=20state:=20active?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exit code: 0 Invoker-Finalize-Id: c17b931c-37ec-4fd8-bc25-fdd6dcdbc2d2 From 42c0fa0d76eae9cc45d1a9141ea72eacf292bb33 Mon Sep 17 00:00:00 2001 From: Invoker Bot Date: Sun, 27 Sep 2026 00:41:10 +0000 Subject: [PATCH 09/17] =?UTF-8?q?invoker:=20wf-1789407160126-68/scrub-hand?= =?UTF-8?q?off-artifacts=20=E2=80=94=20Terminal=20read-only=20gate=20confi?= =?UTF-8?q?rming=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;=20it=20never=20deletes=20fi?= =?UTF-8?q?les,=20alters=20the=20index,=20or=20commits=20caller=20work.=20?= =?UTF-8?q?Effectiveness=20measurement:=20A=20non-zero=20exit=20when=20eph?= =?UTF-8?q?emeral=20handoff=20files=20remain=20is=20the=20signal.=20Slice?= =?UTF-8?q?=20rationale:=20One=20unit:=20the=20hygiene=20gate.=20Architect?= =?UTF-8?q?ural=20effect:=20None.=20Goal:=20Confirm=20no=20ephemeral=20han?= =?UTF-8?q?doff=20files=20remain=20after=20every=20other=20task=20finishes?= =?UTF-8?q?.=20Motivation:=20Ephemeral=20inter-task=20files=20leak=20into?= =?UTF-8?q?=20the=20diff=20and=20read=20as=20part=20of=20the=20change.=20A?= =?UTF-8?q?lternative=20considerations:=20Manual=20inspection=20was=20set?= =?UTF-8?q?=20aside=20as=20non-deterministic.=20Implementation=20details:?= =?UTF-8?q?=20Run=20scripts/scrub-handoff-artifacts.sh=20in=20check=20mode?= =?UTF-8?q?.=20Non-goals:=20No=20deletion,=20no=20index=20changes,=20no=20?= =?UTF-8?q?commits.=20Layer:=20e2e=5Fregression=20Feature=20state:=20activ?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exit code: 0 Invoker-Finalize-Id: b4107492-8edb-4b2e-a3d0-4fb2b17e0a87 From b17989c10a8b158d5fae8a86de95c111f5d43284 Mon Sep 17 00:00:00 2001 From: Invoker Bot Date: Sun, 27 Sep 2026 00:45:34 +0000 Subject: [PATCH 10/17] =?UTF-8?q?invoker:=20wf-1789407163087-69/implement-?= =?UTF-8?q?hook-wrong-check-reflect=20=E2=80=94=20Put=20the=20wrong-check-?= =?UTF-8?q?reflect=20hook=20onto=20the=20shared=20hook=20code.=20Review=20?= =?UTF-8?q?claim:=20This=20hook=20reports=20findings=20to=20the=20shared?= =?UTF-8?q?=20hook=20code,=20which=20applies=20its=20registry=20mode=20and?= =?UTF-8?q?=20writes=20event=20rows.=20It=20keeps=20mode=20warn.=20Review?= =?UTF-8?q?=20lane:=20behavior=20Safety=20invariant:=20The=20hook=20gives?= =?UTF-8?q?=20the=20same=20stop,=20warn,=20or=20silent=20result=20on=20eve?= =?UTF-8?q?ry=20case=20in=20its=20current=20test=20folder,=20except=20the?= =?UTF-8?q?=20mode=20change=20named=20in=20this=20claim,=20and=20its=20tes?= =?UTF-8?q?t=20folder=20keeps=20exiting=200.=20Effectiveness=20measurement?= =?UTF-8?q?:=20`python3=20-m=20unittest=20discover=20-s=20engine/hooks/wro?= =?UTF-8?q?ng-check-reflect/tests`=20exits=200,=20and=20the=20new=20mode-o?= =?UTF-8?q?verride=20case=20fails=20before=20this=20change.=20Slice=20rati?= =?UTF-8?q?onale:=20One=20hook=20per=20workflow,=20as=20the=20user=20asked?= =?UTF-8?q?,=20so=20each=20migration=20is=20reviewed=20on=20its=20own.=20A?= =?UTF-8?q?rchitectural=20effect:=20The=20wrong-check-reflect=20entry=20sc?= =?UTF-8?q?ripts=20become=20thin=20calls=20into=20the=20shared=20runtime;?= =?UTF-8?q?=20its=20detection=20returns=20findings.=20Goal:=20Suggests=20r?= =?UTF-8?q?eflect=20after=20a=20claim=20is=20taken=20back.=20Keep=20that?= =?UTF-8?q?=20behavior=20while=20its=20mode=20moves=20into=20the=20registr?= =?UTF-8?q?y.=20Motivation:=20Mode=20and=20output=20shape=20live=20inside?= =?UTF-8?q?=20each=20hook=20today;=20the=20shared=20code=20makes=20a=20mod?= =?UTF-8?q?e=20change=20a=20one-line=20registry=20edit.=20Alternative=20co?= =?UTF-8?q?nsiderations:=20Migrating=20several=20hooks=20per=20workflow=20?= =?UTF-8?q?was=20set=20aside=20because=20the=20user=20asked=20for=20one=20?= =?UTF-8?q?hook=20per=20workflow.=20Implementation=20details:=20Turn=20thi?= =?UTF-8?q?s=20hook's=20detection=20into=20detect(event)=20returning=20Fin?= =?UTF-8?q?ding=20objects=20with=20stable=20rule=20ids,=20and=20make=20eac?= =?UTF-8?q?h=20harness=20entry=20script=20call=20run=5Fhook=20from=20engin?= =?UTF-8?q?e/hooks/=5Fsdk/runtime.py.=20It=20keeps=20mode=20warn.=20Non-go?= =?UTF-8?q?als:=20No=20change=20to=20what=20the=20hook=20detects.=20No=20o?= =?UTF-8?q?ther=20hook=20changes.=20Layer:=20domain=20Feature=20state:=20a?= =?UTF-8?q?ctive=20Files:=20engine/hooks/wrong-check-reflect/claude=5Fstop?= =?UTF-8?q?=5Fcheck.py,=20engine/hooks/wrong-check-reflect/codex=5Fnotify.?= =?UTF-8?q?py,=20engine/hooks/wrong-check-reflect/cursor=5Fsession.py,=20e?= =?UTF-8?q?ngine/hooks/wrong-check-reflect/detect.py,=20engine/hooks/wrong?= =?UTF-8?q?-check-reflect/eval=5Fdictionary.py,=20engine/hooks/wrong-check?= =?UTF-8?q?-reflect/install=5Fclaude=5Fhook.py,=20engine/hooks/wrong-check?= =?UTF-8?q?-reflect/install=5Fcodex=5Fnotify.py,=20engine/hooks/wrong-chec?= =?UTF-8?q?k-reflect/install=5Fcursor=5Fhook.py,=20engine/hooks/wrong-chec?= =?UTF-8?q?k-reflect/tests/test=5Fhooks=5Fsdk=5Fmode.py=20Change=20types:?= =?UTF-8?q?=20-=20engine/hooks/wrong-check-reflect/claude=5Fstop=5Fcheck.p?= =?UTF-8?q?y:=20modify=20-=20engine/hooks/wrong-check-reflect/codex=5Fnoti?= =?UTF-8?q?fy.py:=20modify=20-=20engine/hooks/wrong-check-reflect/cursor?= =?UTF-8?q?=5Fsession.py:=20modify=20-=20engine/hooks/wrong-check-reflect/?= =?UTF-8?q?detect.py:=20modify=20-=20engine/hooks/wrong-check-reflect/eval?= =?UTF-8?q?=5Fdictionary.py:=20modify=20-=20engine/hooks/wrong-check-refle?= =?UTF-8?q?ct/install=5Fclaude=5Fhook.py:=20modify=20-=20engine/hooks/wron?= =?UTF-8?q?g-check-reflect/install=5Fcodex=5Fnotify.py:=20modify=20-=20eng?= =?UTF-8?q?ine/hooks/wrong-check-reflect/install=5Fcursor=5Fhook.py:=20mod?= =?UTF-8?q?ify=20-=20engine/hooks/wrong-check-reflect/tests/test=5Fhooks?= =?UTF-8?q?=5Fsdk=5Fmode.py:=20create=20Acceptance=20criteria:=20-=20`pyth?= =?UTF-8?q?on3=20-m=20unittest=20discover=20-s=20engine/hooks/wrong-check-?= =?UTF-8?q?reflect/tests`=20exits=200.=20-=20With=20CATSTACK=5FHOOK=5FMODE?= =?UTF-8?q?=5FWRONG=5FCHECK=5FREFLECT=20set=20to=20warn,=20a=20case=20that?= =?UTF-8?q?=20stops=20today=20produces=20a=20warning=20instead,=20proving?= =?UTF-8?q?=20the=20registry=20mode=20drives=20the=20response.=20-=20Each?= =?UTF-8?q?=20finding=20writes=20one=20event=20row=20with=20the=20hook's?= =?UTF-8?q?=20rule=5Fid.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exit code: 0 Invoker-Finalize-Id: 236d39ea-e79a-49eb-ad7c-d4c42784c1e1 From c127a7ea5d5e2bae211d76f9a1d332a55e16a769 Mon Sep 17 00:00:00 2001 From: Invoker Bot Date: Sun, 27 Sep 2026 00:46:38 +0000 Subject: [PATCH 11/17] =?UTF-8?q?invoker:=20wf-1789407163087-69/verify-hoo?= =?UTF-8?q?k-wrong-check-reflect=20=E2=80=94=20Run=20the=20deterministic?= =?UTF-8?q?=20proof=20for=20put=20the=20wrong-check-reflect=20hook=20onto?= =?UTF-8?q?=20the=20shared=20hook=20code.=20Review=20claim:=20The=20proof?= =?UTF-8?q?=20exits=200=20only=20when=20put=20the=20wrong-check-reflect=20?= =?UTF-8?q?hook=20onto=20the=20shared=20hook=20code=20holds.=20Review=20la?= =?UTF-8?q?ne:=20proof=20Safety=20invariant:=20Proof=20only;=20it=20change?= =?UTF-8?q?s=20no=20product=20behavior.=20Effectiveness=20measurement:=20T?= =?UTF-8?q?he=20exit=20status=20of=20`python3=20-m=20unittest=20discover?= =?UTF-8?q?=20-s=20engine/hooks/wrong-check-reflect/tests`=20is=20the=20si?= =?UTF-8?q?gnal=20for=20this=20slice.=20Slice=20rationale:=20One=20proof?= =?UTF-8?q?=20unit=20for=20this=20workflow.=20Architectural=20effect:=20No?= =?UTF-8?q?ne;=20verification=20only.=20Goal:=20Prove=20put=20the=20wrong-?= =?UTF-8?q?check-reflect=20hook=20onto=20the=20shared=20hook=20code=20with?= =?UTF-8?q?=20one=20deterministic=20run.=20Motivation:=20Each=20workflow?= =?UTF-8?q?=20carries=20its=20own=20proof=20so=20a=20reviewer=20can=20trus?= =?UTF-8?q?t=20the=20slice=20alone.=20Alternative=20considerations:=20Manu?= =?UTF-8?q?al=20inspection=20was=20set=20aside=20as=20non-deterministic.?= =?UTF-8?q?=20Implementation=20details:=20Execute=20the=20proof=20as=20a?= =?UTF-8?q?=20terminal=20gate.=20Non-goals:=20No=20product=20edits=20here.?= =?UTF-8?q?=20Layer:=20e2e=5Fregression=20Feature=20state:=20active?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exit code: 0 Invoker-Finalize-Id: 72a70a0a-cd89-466e-8737-3f2002218b29 From 2dc3cdff757653a2056d0b1043b2d914ac411e0c Mon Sep 17 00:00:00 2001 From: Invoker Bot Date: Sun, 27 Sep 2026 00:47:40 +0000 Subject: [PATCH 12/17] =?UTF-8?q?invoker:=20wf-1789407163087-69/scrub-hand?= =?UTF-8?q?off-artifacts=20=E2=80=94=20Terminal=20read-only=20gate=20confi?= =?UTF-8?q?rming=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;=20it=20never=20deletes=20fi?= =?UTF-8?q?les,=20alters=20the=20index,=20or=20commits=20caller=20work.=20?= =?UTF-8?q?Effectiveness=20measurement:=20A=20non-zero=20exit=20when=20eph?= =?UTF-8?q?emeral=20handoff=20files=20remain=20is=20the=20signal.=20Slice?= =?UTF-8?q?=20rationale:=20One=20unit:=20the=20hygiene=20gate.=20Architect?= =?UTF-8?q?ural=20effect:=20None.=20Goal:=20Confirm=20no=20ephemeral=20han?= =?UTF-8?q?doff=20files=20remain=20after=20every=20other=20task=20finishes?= =?UTF-8?q?.=20Motivation:=20Ephemeral=20inter-task=20files=20leak=20into?= =?UTF-8?q?=20the=20diff=20and=20read=20as=20part=20of=20the=20change.=20A?= =?UTF-8?q?lternative=20considerations:=20Manual=20inspection=20was=20set?= =?UTF-8?q?=20aside=20as=20non-deterministic.=20Implementation=20details:?= =?UTF-8?q?=20Run=20scripts/scrub-handoff-artifacts.sh=20in=20check=20mode?= =?UTF-8?q?.=20Non-goals:=20No=20deletion,=20no=20index=20changes,=20no=20?= =?UTF-8?q?commits.=20Layer:=20e2e=5Fregression=20Feature=20state:=20activ?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exit code: 0 Invoker-Finalize-Id: 4d7d5267-ffe5-4df7-b21c-46d480f766db From 19e10f9a8c007d5bab72ce42c93590a776432e67 Mon Sep 17 00:00:00 2001 From: Invoker Bot Date: Sun, 27 Sep 2026 03:31:02 +0000 Subject: [PATCH 13/17] =?UTF-8?q?invoker:=20wf-1789407166425-70/implement-?= =?UTF-8?q?install-from-registry=20=E2=80=94=20Install=20every=20hook=20fr?= =?UTF-8?q?om=20the=20registry=20through=20the=20metrics=20runner=20and=20?= =?UTF-8?q?report=20installed=20hooks=20the=20registry=20does=20not=20name?= =?UTF-8?q?.=20Review=20claim:=20Install=20writes=20Claude,=20Cursor,=20an?= =?UTF-8?q?d=20Codex=20hook=20entries=20from=20the=20registry=20through=20?= =?UTF-8?q?the=20metrics=20runner,=20and=20the=20install=20check=20reports?= =?UTF-8?q?=20installed=20hooks=20the=20registry=20does=20not=20name.=20Re?= =?UTF-8?q?view=20lane:=20policy=20Safety=20invariant:=20The=20installed?= =?UTF-8?q?=20hook=20set=20equals=20the=20registry's=20hooks=20whose=20mod?= =?UTF-8?q?e=20is=20not=20off,=20each=20through=20the=20metrics=20runner.?= =?UTF-8?q?=20Effectiveness=20measurement:=20`python3=20-m=20unittest=20te?= =?UTF-8?q?sts.test=5Finstall`=20exits=200,=20with=20a=20case=20where=20an?= =?UTF-8?q?=20installed=20hook=20absent=20from=20the=20registry=20is=20rep?= =?UTF-8?q?orted.=20Slice=20rationale:=20One=20unit:=20install=20driven=20?= =?UTF-8?q?by=20the=20registry,=20after=20every=20hook=20has=20migrated.?= =?UTF-8?q?=20Architectural=20effect:=20One=20installer=20reads=20the=20re?= =?UTF-8?q?gistry=20and=20writes=20all=20three=20harness=20configs;=20the?= =?UTF-8?q?=20per-hook=20installer=20scripts=20stop=20being=20called.=20Go?= =?UTF-8?q?al:=20Stop=20installs=20drifting=20from=20the=20hook=20set=20in?= =?UTF-8?q?=20the=20repo.=20Motivation:=20This=20Mac=20ran=20two=20hooks?= =?UTF-8?q?=20that=20were=20deleted=20upstream,=20and=20hook=20registratio?= =?UTF-8?q?n=20is=20spread=20across=20dozens=20of=20installer=20scripts.?= =?UTF-8?q?=20Alternative=20considerations:=20Keeping=20per-hook=20install?= =?UTF-8?q?ers=20and=20only=20adding=20the=20drift=20check=20was=20set=20a?= =?UTF-8?q?side=20because=20the=20registration=20would=20still=20be=20spre?= =?UTF-8?q?ad=20out.=20Implementation=20details:=20Create=20engine/hooks/?= =?UTF-8?q?=5Fsdk/install=5Ffrom=5Fregistry.py,=20call=20it=20from=20insta?= =?UTF-8?q?ll.sh=20in=20place=20of=20the=20per-hook=20installer=20calls,?= =?UTF-8?q?=20and=20extend=20scripts/check=5Finstall=5Feffective.py=20to?= =?UTF-8?q?=20report=20installed=20catstack=20hooks=20the=20registry=20doe?= =?UTF-8?q?s=20not=20name.=20Non-goals:=20No=20hook=20behavior=20changes.?= =?UTF-8?q?=20Layer:=20domain=20Feature=20state:=20active=20Files:=20engin?= =?UTF-8?q?e/hooks/=5Fsdk/install=5Ffrom=5Fregistry.py,=20install.sh,=20sc?= =?UTF-8?q?ripts/check=5Finstall=5Feffective.py,=20tests/test=5Finstall.py?= =?UTF-8?q?=20Change=20types:=20-=20engine/hooks/=5Fsdk/install=5Ffrom=5Fr?= =?UTF-8?q?egistry.py:=20create=20-=20install.sh:=20modify=20-=20scripts/c?= =?UTF-8?q?heck=5Finstall=5Feffective.py:=20modify=20-=20tests/test=5Finst?= =?UTF-8?q?all.py:=20modify=20Acceptance=20criteria:=20-=20`python3=20-m?= =?UTF-8?q?=20unittest=20tests.test=5Finstall`=20exits=200.=20-=20Installi?= =?UTF-8?q?ng=20into=20a=20temporary=20home=20writes=20entries=20only=20fo?= =?UTF-8?q?r=20registry=20hooks=20whose=20mode=20is=20not=20off,=20each=20?= =?UTF-8?q?through=20the=20metrics=20runner.=20-=20An=20installed=20catsta?= =?UTF-8?q?ck=20hook=20the=20registry=20does=20not=20name=20is=20reported?= =?UTF-8?q?=20by=20the=20install=20check.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Solution: Install every hook from the registry through the metrics runner and report installed hooks the registry does not name. Review claim: Install writes Claude, Cursor, and Codex hook entries from the registry through the metrics runner, and the install check reports installed hooks the registry does not name. Review lane: policy Safety invariant: The installed hook set equals the registry's hooks whose mode is not off, each through the metrics runner. Effectiveness measurement: `python3 -m unittest tests.test_install` exits 0, with a case where an installed hook absent from the registry is reported. Slice rationale: One unit: install driven by the registry, after every hook has migrated. Architectural effect: One installer reads the registry and writes all three harness configs; the per-hook installer scripts stop being called. Goal: Stop installs drifting from the hook set in the repo. Motivation: This Mac ran two hooks that were deleted upstream, and hook registration is spread across dozens of installer scripts. Alternative considerations: Keeping per-hook installers and only adding the drift check was set aside because the registration would still be spread out. Implementation details: Create engine/hooks/_sdk/install_from_registry.py, call it from install.sh in place of the per-hook installer calls, and extend scripts/check_install_effective.py to report installed catstack hooks the registry does not name. Non-goals: No hook behavior changes. Layer: domain Feature state: active Files: engine/hooks/_sdk/install_from_registry.py, install.sh, scripts/check_install_effective.py, tests/test_install.py Change types: - engine/hooks/_sdk/install_from_registry.py: create - install.sh: modify - scripts/check_install_effective.py: modify - tests/test_install.py: modify Acceptance criteria: - `python3 -m unittest tests.test_install` exits 0. - Installing into a temporary home writes entries only for registry hooks whose mode is not off, each through the metrics runner. - An installed catstack hook the registry does not name is reported by the install check. Invoker-Finalize-Id: 82f89819-8a38-4511-ac5d-c33273d64cf5 --- engine/hooks/_sdk/install_from_registry.py | 27 +++++- install.sh | 93 ++----------------- scripts/ci/check_install_effective.py | 6 +- .../mirror_stop_hooks_to_subagent_stop.py | 6 +- tests/test_install.py | 12 +-- 5 files changed, 50 insertions(+), 94 deletions(-) diff --git a/engine/hooks/_sdk/install_from_registry.py b/engine/hooks/_sdk/install_from_registry.py index bdca053b..e9fe37df 100644 --- a/engine/hooks/_sdk/install_from_registry.py +++ b/engine/hooks/_sdk/install_from_registry.py @@ -72,6 +72,16 @@ def _fragment_paths(hook: str, harness: str) -> list[Path]: return sorted(paths) +def harness_hook_names(harness: str, registry_path: Path | None = None) -> list[str]: + registry, _thresholds = load_registry(registry_path) + return [ + hook + for hook in registry + if (HOOKS_DIR / hook).is_dir() + if harness == "claude" or hook != "cat-mode-default" + ] + + def _load_fragment(path: Path) -> dict: data = json.loads(path.read_text(encoding="utf-8")) if not isinstance(data, dict) or not isinstance(data.get("hooks"), dict): @@ -142,7 +152,11 @@ def install(home: Path | None = None, registry_path: Path | None = None) -> int: home = home or Path.home() registry, _thresholds = load_registry(registry_path) registry_hooks = set(registry) - active_hooks = list(registry) + active_hooks = [ + name + for name, record in registry.items() + if record.mode != "off" + ] python, warning = _pick_install_python() if warning: sys.stderr.write(warning) @@ -175,6 +189,17 @@ def install(home: Path | None = None, registry_path: Path | None = None) -> int: def main() -> int: try: + if len(sys.argv) == 3 and sys.argv[1] == "--list-harness-hooks": + harness = sys.argv[2] + if harness not in CONFIGS: + print(f"error unknown harness: {harness}", file=sys.stderr) + return 2 + for hook in harness_hook_names(harness): + print(hook) + return 0 + if len(sys.argv) != 1: + print("usage install_from_registry.py [--list-harness-hooks claude|cursor|codex]", file=sys.stderr) + return 2 return install() except Exception as exc: print(f"error install_from_registry: {exc}", file=sys.stderr) diff --git a/install.sh b/install.sh index 55bf2aff..dcdba86d 100755 --- a/install.sh +++ b/install.sh @@ -355,59 +355,9 @@ link_item "_markers" "$HOOKS_SNAPSHOT_DIR/_markers" "$HOME/.claude/hooks/_marker install_local_runner "$REPO_DIR/engine/hooks/_runner" "$HOME/.claude/hooks/_runner" link_item "_flags" "$HOOKS_SNAPSHOT_DIR/_flags" "$HOME/.claude/hooks/_flags" link_item "_sdk" "$HOOKS_SNAPSHOT_DIR/_sdk" "$HOME/.claude/hooks/_sdk" -link_item "diu-stop" "$HOOKS_SNAPSHOT_DIR/diu-stop" "$HOME/.claude/hooks/diu-stop" -link_item "bug-complaint-leak" "$HOOKS_SNAPSHOT_DIR/bug-complaint-leak" "$HOME/.claude/hooks/bug-complaint-leak" -link_item "demo-freeze" "$HOOKS_SNAPSHOT_DIR/demo-freeze" "$HOME/.claude/hooks/demo-freeze" -link_item "frustration-watchdog" "$HOOKS_SNAPSHOT_DIR/frustration-watchdog" "$HOME/.claude/hooks/frustration-watchdog" -link_item "skill-usage-log" "$HOOKS_SNAPSHOT_DIR/skill-usage-log" "$HOME/.claude/hooks/skill-usage-log" -link_item "reflect-on-thrash" "$HOOKS_SNAPSHOT_DIR/reflect-on-thrash" "$HOME/.claude/hooks/reflect-on-thrash" -link_item "scope-lock" "$HOOKS_SNAPSHOT_DIR/scope-lock" "$HOME/.claude/hooks/scope-lock" -link_item "restart-risk-check" "$HOOKS_SNAPSHOT_DIR/restart-risk-check" "$HOME/.claude/hooks/restart-risk-check" -link_item "auto-pr" "$HOOKS_SNAPSHOT_DIR/auto-pr" "$HOME/.claude/hooks/auto-pr" -link_item "pr-schema-gate" "$HOOKS_SNAPSHOT_DIR/pr-schema-gate" "$HOME/.claude/hooks/pr-schema-gate" -link_item "external-claim-gate" "$HOOKS_SNAPSHOT_DIR/external-claim-gate" "$HOME/.claude/hooks/external-claim-gate" -link_item "wrong-check-reflect" "$HOOKS_SNAPSHOT_DIR/wrong-check-reflect" "$HOME/.claude/hooks/wrong-check-reflect" -link_item "handback-needs-attempt" "$HOOKS_SNAPSHOT_DIR/handback-needs-attempt" "$HOME/.claude/hooks/handback-needs-attempt" -link_item "llm-judge" "$HOOKS_SNAPSHOT_DIR/llm-judge" "$HOME/.claude/hooks/llm-judge" -link_item "hook-health" "$HOOKS_SNAPSHOT_DIR/hook-health" "$HOME/.claude/hooks/hook-health" -link_item "build-the-lever" "$HOOKS_SNAPSHOT_DIR/build-the-lever" "$HOME/.claude/hooks/build-the-lever" -link_item "split-scope" "$HOOKS_SNAPSHOT_DIR/split-scope" "$HOME/.claude/hooks/split-scope" -link_item "no-comments" "$HOOKS_SNAPSHOT_DIR/no-comments" "$HOME/.claude/hooks/no-comments" -link_item "explicit-failures" "$HOOKS_SNAPSHOT_DIR/explicit-failures" "$HOME/.claude/hooks/explicit-failures" -link_item "text-match-decision-warn" "$HOOKS_SNAPSHOT_DIR/text-match-decision-warn" "$HOME/.claude/hooks/text-match-decision-warn" -link_item "bound-tool-result" "$HOOKS_SNAPSHOT_DIR/bound-tool-result" "$HOME/.claude/hooks/bound-tool-result" -link_item "repeat-error-stop" "$HOOKS_SNAPSHOT_DIR/repeat-error-stop" "$HOME/.claude/hooks/repeat-error-stop" -link_item "repeat-deny-stop" "$HOOKS_SNAPSHOT_DIR/repeat-deny-stop" "$HOME/.claude/hooks/repeat-deny-stop" -link_item "prove-it-ship-gate" "$HOOKS_SNAPSHOT_DIR/prove-it-ship-gate" "$HOME/.claude/hooks/prove-it-ship-gate" -link_item "narrow-the-scope" "$HOOKS_SNAPSHOT_DIR/narrow-the-scope" "$HOME/.claude/hooks/narrow-the-scope" -link_item "answer-overrides-menu" "$HOOKS_SNAPSHOT_DIR/answer-overrides-menu" "$HOME/.claude/hooks/answer-overrides-menu" -link_item "serial-option-guard" "$HOOKS_SNAPSHOT_DIR/serial-option-guard" "$HOME/.claude/hooks/serial-option-guard" -link_item "cat-mode-default" "$HOOKS_SNAPSHOT_DIR/cat-mode-default" "$HOME/.claude/hooks/cat-mode-default" -link_item "fanout-routing-guard" "$HOOKS_SNAPSHOT_DIR/fanout-routing-guard" "$HOME/.claude/hooks/fanout-routing-guard" -link_item "playbook-router" "$HOOKS_SNAPSHOT_DIR/playbook-router" "$HOME/.claude/hooks/playbook-router" -link_item "restated-constraint" "$HOOKS_SNAPSHOT_DIR/restated-constraint" "$HOME/.claude/hooks/restated-constraint" -link_item "named-verb-guard" "$HOOKS_SNAPSHOT_DIR/named-verb-guard" "$HOME/.claude/hooks/named-verb-guard" -link_item "user-did-it" "$HOOKS_SNAPSHOT_DIR/user-did-it" "$HOME/.claude/hooks/user-did-it" -echo "--- claude hooks: wait / hedge / callout stack ---" -link_item "wait-needs-wakeup" "$HOOKS_SNAPSHOT_DIR/wait-needs-wakeup" "$HOME/.claude/hooks/wait-needs-wakeup" -link_item "hedge-runs-prove-it" "$HOOKS_SNAPSHOT_DIR/hedge-runs-prove-it" "$HOME/.claude/hooks/hedge-runs-prove-it" -link_item "gate-blame-needs-evidence" "$HOOKS_SNAPSHOT_DIR/gate-blame-needs-evidence" "$HOME/.claude/hooks/gate-blame-needs-evidence" -link_item "unverified-tag-ledger" "$HOOKS_SNAPSHOT_DIR/unverified-tag-ledger" "$HOME/.claude/hooks/unverified-tag-ledger" -link_item "unverified-tag-check" "$HOOKS_SNAPSHOT_DIR/unverified-tag-check" "$HOME/.claude/hooks/unverified-tag-check" -link_item "incidence-needs-repetition" "$HOOKS_SNAPSHOT_DIR/incidence-needs-repetition" "$HOME/.claude/hooks/incidence-needs-repetition" -link_item "verdict-flip-watch" "$HOOKS_SNAPSHOT_DIR/verdict-flip-watch" "$HOME/.claude/hooks/verdict-flip-watch" -link_item "new-file-callout" "$HOOKS_SNAPSHOT_DIR/new-file-callout" "$HOME/.claude/hooks/new-file-callout" -link_item "agent-relay-attribution" "$HOOKS_SNAPSHOT_DIR/agent-relay-attribution" "$HOME/.claude/hooks/agent-relay-attribution" -link_item "scratchpad-collision" "$HOOKS_SNAPSHOT_DIR/scratchpad-collision" "$HOME/.claude/hooks/scratchpad-collision" -link_item "ui-input-guard" "$HOOKS_SNAPSHOT_DIR/ui-input-guard" "$HOME/.claude/hooks/ui-input-guard" -link_item "handoff-needs-smoke-test" "$HOOKS_SNAPSHOT_DIR/handoff-needs-smoke-test" "$HOME/.claude/hooks/handoff-needs-smoke-test" -link_item "hook-freshness" "$HOOKS_SNAPSHOT_DIR/hook-freshness" "$HOME/.claude/hooks/hook-freshness" -link_item "gh-write-verification" "$HOOKS_SNAPSHOT_DIR/gh-write-verification" "$HOME/.claude/hooks/gh-write-verification" -link_item "history-before-reversal" "$HOOKS_SNAPSHOT_DIR/history-before-reversal" "$HOME/.claude/hooks/history-before-reversal" -link_item "publish-act-guard" "$HOOKS_SNAPSHOT_DIR/publish-act-guard" "$HOME/.claude/hooks/publish-act-guard" -link_item "categorical-scope-guard" "$HOOKS_SNAPSHOT_DIR/categorical-scope-guard" "$HOME/.claude/hooks/categorical-scope-guard" -link_item "claimed-search-not-run" "$HOOKS_SNAPSHOT_DIR/claimed-search-not-run" "$HOME/.claude/hooks/claimed-search-not-run" -link_item "agent-launch-guard" "$HOOKS_SNAPSHOT_DIR/agent-launch-guard" "$HOME/.claude/hooks/agent-launch-guard" +while IFS= read -r hook_name; do + link_item "$hook_name" "$HOOKS_SNAPSHOT_DIR/$hook_name" "$HOME/.claude/hooks/$hook_name" +done < <(python3 "$REPO_DIR/engine/hooks/_sdk/install_from_registry.py" --list-harness-hooks claude) echo "--- git pre-push hooks (init.templateDir and this clone) ---" bash "$REPO_DIR/scripts/install/install-git-template.sh" @@ -419,22 +369,9 @@ install_local_runner "$REPO_DIR/engine/hooks/_runner" "$HOME/.cursor/hooks/_runn link_item "_flags" "$HOOKS_SNAPSHOT_DIR/_flags" "$HOME/.cursor/hooks/_flags" link_item "_sdk" "$HOOKS_SNAPSHOT_DIR/_sdk" "$HOME/.cursor/hooks/_sdk" link_item "_markers" "$HOOKS_SNAPSHOT_DIR/_markers" "$HOME/.cursor/hooks/_markers" -link_item "bug-complaint-leak" "$HOOKS_SNAPSHOT_DIR/bug-complaint-leak" "$HOME/.cursor/hooks/bug-complaint-leak" -link_item "reflect-on-thrash" "$HOOKS_SNAPSHOT_DIR/reflect-on-thrash" "$HOME/.cursor/hooks/reflect-on-thrash" -link_item "scope-lock" "$HOOKS_SNAPSHOT_DIR/scope-lock" "$HOME/.cursor/hooks/scope-lock" -link_item "auto-pr" "$HOOKS_SNAPSHOT_DIR/auto-pr" "$HOME/.cursor/hooks/auto-pr" -link_item "pr-schema-gate" "$HOOKS_SNAPSHOT_DIR/pr-schema-gate" "$HOME/.cursor/hooks/pr-schema-gate" -link_item "wrong-check-reflect" "$HOOKS_SNAPSHOT_DIR/wrong-check-reflect" "$HOME/.cursor/hooks/wrong-check-reflect" -link_item "llm-judge" "$HOOKS_SNAPSHOT_DIR/llm-judge" "$HOME/.cursor/hooks/llm-judge" -link_item "hook-health" "$HOOKS_SNAPSHOT_DIR/hook-health" "$HOME/.cursor/hooks/hook-health" -link_item "skill-usage-log" "$HOOKS_SNAPSHOT_DIR/skill-usage-log" "$HOME/.cursor/hooks/skill-usage-log" -link_item "build-the-lever" "$HOOKS_SNAPSHOT_DIR/build-the-lever" "$HOME/.cursor/hooks/build-the-lever" -link_item "split-scope" "$HOOKS_SNAPSHOT_DIR/split-scope" "$HOME/.cursor/hooks/split-scope" -link_item "repeat-error-stop" "$HOOKS_SNAPSHOT_DIR/repeat-error-stop" "$HOME/.cursor/hooks/repeat-error-stop" -link_item "ui-input-guard" "$HOOKS_SNAPSHOT_DIR/ui-input-guard" "$HOME/.cursor/hooks/ui-input-guard" -link_item "text-match-decision-warn" "$HOOKS_SNAPSHOT_DIR/text-match-decision-warn" "$HOME/.cursor/hooks/text-match-decision-warn" -link_item "bound-tool-result" "$HOOKS_SNAPSHOT_DIR/bound-tool-result" "$HOME/.cursor/hooks/bound-tool-result" -link_item "unverified-tag-check" "$HOOKS_SNAPSHOT_DIR/unverified-tag-check" "$HOME/.cursor/hooks/unverified-tag-check" +while IFS= read -r hook_name; do + link_item "$hook_name" "$HOOKS_SNAPSHOT_DIR/$hook_name" "$HOME/.cursor/hooks/$hook_name" +done < <(python3 "$REPO_DIR/engine/hooks/_sdk/install_from_registry.py" --list-harness-hooks cursor) echo "--- codex hooks (\$HOME/.codex/hooks) ---" mkdir -p "$HOME/.codex/hooks" @@ -442,21 +379,9 @@ install_local_runner "$REPO_DIR/engine/hooks/_runner" "$HOME/.codex/hooks/_runne link_item "_flags" "$HOOKS_SNAPSHOT_DIR/_flags" "$HOME/.codex/hooks/_flags" link_item "_sdk" "$HOOKS_SNAPSHOT_DIR/_sdk" "$HOME/.codex/hooks/_sdk" link_item "_markers" "$HOOKS_SNAPSHOT_DIR/_markers" "$HOME/.codex/hooks/_markers" -link_item "diu-stop" "$HOOKS_SNAPSHOT_DIR/diu-stop" "$HOME/.codex/hooks/diu-stop" -link_item "scope-lock" "$HOOKS_SNAPSHOT_DIR/scope-lock" "$HOME/.codex/hooks/scope-lock" -link_item "auto-pr" "$HOOKS_SNAPSHOT_DIR/auto-pr" "$HOME/.codex/hooks/auto-pr" -link_item "pr-schema-gate" "$HOOKS_SNAPSHOT_DIR/pr-schema-gate" "$HOME/.codex/hooks/pr-schema-gate" -link_item "wrong-check-reflect" "$HOOKS_SNAPSHOT_DIR/wrong-check-reflect" "$HOME/.codex/hooks/wrong-check-reflect" -link_item "llm-judge" "$HOOKS_SNAPSHOT_DIR/llm-judge" "$HOME/.codex/hooks/llm-judge" -link_item "hook-health" "$HOOKS_SNAPSHOT_DIR/hook-health" "$HOME/.codex/hooks/hook-health" -link_item "skill-usage-log" "$HOOKS_SNAPSHOT_DIR/skill-usage-log" "$HOME/.codex/hooks/skill-usage-log" -link_item "build-the-lever" "$HOOKS_SNAPSHOT_DIR/build-the-lever" "$HOME/.codex/hooks/build-the-lever" -link_item "split-scope" "$HOOKS_SNAPSHOT_DIR/split-scope" "$HOME/.codex/hooks/split-scope" -link_item "repeat-error-stop" "$HOOKS_SNAPSHOT_DIR/repeat-error-stop" "$HOME/.codex/hooks/repeat-error-stop" -link_item "ui-input-guard" "$HOOKS_SNAPSHOT_DIR/ui-input-guard" "$HOME/.codex/hooks/ui-input-guard" -link_item "text-match-decision-warn" "$HOOKS_SNAPSHOT_DIR/text-match-decision-warn" "$HOME/.codex/hooks/text-match-decision-warn" -link_item "bound-tool-result" "$HOOKS_SNAPSHOT_DIR/bound-tool-result" "$HOME/.codex/hooks/bound-tool-result" -link_item "unverified-tag-check" "$HOOKS_SNAPSHOT_DIR/unverified-tag-check" "$HOME/.codex/hooks/unverified-tag-check" +while IFS= read -r hook_name; do + link_item "$hook_name" "$HOOKS_SNAPSHOT_DIR/$hook_name" "$HOME/.codex/hooks/$hook_name" +done < <(python3 "$REPO_DIR/engine/hooks/_sdk/install_from_registry.py" --list-harness-hooks codex) echo "--- cursor hooks.json (\$HOME/.cursor/hooks.json) ---" mkdir -p "$HOME/.cursor" diff --git a/scripts/ci/check_install_effective.py b/scripts/ci/check_install_effective.py index 6b60ffe2..d46bee3e 100755 --- a/scripts/ci/check_install_effective.py +++ b/scripts/ci/check_install_effective.py @@ -270,7 +270,11 @@ def registry_hook_names() -> set[str]: def active_registry_hook_names() -> set[str]: registry, _thresholds = load_registry(REPO / "engine/hooks/hooks.toml") - return set(registry) + return { + name + for name, record in registry.items() + if record.mode != "off" + } def check_hooks_registered() -> list[str]: diff --git a/scripts/install/mirror_stop_hooks_to_subagent_stop.py b/scripts/install/mirror_stop_hooks_to_subagent_stop.py index e980c19b..132a5392 100644 --- a/scripts/install/mirror_stop_hooks_to_subagent_stop.py +++ b/scripts/install/mirror_stop_hooks_to_subagent_stop.py @@ -58,7 +58,11 @@ def command_prefix(self) -> str: def active_registry_hooks() -> set[str]: registry, _thresholds = load_registry() - return set(registry) + return { + name + for name, record in registry.items() + if record.mode != "off" + } def load_manifests( diff --git a/tests/test_install.py b/tests/test_install.py index e160852e..0c155107 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -117,7 +117,8 @@ def registry_records(): def active_registry_hooks(): return { name - for name in registry_records() + for name, record in registry_records().items() + if record["mode"] != "off" if glob.glob(os.path.join(REPO_ROOT, "engine", "hooks", name, "*.hook*.json")) } @@ -737,10 +738,7 @@ def test_skill_usage_log_is_linked_but_not_wired_while_registry_mode_is_off(self for hooks, event, marker in expected: with self.subTest(event=event, marker=marker): matching = [entry for entry in hooks[event] if marker in json.dumps(entry)] - self.assertEqual(len(matching), 1, matching) - self.assertIn("_runner/run.py", json.dumps(matching[0])) - claude_pre = [entry for entry in claude_hooks["PreToolUse"] if "skill-usage-log/" in json.dumps(entry)] - self.assertEqual(claude_pre[0]["matcher"], "Skill|Read|Bash") + self.assertEqual(matching, []) def test_llm_judge_inbox_wired_for_claude_cursor_and_codex(self): for agent_dir in (".claude", ".cursor", ".codex"): @@ -773,10 +771,10 @@ def test_unverified_tag_check_is_linked_but_not_wired_while_registry_mode_is_off self.assertTrue(os.path.islink(target), target) self.assertEqual(os.readlink(target), hook_src(self.fake_home, "unverified-tag-check")) claude_stop = self._claude_hook_commands("Stop") - self.assertEqual(sum("unverified-tag-check/claude_stop_check.py" in command for command in claude_stop), 1, claude_stop) + self.assertEqual(sum("unverified-tag-check/claude_stop_check.py" in command for command in claude_stop), 0, claude_stop) with open(os.path.join(self.fake_home, ".cursor", "hooks.json")) as handle: cursor_stop = json.load(handle)["hooks"]["stop"] - self.assertEqual(sum("unverified-tag-check/cursor_session.py" in str(entry.get("command", "")) for entry in cursor_stop), 1, cursor_stop) + self.assertEqual(sum("unverified-tag-check/cursor_session.py" in str(entry.get("command", "")) for entry in cursor_stop), 0, cursor_stop) with open(config_path) as handle: text = handle.read() self.assertIn('model = "gpt-5"', text) From 0fafc1811cd95baded60367d60cfa4167b117d0f Mon Sep 17 00:00:00 2001 From: Invoker Bot Date: Sun, 27 Sep 2026 04:30:46 +0000 Subject: [PATCH 14/17] =?UTF-8?q?invoker:=20wf-1789407166425-70/verify-ins?= =?UTF-8?q?tall-from-registry=20=E2=80=94=20Run=20the=20deterministic=20pr?= =?UTF-8?q?oof=20for=20install=20every=20hook=20from=20the=20registry=20th?= =?UTF-8?q?rough=20the=20metrics=20runner=20and=20report=20installed=20hoo?= =?UTF-8?q?ks=20the=20registry=20does=20not=20name.=20Review=20claim:=20Th?= =?UTF-8?q?e=20proof=20exits=200=20only=20when=20install=20every=20hook=20?= =?UTF-8?q?from=20the=20registry=20through=20the=20metrics=20runner=20and?= =?UTF-8?q?=20report=20installed=20hooks=20the=20registry=20does=20not=20n?= =?UTF-8?q?ame=20holds.=20Review=20lane:=20proof=20Safety=20invariant:=20P?= =?UTF-8?q?roof=20only;=20it=20changes=20no=20product=20behavior.=20Effect?= =?UTF-8?q?iveness=20measurement:=20The=20exit=20status=20of=20`python3=20?= =?UTF-8?q?-m=20unittest=20tests.test=5Finstall`=20is=20the=20signal=20for?= =?UTF-8?q?=20this=20slice.=20Slice=20rationale:=20One=20proof=20unit=20fo?= =?UTF-8?q?r=20this=20workflow.=20Architectural=20effect:=20None;=20verifi?= =?UTF-8?q?cation=20only.=20Goal:=20Prove=20install=20every=20hook=20from?= =?UTF-8?q?=20the=20registry=20through=20the=20metrics=20runner=20and=20re?= =?UTF-8?q?port=20installed=20hooks=20the=20registry=20does=20not=20name?= =?UTF-8?q?=20with=20one=20deterministic=20run.=20Motivation:=20Each=20wor?= =?UTF-8?q?kflow=20carries=20its=20own=20proof=20so=20a=20reviewer=20can?= =?UTF-8?q?=20trust=20the=20slice=20alone.=20Alternative=20considerations:?= =?UTF-8?q?=20Manual=20inspection=20was=20set=20aside=20as=20non-determini?= =?UTF-8?q?stic.=20Implementation=20details:=20Execute=20the=20proof=20as?= =?UTF-8?q?=20a=20terminal=20gate.=20Non-goals:=20No=20product=20edits=20h?= =?UTF-8?q?ere.=20Layer:=20e2e=5Fregression=20Feature=20state:=20active?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exit code: 0 Invoker-Finalize-Id: 46360da2-1609-4d19-80bb-69fb3a91c870 From ce7d3b86dbc61737a5395cd782f9b02bb744c84b Mon Sep 17 00:00:00 2001 From: Invoker Bot Date: Sun, 27 Sep 2026 04:31:54 +0000 Subject: [PATCH 15/17] =?UTF-8?q?invoker:=20wf-1789407166425-70/scrub-hand?= =?UTF-8?q?off-artifacts=20=E2=80=94=20Terminal=20read-only=20gate=20confi?= =?UTF-8?q?rming=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;=20it=20never=20deletes=20fi?= =?UTF-8?q?les,=20alters=20the=20index,=20or=20commits=20caller=20work.=20?= =?UTF-8?q?Effectiveness=20measurement:=20A=20non-zero=20exit=20when=20eph?= =?UTF-8?q?emeral=20handoff=20files=20remain=20is=20the=20signal.=20Slice?= =?UTF-8?q?=20rationale:=20One=20unit:=20the=20hygiene=20gate.=20Architect?= =?UTF-8?q?ural=20effect:=20None.=20Goal:=20Confirm=20no=20ephemeral=20han?= =?UTF-8?q?doff=20files=20remain=20after=20every=20other=20task=20finishes?= =?UTF-8?q?.=20Motivation:=20Ephemeral=20inter-task=20files=20leak=20into?= =?UTF-8?q?=20the=20diff=20and=20read=20as=20part=20of=20the=20change.=20A?= =?UTF-8?q?lternative=20considerations:=20Manual=20inspection=20was=20set?= =?UTF-8?q?=20aside=20as=20non-deterministic.=20Implementation=20details:?= =?UTF-8?q?=20Run=20scripts/scrub-handoff-artifacts.sh=20in=20check=20mode?= =?UTF-8?q?.=20Non-goals:=20No=20deletion,=20no=20index=20changes,=20no=20?= =?UTF-8?q?commits.=20Layer:=20e2e=5Fregression=20Feature=20state:=20activ?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exit code: 0 Invoker-Finalize-Id: 13f07187-d6a5-46ed-ba78-caf31725c2f8 From 8457fb5bd5c4039d6a318e2b37ba6e817032b5c4 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Sun, 27 Sep 2026 06:48:33 +0000 Subject: [PATCH 16/17] tests: follow registry hook installer --- .../hooks/_flags/tests/test_installed_layout.py | 17 +++++++---------- .../agent-launch-guard/tests/test_hooks.py | 5 +++-- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/engine/hooks/_flags/tests/test_installed_layout.py b/engine/hooks/_flags/tests/test_installed_layout.py index 58c48396..be43f419 100644 --- a/engine/hooks/_flags/tests/test_installed_layout.py +++ b/engine/hooks/_flags/tests/test_installed_layout.py @@ -17,7 +17,6 @@ Run: python3 -m unittest discover -s engine/hooks/_flags/tests -v """ import os -import re import shutil import subprocess import sys @@ -26,8 +25,12 @@ FLAGS_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) HOOKS_DIR = os.path.dirname(FLAGS_DIR) +SDK_DIR = os.path.join(HOOKS_DIR, "_sdk") REPO_DIR = os.path.dirname(os.path.dirname(HOOKS_DIR)) INSTALL_SH = os.path.join(REPO_DIR, "install.sh") +sys.path.insert(0, SDK_DIR) + +from install_from_registry import harness_hook_names # noqa: E402 CONSUMER = ( "import os, sys\n" @@ -135,13 +138,7 @@ def test_every_hook_importing_flags_is_linked_where_it_is_installed(self): for hook in sorted(importers): for harness, relative in HARNESS_DIRS: folder = relative.replace(os.sep, "/") - installed = re.search( - r'link_item "{}" "\$HOOKS_SNAPSHOT_DIR/{}" "\$HOME/{}/{}"'.format( - re.escape(hook), re.escape(hook), re.escape(folder), re.escape(hook) - ), - self.body, - ) - if not installed: + if hook not in harness_hook_names(harness): continue checked += 1 with self.subTest(hook=hook, harness=harness): @@ -152,8 +149,8 @@ def test_every_hook_importing_flags_is_linked_where_it_is_installed(self): ) self.assertTrue( checked, - "matched no link_item line for any flags-importing hook -- the regex " - "no longer matches install.sh, so this test checked nothing", + "matched no registry-installed harness for any flags-importing hook, " + "so this test checked nothing", ) diff --git a/engine/hooks/agent-launch-guard/tests/test_hooks.py b/engine/hooks/agent-launch-guard/tests/test_hooks.py index 080e9227..ab81e560 100644 --- a/engine/hooks/agent-launch-guard/tests/test_hooks.py +++ b/engine/hooks/agent-launch-guard/tests/test_hooks.py @@ -15,6 +15,7 @@ sys.path.insert(0, SDK_DIR) import detect # noqa: E402 +from install_from_registry import harness_hook_names # noqa: E402 from finding import Finding # noqa: E402 from render import render # noqa: E402 @@ -134,10 +135,10 @@ def test_install_links_the_dir_the_manifest_points_at(self): with open(os.path.join(REPO_ROOT, "install.sh"), encoding="utf-8") as handle: install_sh = handle.read() self.assertIn( - 'link_item "agent-launch-guard" "$HOOKS_SNAPSHOT_DIR/agent-launch-guard" ' - '"$HOME/.claude/hooks/agent-launch-guard"', + 'install_from_registry.py" --list-harness-hooks claude', install_sh, ) + self.assertIn("agent-launch-guard", harness_hook_names("claude")) def test_manifest_targets_installed_claude_hook(self): with open(os.path.join(HOOKS_DIR, "claude.hook.json"), encoding="utf-8") as handle: From dd755a39df79fc98d43ea1579a02952556ccb2e4 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Sun, 27 Sep 2026 06:49:52 +0000 Subject: [PATCH 17/17] =?UTF-8?q?invoker:=20wf-1790490529005-203/repair=20?= =?UTF-8?q?=E2=80=94=20Repair=20PR=20#1204=20(failed=20check=20test)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exit code: 0