From de0f5d332f328f7350b03f124c28909f2e94e599 Mon Sep 17 00:00:00 2001 From: Aram Hammoudeh <59990765+aram-devdocs@users.noreply.github.com> Date: Mon, 27 Apr 2026 18:32:17 +0000 Subject: [PATCH 1/3] fix(hooks): bypass delegation guard during merge --- .claude/hooks/delegation-guard.sh | 7 ++++ .../tests/test_delegation_guard_subagent.sh | 33 ++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/.claude/hooks/delegation-guard.sh b/.claude/hooks/delegation-guard.sh index 91d95f0..e383443 100755 --- a/.claude/hooks/delegation-guard.sh +++ b/.claude/hooks/delegation-guard.sh @@ -46,6 +46,13 @@ case "$rel_path" in .claude/*|.agents/*|docs/*|.github/*) hook_allow ;; esac +# Merge conflict resolution is mechanical arbitration, not new Rust +# implementation work. Let the root orchestrator edit Rust sources while +# the worktree has unmerged paths. +if [ -n "$(git -C "$HOOK_CWD" diff --name-only --diff-filter=U 2>/dev/null || true)" ]; then + hook_allow +fi + if [ "$role" = "root" ] || [ -z "$role" ]; then hook_block "Root orchestrator should dispatch to a subagent for Rust implementation (file: $file_path). Use the 01-implementer agent." fi diff --git a/.claude/hooks/tests/test_delegation_guard_subagent.sh b/.claude/hooks/tests/test_delegation_guard_subagent.sh index 68ca4d2..02d642c 100755 --- a/.claude/hooks/tests/test_delegation_guard_subagent.sh +++ b/.claude/hooks/tests/test_delegation_guard_subagent.sh @@ -118,7 +118,38 @@ for path in ".claude/hooks/role-marker.sh" ".agents/rules/foo.md" "docs/src/intr done pass "guard allows root edits of infra files (.claude/, .agents/, docs/, .github/)" -# --- Step 6: Backwards-compat — if a role file exists with +# --- Step 6: Merge conflict resolution is allowed from root. Conflict +# marker arbitration is mechanical, so the root orchestrator may edit +# Rust source files while the worktree has unmerged paths. +merge_repo="$TMP_DIR/merge-repo" +mkdir -p "$merge_repo/crates/plumb-core/src" +git -C "$merge_repo" init -q +git -C "$merge_repo" config user.name "Hook Test" +git -C "$merge_repo" config user.email "hook-test@example.com" +printf 'pub fn value() -> u8 { 1 }\n' > "$merge_repo/crates/plumb-core/src/lib.rs" +git -C "$merge_repo" add crates/plumb-core/src/lib.rs +git -C "$merge_repo" commit -qm "initial" +git -C "$merge_repo" checkout -qb feature +printf 'pub fn value() -> u8 { 2 }\n' > "$merge_repo/crates/plumb-core/src/lib.rs" +git -C "$merge_repo" commit -am "feature" -q +git -C "$merge_repo" checkout -q - +printf 'pub fn value() -> u8 { 3 }\n' > "$merge_repo/crates/plumb-core/src/lib.rs" +git -C "$merge_repo" commit -am "main" -q +git -C "$merge_repo" merge feature >/dev/null 2>&1 && fail "expected merge conflict" + +merge_input="$(jq -n \ + --arg sid "$session_id" \ + --arg cwd "$merge_repo" \ + --arg fp "$merge_repo/crates/plumb-core/src/lib.rs" \ + '{session_id: $sid, cwd: $cwd, hook_event_name: "PreToolUse", tool_name: "Edit", tool_input: {file_path: $fp, content: "x"}}')" +guard_output="$(env -u CLAUDE_SESSION_ID \ + CLAUDE_PROJECT_DIR="$merge_repo" \ + bash "$HOOKS_DIR/delegation-guard.sh" <<<"$merge_input")" +decision="$(printf '%s' "$guard_output" | jq -r '.decision // empty' 2>/dev/null || true)" +[ "$decision" = "allow" ] || fail "guard should ALLOW root .rs edit during merge conflict resolution, got: $guard_output" +pass "guard allows root Rust edits while unmerged paths exist" + +# --- Step 7: Backwards-compat — if a role file exists with # subagent:, guard still allows. This preserves the existing # escape hatch for harnesses that DO populate agent_type at # SessionStart (future Claude Code versions) or for manual override. From 0286f15cab9df404c5503686a64fb6bdc9768146 Mon Sep 17 00:00:00 2001 From: Aram Hammoudeh <59990765+aram-devdocs@users.noreply.github.com> Date: Mon, 27 Apr 2026 19:19:52 +0000 Subject: [PATCH 2/3] fix(ci): clear PR preflight blockers --- .gitignore | 1 + crates/plumb-mcp/AGENTS.md | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index a22304a..4ead483 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,5 @@ .claude/worktrees/ .claude/memory/ .claude/settings.local.json +.claude-pr/ node_modules/ diff --git a/crates/plumb-mcp/AGENTS.md b/crates/plumb-mcp/AGENTS.md index d1ccb9d..1480919 100644 --- a/crates/plumb-mcp/AGENTS.md +++ b/crates/plumb-mcp/AGENTS.md @@ -40,8 +40,6 @@ See `.agents/rules/mcp-tool-patterns.md`. Summary: 3. Add a protocol test case in `crates/plumb-cli/tests/mcp_stdio.rs`. 4. Update `docs/src/mcp.md` tool table. -Use the `09-mcp-tool-author` subagent for cookie-cutter execution. - ## Depends on - `plumb-core` (types; `test-fake` feature enabled so `lint_url` can From d44b496f63159407db26bafe8c22ddc4614a709f Mon Sep 17 00:00:00 2001 From: Aram Hammoudeh <59990765+aram-devdocs@users.noreply.github.com> Date: Mon, 27 Apr 2026 19:26:09 +0000 Subject: [PATCH 3/3] fix(ci): address delegation guard review --- .claude/hooks/delegation-guard.sh | 7 ++++--- .../tests/test_delegation_guard_subagent.sh | 21 +++++++++++++++++++ crates/plumb-mcp/AGENTS.md | 2 +- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/.claude/hooks/delegation-guard.sh b/.claude/hooks/delegation-guard.sh index e383443..51c54eb 100755 --- a/.claude/hooks/delegation-guard.sh +++ b/.claude/hooks/delegation-guard.sh @@ -47,9 +47,10 @@ case "$rel_path" in esac # Merge conflict resolution is mechanical arbitration, not new Rust -# implementation work. Let the root orchestrator edit Rust sources while -# the worktree has unmerged paths. -if [ -n "$(git -C "$HOOK_CWD" diff --name-only --diff-filter=U 2>/dev/null || true)" ]; then +# implementation work. Limit the exception to the Rust file that is +# currently unmerged so unrelated implementation files stay guarded. +conflicted_paths="$(git -C "$HOOK_CWD" diff --name-only --diff-filter=U 2>/dev/null || true)" +if printf '%s\n' "$conflicted_paths" | grep -qxF "$rel_path"; then hook_allow fi diff --git a/.claude/hooks/tests/test_delegation_guard_subagent.sh b/.claude/hooks/tests/test_delegation_guard_subagent.sh index 02d642c..e2f263e 100755 --- a/.claude/hooks/tests/test_delegation_guard_subagent.sh +++ b/.claude/hooks/tests/test_delegation_guard_subagent.sh @@ -149,6 +149,27 @@ decision="$(printf '%s' "$guard_output" | jq -r '.decision // empty' 2>/dev/null [ "$decision" = "allow" ] || fail "guard should ALLOW root .rs edit during merge conflict resolution, got: $guard_output" pass "guard allows root Rust edits while unmerged paths exist" +unrelated_merge_input="$(jq -n \ + --arg sid "$session_id" \ + --arg cwd "$merge_repo" \ + --arg fp "$merge_repo/crates/plumb-cli/src/main.rs" \ + '{session_id: $sid, cwd: $cwd, hook_event_name: "PreToolUse", tool_name: "Edit", tool_input: {file_path: $fp, content: "x"}}')" +guard_output="$(env -u CLAUDE_SESSION_ID \ + CLAUDE_PROJECT_DIR="$merge_repo" \ + bash "$HOOKS_DIR/delegation-guard.sh" <<<"$unrelated_merge_input")" +decision="$(printf '%s' "$guard_output" | jq -r '.decision // empty' 2>/dev/null || true)" +[ "$decision" = "block" ] || fail "guard should BLOCK unrelated root .rs edit during merge conflict resolution, got: $guard_output" +pass "guard keeps unrelated Rust files blocked during merge conflict resolution" + +printf 'pub fn value() -> u8 { 2 }\n' > "$merge_repo/crates/plumb-core/src/lib.rs" +git -C "$merge_repo" add crates/plumb-core/src/lib.rs +guard_output="$(env -u CLAUDE_SESSION_ID \ + CLAUDE_PROJECT_DIR="$merge_repo" \ + bash "$HOOKS_DIR/delegation-guard.sh" <<<"$merge_input")" +decision="$(printf '%s' "$guard_output" | jq -r '.decision // empty' 2>/dev/null || true)" +[ "$decision" = "block" ] || fail "guard should BLOCK root .rs edit after conflict resolution, got: $guard_output" +pass "guard blocks root Rust edits after the conflict is resolved" + # --- Step 7: Backwards-compat — if a role file exists with # subagent:, guard still allows. This preserves the existing # escape hatch for harnesses that DO populate agent_type at diff --git a/crates/plumb-mcp/AGENTS.md b/crates/plumb-mcp/AGENTS.md index 1480919..36f2000 100644 --- a/crates/plumb-mcp/AGENTS.md +++ b/crates/plumb-mcp/AGENTS.md @@ -33,7 +33,7 @@ Built on `rmcp 0.2.x` with the `#[tool_router]` + `#[tool]` + ## Adding a new tool -See `.agents/rules/mcp-tool-patterns.md`. Summary: +See `.agents/rules/mcp-tool-patterns.md` for the handoff path. Summary: 1. Add a `Deserialize + JsonSchema` struct for the tool's args. 2. Add a `#[tool(description = "…")]` async method on `PlumbServer`.