Skip to content

Commit e033ae0

Browse files
Merge remote-tracking branch 'origin/main' into worktree-20260324-144933
2 parents f7bf287 + 1499011 commit e033ae0

File tree

7 files changed

+133
-17
lines changed

7 files changed

+133
-17
lines changed

.test-index

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
# Maps source files to test files that fuzzy match misses.
33
# Format: source/path: test/path1[, test/path2...]
44

5-
plugins/dso/.claude-plugin/plugin.json:tests/plugin/test_fixture_minimal_plugin_consumer.py,tests/hooks/test-plugin-docs.sh,tests/scripts/test-plugin-retro-gather.sh,tests/scripts/test-dso-shim-plugin-root.sh,tests/scripts/test-plugin-scripts.sh,tests/scripts/test-collect-discoveries-plugin-root.sh,tests/scripts/test-plugin-scripts-no-relative-paths.sh,tests/scripts/test-check-plugin-test-needed.sh,tests/scripts/test-plugin-dir-structure.sh,tests/scripts/test-plugin-reference-catalog.sh,tests/scripts/test-no-unguarded-plugin-refs.sh
5+
plugins/dso/.claude-plugin/plugin.json:tests/plugin/test_fixture_minimal_plugin_consumer.py,tests/hooks/test-plugin-docs.sh,tests/hooks/test-hooks-json-paths.sh,tests/hooks/test-run-hook-relative-paths.sh,tests/scripts/test-plugin-retro-gather.sh,tests/scripts/test-dso-shim-plugin-root.sh,tests/scripts/test-plugin-scripts.sh,tests/scripts/test-collect-discoveries-plugin-root.sh,tests/scripts/test-plugin-scripts-no-relative-paths.sh,tests/scripts/test-check-plugin-test-needed.sh,tests/scripts/test-plugin-dir-structure.sh,tests/scripts/test-plugin-reference-catalog.sh,tests/scripts/test-no-unguarded-plugin-refs.sh
6+
plugins/dso/hooks/run-hook.sh:tests/hooks/test-run-hook-relative-paths.sh,tests/hooks/test-hooks-json-paths.sh
67
plugins/dso/commands/commit.md:tests/plugin/test-commit-step1-5-skip.sh,tests/workflows/test-commit-breadcrumbs.sh,tests/hooks/test-pre-commit-review-gate.sh,tests/hooks/test-commit-tracker.sh,tests/hooks/test-pre-commit-test-gate.sh,tests/scripts/test-precommit-format-fix-config-paths.sh,tests/scripts/test-commit-workflow-step-1-5.sh,tests/scripts/test-commit-failure-tracker.sh,tests/scripts/test-pre-commit-wrapper.sh,tests/scripts/test-ensure-precommit-config-paths.sh,tests/skills/test_end_skill_learnings_step_before_commit.py,tests/skills/test_end_skill_bug_tickets_before_commit.py
78
plugins/dso/commands/end.md:tests/scripts/test-end-session-error-sweep.sh,tests/skills/test_end_skill_final_verification_step.py,tests/skills/test-project-setup-dependencies.sh,tests/skills/test_end_session_ticket_conflict_guard.py,tests/skills/test_end_session_no_auto_sync.py,tests/skills/test-end-session-error-sweep.sh,tests/skills/test_end_skill_summary_displays_stored_learnings.py,tests/skills/test_end_skill_learnings_step_before_commit.py,tests/skills/test_end_skill_dirty_worktree_resolution.py,tests/skills/test_end_skill_bug_tickets_before_commit.py
89
plugins/dso/commands/review.md:plugins/dso/skills/preplanning/tests/test_adversarial_review_prompts.sh,tests/plugin/test-verify-review-diff.sh,tests/workflows/test-review-workflow-no-snapshot.sh,tests/hooks/test-pre-commit-review-gate.sh,tests/hooks/test-review-gate.sh,tests/hooks/test-review-gate-non-reviewable.sh,tests/hooks/test-review-gate-telemetry.sh,tests/hooks/test-two-layer-review-gate.sh,tests/hooks/test-review-gate-self-healing.sh,tests/hooks/test-validate-review-output.sh,tests/hooks/test-plan-review-gate.sh,tests/hooks/test-review-gate-bypass-sentinel.sh,tests/hooks/test-record-review.sh,tests/hooks/test-review-gate-allowlist.sh,tests/hooks/test-record-review-crossval.sh,tests/hooks/test-record-review-config-exclusions.sh,tests/hooks/test-review-stop.sh,tests/scripts/test-value-reviewer-signals.sh,tests/scripts/test-write-reviewer-findings.sh,tests/scripts/test-review-gate.sh,tests/scripts/test-skip-review-check.sh,tests/skills/test_implementation_plan_tdd_reviewer.py

