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
4 changes: 3 additions & 1 deletion .claude/scripts/bot-gh.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
# Usage: .claude/scripts/bot-gh.sh pr create --title "..." --body "..."
set -euo pipefail

root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
# Two-root derivation (issue #63): script_dir = sibling scripts, root = consumer project.
# shellcheck source=resolve-roots.sh
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/resolve-roots.sh"
if [ -f "$root/.env" ]; then
set -a
# shellcheck disable=SC1091
Expand Down
16 changes: 11 additions & 5 deletions .claude/scripts/cockpit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,15 @@
# generated artifact). Pass a second positional arg to write elsewhere.
set -uo pipefail

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

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

# ---- adapter (review lenses/skills, budget) ------------------------------------
node -e '
Expand Down Expand Up @@ -310,11 +316,11 @@ function phaseBadge(phase) {
}
}
function renderLiveProgress() {
const latest = new Map(); // "roletask" -> event
const latest = new Map(); // "role\u0000task" -> event
for (const ev of events) {
const role = ev.role != null ? String(ev.role) : "";
const task = ev.task != null ? String(ev.task) : "";
const key = role + "" + task;
const key = role + "\u0000" + task;
latest.set(key, ev); // later lines overwrite earlier ones for the same key
}
const workers = [...latest.values()];
Expand Down
9 changes: 5 additions & 4 deletions .claude/scripts/gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
set -uo pipefail

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

# Dependency-freshness preflight (pnpm-gated: no-ops unless the repo uses pnpm).
# pnpm copies the resolved lockfile to node_modules/.pnpm/lock.yaml on every
Expand Down
6 changes: 4 additions & 2 deletions .claude/scripts/log-event.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,10 @@ while [ $# -gt 0 ]; do
shift || break
done

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

events_file="${CLAUDE_EVENTS_FILE:-$root/.claude/state/events.jsonl}"
max_lines="${EVENTS_MAX_LINES:-2000}"
Expand Down
6 changes: 4 additions & 2 deletions .claude/scripts/merge-ready.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@
set -euo pipefail
export PATH="$HOME/.local/bin:$PATH"

root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
# Two-root derivation (issue #63): script_dir = sibling scripts, root = consumer project.
# shellcheck source=resolve-roots.sh
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/resolve-roots.sh"
# Route EVERY gh call (list/view/merge) through the bot identity (see bot-gh.sh).
gh() { bash "$root/.claude/scripts/bot-gh.sh" "$@"; }
gh() { bash "$script_dir/bot-gh.sh" "$@"; }
repo="${1:-$(gh repo view --json nameWithOwner -q .nameWithOwner)}"
owner="${MERGE_APPROVER:-${repo%%/*}}" # the approver whose APPROVED review authorizes a merge
gates="$root/.claude/gates.json"
Expand Down
6 changes: 4 additions & 2 deletions .claude/scripts/notify-poll.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
# Repo is derived from the current git remote; override with $1 (owner/repo).
set -euo pipefail

root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
# Two-root derivation (issue #63): script_dir = sibling scripts, root = consumer project.
# shellcheck source=resolve-roots.sh
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/resolve-roots.sh"
# Route EVERY gh call through the bot identity (see bot-gh.sh). Defined before the
# first gh use below so the repo-derivation call already runs as the bot.
gh() { bash "$root/.claude/scripts/bot-gh.sh" "$@"; }
gh() { bash "$script_dir/bot-gh.sh" "$@"; }
repo="${1:-$(gh repo view --json nameWithOwner -q .nameWithOwner)}"
owner="${MERGE_APPROVER:-${repo%%/*}}" # the human whose APPROVED review gates a merge
state_dir="$root/.claude/state" # add .claude/state/ to .gitignore
Expand Down
6 changes: 4 additions & 2 deletions .claude/scripts/pr-feedback.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
# Invoke as `bash .claude/scripts/pr-feedback.sh` (pre-approve that exact command).
set -euo pipefail

root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
# Two-root derivation (issue #63): script_dir = sibling scripts, root = consumer project.
# shellcheck source=resolve-roots.sh
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/resolve-roots.sh"
# Route EVERY gh call through the bot identity (see bot-gh.sh).
gh() { bash "$root/.claude/scripts/bot-gh.sh" "$@"; }
gh() { bash "$script_dir/bot-gh.sh" "$@"; }
repo="${1:-$(gh repo view --json nameWithOwner -q .nameWithOwner)}"
bot="${BOT_LOGIN:-robercano-ghbot}"
marker="<!-- claude-addressed -->"
Expand Down
5 changes: 3 additions & 2 deletions .claude/scripts/prepare-pr.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ case "$pr" in
''|*[!0-9]*) echo "prepare-pr: PR number must be numeric (got '$pr')" >&2; exit 2 ;;
esac

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

gates="$root/.claude/gates.json"
Expand Down
31 changes: 31 additions & 0 deletions .claude/scripts/resolve-roots.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# resolve-roots.sh — the ONE implementation of the two-root derivation
# (issue #63). SOURCE this from a sibling script; do not execute it.
#
# There are two distinct roots, and the plugin install cache broke scripts
# that conflated them with a single `../..` hop:
#
# script_dir — where SIBLING scripts live (bot-gh.sh, gate.sh, …). This is
# simply this file's own directory, correct in BOTH layouts:
# repo/worktree checkout: <root>/.claude/scripts/
# plugin install cache: ~/.claude/plugins/cache/<mp>/<plugin>/<ver>/scripts/
# (the cache strips the .claude/ prefix, so `../..` lands outside the plugin).
#
# root — the CONSUMER PROJECT root, where .env (GH_BOT_TOKEN), gates.json,
# .claude/state/, worktrees, and the git repo live. Resolution order:
# 1. repo-tracked layout (<x>/.claude/scripts) → <x>. This must win over
# CLAUDE_PROJECT_DIR: worktree implementers run the WORKTREE's copy of
# gate.sh and need the worktree as root, while the harness env var
# points at the main checkout.
# 2. $CLAUDE_PROJECT_DIR — set by the harness; the only reliable signal
# when running from the plugin cache.
# 3. git toplevel of the cwd, then cwd — callers invoke these scripts
# from the consumer repo, so this is the right last resort.
#
# Never exits/fails (log-event.sh sources this and must never block a worker):
# every step has a fallback.
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
case "$script_dir" in
*/.claude/scripts) root="${script_dir%/.claude/scripts}" ;;
*) root="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}" ;;
esac
4 changes: 3 additions & 1 deletion .claude/scripts/seed-issues.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ command -v gh >/dev/null 2>&1 || { echo "gh not on PATH (try: export PATH=\"\$
command -v node >/dev/null 2>&1 || { echo "node not on PATH"; exit 1; }
gh auth status >/dev/null 2>&1 || { echo "Not authenticated. Run: gh auth login"; exit 1; }

root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
# Two-root derivation (issue #63): script_dir = sibling scripts, root = consumer project.
# shellcheck source=resolve-roots.sh
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/resolve-roots.sh"
gates="$root/.claude/gates.json"
REPO="$(gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null)"
echo "Seeding issues into ${REPO:-<current repo>}"
Expand Down
5 changes: 3 additions & 2 deletions .claude/scripts/worktree.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ case "$phase" in
*) echo "worktree.sh: phase must be 'setup' or 'teardown' (got '$phase')"; exit 2 ;;
esac

