Skip to content

feat(scope-lock): session-scoped nag + claim/abandon helpers#42

Merged
intel352 merged 8 commits into
mainfrom
feat/session-scoped-lock-nag-2026-05-26
May 26, 2026
Merged

feat(scope-lock): session-scoped nag + claim/abandon helpers#42
intel352 merged 8 commits into
mainfrom
feat/session-scoped-lock-nag-2026-05-26

Conversation

@intel352
Copy link
Copy Markdown
Contributor

Summary

  • Restrict the locked-plan nag (UserPromptSubmit, PreCompact, PreToolUse, SubagentStop, Stop) to plans attributed to the current session via .claude/autodev-state/session-locks.jsonl. Previous behavior fell back to a workspace-wide grep when only one locked plan existed, causing stale locks from prior sessions to nag unrelated work.
  • Add hooks/scope-lock-claim <plan-path> — attribute an existing locked plan to the current session. Use case: a session was interrupted (computer restart, host crash, accidental /clear) and a fresh agent is resuming the same work; one bash call re-attributes the lock.
  • Add hooks/scope-lock-abandon <plan-path> --reason "<reason>" — close a never-completed lock. Flips Status to Abandoned <UTC> — <reason>, removes .scope-lock, prunes session-lock + in-progress traces, and appends a st:"abandoned" row to phase-progress.jsonl. Distinct from scope-lock-complete so retros can tell verified-done from stopped-pursuing.
  • Fix a latent substring-grep bug observed live during this PR's own adversarial review: nag hooks used grep -q '**Status:** Locked' (substring), which matched any plan whose body discussed lock mechanics. All five nag hooks now use grep -qE '^**Status:**[[:space:]]+Locked' anchored to line start.
  • Centralize the recognized helper-name list in pre-tool-scope-guard's SESSION_LOCK_RECOGNIZED variable. Dedupe writes so re-claiming the same plan produces no duplicate row.
  • subagent-scope-guard becomes session-aware (was workspace-wide).

Files

  • hooks/scope-lock-claim (new), hooks/scope-lock-abandon (new)
  • hooks/pre-tool-scope-guard, hooks/prompt-strict-interpretation, hooks/pre-compact-snapshot, hooks/completion-claim-guard, hooks/subagent-scope-guard (anchored grep + drop workspace fallback)
  • skills/scope-lock/SKILL.md (Abandoned terminal state + claim/abandon sections)
  • tests/hook-contracts.sh (+16 tests; shared emit_locked_fixture/emit_draft_fixture printf helpers; 2 legacy fallback tests deleted)

Test plan

  • bash tests/hook-contracts.sh — all 47 tests PASS (was 33; +16 new, −2 removed)
  • bash tests/plan-scope-check.sh — PASS
  • bash tests/version-check.sh — PASS
  • bash tests/skill-cross-refs.sh — PASS
  • End-to-end manual: claim → next prompt nags; abandon → next prompt silent; fresh session with no claim → silent.

Design + plan

  • Design: docs/plans/2026-05-26-session-scoped-lock-nag-design.md (adversarial review PASS; Critical + 5 Important findings resolved inline)
  • Plan: docs/plans/2026-05-26-session-scoped-lock-nag.md (Status: Complete)
  • ADR 0001 (decisions/0001-complete-scope-locks.md) is the canonical lifecycle parent — this PR extends the same protocol to the unhappy paths.

🤖 Generated with Claude Code

Jon Langevin added 8 commits May 26, 2026 17:36
Draft design for tightening the locked-plan nag to session-attributed
plans and introducing scope-lock-claim (resume after restart) and
scope-lock-abandon (close out a never-completed lock). Cites ADR 0001.
…h check

Resolves all Critical + Important adversarial findings from the design-phase
review. Key changes:

- §Approach 0 added: tighten substring grep across all four nag hooks to an
  anchored line-start match (^**Status:** Locked). The existing substring
  match false-positives on any plan whose body discusses lock mechanics —
  demonstrated live by subagent stops being swallowed during this review.
- §Approach 2 (claim): claim verifies manifest hash at claim time; documents
  why re-running scope-lock-apply is unsafe (silent rehash); adds liveness
  read-back; centralizes recognized-command list.
- §Approach 3 (abandon): adds reason sanitization (newline collapse, 200-char
  cap, ** -> __); explicitly rejects auto-ADR.
- §Multi-component validation: adds end-to-end claim->nag and
  abandon->silence tests plus anchored-grep regression test.
