feat(6855): the plugin asks before a session writes to a project other than its own
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Failing after 1m27s
CI & Build / integration (push) Successful in 2m1s
CI & Build / Build & push image (push) Skipped

plugin/hooks/grimoire_project_fence.sh runs as PreToolUse on editor writes,
Bash and Grimoire's tools. A session belongs to the project its starting
directory resolves to (CLAUDE_PROJECT_DIR: a .grimoire marker, else the git
remote's binding). A session that starts outside any project belongs to the
first one it enters, which PostToolUse on enter_project records.

These acts stop on permissionDecision "ask", with the server's reason naming
the other project:
- an edit in another project's repo or a worktree of it;
- a git commit, push, merge, rebase, pull, cherry-pick, revert, tag, am, reset
  or worktree add there, found through git -C, a cd before it, or the shell's
  current directory;
- a Grimoire write that lands in another project.

A live probe showed "ask" holds in bypassPermissions. Filing an issue and
every read go ahead. An edit or git command in the session's own repo, and any
tool named like a read, never calls the server. Grimoire writes send only
their short arguments, so the URL stays small.

When the instance doesn't answer, the act goes ahead and the hook says once
per outage that it couldn't check. check_plugin.py now holds one outage line
per hook and has a smoke event for the fence.

GET /api/plugin/write-scope takes fallback_project_id for the entered project.

tests/test_project_fence.py drives the hook against real git repos and a
stand-in server. tests/test_write_scope.py holds the hook's
FENCE_READ_PREFIXES equal to services/write_scope.READ_PREFIXES. The plugin
README and PACKAGING describe the fence, and the plugin is minted to
2026.11.05.0245.

Milestone 583, step 3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-04 21:45:37 -05:00
co-authored by Claude Opus 5.5
parent 16b1d68ebd
commit 805fda5cb1
9 changed files with 519 additions and 7 deletions
+14
View File
@@ -79,6 +79,20 @@ On install you'll be asked for:
message, as a short menu of titles and matched passages.
- `hooks/hooks.json` → PreToolUse hook on `Bash` (`hooks/grimoire_tool_rules.sh`)
→ `GET /api/plugin/tool-rules`: the rules that match the command about to run.
- `hooks/hooks.json` → PreToolUse hook on editor writes, `Bash` and Grimoire's
tools (`hooks/grimoire_project_fence.sh`) → `GET /api/plugin/write-scope`:
holds a session to its own project. A session belongs to the project its
starting directory resolves to (a `.grimoire` marker, else the git remote's
binding), or, started outside any, to the first project it enters. Editing
another project's files, a git commit, push, merge, rebase, pull, tag or
reset in its repo (from `git -C`, after `cd`, or in a worktree of it), and a
Grimoire write that lands in another project each stop on a permission
prompt naming that project, in every permission mode. Filing an issue there
(`create_task(kind="issue")`) and every read go ahead. An edit in the
session's own repo never calls the server. It can't see a non-git shell
write into another repo (`sed -i`, a redirect), but it fences that repo's
commit and push. When the instance doesn't answer, the act goes ahead and
the hook says so once per outage.
- `hooks/hooks.json` → PreToolUse hook on every tool (`hooks/grimoire_moment.sh`)
→ `POST /api/plugin/moment`: the rules mounted on the moments the call
reaches. It skips tools that `GET /api/plugin/moment-tools` says reach