plugins/dso/.claude-plugin/plugin.json

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "dso",
3-
"version": "0.25.21",
3+
"version": "0.25.23",
44
"description": "Workflow infrastructure plugin for Claude Code projects",
55
"commands": "./commands/",
66
"skills": "./skills/",
@@ -11,7 +11,7 @@
1111
"hooks": [
1212
{
1313
"type": "command",
14-
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.sh ${CLAUDE_PLUGIN_ROOT}/hooks/dispatchers/session-start.sh"
14+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.sh dispatchers/session-start.sh"
1515
}
1616
]
1717
}
@@ -22,7 +22,7 @@
2222
"hooks": [
2323
{
2424
"type": "command",
25-
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.sh ${CLAUDE_PLUGIN_ROOT}/hooks/dispatchers/pre-bash.sh"
25+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.sh dispatchers/pre-bash.sh"
2626
}
2727
]
2828
},
@@ -31,7 +31,7 @@
3131
"hooks": [
3232
{
3333
"type": "command",
34-
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.sh ${CLAUDE_PLUGIN_ROOT}/hooks/dispatchers/pre-edit.sh"
34+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.sh dispatchers/pre-edit.sh"
3535
}
3636
]
3737
},
@@ -40,7 +40,7 @@
4040
"hooks": [
4141
{
4242
"type": "command",
43-
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.sh ${CLAUDE_PLUGIN_ROOT}/hooks/dispatchers/pre-write.sh"
43+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.sh dispatchers/pre-write.sh"
4444
}
4545
]
4646
},
@@ -49,7 +49,7 @@
4949
"hooks": [
5050
{
5151
"type": "command",
52-
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.sh ${CLAUDE_PLUGIN_ROOT}/hooks/dispatchers/pre-exitplanmode.sh"
52+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.sh dispatchers/pre-exitplanmode.sh"
5353
}
5454
]
5555
},
@@ -58,7 +58,7 @@
5858
"hooks": [
5959
{
6060
"type": "command",
61-
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.sh ${CLAUDE_PLUGIN_ROOT}/hooks/dispatchers/pre-agent.sh"
61+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.sh dispatchers/pre-agent.sh"
6262
}
6363
]
6464
},
@@ -67,7 +67,7 @@
6767
"hooks": [
6868
{
6969
"type": "command",
70-
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.sh ${CLAUDE_PLUGIN_ROOT}/hooks/dispatchers/pre-taskoutput.sh"
70+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.sh dispatchers/pre-taskoutput.sh"
7171
}
7272
]
7373
}
@@ -78,7 +78,7 @@
7878
"hooks": [
7979
{
8080
"type": "command",
81-
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.sh ${CLAUDE_PLUGIN_ROOT}/hooks/dispatchers/post-bash.sh"
81+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.sh dispatchers/post-bash.sh"
8282
}
8383
]
8484
},
@@ -87,7 +87,7 @@
8787
"hooks": [
8888
{
8989
"type": "command",
90-
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.sh ${CLAUDE_PLUGIN_ROOT}/hooks/dispatchers/post-edit.sh"
90+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.sh dispatchers/post-edit.sh"
9191
}
9292
]
9393
},
@@ -96,7 +96,7 @@
9696
"hooks": [
9797
{
9898
"type": "command",
99-
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.sh ${CLAUDE_PLUGIN_ROOT}/hooks/dispatchers/post-write.sh"
99+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.sh dispatchers/post-write.sh"
100100
}
101101
]
102102
}
@@ -107,7 +107,7 @@
107107
"hooks": [
108108
{
109109
"type": "command",
110-
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.sh ${CLAUDE_PLUGIN_ROOT}/hooks/dispatchers/stop.sh"
110+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.sh dispatchers/stop.sh"
111111
}
112112
]
113113
}
@@ -118,7 +118,7 @@
118118
"hooks": [
119119
{
120120
"type": "command",
121-
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.sh ${CLAUDE_PLUGIN_ROOT}/hooks/dispatchers/post-failure.sh"
121+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.sh dispatchers/post-failure.sh"
122122
}
123123
]
124124
}