- §Rollback: notes abandon is not reversible.

Open questions resolved inline.
Original plan tripped plan-scope-check.sh because heredoc-based test fixtures
embedded literal `## Scope Manifest` lines at column 0, which the awk parser
treated as nested manifest sections. New plan replaces all heredoc fixtures
with two shared printf helpers (emit_locked_fixture, emit_draft_fixture)
defined once, so the heading string is hidden inside a quoted format string.

Plan body now compresses cleanly: 9 tasks, ~half the length, no duplicated
fixture content per test.
Tighten the locked-plan nag (UserPromptSubmit, PreCompact, PreToolUse,
SubagentStop, Stop) to fire only for plans attributed to the current
session via .claude/autodev-state/session-locks.jsonl. Adds two new
agent-driven lifecycle helpers and fixes a latent substring-grep bug
that caused stale workspace locks to nag unrelated sessions.

Changes:

1. Anchored status-line grep across all five nag hooks
   (pre-tool-scope-guard, prompt-strict-interpretation, pre-compact-snapshot,
   completion-claim-guard, subagent-scope-guard). The old substring grep
   matched any plan whose prose body quoted "**Status:** Locked" — a
   live false-positive observed during this PR's own adversarial review,
   where subagent stops were swallowed by the very bug being fixed.

2. Drop the single-workspace-lock fallback in prompt-strict-interpretation,
   pre-compact-snapshot, and completion-claim-guard. When the host exposes
   transcript_path but session-locks.jsonl has no attribution, no nag
   fires (was: pick the workspace plan if exactly one exists). The
   workspace-wide path is retained only when transcript_path is absent
   (no session identity available).

3. subagent-scope-guard becomes session-aware (was workspace-wide). Reads
   transcript_path + session_key like the other hooks; greps only plans
   attributed to the current session.

4. New helper hooks/scope-lock-claim <plan-path>. Attributes an existing
   locked plan to the current session so a fresh agent resuming after a
   restart sees the nag. Verifies the plan is Locked, has a .scope-lock
   sidecar, and (when tests/plan-scope-check.sh is present) that the
   manifest hash still matches — drift is rejected. The actual JSONL
   write is performed by pre-tool-scope-guard's record_session_lock,
   which now recognizes scope-lock-claim alongside scope-lock-apply via
   a centralized SESSION_LOCK_RECOGNIZED variable. Writes are now
   deduplicated so re-claiming produces no duplicate row.

5. New helper hooks/scope-lock-abandon <plan-path> --reason "<reason>".
   Closes a never-completed lock. Flips status to "Abandoned <UTC> —
   <reason>", removes .scope-lock, prunes session-lock + in-progress
   traces across all sessions, and appends a phase-progress row with
   st:"abandoned". Required --reason is sanitized (newline/tab collapse,
   200-char cap, ** -> __) so the single-line status invariant holds.
   Does not write an ADR and does not verify the manifest hash (drift
   is expected for abandoned work).

6. skills/scope-lock/SKILL.md learns the Abandoned terminal state and
   documents both new helpers with the resume-after-restart and
   stopped-pursuing use cases.

7. tests/hook-contracts.sh adds 16 new tests:
   - 4 anchored-grep prose-mention regressions (one per nag hook)
   - 1 subagent-scope-guard "ignores unattributed workspace lock"
   - 1 subagent-scope-guard "blocks on attributed drift"
   - 3 "no single-workspace-lock fallback" assertions (replacing prior
     "falls back to single workspace lock" assertions)
   - 4 scope-lock-claim tests (writes attribution, idempotent dedupe,
     rejects unlocked plan, rejects drift)
   - 4 scope-lock-abandon tests (flip/prune/append, requires reason,
     sanitizes reason, refuses non-Locked)
   - 3 end-to-end tests (claim -> nag, abandon -> silence, fresh session
     no claim -> silence)
   Shared fixture helpers (emit_locked_fixture, emit_draft_fixture) use
   printf rather than heredocs so column-0 occurrences of "**Status:**
   Locked" in this test file don't trip the project's own plan-scope-check
   parser or the project's own nag hooks when CI runs the suite against
   this repo.

Implements docs/plans/2026-05-26-session-scoped-lock-nag.md (locked).
@intel352 intel352 merged commit 694dd67 into main May 26, 2026
6 checks passed
@intel352 intel352 deleted the feat/session-scoped-lock-nag-2026-05-26 branch May 26, 2026 22:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant