feat(scope-lock): session-scoped nag + claim/abandon helpers#42
Merged
Conversation
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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
.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.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.hooks/scope-lock-abandon <plan-path> --reason "<reason>"— close a never-completed lock. Flips Status toAbandoned <UTC> — <reason>, removes.scope-lock, prunes session-lock + in-progress traces, and appends ast:"abandoned"row tophase-progress.jsonl. Distinct fromscope-lock-completeso retros can tell verified-done from stopped-pursuing.grep -q '**Status:** Locked'(substring), which matched any plan whose body discussed lock mechanics. All five nag hooks now usegrep -qE '^**Status:**[[:space:]]+Locked'anchored to line start.pre-tool-scope-guard'sSESSION_LOCK_RECOGNIZEDvariable. Dedupe writes so re-claiming the same plan produces no duplicate row.subagent-scope-guardbecomes 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; sharedemit_locked_fixture/emit_draft_fixtureprintf 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— PASSbash tests/version-check.sh— PASSbash tests/skill-cross-refs.sh— PASSDesign + plan
docs/plans/2026-05-26-session-scoped-lock-nag-design.md(adversarial review PASS; Critical + 5 Important findings resolved inline)docs/plans/2026-05-26-session-scoped-lock-nag.md(Status: Complete)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