Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .deepreview
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,52 @@ update_learning_agents_architecture:
additional_context:
unchanged_matching_files: true

agent_tools_fully_qualified:
description: "Claude Code agent definition files must list every tool by fully-qualified name, never by wildcard."
match:
include:
- "**/agents/*.md"
review:
strategy: individual
instructions: |
This file defines a Claude Code subagent (the YAML frontmatter at the
top of the file configures the agent's name, description, model, and
tool grants). Check the `tools:` frontmatter field.

Rule: every tool entry MUST be a fully-qualified tool name. Wildcard
or glob patterns (any entry containing `*`, `?`, `[`, or ending in
`__*`) are non-conforming.

Rationale: wildcard patterns in subagent `tools:` frontmatter do not
reliably match deferred MCP tools at runtime. Observed failure mode:
a frontmatter entry like `mcp__deepwork-dev__*` is accepted at parse
time, but when the subagent tries to invoke
`mcp__deepwork-dev__mark_review_as_passed` the runtime responds with
`Error: No such tool available`. Enumerating each MCP tool by its
full name (e.g., `mcp__deepwork-dev__mark_review_as_passed`) avoids
this failure mode and also documents the exact tool surface the
agent relies on. This caught us in the e2e merge-queue run that
failed PR #390.

This rule does NOT apply to files that are not Claude Code agent
definitions — if the file has no YAML frontmatter with name/tools
fields (e.g., it is a context-injection document or a skill body
that happens to live under an `agents/` path), this review passes
vacuously.

Check for:
- Any entry in `tools:` containing `*`, `?`, or `[...]` glob syntax.
- Any entry ending in `__*` (the common MCP-wildcard form).
- Any quoted glob pattern such as `"mcp__<server>__*"`.

Output Format:
- PASS: `tools:` field is missing, empty, or every entry is a
fully-qualified name.
- FAIL: list each wildcard entry with its line number and the
fully-qualified tool names it should be replaced with (if
determinable from the agent's body; otherwise flag the need for
enumeration).

shell_code_review:
description: "Review shell scripts for correctness, safety, and project conventions."
match:
Expand Down
16 changes: 8 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- New `PLUG-REQ-001.15: Hook Script CLI Invocation` requirement in `doc/specs/deepwork/cli_plugins/PLUG-REQ-001-claude-code-plugin.md`

### Changed

- `claude_plugin_hook_deepwork_invocation` review rule now requires plugin hook scripts to invoke the CLI via `uvx deepwork` instead of merely providing a `uvx deepwork` fallback (PLUG-REQ-001.15)
- Plugin hook scripts (`post_commit_reminder.sh`, `deepschema_write.sh`, `post_compact.sh`) now invoke the `deepwork` CLI exclusively via `uvx deepwork ...`, matching the MCP server launch in `plugins/claude/.mcp.json`
- Flake `shellHook` no longer runs `uv tool install -e` — the editable user-level `deepwork` install is redundant now that plugin hooks go through `uvx`

### Fixed

- Plugin hooks no longer fail when the end user has a stale user-level `deepwork` install (e.g., `uv tool install deepwork` pinned to an older release) that wins PATH lookup but lacks the hook module being requested. The 0.13.9 fallback still used PATH first; this release bypasses PATH entirely so hooks resolve to the same `uvx` cache that the MCP server populated

### Removed
## [0.13.9] - 2026-04-16

### Added

- New `claude_plugin_hook_deepwork_invocation` review rule in `plugins/claude/.deepreview` that flags plugin hook scripts which call bare `deepwork` without a `uvx deepwork` fallback

### Changed

### Fixed

