Skip to content

feat(gate-preconditions): establish environment/repo-state preconditions before gates run - #1675

Merged
tvna merged 21 commits into
mainfrom
claude/gate-preconditions-mechanism-c0wevy
Sep 2, 2026
Merged

feat(gate-preconditions): establish environment/repo-state preconditions before gates run#1675
tvna merged 21 commits into
mainfrom
claude/gate-preconditions-mechanism-c0wevy

Conversation

@tvna

@tvna tvna commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a shared preconditions mechanism to the gate registry
(.gitapex/ssot.json), plus three concrete precondition-establishment
fixes (shallow-clone auto-unshallow before local-preflight runs, a
fail-closed dependency check before a PreToolUse hook's own bare-python3
tier-1 invocation, and a worktree merge-base assertion at task-dispatch
time), closing the shared root cause behind four duplicate-closed
findings consolidated under issue #1566.

Facts

Assumptions

None outstanding -- Task 4's own dispatch confirmed the shared plan
branch's name is not threaded in from the main thread today; it resolves
it from the worktree's own branch reflog instead (see Execution log).

Risk / blast radius

  • The preconditions field is additive-only (.gitapex/ssot.schema.json
    keeps additionalProperties: false, bumping meta.schema_version
    1.4.0 -> 1.5.0); no existing gate entry's behavior changes unless it
    explicitly declares a new preconditions sub-key.
  • gitapex_gate_local_preflight.py's new auto-unshallow path only fires
    when a wired gate declares requires_full_history AND the repo is
    currently shallow -- a no-op (no new subprocess) on an already-full
    clone, which is the common case for most contributors' local machines.
  • check-pr-skill-audit-disclosure.sh's new precondition check narrows
    scope precisely to the dependency-missing cause; every other tier-1
    failure cause keeps falling through to the existing tier-2 warning path
    unchanged -- CI's skill-audit-gate.yml remains the authoritative
    backstop regardless.
  • Task 4's worktree merge-base assertion piggybacks on the task agent's
    own first Bash call (no SubagentStart hook exists) -- disclosed as an
    asymmetric-strength backstop, not a true "before any tool call" gate.
    Step 8's own adversarial review found and disclosed a second,
    structural limitation on top of that: the checker's own branch-name
    resolution only recognizes a local branch reflog entry, so a
    worktree created directly from a remote-tracking ref (e.g.
    origin/main) resolves to "cannot determine" and the check fails
    open -- reproduced live inside that review's own dispatched worktree.
    A real fix needs the shared branch name threaded in at dispatch time
    (a separate, larger change to this skill's own dispatch mechanism);
    documented as a known limitation in the checker's own docstring, the
    operator-facing warning it emits, and both reference docs, rather than
    patched around with a comparison against main (which is not the
    shared plan branch and would falsely deny unrelated legitimate work).
  • Step 6 per-task screening (screening-a-low-trust-contribution checks
    2-8, dispatched to review-persona) hard-flagged check 3 and/or check 4
    on all four tasks -- unconditional per that skill's own rule (fires on
    path alone: .gitapex/ssot.json/ssot.schema.json, agents/branch-plan-task.md,
    and every touched .github/scripts/**/hooks/**/skills/*/scripts/**
    file). All are exactly this PR's own plan-sanctioned deliverable --
    gate/hook infrastructure is what issue gate-proposal-umbrella: environment/repo-state preconditions #1566 asks to change. Every
    review-persona report read the substantive diff content and found no
    weakened fail-closed logic and no instruction-bearing/injected content
    (check 8 clear on all four). Disclosed here rather than silently
    cleared, per this skill's own screening discipline.
  • Wave-1 screening also caught a real scope violation before it merged:
    each wave-1 task's own worktree had, outside its Owns list and
    undisclosed in its own summary, deleted
    docs/superpowers/plans/2026-09-02-gate-preconditions-mechanism-c0wevy.md
    as an apparent unintended side effect. Corrected by restoring the
    file's original blob into each task's merged commit rather than merging
    the deletion.
  • Wave-2 screening (Task 3) found one real, actionable finding, fixed
    before merge: check-pr-skill-audit-disclosure.sh's new
    python3 "$precondition_script" "${required_packages[@]}" call had no
    -- separator before the array expansion, so a registry-declared
    package name shaped like an argparse flag (e.g. --help) would be read
    as CLI option syntax instead of data, silently skipping the new deny
    path. Fixed by adding --, plus an explicit Warning: line on the
    precondition subprocess's own unparseable-output case.
  • Step 8's own adversarial review found and fixed two further real
    defeat cases beyond wave-2's own screening: (1) the Python-import
    precondition probe (hooks/gitapex_check_python_precondition.py) had
    no subprocess timeout -- a module whose import-time code blocks (a
    network call, a lock, time.sleep) would stall the PreToolUse hook
    indefinitely; fixed with a bounded (10s) timeout, fail-closed on
    expiry, same as the existing OSError handling. (2) The local-preflight
    shallow-clone gate assumed git fetch --unshallow's own exit 0 means
    the repo is now full history -- but when origin is itself a shallow
    clone, that fetch exits 0 while the repo stays shallow, letting the
    gated run proceed and fail mid-run exactly as before this PR; fixed by
    re-probing is_shallow_clone after the fetch rather than trusting its
    exit code alone. (3) A related bug found beyond the four handed-off
    items: the shell hook's own jq -r '.missing // []...' parse exits 0
    with empty output on empty/malformed input from a crashed precondition
    checker -- silently skipping both the deny and the diagnostic warning,
    reintroducing gate-proposal: retro #1545 repair 7: PR-body verdict line rejected by skill-audit-disclosure due to a degraded local fallback and an overly strict regex #1547(a)'s own silent-degrade shape through the new
    check's own failure path. Fixed to verify the {"missing": [...]}
    shape explicitly before trusting an empty result.
  • Post-merge independent review (see Independent review verdict below)
    found and fixed a fourth: hooks/gitapex_check_python_precondition.py's
    is_importable() ran its python3 -c probe with no cwd=/env=
    control, so the probe's ambient working directory was implicitly
    prepended to sys.path[0] -- a HIGH-severity module-shadowing
    vulnerability (CWE-427/CWE-829). Fixed with -I (isolated mode).

Rollback

Revert this PR's merge commit. No schema/data migration beyond the
additive ssot.schema.json field and schema_version bump (both
revertible in the same commit); no new permission scope; no PR-blocking
CI behavior changed for any existing gate.

Verification

Restated from issue #1566's own re-verified Acceptance Criteria Map:

Criterion Proof method Result
[from #1547] PR-body verdict line rejected by skill-audit-disclosure due to a degraded local fallback implementing PR adds the check plus a regression test; confirm it fails against a reintroduced instance of the original defect, then passes Done (side a only; side b is out of scope, covered by a separate gate-proposal-umbrella). hooks/gitapex_check_python_precondition.py added (now with a bounded import-probe timeout and an isolated-mode (-I) probe subprocess, per two independent review rounds' own defeat cases); check-pr-skill-audit-disclosure.sh's tier-1 block denies immediately with an actionable uv sync --group dev message when a required package is not importable, reading the required-package list from .gitapex/ssot.json rather than hardcoding it, and now verifies the checker's own JSON output shape before trusting an empty result. Regression tests cover the flag-shaped-argv defeat case, the blocking-import defeat case, the malformed-checker-output case, and the cwd-shadowing defeat case; every other tier-1 failure cause still falls through to tier 2 unchanged.
[from #1546] Shallow clone caused a false harden-checkout-pin-drift failure on first push implementing PR adds the check plus a regression test; confirm it fails against a reintroduced instance of the original defect, then passes Done (identical fix to the row below; #1546 and #1489 are duplicate reports of the same defect).
[from #1489] harden-checkout-pin-drift failed locally due to a shallow clone implementing PR adds the check plus a regression test; confirm it fails against a reintroduced instance of the original defect, then passes Done. .github/scripts/_gitapex_preconditions.py added (is_shallow_clone/ensure_full_history, reusing _gitapex_base_ref.py's shared run_git wrapper after Step 8's own refactor pass); gitapex_gate_local_preflight.py reads every wired gate's own preconditions.requires_full_history flag from the registry (no gate id hardcoded) and auto-runs git fetch --unshallow once, before any wired gate runs, when needed -- now re-verifying the repo is actually non-shallow afterward rather than trusting the fetch's own exit code alone (Step 8's own defeat case against an itself-shallow origin). Aborts with one clear top-line message if either step fails, never surfacing reactively mid-run. No-op when the repo is already non-shallow or no wired gate needs it. Includes a live reproduction of the original defect against a real shallow clone of this repository (fails pre-fix, passes post-fix).
[from #1508] Worktree base mismatch (Wave 1 dispatch) implementing PR adds the check plus a regression test; confirm it fails against a reintroduced instance of the original defect, then passes Done, with one disclosed structural limitation (see Risk / blast radius). gitapex_check_task_worktree_base.py added, chained into check_task_bash_safety.sh's existing PreToolUse Bash hook; reproduces the exact stale-fork-point shape and confirms deny, plus confirms allow on a matching fork point and fail-open on an unresolvable branch name (including the remote-tracking-ref case Step 8's own adversarial review found and documented). tests/test_gitapex_check_task_worktree_base_properties.py (Hypothesis property tests, required by the already-wired detection-logic-property-coverage/function-body-test-coverage gates) found and fixed a real line-splitting bug in the new module.

Full repo suite (7976 tests) and all 44 gitapex_gate_local_preflight.py
gates pass on the current head (724cd21d).

Checklist

  • Tests pass locally
  • Docs updated if behavior changed
  • Issue number cited in every commit
  • If this PR adds/modifies a skills/*/SKILL.md, a docs/superpowers/specs/*.md design doc, a security-relevant skill, or a deterministic checker script (skills/*/scripts/*.py, evals/scripts/*.py, .github/scripts/*.py), a ## Skill audit evidence section discloses the required verdicts/waivers (see .github/scripts/gitapex_gate_skill_audit_disclosure.py)
  • If this PR adds a new Kept-edit-log entry to any evals/*/split.md, that entry discloses a Transfer check line (see .github/scripts/gitapex_gate_transfer_check_disclosure.py) -- N/A, no evals/*/split.md touched
  • If this PR adds or increases a skills/*/SKILL.md's Stop-boundary bullets or named dispatch branches, evals//tasks/*.yaml gained at least as many new fixtures (see .github/scripts/gitapex_gate_skill_branch_fixture_coverage.py) -- N/A, no SKILL.md touched

Skill audit evidence

This PR adds/modifies deterministic checker scripts (.github/scripts/_gitapex_preconditions.py,
.github/scripts/gitapex_gate_local_preflight.py, .github/scripts/gitapex_scan_ssot_schema.py,
hooks/gitapex_check_python_precondition.py, hooks/check-pr-skill-audit-disclosure.sh,
skills/executing-a-branch-plan/scripts/gitapex_check_task_worktree_base.py,
skills/executing-a-branch-plan/scripts/_gitapex_hook_cwd.py) and
.gitapex/ssot.json itself.

  • checker-script-adversarial-review: RAN -- executing-a-branch-plan's own
    Step 8 dispatched a fresh, independent subagent (no prior design
    context) to review the full accumulated diff. It found and fixed a
    real bug beyond the mandatory defeat-case scope: check-pr-skill-audit-disclosure.sh's
    own jq -r '.missing // []...' parse silently accepted empty/malformed
    precondition-checker output as "nothing missing," reintroducing
    gate-proposal: retro #1545 repair 7: PR-body verdict line rejected by skill-audit-disclosure due to a degraded local fallback and an overly strict regex #1547(a)'s own silent-degrade shape through the new check's own
    failure path. Fixed to verify the checker's own JSON shape explicitly.
    A second, post-merge independent review round (see Independent review
    verdict below) found and fixed the HIGH-severity cwd-shadowing bug in
    the same probe script.
  • deterministic-gate-quality: RAN -- read against
    skills/evaluating-deterministic-gate-quality/references/dimensions.md,
    dimension 15 (fail-closed default on incomplete or malformed input)
    specifically: the same Step 8 adversarial-review dispatch
    independently constructed and ran, live, a boundary/missing-dependency
    input against each of the three new gate/check scripts (see the three
    defeat cases below) rather than relying on the tasks' own bundled
    happy-path tests alone.
  • defeat-test-disclosure: RAN -- a case was constructed specifically to
    defeat each of the three new deterministic gate/check scripts on its
    own terms (not merely exercising its happy path), per
    references/refactor-and-review-gate.md's own mandatory
    "Deterministic gate/check script scrutiny":
    1. gitapex_check_task_worktree_base.py: a worktree created from
      origin/main (a remote-tracking ref) instead of a local branch
      name -- reproduced live inside the review's own dispatched
      worktree, whose own reflog read exactly this shape with the branch
      severely stale. The check failed open. A real fix needs the shared
      branch name threaded in at dispatch time (a larger, separate change
      to this skill's own dispatch mechanism); explicitly disclosed as a
      known structural limitation in the checker's own docstring, its
      operator-facing warning, and both reference docs, rather than
      patched around in a way that would widen the check's own blast
      radius (a comparison against main would deny legitimate work
      whenever main -- not the shared plan branch -- simply advances).
    2. hooks/gitapex_check_python_precondition.py: a module whose
      import-time code blocks (time.sleep) rather than succeeding or
      failing, falling outside the probe's own returncode == 0
      heuristic entirely. Fixed with a bounded (10s) timeout,
      fail-closed on expiry. The same script's own probe subprocess was
      later found, in the post-merge independent review round, to also
      be defeatable by a cwd-planted shadow file -- see Independent
      review verdict below.
    3. gitapex_gate_local_preflight.py's new precondition-gating logic:
      an origin that is itself a shallow clone, where git fetch --unshallow exits 0 while the repo remains shallow. Fixed by
      re-verifying is_shallow_clone() after the fetch rather than
      trusting its exit code alone.
      Each defeat case is committed as a permanent regression test in its
      own script's test suite, not a throwaway.

Merge gate: independent review

This PR is also subject to the independent-review-pending required
status check (see .github/workflows/independent-review-pending.yml /
.github/scripts/gitapex_gate_independent_review_pending.py). It stays
pending/failing until a ## Independent review verdict section naming
this PR's current head commit is recorded in this body --
drafting-a-pr-to-merge's own Step 8 records it once its independent
review completes. There is nothing for you to do here now: do not
pre-fill this section yourself, and do not remove this note.

Independent review verdict

  • Verdict: CLEAN
  • Verified commit: 724cd21

Outer layer: GitHub Copilot review requested twice
(mcp__github__request_copilot_review) against this PR, most recently
against this exact head commit. No response was posted from
copilot-pull-request-reviewer[bot] either time (confirmed via
pull_request_read method=get_reviews, empty result). CodeRabbit posted
only its own automatic skip notice ("This repository does not receive
automatic reviews because it has fewer than 10 stars"). No "Claude Code
Review" GitHub App is configured on this repository. This layer is
treated as unreachable / not configured, disclosed rather than silently
omitted.

Inner layer (reviewing-an-artifact skill, low effort): two rounds ran
against this PR's diff.

  • Round 1 (against commit a70fec4): 5-axis fan-out found one
    HIGH-severity confirmed finding and three lower-severity confirmed
    findings, all fixed:

    • Security: gitapex_check_python_precondition.py's is_importable()
      spawned its python3 -c probe subprocess with no cwd=/env=
      control. python3 -c implicitly prepends the process's own ambient
      working directory to sys.path[0], so a file shaped like the
      probed module's own name (e.g. pydantic.py) placed in that
      directory would be imported -- and its top-level code executed --
      instead of the real module (CWE-427/CWE-829). Independently
      reproduced directly (a planted shadow file executed without the
      fix) before fixing. Fixed by adding -I (isolated mode) to the
      probe's subprocess.run argv.
    • Reuse (two findings): gitapex_check_task_worktree_base.py's own
      _resolve_cwd was a byte-for-byte duplicate of
      gitapex_check_task_full_verification.py's identical helper --
      extracted into a new shared
      skills/executing-a-branch-plan/scripts/_gitapex_hook_cwd.py,
      matching this directory's own established
      _gitapex_path_normalize.py sibling-import convention.
      tests/test_gitapex_check_task_worktree_base_properties.py's own
      git-repo fixture reimplemented tests/conftest.py's
      init_git_repo/commit_file bootstrap inline instead of importing
      it -- switched to the shared helpers.
    • Blast-radius (doc staleness): CONTRIBUTING.md and
      gitapex_gate_local_preflight.py's own
      DEFAULT_TIMEOUT_SECONDS comment both still claimed behind-base
      (issue feat(gates): fail the local preflight when the branch is behind its base #985) is this runner's first gate that makes a network
      call -- no longer accurate once ensure_wired_gate_preconditions's
      own shallow-clone auto-unshallow fetch (this PR) can run even
      earlier, before any wired gate at all. Both updated.
      All four fixed and pushed (commits f41e9fd, 31b627b, d338d52),
      plus one deterministic-gate-driven addition the fix itself required:
      the extracted resolve_cwd had no test of its own directly mentioning
      it by name, which gitapex_gate_function_body_test_coverage.py's own
      "Existing-coverage check" requires in the same diff -- added
      tests/test__gitapex_hook_cwd.py (commit d338d52), confirmed via a
      clean local gitapex_gate_local_preflight.py run (44/44) afterward.
  • Round 2 (against the diff between commits a70fec4 and d338d52, i.e.
    re-reviewing round 1's own fix): correctness, blast-radius, and
    reuse-and-simplification axes report zero findings. Convention axis
    found two real nits, both fixed (commit 724cd21): the new
    tests/test__gitapex_hook_cwd.py used double-backtick RST-style code
    spans where every comparable test file in this repository uses single
    backticks; and its top-level tests/ placement (rather than
    colocated next to _gitapex_hook_cwd.py, this directory's own usual
    convention for a plain unit test) was undocumented -- now explained
    directly in the file's own module docstring as a deliberate,
    gate-driven placement (gitapex_gate_function_body_test_coverage.py
    only ever accepts tests/test_{stem}.py, unconditionally, regardless
    of the source file's own directory).

    Security axis raised one concern on the -I fix itself, at high
    confidence on documented semantics but explicitly flagged as not
    independently executed by that reviewer: current CPython docs attach
    "Added in version 3.11" to the -P flag specifically, and describe
    -I as implying -P -- read literally, this could mean -I alone
    did not exclude the probe's cwd from sys.path before Python 3.11,
    leaving a residual gap on an older ambient python3. Independently
    verified empirically rather than taken on the reviewer's own
    confidence: ran the exact shadow-file reproduction directly against
    every Python interpreter available in this session's own environment
    -- 3.10.20, 3.11.15, 3.12.3, and 3.13.12 -- confirming via sys.path
    inspection that -I excludes the empty-string cwd entry on 3.10 (a
    version that predates -P existing as a flag at all: python3.10 -P
    itself errors "Unknown option: -P") exactly as it does on 3.11+, and
    confirming the shadow-import attempt correctly raises
    ModuleNotFoundError under -I on all four versions tested. This
    finding did not reproduce and is not treated as a confirmed defect;
    disclosed anyway per this skill's own unconditional security-tier
    rule, together with the one residual gap this verification does not
    close: Python versions older than 3.10 were not available to test
    directly in this environment. Practical exposure is low regardless --
    this repository's own requires-python = ">=3.12" and its documented
    uv-provisioned install path are what the probe's PATH-resolved
    python3 is expected to resolve to in this repository's own
    deployment.

Related Issue

Refs #1566

Execution log

PlanApproved{run_id: 1481e3d}
TaskStarted{run_id: 1481e3d, task_id: task-1}
TaskStarted{run_id: 1481e3d, task_id: task-4}
TaskCompleted{run_id: 1481e3d, task_id: task-1, commit_sha: 28f01ed}
TaskCompleted{run_id: 1481e3d, task_id: task-4, commit_sha: 0427602}
TaskStarted{run_id: 1481e3d, task_id: task-2}
TaskStarted{run_id: 1481e3d, task_id: task-3}
TaskCompleted{run_id: 1481e3d, task_id: task-2, commit_sha: 9765d45}
TaskCompleted{run_id: 1481e3d, task_id: task-3, commit_sha: c00a847}
StageDeviated{run_id: 1481e3d, task_id: null, reason: "step 8 refactor/simplify pass merged (commit 98f35e1)", action: retry}
StageDeviated{run_id: 1481e3d, task_id: null, reason: "step 8 adversarial code review merged, 3 defeat cases found and fixed/disclosed (commit 6a650fe)", action: retry}
StageDeviated{run_id: 1481e3d, task_id: null, reason: "step 8 independent review round 1 findings fixed and verified (commits f41e9fd, 31b627b, d338d52)", action: retry}
StageDeviated{run_id: 1481e3d, task_id: null, reason: "step 8 independent review round 2 findings fixed and verified (commit 724cd21)", action: retry}

Decomposes the re-verified Acceptance Criteria Map into four tasks
(registry preconditions field, shallow-clone auto-establish, dependency
precondition fail-closed, worktree merge-base assertion) across two
waves. Refs #1566.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv5N1QfPRh5sruiGzzHA43
@tvna
tvna deployed to ruleset-verify September 2, 2026 13:42 — with GitHub Actions Active
@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: b86a748b-e00b-4c9c-872d-f0c3b0f76fb3


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.66%. Comparing base (b07fabc) to head (724cd21).

Additional details and impacted files
@@           Coverage Diff            @@
##             main    #1675    +/-   ##
========================================
  Coverage   99.66%   99.66%            
========================================
  Files         149      152     +3     
  Lines       24479    24725   +246     
  Branches     2960     2967     +7     
========================================
+ Hits        24397    24643   +246     
  Misses         82       82            

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Adds an optional preconditions object to the gate $defs in
ssot.schema.json (additionalProperties: false, matching every other
object in this schema), with two optional sub-keys:
requires_full_history (boolean) and requires_python_packages (array of
non-empty strings, minItems: 1). Bumps ssot.json's meta.schema_version
from 1.4.0 to 1.5.0 per the schema file's own docstring requirement.

Declares the field on the two gate entries whose scripts genuinely need
it, confirmed by reading each script directly rather than trusting the
plan's prior read: harden-checkout-pin-drift gets
requires_full_history: true (its own gitapex_scan_harden_checkout_pin_drift.py
raises RuntimeError on a shallow-clone boundary artifact when resolving
a file's true last-touching commit via unbounded git log -- distinct
from the other local-plane gates touching _gitapex_base_ref.py, which
either fetch their own specific base ref or gracefully degrade to
"unverifiable" rather than hard-failing). skill-audit-disclosure gets
requires_python_packages: ["pydantic"] (its own
gitapex_gate_skill_audit_disclosure.py transitively imports pydantic via
gitapex_compute_skill_audit_flags.py -> gitapex_detect_changed_gate_scripts.py,
confirmed by reading each import chain directly).

Adds a matching optional GatePreconditions submodel (extra="forbid") to
gitapex_scan_ssot_schema.py's own pydantic Gate model, so a
preconditions-carrying entry does not fail that script's stricter parse.

Adds two test cases to test_gitapex_scan_ssot_schema.py: a
preconditions-carrying gate entry validates against both the JSON Schema
and the pydantic model, and an entry with an unrecognized precondition
sub-key is rejected by both layers.

This is the foundational task other tasks in this branch plan build on;
field names kept exactly as specified for the later-wave tasks that read
this shape.

Refs #1566.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…anch tip

Task 4 of the gate-preconditions-mechanism branch plan. Adds
gitapex_check_task_worktree_base.py, a stdlib-only classifier chained
into check_task_bash_safety.sh's own existing PreToolUse "Bash" hook
(as a second sibling call alongside gitapex_check_task_bash_safety.py,
matching that script's own convention -- no second hooks.PreToolUse
frontmatter entry exists to hang it off instead, confirmed by reading
.claude/agents/branch-plan-task.md directly first). Resolves the
shared plan branch's own name purely from local git state (the
worktree's own branch reflog "branch: Created from <name>" entry,
verified to resolve to an existing local branch -- no value threaded
in from the main thread), then asserts the shared branch's own current
tip is still an ancestor of the worktree's HEAD, denying with both SHAs
named on a confirmed mismatch. Reproduces the exact original defect
shape (a worktree forked from a now-stale base, issue #1508) in a
fixture-repo test, confirms the check denies there and allows once the
fork point matches, and confirms an unresolvable branch name (no
worktree, a detached HEAD, or an unrelated worktree's own reflog) fails
open with a warning rather than blocking.

Documents the mechanism, its asymmetric fail-open/fail-closed design,
and its disclosed residual (piggybacks on the task's own first Bash
call, not a true before-any-tool-call gate) in
references/execution-and-dispatch.md and
references/threat-model-and-authorization.md, and adds a prompt-only
paragraph to the plugin-distributed agents/branch-plan-task.md variant
(no hooks field there, matching this skill's own established
two-variant asymmetry convention).

Also adds tests/test_gitapex_check_task_worktree_base_properties.py (a
top-level tests/ file, outside this task's originally-listed Owns list):
two already-wired deterministic gates
(detection-logic-property-coverage, function-body-test-coverage)
required it to accept the new module's own detection logic; a Hypothesis
property test in it found and fixed a real bug in the new module (a
str.splitlines()-based scan over-splitting on non-newline Unicode
line-boundary characters).

Squashes three worktree-local commits (feat: assert worktree base
matches shared branch tip; test: add worktree-base property coverage,
fix line-split bug; test: call main directly for
function-body-test-coverage) into one clean commit for merge-back, and
restores docs/superpowers/plans/2026-09-02-gate-preconditions-mechanism-c0wevy.md
(this branch's own task-list file), which the dispatched task's own
worktree had inadvertently deleted outside its Owns scope -- caught by
this skill's own step 6 file-ownership-consistency screening before
merge, corrected here rather than merged as-is.

Refs #1566.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Merges Task 4 of the gate-preconditions-mechanism branch plan onto the
shared branch. Refs #1566.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@tvna
tvna deployed to ruleset-verify September 2, 2026 14:55 — with GitHub Actions Active
Refs #1566. Consolidates #1546/#1489: a shallow clone reaching
harden-checkout-pin-drift directly used to surface RuntimeError only
reactively, mid-run, from that one gate's own git-log call.

Add .github/scripts/_gitapex_preconditions.py: is_shallow_clone()
wraps `git rev-parse --is-shallow-repository`, ensure_full_history()
wraps `git fetch --unshallow`. Both raise a typed PreconditionsError
on any subprocess failure -- never a silent "not shallow".

gitapex_gate_local_preflight.py's load_local_checks() now carries a
gate's own preconditions.requires_full_history off the same registry
parse (LocalCheck grows that field), and a new
ensure_wired_gate_preconditions() runs before any wired gate: no
subprocess at all when no wired gate needs it, the local shallow
probe only when at least one does, and the network fetch only when
that probe confirms the repo actually is shallow. A failure at either
step aborts with one top-line stderr message and exit 1 before any
gate runs.

Tests: tests/test__gitapex_preconditions.py unit-tests the two new
helpers against real local bare-repo/shallow-clone fixtures.
tests/test_gitapex_gate_local_preflight.py adds registry-parsing
tests plus ensure_wired_gate_preconditions coverage (no-op when
unneeded, fetch-skip when already non-shallow, fetch-once when
shallow, abort-with-message on either failure), and two
@pytest.mark.slow main() integration tests: one reproduces the
original defect directly (the fixture gate script, shaped like
harden-checkout-pin-drift's own current_action_sha(), fails against a
still-shallow clone run directly, then passes once main() establishes
full history first) and one confirms the abort happens before any
wired gate runs when the unshallow fetch itself fails.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ention

Refs #1566. The stdlib-only-claim-drift gate flags any file whose diff
adds a new top-level import if that file's own content contains an
un-negated "stdlib-only" phrase with no nearby unindented "uv run"
mention. This file's own docstring already named the registered
stdlib-only-claim-drift gate by its literal registry id in an
unrelated bullet about origin/main-needing local_stdin producers --
harmless before this change, but the new `import _gitapex_preconditions`
line added in the prior commit is itself exactly the kind of new
top-level import that trigger checks for, so it now scans this file's
own content and finds that unrelated gate-name mention.

Move the "uv run" disclosure onto the bullet's own opening (unindented)
line rather than a continuation line, since the gate's own proximity
check deliberately excludes indented lines (issue #1052's own
Usage::-boilerplate false-positive fix) -- and the disclosure is
accurate on its own merits: every local_stdin producer in the
registry genuinely is invoked via `uv run`.

Verified against the real gitapex_gate_stdlib_only_claim_drift.py,
fed this branch's own diff directly (bypassing this worktree's stale
origin/main): failed before this commit, passes after. The full local
preflight (44 wired gates) now passes end to end.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Refs #1566. Closes #1547(a): hooks/check-pr-skill-audit-disclosure.sh's
tier-1 path attempted a bare python3 invocation of
gitapex_gate_skill_audit_disclosure.py, which transitively imports
pydantic. When that package was not importable by python3, the
invocation crashed with no recognizable FAIL: line, and the existing
fall-through logic silently downgraded that to a warning and degraded
to tier 2's weaker, SKILL.md-only check -- never telling the caller a
dependency was missing.

Add hooks/gitapex_check_python_precondition.py, a stdlib-only checker
that probes each given module's importability in a separate python3
subprocess (never this process's own import statement), so a missing
module cannot crash the checker itself. Wire it into
check-pr-skill-audit-disclosure.sh's tier-1 block: before attempting
the full_gate invocation, read the skill-audit-disclosure gate's
preconditions.requires_python_packages array from .gitapex/ssot.json
(via jq, never hardcoded) and invoke the new checker against it. If any
required package is missing, deny immediately with an actionable "uv
sync --group dev" message instead of falling through. Every other
tier-1 failure cause (unresolvable git state, unreadable registry, a
bug in the gate script itself) still falls through to tier 2 with a
warning exactly as before -- this fix is narrowly scoped to the
dependency-missing cause only, and fails open (skips the new check
entirely) when the registry cannot be read or declares no required
packages for this gate.

New tests in hooks/test_gitapex_check_pr_skill_audit_disclosure_shell.py
reproduce the original defect (a deliberately-unimportable module name
denies with the actionable message rather than warning-and-falling-
through), confirm a genuinely unrelated tier-1 failure (a corrupted
gate script) still falls through unchanged, and confirm the existing
happy-path and skip-when-no-preconditions-declared behavior is
unaffected. hooks/test_gitapex_check_python_precondition.py unit-tests
the new probe directly.

Amended by this branch's own step-6 screening (review-persona check 4):
the python3 "$precondition_script" "${required_packages[@]}" call had no
`--` separator before the array expansion, so a registry-declared
package name shaped like an argparse flag (e.g. "--help") would be
read as CLI option syntax instead of data -- triggering the probe
script's own --help handling (usage text, exit 0) rather than an
import check, which this hook's jq parse would then read as an empty,
inconclusive result and silently skip the new deny path. Fixed by
adding `--` before the array expansion, and by adding an explicit
Warning: line (matching this file's own established convention) when
the precondition subprocess's output cannot be parsed, instead of
silently treating that as "nothing missing". New regression test
test_tier1_precondition_treats_a_flag_shaped_package_name_as_data
reproduces the exact shape and confirms it now denies correctly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Merges Task 2 of the gate-preconditions-mechanism branch plan onto the
shared branch. Refs #1566.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Merges Task 3 of the gate-preconditions-mechanism branch plan onto the
shared branch, including the step-6 screening fix (argv `--` separator
and an explicit parse-failure warning). Refs #1566.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@tvna
tvna deployed to ruleset-verify September 2, 2026 15:44 — with GitHub Actions Active
…ition probe

Codecov flagged 3 uncovered lines in the new
gitapex_check_python_precondition.py (89.65% patch coverage): the
except OSError branch in is_importable(), reached when the python
executable itself cannot be launched at all (not on PATH, not
executable) -- distinct from the probed module simply being missing.
Adds a direct regression test calling is_importable() with a
nonexistent interpreter path, confirming it returns False and warns
rather than letting the OSError escape uncaught.

Refs #1566.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@tvna
tvna deployed to ruleset-verify September 2, 2026 15:52 — with GitHub Actions Active
… branch

Refs #1566. Step 8 refactor/simplify pass over the full accumulated diff
of the four gate-preconditions tasks. Behavior-preserving throughout: no
test removed or weakened, the same 7954 tests pass, and all 44 wired
local gates still pass.

Reuse, .github/scripts/_gitapex_preconditions.py: is_shallow_clone and
ensure_full_history each carried their own near-identical subprocess.run
wrapper (fixed argv, capture_output, errors="replace", check=False, plus
a TimeoutExpired and an OSError/ValueError/SubprocessError handler).
_gitapex_base_ref.run_git in the same directory already is that wrapper,
and its own error_cls parameter exists precisely so a sibling keeps
raising its own exception type -- its docstring names "a second,
near-identical subprocess.run wrapper in that caller" as the
duplicate-then-drift it exists to prevent. Both functions now call it
with error_cls=PreconditionsError, and GIT_TIMEOUT_SECONDS is imported
from it rather than redefined as a second literal 60. The nonzero-exit
messages are byte-identical to before; the subprocess-layer messages
keep every fact (the git command, the repo root, the timeout) in
run_git's own established phrasing. The module docstring's stdlib-only
paragraph is rewritten to describe the import it now takes.

Reuse, tests/: tests/test__gitapex_preconditions.py and
tests/test_gitapex_gate_local_preflight.py each defined a verbatim copy
of the same five-step shallow-clone fixture chain (run git, init repo,
commit file, bare origin with two commits, depth-1 file:// clone). Moved
to tests/conftest.py, this suite's own established home for a helper two
files must agree on, and imported by both.

Documentation accuracy: the property suite's strategy comment described
_reflog_created_from as scanning a splitlines() loop, which that same
task's own Hypothesis-found fix had already replaced with an explicit
split("\n"); and _reflog_created_from's docstring pointed at its module
docstring "for why more than one entry can exist", which that docstring
never says. Both now state what the code actually does, the second
grounded in observed git output (reflog show prints newest-first, so a
later "branch: Reset to <x>" entry pushes the creation entry down).

Verified: uv run --frozen python3 -m pytest --no-cov -q, and uv run
--frozen python3 .github/scripts/gitapex_gate_local_preflight.py.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Merges the mandatory aggregate refactor/simplify pass over the full
accumulated diff: reuses _gitapex_base_ref.py's run_git wrapper instead
of a near-duplicate subprocess wrapper in _gitapex_preconditions.py,
dedupes a verbatim-duplicated shallow-clone test fixture into
tests/conftest.py, and fixes two cross-task doc staleness spots. No
behavior change (7954 tests, all 44 local-preflight gates, before and
after). Refs #1566.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@tvna
tvna deployed to ruleset-verify September 2, 2026 16:10 — with GitHub Actions Active
…nshallow fetch

Refs #1566

Step 8 adversarial review, deterministic gate/check script scrutiny for
.github/scripts/gitapex_gate_local_preflight.py's precondition gating.

Defeat case, live-verified rather than reasoned about: when a repository's
own origin is itself a shallow clone, "git fetch --unshallow" exits 0 with
empty stderr and the repository is STILL shallow -- git propagates the
source's own shallow boundary, deepens as far as the source can offer, and
reports success. ensure_wired_gate_preconditions treated that zero exit as
proof of full history and returned "safe to proceed", so the
requires_full_history gate then ran against a still-shallow repo and failed
reactively, mid-run, with its own confusing error -- exactly the outcome
issue #1489 asked to replace with one clean top-line abort.

This is an ordinary fixable gap, not a structural limit, so it is fixed
rather than disclosed. The fix applies the same "never trust the fetch's
exit code alone" discipline _gitapex_base_ref.py already establishes for
its sibling fetch_destination_refspec/peeled_ref_exists pair, where the
post-fetch verification is likewise the caller's job and not the fetch
helper's: re-probe is_shallow_clone after the fetch and abort with exit 1
plus a message naming the likely cause. The re-probe runs only on the path
that actually fetched, so an ordinary already-full-history run still makes
exactly one git call.

tests/conftest.py gains shallow_clone_of_a_shallow_origin, shared by both
suites that pin the case. test__gitapex_preconditions.py pins the git
behavior itself at the helper level (ensure_full_history's contract is
deliberately unchanged -- it runs the fetch and raises on failure to run
it; the guarantee belongs to the caller that needs it).
test_gitapex_gate_local_preflight.py pins the runner-level abort, the
probe ordering, the second probe failing to answer, and the same case
end-to-end through main() against a real git fixture with nothing stubbed.

One pre-existing test's stub described an impossible world (a fetch that
succeeds while the repo stays shallow forever) and now models a genuinely
successful unshallow instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…condition check

Refs #1566

Step 8 adversarial review, deterministic gate/check script scrutiny for
hooks/gitapex_check_python_precondition.py and its caller
hooks/check-pr-skill-audit-disclosure.sh. Two confirmed defeat cases, both
ordinary fixable gaps rather than structural limits, so both are fixed.

1. is_importable ran "python3 -c import <mod>" with no subprocess timeout,
   unlike both sibling precondition checkers (_gitapex_preconditions.py
   bounds at 60s via the shared run_git wrapper; gitapex_check_task_worktree
   _base.py at 10s). Defeat case: the condition this probe exists to catch
   -- a declared package python3 cannot actually use -- reshaped to fall
   just outside its own returncode == 0 heuristic, as a module whose import
   neither succeeds nor fails because its import-time code blocks. Verified
   live: a module sleeping 6s took the probe a full 6.0s, so no bound was
   applied at all. That probe runs inside a PreToolUse hook firing on every
   mcp__github__create_pull_request/update_pull_request call in this
   repository, so an unbounded probe stalls the very operation the hook
   gates and produces no verdict at all -- strictly worse than the silent
   tier-2 degrade #1547(a) reports. Now bounded by PROBE_TIMEOUT_SECONDS
   (10.0, a hang guard rather than a budget) with TimeoutExpired handled
   exactly like the already-handled OSError case: fail closed to "cannot
   confirm this is importable", with a warning naming what happened.
   find_missing_modules threads the same bound through per module.

2. The shell caller's own inconclusive-output path never fired. Its jq
   filter was '.missing // [] | join(", ")', and jq exits 0 with empty
   output when its input contains no values (verified directly) -- which is
   exactly what a crashed, missing-interpreter, or syntactically broken
   precondition script produces on stdout. That made missing_packages empty,
   skipped the documented Warning line, and skipped the deny: a completely
   silent no-op of the whole block, reintroducing #1547(a)'s own silent
   degrade through this check's own failure path while its comment claimed
   a warning it never printed. The filter now PROVES the output is the
   expected {"missing": [...]} object instead of merely failing to
   contradict it, so empty input, invalid JSON, a bare null or array, and an
   object without a missing array all reach the Warning. It still falls
   through to tier 2 rather than denying: a checker that cannot run is not
   evidence a dependency is missing, so the fix's narrow scope
   ("dependency-missing cause only") is preserved.

Both cases are committed as permanent regression tests in the scripts' own
suites, not run once and discarded.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…op does not fire here

Refs #1566

Step 8 adversarial review, deterministic gate/check script scrutiny for
skills/executing-a-branch-plan/scripts/gitapex_check_task_worktree_base.py.

Defeat case, and it succeeds: issue #1508's own defect condition -- a task
worktree forked from a base the shared plan branch has since advanced past
-- reshaped to fall just outside this module's reflog heuristic, as a
worktree whose branch was created from a remote-tracking ref rather than
from a local branch by name. _verify_local_branch correctly refuses to
resolve refs/heads/origin/main, the whole resolution chain returns "cannot
resolve", and the check fails OPEN while the stale base it exists to catch
is severely present.

This is not hypothetical. It is the observed shape of a real
branch-plan-task worktree in this repository: the step-8 review dispatch
that found it was itself handed a worktree whose reflog read exactly
"branch: Created from origin/main", sitting at the plan branch's merge-base
with every one of that branch's commits missing. The check did not fire and
the agent had to notice and reset by hand.

Disclosed rather than fixed, because it is a structural limit of the
reflog-based approach and not an ordinary gap this diff introduced.
Resolving refs/remotes/origin/main instead would NOT be a fix: main is not
the shared plan branch, so that comparison would deny every legitimately
based task worktree the moment main advanced -- precisely the false,
blast-radius-widening DENY the module's own design rejected the
main-checkout heuristic for. A real fix needs the shared plan branch's name
threaded in from the dispatching thread, a change to that dispatch step
rather than to this module, named as the open follow-up it earns.

The module's own docstring previously asserted the opposite of what was
observed -- that a startpoint which is not a local branch is "essentially
never" a genuine dispatch. The premise (such a startpoint is not a
resolvable shared plan branch) is sound; the conclusion drawn from it was
wrong, so it is corrected here rather than left as a claim the evidence
contradicts. The fail-open warn message shown to an operator said the case
was "expected" only for an unrelated worktree; it now names the genuine
dispatch case too and tells the reader to verify the base by hand. Matching
disclosures land in references/execution-and-dispatch.md and
references/threat-model-and-authorization.md, and the case is committed as
a permanent regression test rather than run once and discarded.

Also pins the ssot registry's two-layer validation contract. The pydantic
GatePreconditions model accepts requires_python_packages values the JSON
Schema rejects ([] and [""]), but that asymmetry is deliberate and matches
every sibling model in gitapex_scan_ssot_schema.py -- Gate.rule,
Gate.policy_refs, GateFailMode.rationale and GateTargetEntry.ref all carry
the schema's minLength/minItems on the schema side only, and
GateFailMode's own docstring states the division outright. find_drift runs
the strict layer first, so a degenerate list is rejected by the wired gate
regardless. Tightening GatePreconditions alone would make it the single
model duplicating a schema rule. The end-to-end rejection and the layering
convention are pinned instead, so a future edit confronts the convention
rather than silently diverging from it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Merges the mandatory independent adversarial code review over the full
accumulated diff. Constructed defeat cases against all three new
deterministic gate/check scripts: gitapex_check_task_worktree_base.py
(defeated by a worktree created from origin/main instead of a local
branch name -- reproduced live in this review's own worktree; a real
fix needs the branch name threaded in at dispatch, so disclosed as a
structural limitation rather than patched around), the Python-import
precondition probe (defeated by a blocking import with no timeout --
fixed, 10s bound added), and the local-preflight shallow-clone gating
(defeated by an origin that is itself shallow, where `git fetch
--unshallow` exits 0 but the repo stays shallow -- fixed by re-probing
after the fetch). Also fixes a silent-skip bug in the shell hook's own
jq parse of the precondition checker's output, and resolves the four
items the prior refactor pass flagged as out of scope (one real fix:
bounded timeout on the import probe; three confirmed correct as
designed, with reasoning recorded in each script's own docs). Refs
#1566.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@tvna
tvna deployed to ruleset-verify September 2, 2026 16:39 — with GitHub Actions Active
@tvna tvna removed the branch-plan-executing label Sep 2, 2026 — with Claude
@tvna
tvna marked this pull request as ready for review September 2, 2026 16:41
…xture repo

CI's pytest job failed on
test_stale_base_is_NOT_detected_when_the_worktree_was_created_from_a_remote_tracking_ref
with `git commit` exit 128 (unable to auto-detect email address). The
test clones a fixture repo (`git clone`, which does not carry over the
source repo's own local `user.*` config -- `_init_repo` sets that
per-repo, not globally) and then commits directly into the clone. This
passed locally only because this environment's own global git config
already supplied an identity, papering over the gap; a CI runner with
no global git identity configured hits it directly.

Fixed by configuring user.email/user.name on the clone before its first
commit, matching what _init_repo already does for a freshly-initialized
repo. Reproduced the CI failure locally with an isolated HOME and
GIT_CONFIG_NOSYSTEM=1 (no ambient git identity) before the fix, and
confirmed it passes with the fix applied under the same isolation.

Refs #1566.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@tvna
tvna deployed to ruleset-verify September 2, 2026 16:48 — with GitHub Actions Active
Step 8's independent inner-layer security review (issue #1566) found a
HIGH-severity vulnerability in gitapex_check_python_precondition.py's
is_importable(): its subprocess.run call had no cwd=/env=, so it
inherited this process's own ambient working directory. python3 -c
implicitly prepends that cwd to sys.path[0], so a file shaped like the
probed module's own name (e.g. pydantic.py) placed in whatever
directory this checker happens to run from would shadow the real
module and have its own top-level code executed inside the probe
subprocess (CWE-427/CWE-829). check-pr-skill-audit-disclosure.sh's own
new call site lacks the cd "$repo_root" its pre-existing sibling
invocation has, so the cwd here is genuinely uncontrolled.

Reproduced directly: a shadow pydantic.py printing a marker executed
when probed without -I; python3 -I -c ... (isolated mode) blocks it,
raising ModuleNotFoundError instead, while still finding both stdlib
modules and a legitimately uv-installed pydantic.

Fixed by adding -I to the probe's subprocess.run argv. -I also ignores
PYTHONPATH, which broke three existing tests that used PYTHONPATH to
place a fixture "blocking import" module on the probe's search path;
fixed by substituting a fake python3-shaped interpreter that
unconditionally sleeps past the timeout, decoupling those tests from
PYTHONPATH entirely. Added a new regression test reproducing the
shadow-file attack and asserting it is blocked.

Refs #1566.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ture bootstrap

Step 8's independent inner-layer reuse-and-simplification review (issue
#1566) found gitapex_check_task_worktree_base.py's own _resolve_cwd was
a byte-for-byte duplicate of gitapex_check_task_full_verification.py's
identical helper. Extracted both into a shared
_gitapex_hook_cwd.resolve_cwd, matching this directory's own
established _gitapex_path_normalize.py convention for a sibling-import
shared helper.

The same review found tests/test_gitapex_check_task_worktree_base_properties.py's
own _fixture_repo fixture reimplemented tests/conftest.py's
init_git_repo/commit_file bootstrap inline instead of importing it (the
same helpers test__gitapex_preconditions.py already imports from that
directory). Switched to the shared helpers.

Also fixes a doc-staleness finding from the same review:
CONTRIBUTING.md and gitapex_gate_local_preflight.py's own
DEFAULT_TIMEOUT_SECONDS comment both still claimed behind-base (issue
#985) is this runner's first gate that makes a network call -- no
longer accurate now that ensure_wired_gate_preconditions's own
shallow-clone auto-unshallow fetch (issue #1566) can run even earlier,
before any wired gate at all.

Refs #1566.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ctly

The prior dedup commit's own new _gitapex_hook_cwd.resolve_cwd had no
direct test of its own -- each pre-existing test file only drove it
indirectly through gitapex_check_task_worktree_base.py's or
gitapex_check_task_full_verification.py's own main(). This repository's
function-body-test-coverage gate (issue #1498) caught it: a new/changed
function body in a skills/*/scripts/*.py file needs a same-diff test in
tests/test_<stem>.py or tests/test_<stem>_properties.py that mentions
it by name.

Adds tests/test__gitapex_hook_cwd.py directly against the extracted
helper, matching tests/test__gitapex_preconditions.py's own precedent
for a leading-underscore shared module's test naming.

Refs #1566.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@tvna
tvna deployed to ruleset-verify September 2, 2026 17:37 — with GitHub Actions Active
…test

Round-2's own independent review (issue #1566) found two convention
deviations in tests/test__gitapex_hook_cwd.py: it used double-backtick
RST-style code spans, inconsistent with every other test file in this
repository (single backtick throughout, including this file's own
sibling test__gitapex_preconditions.py); and its placement in
top-level tests/ was not explained against
skills/executing-a-branch-plan/scripts/'s own established convention of
colocating a plain unit-test file next to its source (e.g.
test_gitapex__path_normalize.py next to _gitapex_path_normalize.py).

Fixed the backtick style directly. The placement is a deliberate,
gate-driven choice, not an oversight -- kept in tests/ specifically
because gitapex_gate_function_body_test_coverage.py's own
"Existing-coverage check" unconditionally accepts only
tests/test_{stem}.py or tests/test_{stem}_properties.py, regardless of
the source file's own directory, so a colocated-only test would not
clear that deterministic, PR-blocking gate for the new resolve_cwd
function. Documented the reasoning directly in the file's own module
docstring rather than moving it and breaking the gate.

Refs #1566.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@tvna
tvna deployed to ruleset-verify September 2, 2026 17:48 — with GitHub Actions Active
@tvna
tvna merged commit 1e8dd62 into main Sep 2, 2026
39 of 40 checks passed
@tvna
tvna deleted the claude/gate-preconditions-mechanism-c0wevy branch September 2, 2026 21:30
tvna pushed a commit that referenced this pull request Sep 2, 2026
…ll-implementation-lkdypz

Brings in two independently-merged main-branch efforts that landed while
this branch was in flight:

- PR #1677 (issue #1676): rubric.md's Mixed-portability substitute for a
  Dimension-5-exempted target, plus that same session's own re-grade of
  executing-a-branch-plan's Mixed-portability finding against it (kept
  as its own deferral decision-log entry, appended after mine rather
  than overwritten).
- PR #1675 (issues #1508/#1566): a new worktree-base precondition
  backstop (gitapex_check_task_worktree_base.py), chained into
  check_task_bash_safety.sh.

Conflict resolution:
- metadata/gitapex.yaml: kept this branch's full decision log and
  Frontier/requires/relatedTo values (main's own copy predates this
  branch's own fork and never carried any of that), appended main's new
  #1676 deferral entry, and added one missing decision-log entry for
  this branch's own prior failure-and-recovery.md reformatting commit.
- references/execution-and-dispatch.md: this branch already retired
  this file (split into decomposition-and-dispatch.md /
  events-and-review-gate.md / failure-and-recovery.md, per issue
  #1662's own Round 10). Ported main's new "Worktree-base precondition
  backstop" subsection into decomposition-and-dispatch.md's own
  "Execution and dispatch" section (matching where its sibling
  subsections already live), demoted from ## to ###, and repointed
  every stale execution-and-dispatch.md cross-reference (in
  threat-model-and-authorization.md and
  gitapex_check_task_worktree_base.py/its test) to
  decomposition-and-dispatch.md.

Verified post-merge: shape checker 54/54, drift scanner unchanged (same
4 pre-existing findings), full pytest suite passing (the one run mid-
merge showed 7 gitapex_gate_commit_citation.py failures -- confirmed a
transient artifact of running the suite while .git/MERGE_HEAD existed,
merge_in_progress()'s own by-design merge-commit exemption, not a real
defect; re-verified clean after this commit), all 44 local-preflight
gates passed.

Refs #1648, #1662, #1676
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.

2 participants