Fix #71: resync the .agent mirror issue layer; add a parity guard - #74
Fix #71: resync the .agent mirror issue layer; add a parity guard#74laird wants to merge 2 commits into
Conversation
CLAUDE.md declares mirror parity CRITICAL, but nothing enforced it and
the issue layer under .agent/scripts/ drifted badly from
plugins/autocoder/scripts/.
The drift was not cosmetic. .agent/workflows/fix.md calls three verbs
its own issue-fns.sh never defined:
issue_any_claimable fix.md:320
issue_claim fix.md:532, 648
issue_release fix.md:575, 681, 1479
The preflight at fix.md:319-323 branches on the exit status of
issue_any_claimable. An undefined function exits 127, which falls to the
`*)` arm — "Backend error while checking for claimable issues"; exit 1.
So the Antigravity /fix did not run in a degraded mode; it terminated at
its first check on every single invocation.
Two further breaks:
- `--state blocked` was forwarded straight to `gh issue list`, which
rejects anything outside {open,closed,all}. The mirror's stale
issues-file.py rejected it too. Both list-needs-feedback.md and
approve-proposal.md depend on that state.
- The mirror's inline _ifns_gh_list applied no blocking-label filter at
all, so `list --state open` did not mean "claimable" as the 9-verb
contract requires. (It never had #57's `no:label` bug because it had
no filter whatsoever.)
Extent of the drift: issues-gh.sh absent entirely; issues-file.py 438
differing lines and last touched 2026-05-19, lacking claim/release/
any-claimable and the bucket layout (3 bucket references vs 30);
issue-fns.sh 180 differing lines; issue-config.sh 4.
Fixed by syncing all four shared files forward from the maintained
plugins copy — which already implements the full contract the mirror's
own workflows assume. Nothing outside issue-fns.sh referenced the
removed inline internals, so there are no orphaned callers.
Added tests/test_agent_mirror_parity.sh so the drift cannot silently
recur: it asserts the mirror ships every shared file, that each is
byte-identical, that all 9 verbs resolve on both sides, and that
`--state blocked` returns JSON rather than a gh usage error. It runs in
a throwaway git repo with a poisoned gh on PATH, so it cannot touch the
real tracker.
Verified: parity test 13/13 (was 6 passed, 7 failed). Replaying the
fix.md preflight against the mirror now yields "work exists -> proceeds"
with a populated queue and "no claimable work -> clean exit 0" when
empty, instead of the backend-error branch. Full suite 14/14 files
green; shell syntax and python compile clean.
Out of scope, still present: .agent/workflows/fix.md.backup and
fix-loop.md.backup are stale editor backups committed to git.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Flagging before merge; not retargeting it, since Per Merging as-is lands the work on Root cause and the two resolution options are written up in #76 — it's the stale installed autocoder plugin (3.10.0) still shipping the pre-rename |
|
✅ Correction — retracting my previous comment. This PR's base is right. I was wrong. My error: I ran Concretely: once this merges to Sorry for the noise on your PR — no action needed from you. I've retitled #76 to the real (and much smaller) underlying issue: the ship-branch designation is worktree-dependent, and the integration branch's designation goes stale the moment #77 lands. |
The combined tree (#77 + #74 + #78) exposed a defect neither PR's CI could see. #77's parser recognises only the Jest "Tests: N passed" form, and #77 alone never exercises it — the unit suite always skipped, so the parser was dead code. #78 makes the suite actually run, and this repo's own shell tests print "Results: 13 passed, 0 failed" with no "Tests:" prefix. A fully green run was therefore reported "no parseable test summary" and exited 1. That is a false RED — the mirror image of the false GREEN #73 is about, and just as damaging: a gate that cannot be trusted in either direction. The fix (in the merge commit) adds a bare "<N> passed" fallback, mirroring the two-tier approach #77's own E2E block already uses. These assertions pin both directions so neither can regress: - a non-Jest "Results: N passed, M failed" summary is accepted (exit 0, not reported as unparseable) - output with no counts at all is STILL an error — the fallback widens the accepted formats without defeating #77's guard against a runner that never produced a summary Verification on the combined tree: - tests/test_regression_test_config.sh: 16/16 - all tests/test_*.sh: 15/15 - pytest: 124/124 - detect_skip_reason present 3x (definition + Unit Tests + E2E guards) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This PR went CONFLICTING when #83 landed — here is a verified resolutionIt was Exactly one file conflicts:
Neither side is correct alone: this PR's version drops the jira/ado backends The resolution is simpler than it looks
github) _ifns_BACKEND_SCRIPT="issues-gh.sh" ;;
file) _ifns_BACKEND_SCRIPT="issues-file.py" ;;
jira) _ifns_BACKEND_SCRIPT="issues-jira.sh" ;;
ado) _ifns_BACKEND_SCRIPT="issues-ado.sh" ;;So resolve This works because all four backends already speak the same 9-verb CLI. Verified on Verified, not assumedI first hand-wrote the two Not appliedI have not pushed this to your branch — it is yours, and I have already learned this session what happens when I push to a PR I do not own. The change is one For reference, |
Resolves .agent/scripts/issue-fns.sh to the canonical plugins version: the thin 9-verb dispatcher WITH the jira/ado cases. Recorded as a real two-parent merge so master's change is in the ancestry and the later merge to master is clean.
|
Retargeted Still CONFLICTING, and the base change does not fix it — I checked rather than assuming: Once #87 merges into 🤖 Autonomous fix workflow |
|
This PR is the root of a four-deep stack and is currently blocking two green PRs. #87 and #92 both report MERGEABLE, but neither can land while this base is red. Merging #87 into Suggested order: #87 → #92 → #74 → integration. Related: #93 records that neither 🤖 Autonomous fix workflow |
…er (jira/ado) (#87) * Add Jira issue backend parallel with GitHub (#81) Adds a first-class `jira` issue source alongside `file` and `github`, implementing the same uniform 9-verb backend contract (list, get, update, comment, close, create, claim, release, any-claimable) against the Jira REST API v2. - issues-jira.sh: self-contained backend. Maps Jira issues (PROJ-N) to the gh-compatible JSON shape, exposing the numeric key suffix as `number` so it stays compatible with the swarm manifest and the rest of the workflow. Uses API v2 for plain-text descriptions. Non-secret connection settings (baseUrl, project) come from the `jira` object in .autocoder.json; credentials are env-only (JIRA_EMAIL + JIRA_API_TOKEN, or JIRA_AUTH_HEADER for Server/DC PATs) and never committed. - Claimable/open JQL ORs in `labels is EMPTY` so label-less issues are not silently dropped — the Jira analogue of the GitHub `no:label` bug (#57). - Registers `jira` in both dispatchers (plugins + .agent mirror), issue-source-lib.sh validation, and the set-issue-source command. - test_issues_jira.sh: 35 assertions covering JQL, request shaping, output schema, and exit codes, with curl fully stubbed (no network). - Updates CLAUDE.md/GEMINI.md, launcher help strings, and bumps autocoder 4.5.2 -> 4.6.0 and the marketplace 3.26.0 -> 3.27.0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015zJKDkbj54vZeGaTWLJEHv * Add hermetic Jira integration test with a stateful fake server The unit test (test_issues_jira.sh) stubs curl and asserts request/JQL shape; this adds real end-to-end coverage without any network: - tests/fixtures/fake_jira.py: a small stdlib-only STATEFUL fake of the Jira REST API v2 covering exactly the endpoints issues-jira.sh calls (search, issue CRUD, comment, transitions, assignee, myself). It keeps issues in memory and evaluates the JQL the backend emits, so state filters and the lifecycle behave like real Jira. Binds an ephemeral loopback port (printed as "LISTENING <port>") so CI runs never collide. - tests/test_issues_jira_integration.sh: starts the fake, exercises the full lifecycle (create → get → claim/release → comment → update → close) over real HTTP and asserts every state filter — including the label-less "labels is EMPTY" guard a stateless mock cannot exercise — then tears the server down. Skips cleanly if curl/python3 are absent. 17 assertions. - plugins/autocoder/scripts/jira-smoke-test.sh: opt-in check against a REAL Jira instance (JIRA_* env). Kept out of tests/ so CI never needs egress. - CLAUDE.md: document the three test layers. Full shell suite green (15/15); server teardown leaves no stray process. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015zJKDkbj54vZeGaTWLJEHv * Add docs/jira-setup.md — Jira backend setup & testing guide Walks through creating a free Jira Cloud site, project, and API token; wiring the non-secret baseUrl/project into .autocoder.json while keeping credentials in the environment; verifying with jira-smoke-test.sh; and the two hermetic CI tests. No secrets in the doc. Linked from CLAUDE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015zJKDkbj54vZeGaTWLJEHv * Add Azure DevOps issue backend parallel with GitHub/Jira (#82) Adds a first-class `ado` issue source implementing the same uniform 9-verb backend contract against the Azure DevOps Work Item Tracking REST API. - issues-ado.sh: self-contained backend. Work item IDs are integers, so `number` maps to System.Id directly. Labels ↔ work-item Tags (System.Tags), state ↔ System.State (done-state set covers the default Agile/Basic/Scrum/CMMI processes). Uses WIQL for list/any-claimable, workitemsbatch for details, and json-patch for create/update. Non-secret orgUrl/project come from the `ado` object in .autocoder.json; the PAT is env-only (ADO_PAT) and never committed. Overridable work-item type and open/closed state names via env. - WIQL `[System.Tags] NOT CONTAINS 'x'` already matches tag-less items, so untagged work stays claimable without a special empty clause (the ADO analogue of the GitHub no:label / Jira empty-labels traps) — asserted by both tests. - Registers `ado` in both dispatchers (plugins + .agent mirror), issue-source-lib validation, the set-issue-source command, and launcher help strings. - Tests (both hermetic, in CI): test_issues_ado.sh (28 assertions, stubs curl; WIQL + json-patch shape, schema, exit codes) and test_issues_ado_integration.sh (18 assertions; real HTTP against a stateful fake, tests/fixtures/fake_ado.py, full lifecycle + WIQL filtering). - Docs: docs/ado-setup.md; updates CLAUDE.md/GEMINI.md. Bumps autocoder 4.6.0 -> 4.7.0 and the marketplace 3.27.0 -> 3.28.0. Full shell suite green (17/17). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015zJKDkbj54vZeGaTWLJEHv * Document all issue backends (file, GitHub, Jira, Azure DevOps) - docs/issue-backends.md: unified overview of the four built-in backends — the 9-verb contract, uniform JSON shape and exit codes, per-backend model mapping, secrets handling, configuration precedence, and the hermetic test layers. Links to the Jira and ADO setup guides. - README: refresh the "Issue Backends" section — full 9-verb contract table (adds claim/release/any-claimable), point at the new docs, and use a genuinely custom name (linear) for the custom-backend example now that jira/ado are first-class. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015zJKDkbj54vZeGaTWLJEHv * Complete the .agent issue-layer mirror and widen the parity guard (#71) Two gaps found by a dry-run of the full merge chain. 1. issue-source-lib.sh existed only under plugins/autocoder/scripts/. It is issue-source resolution used by the swarm launch/lifecycle scripts — shared issue-layer infrastructure, not platform-specific — so the mirror was incomplete. Copied verbatim. 2. The parity guard's SHARED list covered only the two original backends (file, gh). issues-jira.sh and issues-ado.sh arrived with #83 and were never checked, and neither was issue-source-lib.sh. A backend that ships only under plugins/ is invisible to the guard — which is exactly how the drift in #71 went unnoticed: the mirror looked healthy because nothing checked the files missing from it. SHARED now lists the whole issue layer (7 files). Verification: parity guard 19/19, up from 13/13. Negative-tested both new assertion classes — removing issue-source-lib.sh from the mirror fails 2 assertions, and perturbing issues-jira.sh fails the divergence check — so the added coverage is not vacuous. 18/18 suites pass, build clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
Closes #71
Problem
CLAUDE.md declares mirror parity CRITICAL, but nothing enforced it, and the issue layer under
.agent/scripts/drifted badly fromplugins/autocoder/scripts/.The drift was not cosmetic.
.agent/workflows/fix.mdcalls three verbs its ownissue-fns.shnever defined:issue_any_claimablefix.md:320issue_claimfix.md:532,fix.md:648issue_releasefix.md:575,fix.md:681,fix.md:1479The preflight at
fix.md:319-323branches onissue_any_claimable's exit status. An undefined function exits 127, which falls to the*)arm — "Backend error while checking for claimable issues"; exit 1.So the Antigravity
/fixwas not running in a degraded mode. It terminated at its first check on every single invocation.Two further breaks
--state blockedwas forwarded straight togh issue list, which rejects anything outside{open,closed,all}. The mirror's staleissues-file.pyrejected it too. Bothlist-needs-feedback.mdandapprove-proposal.mddepend on that state._ifns_gh_listapplied no blocking-label filter at all, solist --state opendid not mean "claimable" as the 9-verb contract requires. (It never had issues-gh.sh list uses invalidno:labelsearch syntax — autonomous loop sees zero issues #57'sno:labelbug because it had no filter whatsoever.)Extent
issues-gh.shissues-file.pyclaim/release/any-claimable; 3 bucket refs vs 30issue-fns.shissue-config.shSolution
Sync all four shared files forward from the maintained plugins copy — which already implements the full contract the mirror's own workflows assume. Verified nothing outside
issue-fns.shreferenced the removed inline internals (_ifns_gh_*,_ifns_file), so there are no orphaned callers.Guard against recurrence
New
tests/test_agent_mirror_parity.shasserts:--state blockedreturns JSON rather than aghusage error.It runs in a throwaway git repo with a poisoned
ghonPATH, so it cannot touch the real tracker — same pattern astest_issue_fns.shafter #58.Verification
Replaying the
fix.mdpreflight against the mirror:Previously both landed on the backend-error branch with rc=127.
Full suite 14/14 files green;
bash -nacross plugins,.agent, and tests clean;py_compileclean on bothissues-file.pycopies.Out of scope, noted
.agent/workflows/fix.md.backupandfix-loop.md.backupare stale editor backups committed to git.🤖 Generated with Claude Code