- Plugin hook scripts (`post_commit_reminder.sh`, `deepschema_write.sh`, `post_compact.sh`) now fall back to `uvx deepwork` when the bare `deepwork` binary is not on PATH. End-user installs launch the MCP server via `uvx deepwork serve`, so `deepwork` is not available as a command — previously these hooks failed with exit 127 on every Bash tool use, and Claude Code reported them as failed PostToolUse hooks (regression introduced in PR #361)

### Removed
## [0.13.8] - 2026-04-14

### Added
Expand Down
5 changes: 4 additions & 1 deletion doc/platforms/claude/cli_configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,10 @@ plugins/claude/
│ └── configure_reviews/SKILL.md # Set up review rules
├── hooks/ # Hook configuration
│ ├── hooks.json
│ └── post_commit_reminder.sh
│ ├── deepschema_write.sh # PostToolUse: validate writes against DeepSchemas
│ ├── post_commit_reminder.sh # PostToolUse: nudge /review after git commit
│ ├── post_compact.sh # SessionStart(compact): restore workflow context
│ └── startup_context.sh # SessionStart/SubagentStart: inject session/agent IDs
└── .mcp.json # MCP server config (uvx deepwork serve)
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,8 @@ The Claude Code plugin is the primary distribution mechanism for DeepWork on the
4. The agent body MUST instruct the subagent to read the instruction file from the user prompt, perform the review against the criteria in that file, and call `mark_review_as_passed` to report results.
5. The agent body MUST instruct the subagent not to edit files and not to explore beyond what the review instructions direct.
6. When the review formatter renders tasks with no per-rule agent persona specified (`agent_name` is `None`), it MUST default to `"reviewer"` as the `subagent_type` (see REVIEW-REQ-006.3.3c).

### PLUG-REQ-001.15: Hook Script CLI Invocation

1. Plugin hook scripts under `plugins/claude/hooks/` MUST invoke the `deepwork` CLI via `uvx deepwork ...`, never via a bare `deepwork` lookup on `PATH` (including `uv run deepwork` or absolute paths to `PATH`-installed binaries). This matches the MCP server launch command in `plugins/claude/.mcp.json` (`uvx deepwork serve`) so that hooks and the MCP server resolve to the same `deepwork` version from the same `uvx` cache.
2. Plugin hook scripts MUST NOT rely on a `deepwork` binary resolved through `PATH`, because (a) the binary is absent from `PATH` in end-user installs that run the plugin via `uvx`, which produces exit 127, and (b) when present via a user-level install such as `uv tool install deepwork`, it can be older than the Python module the hook is asking for, which produces "Hook '...' not found" or similar errors. Both failure modes surface to Claude Code as a failed hook.
3 changes: 0 additions & 3 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,6 @@
uv sync --extra dev --quiet 2>/dev/null || true
export PATH="$REPO_ROOT/.venv/bin:$PATH"

# Also register as a uv tool so `uvx deepwork serve` uses local source
uv tool install -e "$REPO_ROOT" --quiet 2>/dev/null || true

# Create claude wrapper script so direnv (which can't export functions) works
_claude_real=$(PATH="$(echo "$PATH" | sed "s|$REPO_ROOT/.venv/bin:||g")" command -v claude)
if [ -n "$_claude_real" ]; then
Expand Down
39 changes: 21 additions & 18 deletions plugins/claude/.deepreview
Original file line number Diff line number Diff line change
@@ -1,37 +1,40 @@
claude_plugin_hook_deepwork_invocation:
description: "Plugin hook scripts must fall back to `uvx deepwork` when the bare binary is not on PATH."
description: "PLUG-REQ-001.15: Plugin hook scripts must invoke the `deepwork` CLI via `uvx deepwork`, never via a bare binary on PATH."
match:
include:
- "hooks/*.sh"
review:
strategy: individual
instructions: |
Plugin hook scripts run in end-user installs where the MCP server is
launched via `uvx deepwork serve` (see plugins/claude/.mcp.json). In
that environment the bare `deepwork` binary is NOT on PATH. Any hook
that calls `deepwork ...` (or `uv run deepwork ...`) directly will
exit 127, and Claude Code reports it as a failed hook on every Bash
tool use. Regression history: PR #361 reintroduced this exact bug.
launched via `uvx deepwork serve` (see plugins/claude/.mcp.json) and
the bare `deepwork` binary may be missing from PATH, or present but
stale — e.g., a user-level `uv tool install deepwork` pinned to an
older release. A stale bare binary produces errors like "Hook
'...' not found" (when the hook module did not exist in that
version) on every Bash tool use, which Claude Code surfaces as a
failed hook.

For each `deepwork` (or `uv run deepwork`) invocation in this script,
verify it uses a fallback pattern equivalent to:
To avoid both failure modes, plugin hook scripts MUST invoke the
CLI the same way the MCP server is launched: via `uvx deepwork`.
A bare `deepwork ...` (or `uv run deepwork ...`, or any invocation
that uses PATH lookup) is a FAIL.

Expected pattern:

```bash
if command -v deepwork >/dev/null 2>&1; then
echo "${INPUT}" | deepwork hook some_hook
else
echo "${INPUT}" | uvx deepwork hook some_hook
fi
echo "${INPUT}" | uvx deepwork hook some_hook
```

A bare `deepwork ...` call without a `command -v deepwork` guard (or
equivalent fallback to `uvx deepwork ...`) is a FAIL.
For each `deepwork` invocation in this script, verify the command
prefix is `uvx deepwork`. Shell word-splitting counts — the word
immediately before `deepwork` on the command line must be `uvx`.

Output Format:
- PASS: Every `deepwork` invocation has a `uvx` fallback, or the
- PASS: Every `deepwork` invocation is prefixed with `uvx`, or the
script makes no `deepwork` calls.
- FAIL: List each unguarded `deepwork` invocation with its line
number and the suggested fallback edit.
- FAIL: List each non-`uvx` `deepwork` invocation with its line
number and the required edit.

claude_plugin_skill_instructions:
description: "PLUG-REQ-001 & REVIEW-REQ-007: Verify skill instructions adequately convey behavioral requirements."
Expand Down
4 changes: 2 additions & 2 deletions plugins/claude/agents/reviewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ tools:
- Grep
- Glob
- Bash
- "mcp__plugin_deepwork_deepwork__*"
- "mcp__deepwork-dev__*"
- mcp__plugin_deepwork_deepwork__mark_review_as_passed
- mcp__deepwork-dev__mark_review_as_passed
---

