100%

Section 07 — commit-push Redesign (Path-Scoped)

Cites §01 north star (invariants 7, 8, 18); grounded in Pass 1D/N2 (ScopeRestriction already exists). The engine (§06) invokes the path-scoped-commit capability defined here.

Intelligence Reconnaissance

Run 2026-05-29:

  • scripts/intel-query.sh dag-ascii scripts-first-restructure — §07 linear-walk position §06 → §07 → §08; depends_on: ["06"] (sole direct predecessor; §06 complete + reviewed).
  • scripts/intel-query.sh plan-status scripts-first-restructure — §06 status: complete; §07 in-review; §08-§12 not-started.
  • Remaining bullets are direct file citations (wrapper Python is not reliably indexed in the intel graph, so they anchor on the source file, not a file-symbols query):
  • scripts/plan_orchestrator/gates.py:273 load_plan_touches(plan_dir, repo_root) -> list[str] | None — the touches read-site §07.1 wires; returns None on absent touches: (gates.py:310-313), falling through to wrapper-root default.
  • scripts/commit_push/triage.py:202 compute_scope_restriction(repo, *, since_sha, exclude_paths, from_orchestrator_scope) — has NO touches/include_paths param; ScopeRestriction.include_paths (triage.py:97) is populated internally from from_orchestrator_scope JSON session_touched_paths (triage.py:240-245). §07.1 bridges touches: into the inclusion filter.
  • scripts/commit_push/staging.py:157 stage_and_commit_all(..., scope_restrictions=...) raises HookFailure (staging.py:57, carries .repo/.halt_reason/.stderr) on gate failure; returns list[CommitResult] (staging-side CommitResult at staging.py:48, distinct from route-side commit_capability.CommitResult at :94) on success.
  • scripts/plan_orchestrator/commit_capability.py:135 CommitBackend protocol (commit(scope: CommitScope) -> CommitResult at :144); route-side CommitResult.__post_init__ (:117) enforces ok=False ⇒ halt_reason+error_payload set, ok=True ⇒ commit_sha set. §07 wires the real backend (:168 comment).
  • lefthook.yml:77 bug-tracker-render-hook runs render --write-back && git add — a live mutating-and-staging hook, the INV-17-banned pattern §07.2 audits.

Goal

See frontmatter. Two changes, both cutting parallel-work friction:

  • 07.1 — path-scoped staging.
  • 07.2 — structural-gate removal (JSON-v6 nodes only; legacy markdown retains gates until §09/§10 close).

