wr-architect: the edit gate binds to the Edit/Write tool, so Bash-routed edits of governed files bypass it entirely
architect-enforce-edit.sh is registered in hooks/hooks.json under "matcher": "Edit|Write". It therefore fires on the tool name, not on the predicate "a file under docs/decisions/ is being modified". Any edit routed through Bash (a node script, a perl -pi, a sed -i) modifies governed files with no gate firing at all.
Checked against 0.20.2.
What we saw
A node script invoked through Bash rewrote 141 link targets across 47 files, including 14 ADR bodies under docs/decisions/. No gate fired at any point.
The gate then blocked the very next Edit-tool call against one of those same ADRs. That asymmetry is the only reason we noticed: the ungated batch left no signal of its own.
The same Edit|Write scoping applies on the PostToolUse side, so architect-refresh-hash.sh and architect-compendium-update-entry.sh also did not fire for those 141 edits. Stored content hashes are consequently stale against the 14 edited ADR bodies, and the compendium is missing their entries. That direction fails safe: the next Edit-tool touch blocks on decision-drift rather than silently accepting. The operator still experiences it as an unexplained gate refusal with no obvious cause.
The channel is already wired, which makes this cheap
0.20.2 already registers Bash on both sides:
PreToolUse "matcher": "Bash" -> architect-readme-pairing-check.sh
PostToolUse "matcher": "Agent|Bash|Skill" -> architect-slide-marker.sh
So this isn't a missing capability. The plugin subscribes to Bash for marker-sliding and for a README pairing check; it just doesn't subscribe for governed-file detection.
Why matcher-widening can't be the fix
Widening the PreToolUse matcher to include Bash cannot work: node scripts/fix-links.mjs carries zero information about which files it will write. The governed path set only exists after execution.
So the fix shape looks PostToolUse-diff-based: after a Bash call, check whether any path under the governed globs changed, and if so refresh the hash and demand review before the next gated action. architect-slide-marker.sh is already firing on exactly that event, so there is somewhere obvious to hang it.
A commit-time check is the other candidate: assert every governed file in the staged set carries a fresh architect marker. Later than detection-time, but path-complete and not bypassable by choice of tool.
We haven't measured the cost of diffing after every Bash invocation. That is your call, since it depends on how the hook budget is allocated across the suite.
Relationship to #410
We deliberately did not append this to #410 (wr-risk-scorer: text-matching command detection).
#410 is an incomplete command-position grammar: its anchor set enumerates some command-introduction positions and misses others, and both of its proposed fixes repair a matcher that exists and does fire. Here the matcher never subscribes to the channel doing the writing.
Different plugin, different matcher, different fix shape. Folding them together would let #410 close in full while this stays open.
They do share a class, which may be worth naming somewhere: a governance gate observing a proxy (the tool name, or the command text) instead of the governed action. #410's root-cause section already articulates that class.
Workaround we're using
Consult the architect agent voluntarily before any bulk edit that will touch docs/decisions/. That is what happened in the originating batch: two review passes, both returning ISSUES FOUND, both materially changing the work.
It is not a control. It depended entirely on the agent choosing to ask, which is the property a gate exists to remove.
We'll send a PR if the PostToolUse-diff shape is the direction you want.
wr-architect: the edit gate binds to the Edit/Write tool, so Bash-routed edits of governed files bypass it entirely
architect-enforce-edit.shis registered inhooks/hooks.jsonunder"matcher": "Edit|Write". It therefore fires on the tool name, not on the predicate "a file underdocs/decisions/is being modified". Any edit routed through Bash (anodescript, aperl -pi, ased -i) modifies governed files with no gate firing at all.Checked against 0.20.2.
What we saw
A node script invoked through Bash rewrote 141 link targets across 47 files, including 14 ADR bodies under
docs/decisions/. No gate fired at any point.The gate then blocked the very next
Edit-tool call against one of those same ADRs. That asymmetry is the only reason we noticed: the ungated batch left no signal of its own.The same
Edit|Writescoping applies on the PostToolUse side, soarchitect-refresh-hash.shandarchitect-compendium-update-entry.shalso did not fire for those 141 edits. Stored content hashes are consequently stale against the 14 edited ADR bodies, and the compendium is missing their entries. That direction fails safe: the next Edit-tool touch blocks on decision-drift rather than silently accepting. The operator still experiences it as an unexplained gate refusal with no obvious cause.The channel is already wired, which makes this cheap
0.20.2 already registers Bash on both sides:
PreToolUse"matcher": "Bash"->architect-readme-pairing-check.shPostToolUse"matcher": "Agent|Bash|Skill"->architect-slide-marker.shSo this isn't a missing capability. The plugin subscribes to Bash for marker-sliding and for a README pairing check; it just doesn't subscribe for governed-file detection.
Why matcher-widening can't be the fix
Widening the PreToolUse matcher to include Bash cannot work:
node scripts/fix-links.mjscarries zero information about which files it will write. The governed path set only exists after execution.So the fix shape looks PostToolUse-diff-based: after a Bash call, check whether any path under the governed globs changed, and if so refresh the hash and demand review before the next gated action.
architect-slide-marker.shis already firing on exactly that event, so there is somewhere obvious to hang it.A commit-time check is the other candidate: assert every governed file in the staged set carries a fresh architect marker. Later than detection-time, but path-complete and not bypassable by choice of tool.
We haven't measured the cost of diffing after every Bash invocation. That is your call, since it depends on how the hook budget is allocated across the suite.
Relationship to #410
We deliberately did not append this to #410 (
wr-risk-scorer: text-matching command detection).#410 is an incomplete command-position grammar: its anchor set enumerates some command-introduction positions and misses others, and both of its proposed fixes repair a matcher that exists and does fire. Here the matcher never subscribes to the channel doing the writing.
Different plugin, different matcher, different fix shape. Folding them together would let #410 close in full while this stays open.
They do share a class, which may be worth naming somewhere: a governance gate observing a proxy (the tool name, or the command text) instead of the governed action. #410's root-cause section already articulates that class.
Workaround we're using
Consult the architect agent voluntarily before any bulk edit that will touch
docs/decisions/. That is what happened in the originating batch: two review passes, both returning ISSUES FOUND, both materially changing the work.It is not a control. It depended entirely on the agent choosing to ask, which is the property a gate exists to remove.
We'll send a PR if the PostToolUse-diff shape is the direction you want.