# Feature: Scope self-check (Phase 3 Step 3.7)

**Pattern**: Phase 4 reconstructs everything from the diff. The one thing it cannot reconstruct is why each file was touched and what was left out on purpose, so Dev states both before the handoff, and a deterministic gate checks the statement against the real diff. The record also carries the code-simplifier rationales (Step 3.6) that used to be discarded, and it feeds two later consumers: the `<scope-self-check>` block in the Phase 4 reviewer prefix and the PR body in Phase 6.

## The record

`$WORKTREE/.pipeline/scope-check.json`, schema `schemas/scope-check.schema.json`:

```json
{
  "version": "1.0.0",
  "taskId": "<taskId>",
  "files": [{ "path": "<repo-relative path>", "reason": "<why this task needs this exact file>" }],
  "notDone": [{ "what": "<change considered and not made>", "why": "out-of-scope | follow-up | rejected-abstraction" }],
  "simplifier": { "applied": 0, "skipped": 0, "rationales": ["<Step 3.6 rationale strings>"] }
}
```

Rules:

- One entry per file in the diff, no entry for a file outside it. A reason names the task requirement, never "while I was here".
- `notDone` carries every hypothetical the dev chose not to defend against and every abstraction it considered and rejected, so review does not re-propose them.
- Component tasks (`taskType === "component"`) write the record too; the plugin skill's file table is the `files[]` source.

## The gate

```bash
git -C "$WORKTREE" diff --name-only "origin/$BASE_BRANCH"...HEAD \
  | node $HOME/.claude/scripts/scope-check-gate.mjs --check "$WORKTREE/.pipeline/scope-check.json" --diff-files -
```

Output `{ok, justified, unjustified[], unlisted[], notDone}`. Exit 1 lists `unjustified[]` (in the diff, no reason or an empty one); `unlisted[]` (in the record, not in the diff) is reported and never fatal. Complete the record once and re-run. A second exit 1 does not block: log `dev.scope_check=incomplete unjustified=<n>` and hand off; Phase 4 receives the gate output inside `<scope-self-check>`, so reviewers see which files arrived without a stated reason. `--advisory` (used by Phase 4) always exits 0. Progress line: `    → checking scope self-check ({n} files, {m} not done)`.

## Consumers

- Phase 4 Step 2.2 renders file reasons, unjustified files and `notDone[]` into the shared reviewer prefix (`features/review-delta.md`).
- Phase 6 Step 3 builds the PR `## Changes` bullets from `files[].reason` and lists `notDone[]` under `## Related` as "Follow-ups not done in this PR", merged with the final triage `deferred[]` (`channels/pr.md`).

## Reference

Script: `$HOME/.claude/scripts/scope-check-gate.mjs`. Schema: `$HOME/.claude/schemas/scope-check.schema.json`. Smoke: `smoke-scope-check.sh`. Unit: `test/scope-check-gate.test.mjs`.