07.1 — Path-scoped staging (wiring, not new machinery)

  • Author touches: on plans/scripts-first-restructure/00-overview.md frontmatter (the touches SoT this plan currently lacks): list the plan-dir + scripts/ path-set the route walk owns, so load_plan_touches returns a non-empty list instead of None. Without this, scripts/plan_orchestrator/gates.py:load_plan_touches (gates.py:310-313) returns None on absent touches: and the scope restriction falls through to the wrapper-root default (whole-tree staging). The per-node touches source is the active plan’s overview frontmatter — pin it here as the touches read-site contract. DONE: touches: [plans/scripts-first-restructure/**, scripts/plan_orchestrator/**, scripts/commit_push/**] on 00-overview.md; load_plan_touches returns the non-empty list.
  • Author scripts/commit_push/scoped_commit.py: read the active plan’s touches: via scripts.plan_orchestrator.gates.load_plan_touches(plan_dir, repo_root) (scripts/plan_orchestrator/gates.py:273; returns list[str] | None), then BRIDGE touches: into the restriction’s inclusion filter — compute_scope_restriction (scripts/commit_push/triage.py:202) has NO touches/include_paths parameter (include_paths at triage.py:97 is populated only from from_orchestrator_scope JSON session_touched_paths at triage.py:240-245), so EITHER construct from_orchestrator_scope={"session_touched_paths": list(touches)} and pass it to compute_scope_restriction(...), OR build ScopeRestriction(include_paths=tuple(touches), ...) directly (the field exists at triage.py:97). Pass the resulting ScopeRestriction to staging.stage_and_commit_all(..., scope_restrictions=...) (scripts/commit_push/staging.py:157, scope_restrictions param :166). Acceptance asserts a non-empty touches: yields a ScopeRestriction whose include_paths equals the touches set (no silent wrapper-root fall-through when touches is present).
  • staging._build_staging_list (staging.py:103) already applies apply_scope_restriction (triage.py:297) when scope_restriction is non-noop (staging.py:133-136) — wire it; no new staging code (N2). DONE: ScopedCommitBackend.commit feeds scope_restrictions={str(repo): restriction} into stage_and_commit_all (the existing param at staging.py:166); no new staging code added.
  • Implement the CommitBackend protocol (scripts/plan_orchestrator/commit_capability.py:135, commit(self, scope: CommitScope) -> CommitResult at :144) over the scoped-staging path: scoped_commit.py provides a concrete backend whose commit(scope) runs the touches:compute_scope_restrictionstage_and_commit_all wiring and maps the outcome onto the route-side CommitResult (commit_capability.py:94) so its __post_init__ invariants (commit_capability.py:117: ok=False ⇒ halt_reason+error_payload both set; ok=True ⇒ commit_sha set) hold: wrap stage_and_commit_all in try/except HookFailure (staging.py:57) — on HookFailure hf return CommitResult(ok=False, halt_reason=hf.halt_reason, error_payload={"repo": hf.repo, "stderr": hf.stderr}); on the success list[CommitResult] (staging-side, staging.py:48) return CommitResult(ok=True, commit_sha=results[0].sha). Never construct a CommitResult that violates the post-init invariants. invoke_commit(touches, scope, *, backend) (commit_capability.py:148) then routes through this backend, fulfilling the §06.5↔§07 interface contract the commit_capability.py:168 “§07 owns the real backend” comment pins. No field additions to CommitScope (commit_capability.py:76) or CommitResult.
  • Dirty paths outside the active set are ignored — no whole-tree-clean halt. DONE: test_touches_scope_stages_only_owned_paths asserts an unrelated compiler_repo/... dirty path is excluded while owned paths are kept.
  • Author scripts/commit_push/tests/test_scoped_commit.py: assert (a) touches:-derived ScopeRestriction stages only owned paths and ignores an unrelated dirty path; (b) the CommitBackend real backend returns CommitResult(ok=True, commit_sha=...) on success and CommitResult(ok=False, halt_reason=..., error_payload=...) on failure (real-backend ok/failed parity mirroring §06.5 test_commit_capability); (c) absent touches: yields the wrapper-root fall-through that the §07.1 prerequisite task closes. DONE: 12 tests green.
  • Subsection close (07.1) — all [x]; status: complete.

07.2 — Remove structural gates; keep correctness gates

  • Remove structural checks (non-linear, cyclic, routing-drift, budget) from the commit-push gate set ONLY for schema-validated JSON-v6 route nodes — enforcement moves to authoring time (schema makes bad structure unrepresentable, §03.1) + execution-time auto-cure (§06 cure_dispatch) per refinement / R1Q4. The removed budget is the STRUCTURAL section-budget; the INV-18 content-size cap re-homes to authoring + review-as-phase (§03 lint + §06.4 gate), NOT commit-push (per decisions/02-content-as-context-unit.md). DONE: scripts/commit_push/structural_gate_selection.py STRUCTURAL_GATES = {non_linear, cyclic, routing_drift, budget}; structural_gates_apply(plan_dir) returns False for a JSON-v6 node. The markdown structural lefthook hooks (cross-section-closure-claim, budget-check) glob section-*.md and a v6 plan dir has none, so they skip v6 by construction; test_cross_section_check_skips_v6_node pins it.
  • Gate the removal on JSON-v6 coverage: legacy markdown plans not yet on JSON-v6 KEEP the legacy structural gates active, so a not-yet-migrated markdown plan never loses structural enforcement. The commit-push gate selects per node: JSON-v6 node -> structural gates off (schema + cure own them); legacy markdown node -> structural gates retained. DONE: structural_gates_apply defaults to RETAIN (True) for legacy markdown + unresolvable plan_dir (fail-safe); the SSOT node-kind discriminator is scripts/plan_corpus/lazy_migration_lint.is_json_v6_plan (plan.json presence). The selector ships now defaulting to retain (every current plan is legacy markdown, so zero coverage hole); the corpus-wide ACTIVATION of the v6-off branch (when v6 + legacy plans coexist + skills consume them) is owned by §10 (corpus-wide migration), with §09 (skill rewrites) preceding it — test_structural_gates_retained_for_legacy_markdown pins the retain default.
  • Keep correctness gates unconditionally (both node kinds): test_all, clippy, genuine lints, banned-msg. DONE: CORRECTNESS_GATES is disjoint from STRUCTURAL_GATES; correctness_gates_apply() is unconditional True; test_correctness_gates_apply_both_kinds pins the disjoint partition.
  • INV-17 pure-gate hook audit (anchors success_criterion “Audit finds zero hook-body mutation”): author scripts/commit_push/hook_mutation_audit.py that scans lefthook.yml commit-time hook bodies (pre-commit / commit-msg / pre-push) for mutate-and-stage patterns (git add, --write-back, sed -i) and FAILS-on-detection with a regenerate instruction. Convert the known violator lefthook.yml bug-tracker-render-hook to a verify-only fail-on-stale check (render --check renders in memory + diffs; rejects with a regenerate instruction on drift; NEVER mutate+stage). DONE: render.py gained check_by_subsystem + --check; the hook now runs ... --split-per-section --check; audit_lefthook on the real lefthook.yml returns zero findings; test_hook_mutation_audit_* + test_bug_tracker_render_hook_no_longer_mutates pin it; --self-test green.
  • Author the structural-gate-removal test in scripts/commit_push/tests/test_scoped_commit.py: assert (a) a JSON-v6 route node has structural gates off; (b) a legacy markdown plan retains the structural gate (no coverage hole); (c) correctness gates fire for both node kinds. DONE: test_structural_gates_off_for_json_v6_node, test_structural_gates_retained_for_legacy_markdown, test_correctness_gates_apply_both_kinds, test_cross_section_check_skips_v6_node green.
  • Subsection close (07.2) — all [x]; status: complete.

Absorbed defects (bug-tracker triage 2026-05-26)

Subsumed by §07’s path-scoped staging (the whole-tree-clean / dirty_after_commit gate is removed; paths outside the active touches: set are ignored). Deliverable MUST resolve; tracker entry closed obe-via-absorb pointing here.

BugSymptom subsumed
BUG-07-084parallel-session .review-checkpoints/ sidecars surface untracked at dirty_after_commit (orphan paths outside active set no longer fire the gate)

07.R Third Party Review Findings

  • None.

07.N Completion Checklist

  • 07.1-07.2 [x] and status: complete.
  • All success criteria have [x] checkboxes.
  • pytest scripts/commit_push/tests/test_scoped_commit.py green (scope-restriction from touches:; unrelated dirty path ignored; absent-touches: fall-through; CommitBackend ok/failed parity; structural-gate removal for JSON-v6 node + retention for legacy markdown plan). DONE: 12 passed.
  • python -m scripts.plan_corpus check plans/scripts-first-restructure/section-07-*.md exit 0.
  • /tpr-review passed (final, full-section). DONE: plan-level /tpr-review (review-plan Step 6) converged 2 rounds clean across codex/agy/opencode + adjudicator re-verify (third_party_review.status: clean). Implementation is wrapper tooling (scripts/**) — per CLAUDE.md compiler-only carve-out the implementation /tpr-review + /impl-hygiene-review gate does not apply; SRP/SSOT verified inline (SSOT node-kind classifier is_json_v6_plan; no duplicated plan.json check; render --check reuses the render_bug_tracker_md SSOT).

References

  • Pass 1D/N2: scripts/commit_push/staging.py (CommitResult:48, _build_staging_list:103, stage_and_commit_all:157), scripts/commit_push/triage.py (ScopeRestriction:77, compute_scope_restriction:202, apply_scope_restriction:297).
  • Touches read-site: scripts/plan_orchestrator/gates.py:load_plan_touches (:273; returns None on absent touches: at :310-313) — NOT a commit_push symbol; §07.1 imports it cross-package.
  • §06.5 commit-backend contract: scripts/plan_orchestrator/commit_capability.py (CommitScope:76, CommitResult:94, CommitBackend:135 with commit(scope)->CommitResult:144, invoke_commit:148; :168 pins “§07 owns the real backend”).
  • R1Q4 (enforce at authoring + execution, not commit); §01 invariants 7, 8.

HISTORY

  • 2026-05-29 — Autopilot auto-cure: review_plan_redispatch_loop reset_lost_dispatch (autopilot_run_id=72ece7c1a87349c08ab6dcbd6367019d); chain log recorded a phantom /review-plan dispatch but section frontmatter showed zero review evidence; chain counter reset and /review-plan re-dispatched (per state-discipline.md §4 Hard-abort terminal-state semantics + skill-control-contract.md §Autopilot Mode unified hook-failure continuation clause).