Skip to content

fix: scope stop-guard to originating repo + force tri-* dispatch - #92

Merged
5uck1ess merged 3 commits into
mainfrom
fix/stop-hook
Apr 15, 2026
Merged

fix: scope stop-guard to originating repo + force tri-* dispatch#92
5uck1ess merged 3 commits into
mainfrom
fix/stop-hook

Conversation

@5uck1ess

@5uck1ess 5uck1ess commented Apr 15, 2026

Copy link
Copy Markdown
Owner

Closes #91

Summary

Scope the devkit-stop-guard incomplete-workflow nag to the repo the workflow was started in. Opening a Claude Code session in a different repo now sees an approve verdict instead of blocking on a workflow that doesn't belong to that project. The block remains active when the user returns to the originating repo — scope restriction, not a bypass.

Changes

  • src/lib/state_json.go — add RepoRoot field to SessionState (JSON repo_root, omitempty).
  • src/mcp/tools.go — record s.repoRoot into state on devkit_start.
  • src/cmd/guard.go — add currentRepoRoot() (prefers CLAUDE_PROJECT_DIR, falls back to pwd-walk for .git) and samePath() (abs + EvalSymlinks + Clean). New scope check in runStopGuard approves silently on mismatch; empty state.RepoRoot (pre-devkit-stop-guard fires across repos — hook is session-scoped, should be repo/branch-scoped #91 sessions) and empty current repo root (missing hook env) fall through to legacy block — no silent approve on missing signal.
  • src/cmd/guard_test.goTestGuardStopHookRepoScope table test covering matching/mismatched/trailing-slash/empty-state/empty-env cases.

Test plan

Record repoRoot on devkit_start and approve Stop silently when the
current Claude Code session's repo differs. Block remains active in
the originating repo — no TTL escape, no branch carve-out, no env
override. Empty state.RepoRoot (pre-#91) and missing CLAUDE_PROJECT_DIR
fall through to the legacy block so a missing signal never silently
approves.
Prepend a DISPATCH-DO-NOT-ANSWER preamble to every tier step in
tri-review / tri-debug / tri-security / tri-dispatch so the calling
model cannot silently roleplay all three tiers when the workflow runs
via MCP. The previous phrasing ("You are the senior reviewer. Review
this code thoroughly:") read as a role assignment to Claude instead of
a dispatch instruction, defeating the multi-model property of tri-*.

Each tier now names a concrete subagent to dispatch to
(codex:codex-rescue for the smart tier, Gemini or a distinct external
for general/fast) and mandates stopping if no external subagent is
available rather than silently answering as the caller.
@5uck1ess 5uck1ess changed the title fix(hooks): scope stop-guard to originating repo fix: scope stop-guard to originating repo + force tri-* dispatch Apr 15, 2026
Fills three gaps from PR review:

- TestGuardStopHookRepoScopeWalkUp: exercises the pwd-walk fallback in
  currentRepoRoot() (existing suite always sets CLAUDE_PROJECT_DIR so
  the .git-finding loop never ran). Includes the nested-subdir case.
- TestGuardStopHookRepoScopeSymlink: proves samePath() actually uses
  EvalSymlinks — a symlinked path pointing at the originating repo
  must compare equal. Regression guard for macOS /var → /private/var.
- TestGuardStopHookRepoScopeWorktree: pins worktree support where
  .git is a FILE (gitlink), not a directory. devkit itself runs from
  worktrees — a future IsDir() refactor would silently bypass the
  block for every worktree user without this test.

go test ./... → 321 passed (was 316).
@5uck1ess
5uck1ess merged commit 3a529da into main Apr 15, 2026
6 checks passed
@5uck1ess
5uck1ess deleted the fix/stop-hook branch April 15, 2026 15:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

devkit-stop-guard fires across repos — hook is session-scoped, should be repo/branch-scoped

1 participant