plugins/dso/hooks/run-hook.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ fi
2525
HOOK="$1"
2626
shift
2727

28+
# Resolve relative paths against the hooks/ directory (e.g., "dispatchers/pre-bash.sh").
29+
# This allows plugin.json to use relative dispatcher paths, reducing
30+
# ${CLAUDE_PLUGIN_ROOT} occurrences in error messages (bug e724-31a3).
31+
if [[ -n "$HOOK" && "${HOOK:0:1}" != "/" && ! -f "$HOOK" ]]; then
32+
HOOK="$CLAUDE_PLUGIN_ROOT/hooks/$HOOK"
33+
fi
34+
2835
if [[ -z "$HOOK" || ! -f "$HOOK" ]]; then
2936
# No hook specified or file doesn't exist — fail-open
3037
exit 0

tests/hooks/test-hooks-json-paths.sh

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,47 @@ else
7575
fi
7676
assert_eq "test_hooks_json_plugin_root_refs" "all_have_plugin_root" "$actual"
7777

78+
# ─────────────────────────────────────────────────────────────
79+
# test_hooks_json_max_one_plugin_root_per_command
80+
# Bug e724-31a3: Claude Code displays raw ${CLAUDE_PLUGIN_ROOT} in hook error
81+
# messages. Each command should use ${CLAUDE_PLUGIN_ROOT} at most once (for the
82+
# entry point). Dispatcher arguments should be relative paths resolved by
83+
# run-hook.sh to keep error messages clean.
84+
# ─────────────────────────────────────────────────────────────
85+
if [[ -f "$HOOKS_JSON" ]]; then
86+
multi_ref_cmds=$(HOOKS_JSON_PATH="$HOOKS_JSON" python3 -c "
87+
import json, os, sys
88+
89+
hooks_json_path = os.environ['HOOKS_JSON_PATH']
90+
with open(hooks_json_path) as f:
91+
d = json.load(f)
92+
93+
bad = []
94+
hooks_section = d.get('hooks', {})
95+
for event, groups in hooks_section.items():
96+
for group in groups:
97+
for h in group.get('hooks', []):
98+
cmd = h.get('command', '')
99+
count = cmd.count('\${CLAUDE_PLUGIN_ROOT}')
100+
if count > 1:
101+
bad.append(f'{event}: {cmd} (count={count})')
102+
103+
if bad:
104+
for b in bad:
105+
print(b)
106+
sys.exit(1)
107+
sys.exit(0)
108+
" 2>&1)
109+
if [[ $? -eq 0 ]]; then
110+
actual="max_one_ref"
111+
else
112+
actual="multiple_refs: $multi_ref_cmds"
113+
fi
114+
else
115+
actual="missing_file"
116+
fi
117+
assert_eq "test_hooks_json_max_one_plugin_root_per_command" "max_one_ref" "$actual"
118+
78119
# ─────────────────────────────────────────────────────────────
79120
# test_hooks_json_no_absolute_paths
80121
# No 'command' values in plugin.json hooks may contain '/Users/' or '/home/'.
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#!/usr/bin/env bash
2+
# tests/hooks/test-run-hook-relative-paths.sh
3+
# Bug e724-31a3: Verifies run-hook.sh resolves relative dispatcher paths
4+
# against its own hooks/ directory, so plugin.json can use relative paths
5+
# to minimize ${CLAUDE_PLUGIN_ROOT} occurrences in error messages.
6+
#
7+
# Usage: bash tests/hooks/test-run-hook-relative-paths.sh
8+
9+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10+
REPO_ROOT="$(git rev-parse --show-toplevel)"
11+
DSO_PLUGIN_DIR="$REPO_ROOT/plugins/dso"
12+
RUN_HOOK="$DSO_PLUGIN_DIR/hooks/run-hook.sh"
13+
14+
source "$REPO_ROOT/tests/lib/assert.sh"
15+
16+
# ─────────────────────────────────────────────────────────────
17+
# test_run_hook_resolves_relative_path
18+
# run-hook.sh should resolve a relative path (e.g., "dispatchers/pre-bash.sh")
19+
# against its hooks/ directory, just as it resolves absolute paths.
20+
# We test this by creating a temp hook that echoes "OK" and passing it as
21+
# a relative path.
22+
# ─────────────────────────────────────────────────────────────
23+
TMPDIR_TEST=$(mktemp -d /tmp/test-run-hook-rel.XXXXXX)
24+
trap 'rm -rf "$TMPDIR_TEST"' EXIT
25+
26+
# Create a minimal hook script in a relative subdir mirroring the dispatcher pattern
27+
mkdir -p "$TMPDIR_TEST/hooks/dispatchers"
28+
cat > "$TMPDIR_TEST/hooks/dispatchers/test-echo.sh" <<'HOOKEOF'
29+
#!/usr/bin/env bash
30+
echo "RELATIVE_OK"
31+
exit 0
32+
HOOKEOF
33+
chmod +x "$TMPDIR_TEST/hooks/dispatchers/test-echo.sh"
34+
35+
# Copy run-hook.sh to the temp hooks dir so it can resolve relative paths
36+
cp "$RUN_HOOK" "$TMPDIR_TEST/hooks/run-hook.sh"
37+
chmod +x "$TMPDIR_TEST/hooks/run-hook.sh"
38+
39+
# Also need the hooks/lib dir for CLAUDE_PLUGIN_ROOT resolution fallback
40+
mkdir -p "$TMPDIR_TEST/hooks/lib"
41+
42+
# Test: run-hook.sh with a relative dispatcher path should work
43+
output=$(CLAUDE_PLUGIN_ROOT="$TMPDIR_TEST" "$TMPDIR_TEST/hooks/run-hook.sh" "dispatchers/test-echo.sh" 2>&1)
44+
actual_exit=$?
45+
46+
if [[ "$output" == *"RELATIVE_OK"* && "$actual_exit" -eq 0 ]]; then
47+
actual="resolved"
48+
else
49+
actual="not_resolved (exit=$actual_exit, output=$output)"
50+
fi
51+
assert_eq "test_run_hook_resolves_relative_path" "resolved" "$actual"
52+
53+
# ─────────────────────────────────────────────────────────────
54+
# test_run_hook_absolute_path_still_works
55+
# Absolute paths must continue to work (backwards compatibility).
56+
# ─────────────────────────────────────────────────────────────
57+
output=$(CLAUDE_PLUGIN_ROOT="$TMPDIR_TEST" "$TMPDIR_TEST/hooks/run-hook.sh" "$TMPDIR_TEST/hooks/dispatchers/test-echo.sh" 2>&1)
58+
actual_exit=$?
59+
60+
if [[ "$output" == *"RELATIVE_OK"* && "$actual_exit" -eq 0 ]]; then
61+
actual="works"
62+
else
63+
actual="broken (exit=$actual_exit, output=$output)"
64+
fi
65+
assert_eq "test_run_hook_absolute_path_still_works" "works" "$actual"
66+
67+
print_summary

tests/test-estimate-context-load.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
set -euo pipefail
66

77
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8-
SCRIPT="$SCRIPT_DIR/../scripts/estimate-context-load.sh"
8+
SCRIPT="$SCRIPT_DIR/../plugins/dso/scripts/estimate-context-load.sh"
99
FAILURES=0
1010
TESTS=0
1111

tests/test-verify-baseline-intent-portability.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
set -eu
2020

2121
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
22-
CANONICAL_SCRIPT="$SCRIPT_DIR/../scripts/verify-baseline-intent.sh"
23-
READ_CONFIG_SH="$SCRIPT_DIR/../scripts/read-config.sh"
22+
CANONICAL_SCRIPT="$SCRIPT_DIR/../plugins/dso/scripts/verify-baseline-intent.sh"
23+
READ_CONFIG_SH="$SCRIPT_DIR/../plugins/dso/scripts/read-config.sh"
2424

2525
FAILURES=0
2626
TESTS=0

0 commit comments

Comments
 (0)