From 0f4dbf701e0df71cc568f840833c0e6172235d3a Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Mon, 22 Jun 2026 22:32:54 -0400 Subject: [PATCH] fix(hooks): remove Claude-specific helper guidance --- README.md | 2 +- hooks/completion-claim-guard | 5 ++-- hooks/pre-tool-scope-guard | 4 +-- skills/alignment-check/SKILL.md | 2 +- skills/scope-lock/SKILL.md | 18 +++++++------- tests/hook-contracts.sh | 44 +++++++++++++++++++++++++++++++++ 6 files changed, 60 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index f0332fb..cbcc366 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ Per-skill host-conditional audit: [tests/cross-llm-coverage.md](tests/cross-llm- 7. **alignment-check** - Activates after adversarial review of plan passes. Narrowly structural: every design requirement maps to a plan task; every plan task traces to a design requirement; the Scope Manifest is well-formed (forward + reverse + manifest trace via `tests/plan-scope-check.sh`). -8. **scope-lock** - Activates immediately after `alignment-check` PASS. Stamps the plan with `Locked `, computes the manifest's sha256 into `.scope-lock`, commits both. From this point until completion (or an explicit user-approved amendment), the task list, PR count, and feature scope are immutable. Design backports that do not change the manifest are allowed; manifest changes go through ADR + alignment. The lock hash covers only the `## Scope Manifest` block, so explanatory design/task notes can evolve without invalidating scope. `subagent-driven-development` re-checks the lock between tasks; `finishing-a-development-branch` re-checks before any PR is created. Resume claims are session-owned: `scope-lock-claim` records repo/branch/objective metadata and blocks mismatched objective claims unless the handoff is explicit with `--confirmed`. When the design is fully complete, `bash "${CLAUDE_PLUGIN_ROOT:-.}/hooks/scope-lock-complete" --evidence ""` marks it `Complete`, removes the lock file, and prunes session reminder traces. +8. **scope-lock** - Activates immediately after `alignment-check` PASS. Stamps the plan with `Locked `, computes the manifest's sha256 into `.scope-lock`, commits both. From this point until completion (or an explicit user-approved amendment), the task list, PR count, and feature scope are immutable. Design backports that do not change the manifest are allowed; manifest changes go through ADR + alignment. The lock hash covers only the `## Scope Manifest` block, so explanatory design/task notes can evolve without invalidating scope. `subagent-driven-development` re-checks the lock between tasks; `finishing-a-development-branch` re-checks before any PR is created. Resume claims are session-owned: `scope-lock-claim` records repo/branch/objective metadata and blocks mismatched objective claims unless the handoff is explicit with `--confirmed`. When the design is fully complete, run `bash hooks/scope-lock-complete --evidence ""` from the autodev checkout/plugin root to mark it `Complete`, remove the lock file, and prune session reminder traces. 9. **subagent-driven-development** or **executing-plans** - Activates with a locked plan. Dispatches fresh subagent per task with two-stage review (spec compliance, then code quality). Between tasks, re-runs the scope-lock check; on lock drift, stops the line and surfaces the discrepancy. Phase/task completions are logged in compressed JSONL to `.autodev/state/phase-progress.jsonl` when a locked plan continues. diff --git a/hooks/completion-claim-guard b/hooks/completion-claim-guard index d6bc5a2..95a5804 100755 --- a/hooks/completion-claim-guard +++ b/hooks/completion-claim-guard @@ -26,7 +26,8 @@ hook_input=$(cat || true) cwd_dir=$(printf '%s' "$hook_input" | jq -r '.cwd // empty' 2>/dev/null || true) [ -z "$cwd_dir" ] && cwd_dir="${PWD}" -. "$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" && pwd)/lib-autodev-paths.sh" 2>/dev/null || true +hook_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" && pwd)" +. "${hook_dir}/lib-autodev-paths.sh" 2>/dev/null || true declare -f autodev_repo_root >/dev/null 2>&1 || autodev_repo_root() { printf '%s\n' "${1:-$PWD}"; } ADK_ROOT="$(autodev_repo_root "$cwd_dir")" @@ -157,7 +158,7 @@ If this is only a phase/task completion: - Continue automatically into the next phase/task from the Scope Manifest. Only stop if one of these is true: - - The entire locked design is complete and verified; run bash \"\${CLAUDE_PLUGIN_ROOT:-.}/hooks/scope-lock-complete\" --evidence \"\" to mark it complete and remove lock traces. + - The entire locked design is complete and verified; run bash \"${hook_dir}/scope-lock-complete\" --evidence \"\" to mark it complete and remove lock traces. - There is a hard blocker that requires human input. - The next action is a production deploy or destructive production change without already-recorded human approval. diff --git a/hooks/pre-tool-scope-guard b/hooks/pre-tool-scope-guard index 95bee39..3021545 100755 --- a/hooks/pre-tool-scope-guard +++ b/hooks/pre-tool-scope-guard @@ -342,14 +342,14 @@ case "$tool_name" in [ -z "$fpath" ] && continue # ── 5. scope-lock files ────────────────────────────────────────────── - # Agent path: bash "${CLAUDE_PLUGIN_ROOT}/hooks/scope-lock-apply" + # Agent path: bash hooks/scope-lock-apply from the autodev checkout/plugin root. # Operator path (pre-session): export SUPERPOWERS_SCOPE_LOCK_WRITE=1 if printf '%s' "$fpath" | grep -qE '\.scope-lock$'; then if [ "${SUPERPOWERS_SCOPE_LOCK_WRITE:-}" != "1" ]; then _reason="Writing to '$(basename "$fpath")' is blocked —" _reason="${_reason} .scope-lock files must be written by the scope-lock skill's helper," _reason="${_reason} not via the Write tool." - _reason="${_reason} Run: bash \"\${CLAUDE_PLUGIN_ROOT}/hooks/scope-lock-apply\" ." + _reason="${_reason} Run: bash hooks/scope-lock-apply from the autodev checkout/plugin root." _reason="${_reason} The helper extracts the Scope Manifest section, computes its sha256," _reason="${_reason} and writes the lock file via shell redirection (which is not blocked)." _reason="${_reason} Direct Write/Edit calls break the manifest integrity guarantee" diff --git a/skills/alignment-check/SKILL.md b/skills/alignment-check/SKILL.md index 00898f2..841da3e 100644 --- a/skills/alignment-check/SKILL.md +++ b/skills/alignment-check/SKILL.md @@ -132,7 +132,7 @@ Re-run alignment check after revision. **Max 2 revision cycles** before escalati After alignment passes, **lock the plan's scope** so subsequent execution cannot silently rescope. Invoke `autodev:scope-lock` with the plan path. The scope-lock skill: 1. Stamps the plan's `**Status:**` line with `Locked `. -2. Writes the lock file by running the repo/plugin helper, e.g. `bash hooks/scope-lock-apply ` from the autodev checkout or `bash "${CLAUDE_PLUGIN_ROOT}/hooks/scope-lock-apply" ` when that host env var is present. Do **not** use the Write tool for `*.scope-lock` paths. The helper computes the manifest's sha256 and writes `.scope-lock` via shell redirection. +2. Writes the lock file by running the repo/plugin helper, e.g. `bash hooks/scope-lock-apply ` from the autodev checkout/plugin root. Do **not** use the Write tool for `*.scope-lock` paths. The helper computes the manifest's sha256 and writes `.scope-lock` via shell redirection. 3. Commits both files (`chore: lock scope for (alignment passed)`). After the lock is in place, proceed to execution: diff --git a/skills/scope-lock/SKILL.md b/skills/scope-lock/SKILL.md index 62fe83b..48ac649 100644 --- a/skills/scope-lock/SKILL.md +++ b/skills/scope-lock/SKILL.md @@ -180,14 +180,14 @@ When the whole locked design is genuinely complete and verified, close the lock instead of leaving stale reminders in the workspace: ```bash -bash "${CLAUDE_PLUGIN_ROOT:-.}/hooks/scope-lock-complete" docs/plans/.md --evidence "" +bash hooks/scope-lock-complete docs/plans/.md --evidence "" ``` -The helper verifies the current manifest hash when the checker is available, -changes the plan status from `Locked` to `Complete `, removes -`.scope-lock`, prunes matching `.claude/autodev-state/session-locks.jsonl` -and lock snapshot rows, and appends a compact completion row to -`.autodev/state/phase-progress.jsonl`. +Run the command from the autodev checkout/plugin root. The helper verifies the +current manifest hash when the checker is available, changes the plan status +from `Locked` to `Complete `, removes `.scope-lock`, prunes matching +`.claude/autodev-state/session-locks.jsonl` and lock snapshot rows, and appends +a compact completion row to `.autodev/state/phase-progress.jsonl`. Do not manually edit `.scope-lock` files or leave a completed design in `Locked` state. Stale locks cause future prompt/stop/pre-compact hooks to @@ -204,7 +204,7 @@ and does not survive across sessions — a fresh agent inherits no rows from the killed one. ```bash -bash "${CLAUDE_PLUGIN_ROOT:-.}/hooks/scope-lock-claim" docs/plans/.md +bash hooks/scope-lock-claim docs/plans/.md ``` The helper verifies the plan is Locked, has a `.scope-lock` sidecar, and that @@ -231,7 +231,7 @@ resume-target checkpoint showing: Intentional handoff is still allowed, but it must be explicit: ```bash -bash "${CLAUDE_PLUGIN_ROOT:-.}/hooks/scope-lock-claim" docs/plans/.md --confirmed +bash hooks/scope-lock-claim docs/plans/.md --confirmed ``` Use `--confirmed` only after the user-visible task, repo, branch, and locked @@ -256,7 +256,7 @@ out of capacity), close the lock as **Abandoned** rather than leaving stale state: ```bash -bash "${CLAUDE_PLUGIN_ROOT:-.}/hooks/scope-lock-abandon" docs/plans/.md \ +bash hooks/scope-lock-abandon docs/plans/.md \ --reason "user pivoted away from feature X" ``` diff --git a/tests/hook-contracts.sh b/tests/hook-contracts.sh index f020231..2156d00 100755 --- a/tests/hook-contracts.sh +++ b/tests/hook-contracts.sh @@ -974,6 +974,49 @@ PLAN pass "completion-claim-guard: flattened checkpoint keeps heading separator" } +test_completion_continuation_block_uses_host_neutral_helper_guidance() { + local tmp output reason + tmp="$(mktemp -d)" + trap 'rm -rf "$tmp"' RETURN + mkdir -p "$tmp/docs/plans" "$tmp/tests" + cat >"$tmp/docs/plans/example.md" <<'PLAN' +# Example Plan + +## Scope Manifest + +**PR Count:** 1 +**Tasks:** 1 +**Out of scope:** +- (none) + +**PR Grouping:** + +| PR # | Title | Tasks | Branch | +|------|-------|-------|--------| +| 1 | Example | Task 1 | feat/example | + +**Status:** Locked 2026-05-25T00:00:00Z + +### Task 1: Example +PLAN + cp tests/plan-scope-check.sh "$tmp/tests/plan-scope-check.sh" + chmod +x "$tmp/tests/plan-scope-check.sh" + bash hooks/scope-lock-apply "$tmp/docs/plans/example.md" >/dev/null + + output="$(run_hook completion-claim-guard '{"cwd":"'"$tmp"'","stop_hook_active":false,"last_assistant_message":"Task 1 complete."}')" + reason="$(printf '%s' "$output" | jq -r '.reason')" + + if printf '%s' "$reason" | grep -q 'CLAUDE_PLUGIN_ROOT'; then + fail "completion-claim-guard: helper guidance must not assume Claude host env vars: ${reason}" + return + fi + if ! printf '%s' "$reason" | grep -q 'scope-lock-complete'; then + fail "completion-claim-guard: expected scope-lock-complete helper guidance, got: ${reason}" + return + fi + pass "completion-claim-guard: helper guidance is host-neutral" +} + test_pretool_records_session_lock_for_scope_lock_apply() { local tmp transcript output state_file tmp="$(mktemp -d)" @@ -1891,6 +1934,7 @@ test_scope_lock_complete_preflights_progress_write test_scope_lock_complete_rejects_progress_directory test_completion_continuation_block test_completion_continuation_block_keeps_heading_separator_when_flattened +test_completion_continuation_block_uses_host_neutral_helper_guidance test_pretool_records_session_lock_for_scope_lock_apply test_pretool_ignores_prose_mention_of_locked_status test_completion_ignores_ambiguous_workspace_locks_when_session_has_no_lock