Skip to content

Commit 75ebb6c

Browse files
Merge pull request #66 from robercano/feat/issue-63-plugin-root-derivation
fix(harness): split plugin root from consumer project root in all scripts (closes #63)
2 parents 96ae715 + 941b7b4 commit 75ebb6c

12 files changed

Lines changed: 77 additions & 23 deletions

.claude/scripts/bot-gh.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
# Usage: .claude/scripts/bot-gh.sh pr create --title "..." --body "..."
2323
set -euo pipefail
2424

25-
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
25+
# Two-root derivation (issue #63): script_dir = sibling scripts, root = consumer project.
26+
# shellcheck source=resolve-roots.sh
27+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/resolve-roots.sh"
2628
if [ -f "$root/.env" ]; then
2729
set -a
2830
# shellcheck disable=SC1091

.claude/scripts/cockpit.sh

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,15 @@
3535
# generated artifact). Pass a second positional arg to write elsewhere.
3636
set -uo pipefail
3737

38-
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
39-
root="$(cd "$script_dir/../.." && pwd)"
38+
# Two-root derivation (issue #63): script_dir = sibling scripts, root = consumer project.
39+
# shellcheck source=resolve-roots.sh
40+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/resolve-roots.sh"
4041
self="$script_dir/cockpit.sh"
42+
# Agent definitions ship with the PLUGIN, not the consumer repo: prefer the
43+
# project's own .claude/agents (repo/worktree layout, or a consumer override),
44+
# else the plugin-cache layout where agents/ sits beside scripts/.
45+
agents_dir="$root/.claude/agents"
46+
[ -d "$agents_dir" ] || agents_dir="$script_dir/../agents"
4147

4248
# ---------------------------------------------------------------------------
4349
# Hidden seam: the blocking-graph parser as its own subcommand, so it has
@@ -169,7 +175,7 @@ node -e '
169175
out.push({ role, model, description });
170176
}
171177
fs.writeFileSync(process.argv[2], JSON.stringify(out));
172-
' "$root/.claude/agents" "$tmpdir/agents.json"
178+
' "$agents_dir" "$tmpdir/agents.json"
173179

174180
# ---- adapter (review lenses/skills, budget) ------------------------------------
175181
node -e '
@@ -310,11 +316,11 @@ function phaseBadge(phase) {
310316
}
311317
}
312318
function renderLiveProgress() {
313-
const latest = new Map(); // "role task" -> event
319+
const latest = new Map(); // "role\u0000task" -> event
314320
for (const ev of events) {
315321
const role = ev.role != null ? String(ev.role) : "";
316322
const task = ev.task != null ? String(ev.task) : "";
317-
const key = role + "
323+
const key = role + "\u0000" + task;
318324
latest.set(key, ev); // later lines overwrite earlier ones for the same key
319325
}
320326
const workers = [...latest.values()];

.claude/scripts/gate.sh

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
set -uo pipefail
77

88
key="${1:?usage: gate.sh <gate-name>}"
9-
# Repo root is two levels up from this script (<root>/.claude/scripts/gate.sh) —
10-
# robust whether or not we're nested inside another git repo.
11-
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12-
root="$(cd "$script_dir/../.." && pwd)"
9+
# Two-root derivation (issue #63): script_dir = sibling scripts, root = consumer
10+
# project (repo-tracked <root>/.claude/scripts layout wins — robust in worktrees —
11+
# else CLAUDE_PROJECT_DIR/git-toplevel/cwd for the plugin-cache layout).
12+
# shellcheck source=resolve-roots.sh
13+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/resolve-roots.sh"
1314

1415
# Dependency-freshness preflight (pnpm-gated: no-ops unless the repo uses pnpm).
1516
# pnpm copies the resolved lockfile to node_modules/.pnpm/lock.yaml on every

.claude/scripts/log-event.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,10 @@ while [ $# -gt 0 ]; do
4949
shift || break
5050
done
5151

52-
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)" || exit 0
53-
root="$(cd "$script_dir/../.." 2>/dev/null && pwd)" || exit 0
52+
# Two-root derivation (issue #63) — resolve-roots.sh never fails, matching this
53+
# script's never-block contract.
54+
# shellcheck source=resolve-roots.sh
55+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)/resolve-roots.sh" 2>/dev/null || exit 0
5456

5557
events_file="${CLAUDE_EVENTS_FILE:-$root/.claude/state/events.jsonl}"
5658
max_lines="${EVENTS_MAX_LINES:-2000}"

.claude/scripts/merge-ready.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@
2626
set -euo pipefail
2727
export PATH="$HOME/.local/bin:$PATH"
2828

29-
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
29+
# Two-root derivation (issue #63): script_dir = sibling scripts, root = consumer project.
30+
# shellcheck source=resolve-roots.sh
31+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/resolve-roots.sh"
3032
# Route EVERY gh call (list/view/merge) through the bot identity (see bot-gh.sh).
31-
gh() { bash "$root/.claude/scripts/bot-gh.sh" "$@"; }
33+
gh() { bash "$script_dir/bot-gh.sh" "$@"; }
3234
repo="${1:-$(gh repo view --json nameWithOwner -q .nameWithOwner)}"
3335
owner="${MERGE_APPROVER:-${repo%%/*}}" # the approver whose APPROVED review authorizes a merge
3436
gates="$root/.claude/gates.json"