script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
root="$(cd "$script_dir/../.." && pwd)"
# Two-root derivation (issue #63): script_dir = sibling scripts, root = consumer project.
# shellcheck source=resolve-roots.sh
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/resolve-roots.sh"
# Which adapter to read. Defaults to the project adapter; set GATES_FILE to run a
# different one (e.g. GATES_FILE=.claude/self/gates.json). Relative paths resolve
# from the repo root.
Expand Down
2 changes: 2 additions & 0 deletions .claude/self/smoke-fanout.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ mkdir -p "$repo/.claude/scripts"
cp -R "$fixture/src" "$fixture/test" "$repo/" || fail "copy fixture sources"
cp "$fixture/gates.json" "$repo/.claude/gates.json" || fail "copy adapter"
cp "$root/.claude/scripts/gate.sh" "$repo/.claude/scripts/gate.sh" || fail "copy gate.sh"
# gate.sh sources its sibling resolve-roots.sh (issue #63) — stage it alongside.
cp "$root/.claude/scripts/resolve-roots.sh" "$repo/.claude/scripts/resolve-roots.sh" || fail "copy resolve-roots.sh"
G init -q -b main . || fail "git init"
G add -- .claude src test
G commit -qm "fixture: initial state" || fail "initial commit"
Expand Down
Loading