Skip to content

Commit 01d0ead

Browse files
author
Jon Langevin
committed
fix: resolve plan-scope paths from caller
1 parent 48dab91 commit 01d0ead

3 files changed

Lines changed: 92 additions & 5 deletions

File tree

‎.github/workflows/hooks-check.yml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@ on:
55
- 'hooks/**'
66
- 'tests/hook-contracts.sh'
77
- 'tests/hook-stdout-discipline.sh'
8+
- 'tests/plan-scope-check-contracts.sh'
9+
- 'tests/plan-scope-check.sh'
810
- '.github/workflows/hooks-check.yml'
911
pull_request:
1012
paths:
1113
- 'hooks/**'
1214
- 'tests/hook-contracts.sh'
1315
- 'tests/hook-stdout-discipline.sh'
16+
- 'tests/plan-scope-check-contracts.sh'
17+
- 'tests/plan-scope-check.sh'
1418
- '.github/workflows/hooks-check.yml'
1519

1620
permissions:
@@ -27,5 +31,7 @@ jobs:
2731
run: bash tests/hook-stdout-discipline.sh
2832
- name: Hook contract tests
2933
run: bash tests/hook-contracts.sh
34+
- name: Plan scope checker contract tests
35+
run: bash tests/plan-scope-check-contracts.sh
3036
- name: Subagent scope guard regression tests (#86)
3137
run: bash hooks/subagent-scope-guard.test.sh
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#!/usr/bin/env bash
2+
# tests/plan-scope-check-contracts.sh — direct contracts for plan-scope-check.sh.
3+
4+
set -uo pipefail
5+
6+
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
7+
CHECKER="$REPO_ROOT/tests/plan-scope-check.sh"
8+
APPLY="$REPO_ROOT/hooks/scope-lock-apply"
9+
failures=0
10+
11+
pass() { printf 'PASS: %s\n' "$*"; }
12+
fail() { printf 'FAIL: %s\n' "$*" >&2; failures=$((failures + 1)); }
13+
14+
test_relative_plan_paths_resolve_from_caller_cwd() {
15+
local tmp ws out status
16+
tmp="$(mktemp -d)"
17+
ws="$tmp/workspace"
18+
mkdir -p "$ws/docs/plans"
19+
(
20+
cd "$ws" &&
21+
git init -q &&
22+
git checkout -q -b feat/p &&
23+
git -c user.email=a@b -c user.name=x commit -q --allow-empty -m init
24+
)
25+
cat >"$ws/docs/plans/p.md" <<'PLAN'
26+
# P
27+
28+
## Scope Manifest
29+
30+
**PR Count:** 1
31+
**Tasks:** 1
32+
**Out of scope:**
33+
- (none)
34+
35+
**PR Grouping:**
36+
37+
| PR # | Title | Tasks | Branch |
38+
|------|-------|-------|--------|
39+
| 1 | P | Task 1 | feat/p |
40+
41+
**Status:** Locked 2026-06-23T00:00:00Z
42+
43+
### Task 1: P
44+
PLAN
45+
bash "$APPLY" "$ws/docs/plans/p.md" >/dev/null
46+
47+
set +e
48+
out="$(
49+
cd "$ws" &&
50+
bash "$CHECKER" \
51+
--plan docs/plans/p.md \
52+
--verify-lock docs/plans/p.md \
53+
--against-branch docs/plans/p.md
54+
)"
55+
status=$?
56+
set -e
57+
58+
if [ "$status" = "0" ] && printf '%s' "$out" | grep -q 'PASS: scope-manifest checks succeeded'; then
59+
pass "plan-scope-check: relative plan paths resolve from caller cwd"
60+
else
61+
fail "plan-scope-check: expected caller-relative paths to pass, status=${status}, output=${out}"
62+
fi
63+
rm -rf "$tmp"
64+
}
65+
66+
test_relative_plan_paths_resolve_from_caller_cwd
67+
68+
echo ""
69+
echo "Results: $failures failure(s)"
70+
[ "$failures" -eq 0 ]

‎tests/plan-scope-check.sh‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
set -euo pipefail
3232

33+
CALLER_CWD="$PWD"
3334
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
3435
cd "$REPO_ROOT"
3536

@@ -64,6 +65,13 @@ sha256_stdin() {
6465
fi
6566
}
6667

68+
resolve_input_path() {
69+
case "$1" in
70+
/*) printf '%s\n' "$1" ;;
71+
*) printf '%s/%s\n' "$CALLER_CWD" "$1" ;;
72+
esac
73+
}
74+
6775
# Check the manifest is well-formed. Args: plan path. Echoes problems to stdout.
6876
# Legacy plans (no manifest section) are skipped — only plans that opt into the
6977
# format are enforced. New plans created by writing-plans always include the
@@ -221,6 +229,9 @@ check_lock_hash() {
221229
# in the plan header (defaults to `main` if absent).
222230
check_against_branch() {
223231
local plan="$1"
232+
local plan_dir
233+
plan_dir="$(cd "$(dirname "$plan")" 2>/dev/null && pwd || true)"
234+
[ -n "$plan_dir" ] || plan_dir="$CALLER_CWD"
224235
local base
225236
base="$(grep -E '^\*\*Base branch:\*\*' "$plan" | head -1 \
226237
| sed -E 's/.*\*\*Base branch:\*\*[[:space:]]*([A-Za-z0-9._/-]+).*/\1/' || true)"
@@ -252,8 +263,8 @@ check_against_branch() {
252263

253264
while read -r br; do
254265
[ -z "$br" ] && continue
255-
if ! git rev-parse --verify "refs/heads/${br}" >/dev/null 2>&1 \
256-
&& ! git rev-parse --verify "refs/remotes/origin/${br}" >/dev/null 2>&1; then
266+
if ! git -C "$plan_dir" rev-parse --verify "refs/heads/${br}" >/dev/null 2>&1 \
267+
&& ! git -C "$plan_dir" rev-parse --verify "refs/remotes/origin/${br}" >/dev/null 2>&1; then
257268
printf '%s: planned branch %s does not exist locally or on origin\n' "$plan" "$br"
258269
rc=1
259270
fi
@@ -273,13 +284,13 @@ while [ $# -gt 0 ]; do
273284
case "$1" in
274285
--plan)
275286
[ -n "${2:-}" ] || { usage; exit 3; }
276-
MODE_PLAN+=("$2"); shift 2 ;;
287+
MODE_PLAN+=("$(resolve_input_path "$2")"); shift 2 ;;
277288
--verify-lock)
278289
[ -n "${2:-}" ] || { usage; exit 3; }
279-
MODE_VERIFY_LOCK+=("$2"); shift 2 ;;
290+
MODE_VERIFY_LOCK+=("$(resolve_input_path "$2")"); shift 2 ;;
280291
--against-branch)
281292
[ -n "${2:-}" ] || { usage; exit 3; }
282-
MODE_AGAINST_BRANCH+=("$2"); shift 2 ;;
293+
MODE_AGAINST_BRANCH+=("$(resolve_input_path "$2")"); shift 2 ;;
283294
--strict)
284295
STRICT=1; shift ;;
285296
--help|-h)

0 commit comments

Comments
 (0)