.claude/scripts/notify-poll.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111
# Repo is derived from the current git remote; override with $1 (owner/repo).
1212
set -euo pipefail
1313

14-
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
14+
# Two-root derivation (issue #63): script_dir = sibling scripts, root = consumer project.
15+
# shellcheck source=resolve-roots.sh
16+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/resolve-roots.sh"
1517
# Route EVERY gh call through the bot identity (see bot-gh.sh). Defined before the
1618
# first gh use below so the repo-derivation call already runs as the bot.
17-
gh() { bash "$root/.claude/scripts/bot-gh.sh" "$@"; }
19+
gh() { bash "$script_dir/bot-gh.sh" "$@"; }
1820
repo="${1:-$(gh repo view --json nameWithOwner -q .nameWithOwner)}"
1921
owner="${MERGE_APPROVER:-${repo%%/*}}" # the human whose APPROVED review gates a merge
2022
state_dir="$root/.claude/state" # add .claude/state/ to .gitignore

.claude/scripts/pr-feedback.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
# Invoke as `bash .claude/scripts/pr-feedback.sh` (pre-approve that exact command).
1616
set -euo pipefail
1717

18-
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
18+
# Two-root derivation (issue #63): script_dir = sibling scripts, root = consumer project.
19+
# shellcheck source=resolve-roots.sh
20+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/resolve-roots.sh"
1921
# Route EVERY gh call through the bot identity (see bot-gh.sh).
20-
gh() { bash "$root/.claude/scripts/bot-gh.sh" "$@"; }
22+
gh() { bash "$script_dir/bot-gh.sh" "$@"; }
2123
repo="${1:-$(gh repo view --json nameWithOwner -q .nameWithOwner)}"
2224
bot="${BOT_LOGIN:-robercano-ghbot}"
2325
marker="<!-- claude-addressed -->"

.claude/scripts/prepare-pr.sh

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@ case "$pr" in
4343
''|*[!0-9]*) echo "prepare-pr: PR number must be numeric (got '$pr')" >&2; exit 2 ;;
4444
esac
4545

46-
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
47-
root="$(cd "$script_dir/../.." && pwd)"
46+
# Two-root derivation (issue #63): script_dir = sibling scripts, root = consumer project.
47+
# shellcheck source=resolve-roots.sh
48+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/resolve-roots.sh"
4849
cd "$root"
4950

5051
gates="$root/.claude/gates.json"

.claude/scripts/resolve-roots.sh

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/usr/bin/env bash
2+
# resolve-roots.sh — the ONE implementation of the two-root derivation
3+
# (issue #63). SOURCE this from a sibling script; do not execute it.
4+
#
5+
# There are two distinct roots, and the plugin install cache broke scripts
6+
# that conflated them with a single `../..` hop:
7+
#
8+
# script_dir — where SIBLING scripts live (bot-gh.sh, gate.sh, …). This is
9+
# simply this file's own directory, correct in BOTH layouts:
10+
# repo/worktree checkout: <root>/.claude/scripts/
11+
# plugin install cache: ~/.claude/plugins/cache/<mp>/<plugin>/<ver>/scripts/
12+
# (the cache strips the .claude/ prefix, so `../..` lands outside the plugin).
13+
#
14+
# root — the CONSUMER PROJECT root, where .env (GH_BOT_TOKEN), gates.json,
15+
# .claude/state/, worktrees, and the git repo live. Resolution order:
16+
# 1. repo-tracked layout (<x>/.claude/scripts) → <x>. This must win over
17+
# CLAUDE_PROJECT_DIR: worktree implementers run the WORKTREE's copy of
18+
# gate.sh and need the worktree as root, while the harness env var
19+
# points at the main checkout.
20+
# 2. $CLAUDE_PROJECT_DIR — set by the harness; the only reliable signal
21+
# when running from the plugin cache.
22+
# 3. git toplevel of the cwd, then cwd — callers invoke these scripts
23+
# from the consumer repo, so this is the right last resort.
24+
#
25+
# Never exits/fails (log-event.sh sources this and must never block a worker):
26+
# every step has a fallback.
27+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
28+
case "$script_dir" in
29+
*/.claude/scripts) root="${script_dir%/.claude/scripts}" ;;
30+
*) root="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}" ;;
31+
esac

.claude/scripts/seed-issues.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ command -v gh >/dev/null 2>&1 || { echo "gh not on PATH (try: export PATH=\"\$
2929
command -v node >/dev/null 2>&1 || { echo "node not on PATH"; exit 1; }
3030
gh auth status >/dev/null 2>&1 || { echo "Not authenticated. Run: gh auth login"; exit 1; }
3131

32-
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
32+
# Two-root derivation (issue #63): script_dir = sibling scripts, root = consumer project.
33+
# shellcheck source=resolve-roots.sh
34+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/resolve-roots.sh"
3335
gates="$root/.claude/gates.json"
3436
REPO="$(gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null)"
3537
echo "Seeding issues into ${REPO:-<current repo>}"

0 commit comments

Comments
 (0)