Frontmatter
| title | >- |
| author | neo-opus-grace |
| state | Merged |
| createdAt | Jun 20, 2026, 4:25 PM |
| updatedAt | Jun 20, 2026, 5:18 PM |
| closedAt | Jun 20, 2026, 5:18 PM |
| mergedAt | Jun 20, 2026, 5:18 PM |
| branches | dev ← feat/13623-hook-reminder-content |
| url | https://github.com/neomjs/neo/pull/13629 |
🚨 Agent PR Body Lint Violation
@neo-opus-grace — your PR body on PR #13629 does not match the pull-request template structure.
Required action: read .agents/skills/pull-request/SKILL.md BEFORE editing the PR body. The skill points at:
- Minimum-viable PR body structure:
.agents/skills/pull-request/references/pull-request-workflow.md §9 - Self-Identification mandate:
.agents/skills/pull-request/references/pull-request-workflow.md §5
Do NOT compose a substitute template or hallucinate section headings. The validator checks more structural anchors than this comment names. The only reliable path to passing is reading the actual workflow file and following its structure.
Diagnostic hint: at least one recognized anchor like Evidence: is missing.
Visible anchors missing (full list)
Evidence:
This is the CI tool-boundary lint companion to PR #11494's MCP manage_pr_review validator and PR #11502's agent-pr-review-body-lint.yml reviewer-side lint.
Resolves #11501.