You are a DeepWork review agent. Your only job is to execute one review task and report the result.
Expand Down
30 changes: 20 additions & 10 deletions plugins/claude/hooks/deepschema_write.sh
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
#!/usr/bin/env bash
# DeepSchema write hook
# PostToolUse hook for Write/Edit - validates files against applicable DeepSchemas
# Falls back to `uvx deepwork` so end-user installs (where the plugin's
# MCP server is launched via uvx and `deepwork` is not on PATH) still work.
# deepschema_write.sh - DeepSchema write hook
#
# Registered as a PostToolUse hook on Write/Edit in hooks.json.
# Delegates to the `deepwork hook deepschema_write` Python entry point,
# which validates the written file against applicable DeepSchemas.
#
# Always invokes via `uvx deepwork` to match the MCP server invocation
# in plugins/claude/.mcp.json. This avoids a class of PATH-staleness
# bugs where a user-level `deepwork` binary (e.g., `uv tool install
# deepwork`) is older than the hook module it is being asked to run,
# producing "Hook '...' not found" errors on every tool use.
#
# Input (stdin): JSON from Claude Code PostToolUse hook
# Output (stdout): JSON response for Claude Code (allow/block + context)
# Exit codes:
# 0 on success, non-zero if uvx/the hook crashes (Claude Code
# surfaces non-zero as a failed PostToolUse hook)

set -euo pipefail

INPUT=$(cat)
export DEEPWORK_HOOK_PLATFORM="claude"
if command -v deepwork >/dev/null 2>&1; then
echo "${INPUT}" | deepwork hook deepschema_write
else
echo "${INPUT}" | uvx deepwork hook deepschema_write
fi
exit $?
echo "${INPUT}" | uvx deepwork hook deepschema_write
33 changes: 24 additions & 9 deletions plugins/claude/hooks/post_commit_reminder.sh
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
#!/usr/bin/env bash
# Post-commit reminder hook — delegates to deepwork Python hook.
# Falls back to `uvx deepwork` so end-user installs (where the plugin's
# MCP server is launched via uvx and `deepwork` is not on PATH) still work.
# post_commit_reminder.sh - Post-commit review reminder hook
#
# Registered as a PostToolUse hook on Bash in hooks.json.
# Delegates to the `deepwork hook post_commit_reminder` Python entry
# point, which inspects the Bash command and (when it is a `git commit`)
# nudges the agent to run the `review` skill if matching review rules
# have not been marked as passed for the committed files.
#
# Always invokes via `uvx deepwork` to match the MCP server invocation
# in plugins/claude/.mcp.json. This avoids a class of PATH-staleness
# bugs where a user-level `deepwork` binary (e.g., `uv tool install
# deepwork`) is older than the hook module it is being asked to run,
# producing "Hook '...' not found" errors on every Bash tool use.
#
# Input (stdin): JSON from Claude Code PostToolUse hook
# Output (stdout): JSON response for Claude Code (additionalContext for
# the agent, or empty {} when nothing to add)
# Exit codes:
# 0 on success, non-zero if uvx/the hook crashes (Claude Code
# surfaces non-zero as a failed PostToolUse hook)

set -euo pipefail

INPUT=$(cat)
export DEEPWORK_HOOK_PLATFORM="claude"
if command -v deepwork >/dev/null 2>&1; then
echo "${INPUT}" | deepwork hook post_commit_reminder
else
echo "${INPUT}" | uvx deepwork hook post_commit_reminder
fi
exit $?
echo "${INPUT}" | uvx deepwork hook post_commit_reminder
20 changes: 7 additions & 13 deletions plugins/claude/hooks/post_compact.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,13 @@ if [ -z "$CWD" ]; then
fi

# ==== Fetch active sessions ====
# Fall back to `uvx deepwork` so end-user installs (where the plugin's
# MCP server is launched via uvx and `deepwork` is not on PATH) still work.
if command -v deepwork >/dev/null 2>&1; then
STACK_JSON=$(deepwork jobs get-stack --path "$CWD" 2>/dev/null) || {
echo '{}'
exit 0
}
else
STACK_JSON=$(uvx deepwork jobs get-stack --path "$CWD" 2>/dev/null) || {
echo '{}'
exit 0
}
fi
# Always invoke via `uvx deepwork` to match the MCP server invocation in
# plugins/claude/.mcp.json — avoids PATH-staleness where a user-level
# `deepwork` binary is older than the subcommand it is being asked to run.
STACK_JSON=$(uvx deepwork jobs get-stack --path "$CWD" 2>/dev/null) || {
echo '{}'
exit 0
}

# ==== Check for active sessions ====
SESSION_COUNT=$(echo "$STACK_JSON" | jq '(.active_sessions // []) | length')
Expand Down
Loading