Rectify: GitHub Mutation Guard Argv Parsing Immunity (Combined Plan) - #4680
Open
Trecek wants to merge 16 commits into
Open
Rectify: GitHub Mutation Guard Argv Parsing Immunity (Combined Plan)#4680Trecek wants to merge 16 commits into
Trecek wants to merge 16 commits into
Conversation
…c engine Closes Issue #4655 Defect 1 (rectify Part A). _analyze_gh_api()'s if/elif chain recognized only --method/-X, --input, --field/-F, --raw-field/-f, -H/--header/--hostname/--cache, and --paginate. Any other flag (--jq, --template, -p) fell through to a generic branch that advanced past the flag token without consuming its value, so the value was misread as a second route and tripped a false request_cardinality_unresolved deny. Replace the ad-hoc chain with a declarative {flag: arity} spec table (_GH_API_FLAG_SPEC, verified against a live `gh api --help`) consumed by one shared argv-walking engine (_consume_gh_flag). A flag absent from the table now fails closed with its own distinguishable reason code (unrecognized_gh_api_flag) instead of being silently misparsed as a second route under the same misleading reason code as a genuinely-ambiguous input. - _FlagArity/_GH_API_FLAG_SPEC/_consume_gh_flag added near _flag_value, which they complement rather than replace (curl's extraction is unaffected; migrating it is Part C's job). - _analyze_gh_api's catch-all branch now calls _consume_gh_flag and denies unrecognized flags explicitly. - tests/hooks/test_gh_api_flag_spec_contract.py: live-CLI contract test parsing `gh api --help`'s FLAGS section, asserting every value-taking flag is present in _GH_API_FLAG_SPEC -- catches future gh CLI additions before they silently reintroduce this defect shape. - New tests close the coded-but-untested form-grammar gaps identified by the investigation (--method=, -XPOST, --input=, long-form --raw-field) alongside the three newly-recognized flags, extending the gh-issue-edit 5-form grammar precedent to gh api's value-taking flags. - docs/safety/hooks.md: document unresolved_mutation's reason-code family, including request_cardinality_unresolved (undocumented until now) and the new unrecognized_gh_api_flag. - tests/arch/test_subpackage_isolation.py: bump _command_classification.py's REQ-CNST-010-E10 line-limit exemption 2350 -> 2450 for this addition. Part B (quote-provenance tokenizer, Defect 2) and Part C (extending this spec-table engine to curl/git/pip/write-guard bypass-direction bugs) follow in subsequent commits per the combined rectify plan's green-gate sequencing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Closes Issue #4655 Defect 2 (rectify Part B). _DYNAMIC_SHELL_TOKEN_RE (\$|`|\*|\?|\[) runs against already-shlex-dequoted argv tokens, so a fully single-quoted GraphQL document legitimately using `$` for variables and `[...]` for list literals (e.g. `-f query='mutation( $id: ID!) { addLabels(labelIds: [$id]) { ... } }'`) was misclassified as dynamic_target -- shlex's POSIX dequoting discards quote-type information, so a bare `$id` substring couldn't be distinguished from text a shell would already have expanded. A prior investigation explicitly rejected relaxing the character set itself, since that would also accept a real unquoted command-substitution fragment. This closes the actual gap: proving, at the raw-command-text level and independent of shlex's dequoting, whether a specific argv token sat inside one unbroken `'...'` run -- which is provably shell-inert regardless of which characters it contains. - ArgvToken (text, fully_single_quoted, raw_span) threaded through the tokenizer (driving shlex token-by-token via instream.tell() to read each token's own source span -- no independent re-implementation of shlex's own word-boundary/escaping rules, eliminating the risk of the two disagreeing), _partition_output_redirect_indices (a behavior-preserving refactor exposing the shared index-selection core _partition_output_ redirects already used, now also feeding _select_executable_argv_tokens), _verb_start_index (same refactor for command_verb_and_args), and all 8 _is_dynamic_shell_value call sites. - _flag_value/_consume_gh_flag retyped to return ArgvToken, and (beyond the plan's literal "inherit the whole token's flag" instruction for their own =/bundled forms) upgraded via the new _argv_token_after_prefix to re-derive a value's own quote provenance from its raw span when a known, never-quoted prefix (a flag name, or gh's `key=` field syntax) precedes it -- proven safe even under adversarial prefix-quoting: a True result requires the exact bytes `'<value>'` to appear literally in the raw command, which is only possible if that span really was one unbroken single-quote run, so misalignment can only ever undershoot (a safe false negative), never accept a value that wasn't truly quoted. This was load- bearing, not optional polish: without it, the plan's own primary AC2/AC3 test case (`-f query='...'`, the most common real invocation shape) does not resolve, because the `query=` prefix outside the quotes makes the whole-token flag false even though the value itself is fully quoted. - _analyze_gh_segment/_analyze_github_segment keep their existing str-based semantic dispatch (`args[:2] == ["pr", "review"]` etc.) untouched, adding a parallel argv_args/argv_tokens threaded only to the two leaf calls that need it (_analyze_gh_api, _issue_edit_request_count) -- avoids retyping the whole classification dispatch chain for a change scoped to gh api/curl/issue-edit's dynamic-value provenance. - Argv-payload segments (parsed `subprocess.run([...])` literals) and the hardcoded "/graphql" route both wrap as provably-inert ArgvToken by construction (never shell-parsed / not argv-derived at all), not a fabricated default -- verified via a wiring-check grep that every ArgvToken(...) construction site is either the tokenizer itself, the dedicated re-derivation helper, or one of these two documented exemptions, per the plan's warning about a fabricated-default workaround silently reintroducing the defect. - git_ops_guard.py's independently-drifted _DYNAMIC_TOKEN_RE (missing `*` relative to the shared regex) retired in favor of importing _DYNAMIC_SHELL_TOKEN_RE directly. - docs/safety/hooks.md: document the two-route provenance rule (file- content-provable OR full-single-quote-provable) replacing the prior undocumented state. - tests/arch/test_subpackage_isolation.py: bump _command_classification.py's REQ-CNST-010-E10 line-limit exemption 2450 -> 2700 for this addition. Part C (extending this spec-table/consume-engine architecture to curl/git/ pip/write-guard bypass-direction bugs) follows in a subsequent commit per the combined rectify plan's green-gate sequencing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… bugs
Rectify Part C: a parallel investigation searching for recurrences of Issue
tables consulted by an argv-walking loop that doesn't consume an
unrecognized value flag's following token) found at least nine such tables
across the guard suite. Several fail in the opposite, unsafe direction from
subcommand/remote/no-pip-install-detected-at-all), not just a false deny.
Generalizes Part A's gh-api-specific engine into a CLI-agnostic
_consume_argv_flag (ArgvToken-based, for consumers needing quote-provenance)
plus a _consume_str_flag convenience wrapper (for consumers that only need
flag recognition/arity), and fixes each site according to what its own
consumer contract actually requires -- not a uniform drop-in:
- extract_git_subcommand_and_flags: now returns ("<unresolved>", []) on an
unrecognized global git flag, distinguishable from None. All three
callers updated for their own correct outcome:
is_allowed_protected_path_metadata_command (already correct via its
subcommand-allowlist check), git_ops_guard.py's _classify_git_segment
(-> ambiguous-deny), and _contains_blocked_git_op (a new third call site
created by deleting the duplicate _extract_git_subcommand_and_remaining
-> immediate unconditional deny, bypassing _BLOCKED_GIT_OPS's tuple-match
loop, which "<unresolved>" could otherwise silently fall through).
- _gh_args_have_bare_help_flag: NOT a blanket default-arity flip (an
earlier attempt at exactly that broke a pre-existing test involving a
boolean flag followed by a known value-taking flag followed by a
gh-CLI help token as that flag's literal value -- a blind advance-2
default for the unrecognized-but-boolean first flag jumped straight
past the real, known-value second flag without evaluating it, so the
help token was reached and wrongly trusted as bare, corrupting the scan
and wrongly exempting a genuine review-publishing mutation). Scoped
instead to exactly the ambiguous case: a help-flag token immediately
preceded by an unrecognized flag is not trusted, everything else
(including the known value-taking flag's own correct 2-token skip) is
untouched.
- _find_pip_install (unsafe_install_guard.py): unrecognized global pip flag
threaded as a new "unresolved-pip-flags" kind through
_classify_install_invocation -> _iter_install_segments ->
_is_unsafe_editable_install (unconditional deny), matching the
pre-existing "unresolved-subprocess" kind's treatment -- not left as a
bare None a caller could conflate with "not pip install".
- write_guard.py / core/bash_write_targets.py: NOT a fail-closed-sentinel
fix (both guards' only consumers already treat an empty/None target list
as allow, so a bolted-on sentinel would silently produce an allow, the
opposite of intended). Parse-correctness fix instead: extend each
module's independently-duplicated _GIT_FLAG_WITH_VALUE so the git-flag-
skip loop reaches the real subcommand instead of stopping at an
unrecognized flag's value.
- _analyze_curl_segment / git_ops_guard.py's _classify_fetch: mechanical
rewire onto the generalized engine with a fail-closed default -- new
_CURL_FLAG_SPEC/_GIT_FETCH_FLAG_SPEC tables built from live --help reads.
- unsafe_install_guard.py registered in FAIL_CLOSED_GUARD_BASENAMES;
docs/safety/hooks.md and hooks/guards/AGENTS.md's Fail Modes tables and
"Design principle" sentence updated for both new fail-closed guards.
tests/arch/test_subpackage_isolation.py: bump _command_classification.py's
REQ-CNST-010-E10 line-limit exemption 2700 -> 2950 for this addition.
Part D (test-suite-wide flag-spec coverage enforcement and live-CLI-help
drift detection) follows in a subsequent commit per the combined rectify
plan's green-gate sequencing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…Part D) Rectify Part D (final part): fixes the meta-gap that let Parts A-C's bugs ship untested in the first place -- no convention requiring a CLI-flag spec table's every entry to actually be exercised by a test, and no mechanism to catch a future CLI version adding a new flag before it silently reintroduces this bug class. - Four generative parametrized tests, one per spec table (_GH_API_FLAG_SPEC/_CURL_FLAG_SPEC/_GIT_GLOBAL_FLAG_SPEC/ _PIP_GLOBAL_FLAG_SPEC), parametrized directly from each table's own keys rather than a hand-maintained flag list -- a flag added to a table later is automatically covered with no separate test-list edit, eliminating the drift risk a copied list would carry. - tests/arch/test_guard_flag_spec_coverage.py: standing architectural test (mirrors test_fail_closed_guard_contract.py's AST-scan-and-batch-report shape) asserting every spec table has a live parametrize reference in its corresponding test file, not just a test that happens to exist today. - tests/hooks/test_gh_api_flag_spec_contract.py generalized from Part A's gh-specific live-help-diff test into four CLI contract tests sharing one diff/assert core, each CLI's own help-line-parsing heuristic passed in separately since gh/curl/git/pip's --help formats are structurally different (a per-line FLAGS listing, a bracketed usage synopsis with nested-bracket optional-value syntax, a sectioned options list). gh/git/ pip achieve full live-help containment; curl's contract test is scoped explicitly to the flags this rectify's investigation named plus curl's own already-covered flags (curl's ~250-flag surface is deliberately not exhaustively mirrored in _CURL_FLAG_SPEC -- an unrecognized flag now fails closed, so full coverage would trade availability for flags outside real-world GitHub-mutation-guard usage; the ~194-flag gap is surfaced non-fatally in test output rather than silently dropped). - FLAG_FORM_MATRIX: consolidates five identical ids=["space", "equals", "attached-short"] tuples (Parts A/C's flag-form tests) into one shared constant. - tests/AGENTS.md: documents the three-part convention (generative parametrized test + architectural coverage-table entry + live-CLI-help contract test) as required for any future flag-spec table or CLI-argv-consuming guard test. This is the final part of the combined rectify plan (rectify_github_mutation_guard_argv_parsing_immunity). Parts A-C supply the spec tables and quote-provenance tokenizer this part audits and enforces coverage for. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Addresses the /audit-impl NO GO findings from remediation_rectify_gh_mutation_guard_argv_2026-08-17_113537.md. REQ-065 (fixed): FLAG_FORM_MATRIX was inline in test_command_classification.py instead of the plan's reusable, shared location, and GRAPHQL_DELIVERY_MATRIX/ GRAPHQL_CONTENT_MATRIX were entirely absent. - New tests/hooks/_flag_form_matrix.py, mirroring the existing tests/infra/_pretty_output_helpers.py private-helper-module convention: FLAG_FORM_MATRIX (relocated, unchanged 3-form set), GRAPHQL_DELIVERY_MATRIX, GRAPHQL_CONTENT_MATRIX, and shared GraphQL command builders. - test_command_classification.py: imports the shared matrix instead of defining its own copy; adds a genuine 16-cell delivery x content parametrized test whose expected outcomes were hand-derived from _DYNAMIC_SHELL_TOKEN_RE / _is_dynamic_shell_value and confirmed against the live implementation via direct (non-pytest) execution before being written as assertions. - test_github_mutation_guard.py: adds a 4-cell (delivery x event_factory) sampling test proving the guard's own decision wiring surfaces the classification layer's result, without duplicating the full matrix at the integration layer. - tests/AGENTS.md: updated the FLAG_FORM_MATRIX location pointer and documented the new GraphQL matrices. Also corrects one audit claim: tests/arch/test_guard_flag_spec_coverage.py does not actually reference FLAG_FORM_MATRIX anywhere (grep-confirmed); left that file untouched rather than adding a purposeless import to a file whose actual scope (CLI flag-spec-table coverage) is unrelated to flag-value-form matrices. REQ-044 (disagreement filed, not implemented as specified): the plan asked for _gh_args_have_bare_help_flag's default arity to change from "assume boolean, advance 1" to "assume value-taking, advance 2" for any unrecognized flag. Traced through concretely, this reintroduces a real mutation-guard bypass on a two-hop input the plan's own safety argument does not cover (an unrecognized boolean flag immediately followed by a real _GH_KNOWN_VALUE_FLAGS entry whose value is help-flag-shaped) -- exactly the shape the project's pre-existing regression test for detached help-flag values already pins. Filed a full writeup at .autoskillit/temp/audit-impl/accepted_deviations/req_044_narrower_help_flag_fix.md and ran it through the project's own audit-impl-deviation-evaluator agent, which independently re-executed both the plan's literal fix and the implemented fix against the live code and returned ACCEPT. Verification: pre-commit (ruff format, ruff check, mypy, uv lock --check, gitleaks) passes clean. All 16+4 new matrix cells independently confirmed via direct production-function calls outside pytest. `test_check` itself could not complete a clean run at commit time -- 5/5 attempts failed with an identical OwnedProcessCleanupError traced to tests/execution/test_process_channel_b.py's test_channel_b_timeout_remains_nonterminal, a file this branch does not touch (last modified by an unrelated PR) whose own docstring documents WSL2/xdist-load timing sensitivity; the host was at load average ~51-55 on 16 cores with ~22 concurrent autoskillit cook sessions during all 5 attempts. A clean test_check run is still owed before merge. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Token-by-token span capture via marked[start:instream.tell()].rstrip() does not strip the operator characters ;, |, & that shlex shlex(punctuation_chars=";&|") leaves sitting inside the previous token's source range. A fully-single-quoted token immediately followed by an unseparated `;` would leak the operator into its raw_span and fail the fully_single_quoted == "'<token>'" check, causing legitimate GraphQL mutations to be wrongly denied as dynamic_target -- directly undermining the AC2/AC3 fix this PR is built around. Strip the trailing operator chars (introduced as _SHELL_OPERATOR_CHARS alongside _SHELL_OPERATORS) so the captured span matches the comment's claim that it covers "exactly the characters this lexer itself consumed to produce the token". Targeted by review-pr finding: src/autoskillit/hooks/_command_classification.py:425 [bugs, critical].
…engine _analyze_gh_api duplicated the space- and =-form skip logic for -H/--header/--hostname/--cache even though every one of them is already enumerated in _GH_API_FLAG_SPEC. The hand-roll duplicated the spec-table engine for the same four flags, leaving test_every_gh_api_spec_flag_is_recognized with a coverage illusion for those entries. Drop the hand-roll so the spec table is the sole authority for gh api flag arity; the failing-missing-value path is preserved by _consume_argv_flag's VALUE handling of those same flags. Targeted by review-pr finding: src/autoskillit/hooks/_command_classification.py:2052 [arch, warning].
The rectify renamed _consume_gh_flag to _consume_argv_flag, but four references escaped the rename: a comment in _command_classification.py introducing _GH_API_FLAG_SPEC, two adjacent paragraphs in test_subpackage_isolation.py's _command_classification line-limit exemption history, and a docstring in test_command_classification.test_method_provenance_proof_single_quoted_resolves. Update all four to refer to the real function so future readers can trace the comment claim to the code it describes. Targeted by review-pr findings: src/autoskillit/hooks/_command_classification.py:1685 [slop, warning]; tests/arch/test_subpackage_isolation.py:1157 and :1164 [slop, warning]; tests/hooks/test_command_classification.py:2061 [slop, warning].
The rectify introduced _GIT_FETCH_FLAG_SPEC (git fetch's own flag spec) without registering it under the CLI Flag-Spec Coverage Convention established earlier in this same PR (no entry in _SPEC_TABLE_TEST_FILES, no generative parametrize test). Add both so the standing-invariant scanner proves parametrize genuinely iterates the table, and so a future flag added to the spec extends coverage automatically. test_every_gh_api_spec_flag_is_recognized was also vacuous for -h/--help: _gh_args_have_bare_help_flag short-circuits analyze_github_mutations before _analyze_gh_api is ever reached for those two flags, so the assertion passed regardless of whether -h/ --help were actually wired into the spec engine. Restructure the command for those flags so they are preceded by an unrecognized `-`-prefixed token, causing the help short-circuit to skip them as that token's value and forcing the spec engine path the test is meant to exercise. Targeted by review-pr findings: src/autoskillit/hooks/guards/git_ops_guard.py:156 [arch, warning]; tests/hooks/test_command_classification.py:2203 [tests, warning].
The `else []` arm of the cd_argv_args ternary can never fire: the surrounding `if _normalize_executable(verb) == "cd"` is only entered when verb is the literal "cd", and command_verb_and_args (which returned verb) only returns a non-empty verb when its own _verb_start_index call returned non-None on the same list. The defensive `is not None` re-check duplicates that invariant. Replace the ternary with an assertion followed by unconditional slicing; the unreachable fallback was noise to every reader and a hand-rolled alternative to a single-assertion invariant. Targeted by review-pr finding: src/autoskillit/hooks/_command_classification.py:2721 [overengineering_reachability, warning].
Every other spec-table enum, helper, and dataclass in this PR carries a docstring describing what its members mean; _FlagArity (the enum keying all 5 CLI flag-spec tables) is the lone exception. Add a 6-line docstring documenting the BOOLEAN/VALUE distinction and noting that joined-value forms like -XPOST/--flag=value are handled by the spec consume engine, so readers do not have to trace _consume_argv_flag to find out what each member is for. Targeted by review-pr finding: src/autoskillit/hooks/_command_classification.py:1674 [cohesion, info].
…teral Four call sites in unsafe_install_guard (and one in its test file) hardcoded the literal "unresolved-pip-flags" instead of using the _PIP_INSTALL_UNRESOLVED module constant. The constant is the source of truth (per its own header comment: distinguishable from a bare None for the "ambiguous" case so callers cannot collapse the two); hard-coding it in the four return statements and one consumer comparison is duplication that silently desyncs if the sentinel ever needs to change. Route all five sites through the constant so the literal lives in exactly one place. Targeted by review-pr finding: src/autoskillit/hooks/guards/unsafe_install_guard.py:164 [cohesion, info].
write_guard.py's _GIT_FLAG_WITH_VALUE frozenset was a hand-maintained mirror of _GIT_GLOBAL_FLAG_SPEC's value-arity members, kept "in sync manually" per its comment. This PR extends _GIT_GLOBAL_FLAG_SPEC with two new members; hand-copying them again would let the mirror silently drift if a future table extension forgets to edit the frozenset. write_guard.py already imports ten other names from _command_classification.py via the same sys.path mechanism git_ops_guard.py uses, so the "kept in sync manually since hook scripts import via sys.path" justification was inaccurate. Derive _GIT_FLAG_WITH_VALUE at import time from the spec table directly, making the frozenset a one-line projection of the source of truth. Targeted by review-pr finding: src/autoskillit/hooks/guards/write_guard.py:86 [overengineering_abstraction_surface, warning].
…lved> The destructive-op preflight in _contains_blocked_git_op correctly wires the `<unresolved>` fail-closed sentinel at git_ops_guard.py:206, but until now had no dedicated regression test distinct from _classify_git_segment's checked-out-ref `<unresolved>` test (test_unresolved_global_flag_before_subcommand_fails_closed_for_all_owners). A future refactor that drops the short-circuit and falls through to the _BLOCKED_GIT_OPS loop (where `<unresolved>` would silently match nothing and the destructive op would slip through) would pass all existing tests. test_denies_push_force_with_unresolved_global_flag runs `git --attr-source foo push --force`: `--attr-source` is a genuine git option outside _GIT_GLOBAL_FLAG_SPEC, so extract_git_subcommand_and_flags returns `<unresolved>`, exercising the short-circuit at line 206. The guard's destructive-op denial reason (not the checked-out-ref JSON used by TestCheckedOutRef*) verifies the right preflight handled it. Targeted by review-pr finding: src/autoskillit/hooks/guards/git_ops_guard.py:206 [defense, warning].
The post-validator for finding #1 (critical shlex span bug) flagged that no regression test pins the specific case the strip fix enables: a fully single-quoted GraphQL document immediately followed by an unseparated shell operator (`;`, `|`, or `&`). Without it, the strip's correctness is verified only by the AC2/AC3 single-quoted test (whitespace-separated) and a future regression that swaps the strip-set or removes it entirely would slip through the test suite. parametrize over `;`, `|`, `&` so each punctuation_chars value the shlex lexer bundles into the previous token's source range has a test case asserting the legitimate mutation still resolves to SINGLE_RESOLVED. Mirrors the proven failure mode the strip addresses.
The post-validator for finding #2 (gh api flag engine dedup, commit 6a7d83b) caught a real regression the original fix introduced: by deleting the hand-rolled `-H/--header/--hostname/--cache` skip logic the call site lost the explicit missing_required_value return for those four flags. The spec engine's `_consume_argv_flag` returns `(None, i+1, True)` for VALUE-arity with no next token, which is indistinguishable from BOOLEAN — so `gh api -H` (no value) silently passed as recognized-with-no-value rather than surfacing as missing_required_value. The same architectural gap exists in `_analyze_curl_segment` for any VALUE-arity curl flag not in the special-cased data_flags/value_flags tuples (`--user-agent`, `--proxy`, `--cacert`, `--connect-timeout`, ...). Re-check the spec table at the call site: when `_consume_argv_flag` returns recognized=True with value=None, look up the token in `_GH_API_FLAG_SPEC` / `_CURL_FLAG_SPEC` and distinguish VALUE-arity (no next token) from BOOLEAN via a second check, then return missing_required_value for the VALUE case. Symmetric restoration in both helpers; tests pin the regression for `-H`/`--header`/ `--hostname`/`--cache` on the gh side and `--user-agent`/`--proxy`/ `--cacert`/`--connect-timeout` on the curl side.
Trecek
force-pushed
the
impl-rectify-gh-mutation-guard-argv-20260817-090109
branch
from
August 19, 2026 15:52
8e176c5 to
16c32c3
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This plan was developed and reviewed as four parts; they are combined here into one document for convenience, but must still be implemented sequentially in this order — each part depends on state the previous part establishes:
gh api(closes Issue github_mutation_guard blocks the batched GraphQL and --jq usage AGENTS.md mandates #4655 Defect 1).ArgvToken), closing Issue github_mutation_guard blocks the batched GraphQL and --jq usage AGENTS.md mandates #4655 Defect 2. Depends on Part A (retypes the return value of Part A's_consume_gh_flag).core/bash_write_targets.py. Depends on Part A (reuses its flag-arity-spec engine).Each part below retains its own Summary, How Tests Missed This, Related Issues Found, Architectural Analysis, Immunity Plan (failing tests → implementation → wiring check), and Verification — every part must independently pass
task test-all(this project's configuredtest_check.commandin.autoskillit/config.yaml) before the next begins, per this project's multi-part plan green-gate convention.Three rounds of adversarial review (a control-flow Foundation Audit, a variable-provenance Interface Mapping pass, and a Registry/completeness Trace) were run against the full draft of this plan; their findings are already incorporated into the text below, not listed separately.
A subsequent
/audit-implpass found two gaps against this plan: REQ-065 (a test-suite parametrize-matrix convention specified in Part D was only partially applied) was fixed directly; REQ-044 (Part C's_gh_args_have_bare_help_flagfix) was implemented deliberately narrower than the plan specified, after tracing that the plan's literal default-arity-flip reintroduces a real mutation-guard bypass the project's own pre-existing regression test already pins — filed as a formal deviation and independently confirmed ACCEPT by the project's own deviation-evaluator agent.Closes #4655
Implementation Plan
Plan file:
/home/talon/projects/generic_automation_mcp/.autoskillit/temp/rectify/rectify_github_mutation_guard_argv_parsing_immunity_2026-08-17_061427_combined.md🤖 Generated with Claude Code via AutoSkillit