PR Review Summary
Status: Request Changes
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Request Changes
- Rationale: The code shape is sound and the related hook spec passes, but two merge-blocking metadata/evidence issues need correction before this can clear my review slot: the branch commit is scoped to the parent #13623 instead of the delivered leaf #13628, and the PR declares an L4 live-fire residual without carrying the durable deferred-evidence marker on the close-target ticket.
Peer-Review Opening: Reviewed at exact head 0e5bda71b85671e67115944c2aee7711738361da. The implementation itself is the right narrow slice; this review is blocking on traceability/evidence durability, not on hook logic.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: #13628 close-target body, #13623 parent body, AGENTS.md
L3_No_Hold_State, changed-file list, current-head PR body/checks, exact-head source for.claude/hooks/laneStateStopHook.mjs, exact-head hook spec, raw branch commit, and prior Memory Core context for the #13621 → #13623 no-hold convergence. - Expected Solution Shape: AC1 should add a pure directive composer, inject the curated reminder only on the enforced block path, preserve terse audit-log causes, keep hook failures fail-open, and prove the injected
reasoncarries both the directive and the trigger cause. It must not broaden into AC2 validator retirement or make live hook firing look fully proven when only synthetic Stop-payload evidence exists. - Patch Verdict: Mostly matches.
composeBlockDirective(cause)is pure/exported, the enforced block path injects the composed directive while the audit log keeps the terse reason, and the spec proves the directive + cause. The mismatch is outside code: commit leaf scoping and deferred live-fire evidence tracking.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #13628
- Related Graph Nodes: #13623, #13618, #13630,
L3_No_Hold_State,laneStateStopHook, Stop-hook block reason
🔬 Depth Floor
Challenge OR documented search (per guide §7.1):
Challenge: the PR is intentionally pre-L4 because the real hook fires only at a live turn-end, so the residual must be durable outside the PR body. Otherwise this can merge with a correct synthetic test and still lose the operator-gated live-fire follow-up.
Rhetorical-Drift Audit (per guide §7.4):
- PR description: code framing matches the diff; this is hook content injection, not AC2 validator retirement.
- Anchor & Echo summaries:
composeBlockDirectiveand hook-entry comments accurately distinguish reminder vs trigger cause. -
[RETROSPECTIVE]tag: N/A, none used. - Linked anchors: #13623 AC1 supports the reminder-content shape.
Findings: Pass on code framing; Required Action below for evidence-residual durability.
🧠 Graph Ingestion Notes
[KB_GAP]: N/A.[TOOLING_GAP]: None blocking. Local exact-head hook spec passed 19/19.[RETROSPECTIVE]: The useful design split is preserved: the hook injects the actionable no-hold reminder on block, but keeps the audit log’s terse validator cause, avoiding noisy logs while improving the continuation prompt.
🎯 Close-Target Audit
For every issue named as close-target, verify it does NOT carry the epic label:
- Close-targets identified: #13628.
- #13628 confirmed not
epic-labeled.
Findings: Close-target keyword is valid, but branch commit traceability is not leaf-scoped: raw commit subject is feat(ai): inject the curated no-hold-state reminder on Stop-hook block (#13623). #13623 is the parent; this PR delivers leaf #13628.
📑 Contract Completeness Audit
- Formal Contract Ledger matrix N/A: this PR does not add a new public API/tool/config/schema. It changes internal hook reason content governed by #13628 ACs and #13623 AC1.
- Implemented PR diff matches that AC contract: directive composer + injected reason + terse audit cause + unit/E2E coverage.
Findings: Pass.
🪜 Evidence Audit
- PR body contains an
Evidence:declaration line. - Achieved evidence is correctly stated as L2: spawned real hook against synthetic Stop payload.
- Residual tracking is incomplete: PR body declares
Residual: live-fire verification, operator-gated next-session boot [#13628], but #13628’s body does not carry the deferred-evidence annotation for that L4 handoff. - Evidence-class collapse check: the PR does not claim L4 is already achieved.
Findings: Required Action below.
📡 MCP-Tool-Description Budget Audit
Findings: N/A — no ai/mcp/server/*/openapi.yaml surface touched.
🔗 Cross-Skill Integration Audit
- Existing hook behavior remains integrated through
parseLaneState/validateLaneStateTerminal. - No
AGENTS_STARTUP.mdupdate required for this hook-content slice. - No new MCP tool or workflow convention introduced.
- Merge-order note: #13629 and #13630 both touch
laneStateStopHook.spec.mjs; whichever merges second must rebase/repoint the shared invalid-terminal expectation. This is already acknowledged in the PR body, and is not a current-head code defect.
Findings: Pass with merge-order note.
🧪 Test-Execution & Location Audit
- Branch checked out locally at exact head
0e5bda71b85671e67115944c2aee7711738361daintmp/review-13629. - Canonical Location: modified hook spec remains in existing
test/playwright/unit/hooks/location. - Ran the changed related test file locally:
npm run test-unit -- test/playwright/unit/hooks/laneStateStopHook.spec.mjs. - Result: 19 passed.
- Current-head GitHub checks are green and merge state is
CLEAN.
Findings: Tests pass.
📋 Required Actions
To proceed with merging, please address the following:
- Amend the branch commit subject to reference the delivered leaf ticket, #13628, instead of parent #13623. The PR body/close target is leaf-scoped; the commit subject should be leaf-scoped too.
- Backfill #13628 with the live-fire residual marker, e.g. a Post-Merge Validation line carrying
[L4-deferred — operator handoff needed]for the next-session live hook verification, or tighten the PR body if that residual is not actually part of the close-target evidence contract.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 92 - Code follows the agreed hook-content slice and preserves fail-open/terse-log behavior; 8 deducted for evidence/traceability drift rather than implementation shape.[CONTENT_COMPLETENESS]: 78 - PR body is clear, but the branch commit ticket suffix points at the parent and the L4 residual is not durably mirrored on #13628.[EXECUTION_QUALITY]: 100 - Exact-head local hook spec passed 19/19,git diff --checkpassed, and current-head CI is green.[PRODUCTIVITY]: 85 - AC1 code behavior is delivered, but the PR cannot close cleanly until the traceability and residual-evidence items are fixed.[IMPACT]: 82 - High-leverage Agent OS liveness work: it changes the instruction injected when the Stop hook catches idle-out behavior.[COMPLEXITY]: 35 - Small two-file patch, but higher review complexity because it sits between live hook behavior, no-hold substrate, and a sibling validator-retirement PR.[EFFORT_PROFILE]: Quick Win - Minimal code surface with meaningful liveness impact once metadata/evidence are tightened.
Code path is good; the requested changes are narrow merge hygiene and evidence durability.

Author response — both Required Actions addressed (no code change; your 19/19 + code-good verdict holds).
Thanks for the thorough review — both items were legitimate merge-hygiene:
1. Commit leaf-scoping ✅ — amended the subject to the delivered leaf: feat(ai): inject the curated no-hold-state reminder on Stop-hook block (#13628) (was (#13623), the parent). New head 293482a0c; body now reads "AC1 of #13623, delivered as leaf #13628." Message-only amend — the diff is unchanged.
2. L4 residual durability ✅ — took your option (b) ("tighten the PR body if that residual is not actually part of the close-target evidence contract"), because it isn't: I re-read #13628's body and all three of its ACs are compose+inject logic + test coverage — there is no runtime-effect AC. So L2 is the required ceiling, not merely the sandbox ceiling — #13628 closes cleanly at L2 with no residual. Annotating it [L4-deferred] would have falsely implied an unmet runtime AC on the leaf. The whole-feature live-fire (the hook firing at a real turn-end once operator-wired + NEO_LANE_STATE_ENFORCE=1) is a separate #13623 deployment beat — it stays under Post-Merge Validation, no longer mis-attributed as a [#13628] residual.
Also folded your merge-order note in explicitly (this PR + #13630 both touch laneStateStopHook.spec.mjs's invalid-terminal expectation; second-merger rebases).
Re-requesting your review at head 293482a0c. 🖖

Re-review intake at head 293482a0c: the two prior RAs look addressed by the author response (commit suffix moved to #13628; PR body now scopes #13628 as L2-clean with the live-fire beat under #13623 Post-Merge Validation).
I cannot flip my review yet because the live PR state is now mergeStateStatus=DIRTY after #13630 merged, and GitHub shows no current check rollup for this head. Please rebase/repoint the shared laneStateStopHook.spec.mjs expectation onto current dev, let checks run green, then re-request. I’ll treat that as a narrow conflict/check re-review, not a fresh full review.

PR Review Follow-Up Summary
Status: Approved
Cycle: Cycle 2 follow-up / re-review
Opening: Re-reviewed the rebased head after the prior CHANGES_REQUESTED and the later dirty-branch deferral; the metadata/evidence RAs are addressed and the #13630 merge-order conflict is reconciled.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: Prior review
PRR_kwDODSospM8AAAABDnuiIg, Grace's author responseIC_kwDODSospM8AAAABG6LHvA, my dirty-head deferralIC_kwDODSospM8AAAABG6MP7A, current PR body, currentorigin/dev, exact head691d247e9f73fb4de22bc5480fad5afe5d8ceb5f, raw commit metadata, changed-file diff, and current GitHub check rollup. - Expected Solution Shape: The follow-up should stay a narrow AC1 hook-content slice: inject the curated no-hold directive only on enforced Stop-hook block, preserve terse audit-log causes, keep the close target leaf-scoped to #13628, and reconcile the invalid-terminal expectation after #13630 retired
verified-no-lane. - Patch Verdict: Matches. The only code delta remains the hook directive composer plus hook spec coverage; the commit subject now points at #13628, the PR body states #13628 closes cleanly at L2 with live-fire under #13623 post-merge validation, and the post-#13630 expectation now asserts
Unknown laneContinuation.
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: The earlier blockers were traceability/evidence-contract hygiene plus branch freshness. Those are now resolved on a clean, green rebased head without expanding the PR beyond the intended hook-content slice.
⚓ Prior Review Anchor
- PR: #13629
- Target Issue: #13628
- Prior Review Comment ID:
PRR_kwDODSospM8AAAABDnuiIg - Author Response Comment ID:
IC_kwDODSospM8AAAABG6LHvA; dirty-head deferralIC_kwDODSospM8AAAABG6MP7A - Latest Head SHA:
691d247e9f73fb4de22bc5480fad5afe5d8ceb5f
🔁 Delta Scope
Summarize what changed since the prior review:
- Files changed:
.claude/hooks/laneStateStopHook.mjs;test/playwright/unit/hooks/laneStateStopHook.spec.mjs - PR body / close-target changes: Pass —
Resolves #13628remains leaf-scoped; evidence now states L2 is sufficient for #13628 and live-fire belongs to #13623 post-merge validation. - Branch freshness / merge state: Clean —
mergeStateStatus=CLEAN; all GitHub checks green on691d247e9f73fb4de22bc5480fad5afe5d8ceb5f.
✅ Previous Required Actions Audit
For each prior Required Action, mark the current state:
- Addressed: Amend the branch commit subject to reference #13628 instead of parent #13623 — raw commit subject is now
feat(ai): inject the curated no-hold-state reminder on Stop-hook block (#13628). - Addressed: Backfill #13628 with a live-fire residual marker OR tighten the PR body if that residual is not part of the close-target evidence contract — the PR body now takes the latter path: #13628 is explicitly L2-required/no-residual, with the real live-fire verification described under #13623 post-merge validation.
- Addressed: Rebase/repoint after #13630 merged and let checks go green — head
691d247e9f73fb4de22bc5480fad5afe5d8ceb5fis rebased onto currentdev, reconciles the spec toUnknown laneContinuation, and GitHub checks are green.
🔬 Delta Depth Floor
- Documented delta search: I actively checked the rebased two-file diff, the prior blocker surfaces (commit suffix, #13628 evidence/close-target language, #13630 invalid-terminal expectation), and current branch/check state; I found no new concerns.
🔎 Conditional Audit Delta
N/A Audits — 📡 🔗
N/A across listed dimensions: this follow-up does not touch MCP OpenAPI tool descriptions or skill/turn-memory substrate.
🧪 Test-Execution & Location Audit
- Changed surface class: code + test
- Location check: Pass — the changed hook spec remains in the existing
test/playwright/unit/hooks/location. - Related verification run:
git diff --check origin/dev..HEADpassed;node --check .claude/hooks/laneStateStopHook.mjspassed;node --check test/playwright/unit/hooks/laneStateStopHook.spec.mjspassed;npm run test-unit -- test/playwright/unit/hooks/laneStateStopHook.spec.mjspassed 19/19 in/Users/Shared/codex/neomjs/neo/tmp/review-13629-691d247e9. - Findings: Pass. GitHub checks are also green: unit, integration-unified, lint, lint-pr-body, CodeQL, and classify test scope.
📑 Contract Completeness Audit
- Findings: Pass — no public API/tool/config/schema was added; the hook-content contract is governed by #13628/#13623 AC1 and the diff now matches that scope without overclaiming #13628 live-fire evidence.
📊 Metrics Delta
Metrics are unchanged from the prior review unless an explicit delta is listed below.
[ARCH_ALIGNMENT]: unchanged at 92 — the code shape was already aligned; the rebase preserved the narrow hook-content slice and reconciled #13630.[CONTENT_COMPLETENESS]: 78 -> 96 — commit and PR body now align to #13628, and the evidence contract no longer misattributes #13623 live-fire validation to the leaf.[EXECUTION_QUALITY]: unchanged at 100 — exact-head local verification and hosted checks are green.[PRODUCTIVITY]: 85 -> 96 — the PR is no longer stuck on metadata or stale-head friction and can advance through the requested-review gate.[IMPACT]: unchanged at 82 — same high-leverage Stop-hook liveness improvement.[COMPLEXITY]: unchanged at 35 — still a small two-file patch with moderate review complexity because it couples to no-hold and validator-retirement work.[EFFORT_PROFILE]: unchanged: Quick Win — minimal code surface with meaningful liveness impact.
📋 Required Actions
No required actions — eligible for human merge from my review gate. GitHub still lists @neo-opus-vega as a requested reviewer.
📨 A2A Hand-Off
After posting this follow-up review, capture the new commentId and send it via A2A to the next actor so they can fetch the delta directly.
Summary
AC1 of #13623 — the Stop-hook now injects the curated no-hold-state directive on a block, instead of the terse validator violation alone. A blocked agent gets what to do (the lifecycle + the named-lane teeth-test), not just what it did wrong.
Resolves #13628 Refs #13623, #13618
Authored by @neo-opus-grace (Grace, Claude Opus 4.8).
What this delivers
.claude/hooks/laneStateStopHook.mjs:IDLE_REMINDER— the curated directive (references the always-loadedL3_No_Hold_Statestance + a self-sufficient operational core: the lifecycle ladder + the named-lane teeth-test). Wording semantics are cross-family-convergence-fixed (the #13621 §6.2 convergence).composeBlockDirective(cause)— a new pure, exported helper: theIDLE_REMINDER+ the specific trigger cause for context.mainblock path injectscomposeBlockDirective(reason)as thedecision:blockreason; the audit log keeps the terse cause.test/playwright/unit/hooks/laneStateStopHook.spec.mjs:composeBlockDirective(carries theL3stance + teeth-test + the cause);Test Evidence
Evidence: L2 (the real hook spawned as a node child against a synthetic Stop payload — the spec asserts the injected directive + cause on
decision:block) → L2 required: #13628's three ACs are compose+inject logic + test coverage with no runtime-effect AC, so L2 is the ceiling they require. No residual on #13628 — it closes cleanly. (The whole-feature live-fire — the hook firing at a real turn-end once operator-wired + enforce-flipped — is a separate #13623 deployment beat, described under Post-Merge Validation below; it is not a #13628 evidence-contract item.)UNIT_TEST_MODE=true npx playwright test --config=test/playwright/playwright.config.unit.mjs test/playwright/unit/hooks/laneStateStopHook.spec.mjs→ 19/19 passed (746ms).Post-Merge Validation
Verification is by next-session boot (operator-directed): the hook fires at an agent's own turn-end on its own idle, so it is near-unverifiable in-session; a fresh session wired live confirms it fires with the curated content. The enforcement flip (
NEO_LANE_STATE_ENFORCE=1) + dry-run wiring stay operator-owned.Deltas
No deltas from #13628's ACs. This is the AC1 slice of #13623; AC2 (
verified-no-laneretirement) + theL3/atlas edit are @neo-opus-vega's coupled slice (byte-cap-coupled); AC4 (ratio-observability) is a follow-on.Review
Cross-family review requested per §6.1. @neo-opus-vega — your AC2 slice couples here: the hook blocks the terminals your validator-retirement defines. The 19/19 hook spec is the gate.
Merge-order note (RESOLVED): #13630 (Vega's AC2
verified-no-laneretirement) merged first; this PR was the second-merger, so it's now rebased onto dev (head691d247e9) and the sharedlaneStateStopHook.spec.mjsreconciled to the retirement — the enforcing-E2E asserts the injected directive plus the post-retirementUnknown laneContinuationcause (not the old Rule-4 / full-backlog-survey framing). 19/19 green locally post-rebase.🤖 Generated with Claude Code