Repository navigation
Fix the test, tooling, and skill defects that the #98 benchmark found in #96 #102
Description
Goal
Fix five defects that benchmark 1 of #98 found in the code of #96 and that nobody fixed (D3 to D7): the test-contract registry check accepts a missing toolchain; no test covers the reopen refusal after a change during the settle wait; the fake gh returns pull request fields that the query does not select; a reopen test checks request text instead of the stored result; and the report-section verify command passes when every skill lacks the section. Fix each root cause, so the same kind of defect cannot pass the gate again.
Yolo: the user delegated every decision except the merge to the agent.
Mode: plan-and-implement
Research
Reproductions
Each defect was reproduced on main at 163acb53 (plan-lifecycle 0.17.1). The mutations ran in a temporary detached worktree, which was removed afterwards.
- D3:
scripts/config/test-contracts.jsonlists"toolchains": ["bun-1", "node-24"]forrepo-plan-cli, because the gate runsscripts/tests/plan-cli.mjswith Node and the fake gh (scripts/tests/fixtures/gh-stub/gh) starts with#!/usr/bin/env bun. Withbun-1removed from that row,bun scripts/tests/test-contracts.mjsstill printedPASS test_contracts contracts=18 discovered=18 selected=18. The check (scripts/tests/test-contracts.mjs, the loop overregistry.contracts) asserts only that each runner that it discovers inscripts/ci.mjsis listed. It never discovers the fake gh's runtime, and it never rejects a listed toolchain that nothing needs. - D4:
plan.mjsrefuses withplan #<n> changed during the settle wait; run reopen againwhen the plan is still closed after the settle wait and either its status is no longerfinishedor the latest close differs from the one before the wait (reopen, aftersleep(REOPEN_SETTLE_MS)).grep -rn 'changed during the settle wait' scripts/testsprints nothing: no test reaches either branch. - D5: the fake gh builds pull request nodes with
merged,mergedAt,state,url, andbaseRefNamewhether or not the query selects them (pullRequestNodeand theRepository.pullRequesthandler). WithmergedAtandbaseRefNameremoved fromPULL_REQUEST_FIELDSinplan.mjs,node scripts/tests/plan-cli.mjsstill passed 66 of 66, although GitHub would then return neither field andpullRequestNodeswould reject every closing pull request as malformed. - D6:
scripts/tests/plan-cli/reopen.test.mjs("reopen after a commit close") asserts!writes[0].some((argument) => argument.includes('addAssigneesToAssignable')). That checks the text of the request, not a result that a user can see; the same test already asserts the stored issue and the count of writes. - D7: the Report section rule in
plugins/docks/skills/AGENTS.mdgives the verify commandfor f in …; do sed -n '/^## Report a problem with this skill$/,$p' "$f" | sha256sum; done | sort | uniq -c. On three skills that all lack the section it printed3 e3b0c442…(the digest of empty input): one line whose count equals the number of skills, which the rule calls a pass. No gate check compares the copies.
Decisions
- D3: the registry check discovers fixture runtimes and requires an exact toolchain list.
test-contracts.mjsalso discovers the interpreter of each executable fixture that a suite runs: an executable file with a#!line underscripts/tests/fixtures/, when the suite's files name that fixture's folder.bunmaps tobun-1andnodetonode-24, as the gate runners do. Each contract'stoolchainsmust then equal the discovered set: a missing toolchain fails, and a listed toolchain that nothing needs fails, each with the contract id, the toolchain, and the evidence path. Rejected: a declared field for fixture runtimes (the declaration could drift the same way); a check only forrepo-plan-cli(the next fixture would have the same gap). - D5: the fake gh returns only the selected pull request fields. Each pull request node that the fake gh returns, for a closer, an associated pull request, or
Repository.pullRequest, holds only the fields that the query selects at that level, read with the selection helpers that the fake gh already has (graphqlSelections,ownFields). A test that relied on an unselected field is changed to select it or to stop relying on it. Proof: the D5 mutation above must then fail the plan CLI suite. Rejected: a validation step inplan.mjs(the helper already validates; the gap is in the fake). - D4: two tests cover the settle-wait refusal. The refusal has two triggers, and each test reaches one of them without a change to
plan.mjs: one test replaces the latest close during the settle wait with a later close by the same pull request at anothercreatedAt, so the close differs; one test changes the issue'sstateReasonfromCOMPLETEDtoNOT_PLANNEDduring the wait while the issue stays closed with the same latest close, sostatusFromIssuereadsretiredinstead offinished. A label change cannot reach the refusal, because a closed issue's status comes fromstateReason, not from labels (statusFromIssue). Each test asserts the exact refusal text, exit 1, one settle wait, no write, and the stored issue as the change left it. Proof: with the refusal replaced by a pass-through in a temporary copy, both tests fail. - D6: the reopen test asserts results, not request text. The request-text assertion goes. When the fake gh records a visible result of a claim (for example an assignment event), the test asserts that no such result appears; otherwise the stored issue and the write count, which the test already asserts, are the proof.
- D7: the skills guard checks the Report section.
scripts/skills/guard.mjs, which the gate runs for each plugin's skills, fails when aSKILL.mddoes not end with the canonical Report section, byte for byte. The canonical text lives once, inscripts/config/report-section.md, so the copies in both plugins match one source. The error names each file and says whether the section is missing or differs. The Report section rule inplugins/docks/skills/AGENTS.mdthen names the guard as its check and says that a change to the section changes the canonical file and every copy in one commit. Rejected: a corrected shell command (a reader can still misread its output; the gate never runs it).
Request corrections
Asked: "fix defects first in its own plan" for D2 to D7. Did: this plan fixes D3 to D7; D2, a defect of the report-skill-issue skill, moves to the report-skill-issue plan that follows, by the user's choice in this session. Reason: the user's answer to a publication question.
Engineering standards
- Steps 1, 2, and 5:
fix-workflow(reproduce before, confirm after, with the mutations above) andcode-clarity. - Steps 3 and 4:
test-coverage(tests for behavior that exists; no test of request text) andcode-clarity. - Commits:
commit-discipline. Self-review during code review round 1:code-reviewwithcode-clarity.
Steps
| # | Id | Task | Files | Depends | Effect | Status | Done when |
|---|---|---|---|---|---|---|---|
| 1 | registry_toolchains | Implement decision 1 (D3) | scripts/tests/test-contracts.mjs, scripts/config/test-contracts.json | - | local | done | the current registry passes; with bun-1 removed from repo-plan-cli the check fails and names repo-plan-cli, bun-1, and the fake gh; with an unneeded toolchain added to one row it fails and names that row |
| 2 | stub_selection | Implement decision 2 (D5) | scripts/tests/fixtures/gh-stub/gh, scripts/tests/plan-cli/ | - | local | done | the plan CLI suite passes; with mergedAt and baseRefName removed from PULL_REQUEST_FIELDS in a temporary copy, the suite fails |
| 3 | settle_refusal_tests | Implement decision 3 (D4) | scripts/tests/plan-cli/reopen.test.mjs | 2 | local | done | both new tests pass (a changed close, and stateReason changed to NOT_PLANNED), and both fail when the refusal is replaced by a pass-through in a temporary copy |
| 4 | reopen_outcome | Implement decision 4 (D6) | scripts/tests/plan-cli/reopen.test.mjs | 3 | local | done | no reopen test asserts request text, and the test still passes |
| 5 | report_section_guard | Implement decision 5 (D7) | scripts/skills/guard.mjs, scripts/config/report-section.md, plugins/docks/skills/AGENTS.md | - | local | done | the guard passes on both plugins; it fails and names the file when one skill lacks the section or one byte of a copy differs |
Acceptance
| ID | Command | Expected |
|---|---|---|
| a1 | bun scripts/ci.mjs |
exits 0 for the repo-wide checks and both plugins (root AGENTS.md: the full gate for repo-wide tooling and changes in more than one area) |
| a2 | node scripts/tests/plan-cli.mjs and bun run test:coverage |
the suite passes, and plan.mjs line coverage stays at or above 99% |
| a3 | step 1 mutations in a temporary worktree at the reviewed candidate | the check fails for the removed and for the added toolchain, and passes on the unchanged registry |
| a4 | step 2 mutation in a temporary worktree | the plan CLI suite fails with the two fields removed from the query |
| a5 | step 3 mutation in a temporary worktree | both settle-wait tests fail with the refusal removed |
| a6 | step 5 mutations in a temporary worktree | bun scripts/skills/guard.mjs plugins/docks/skills fails and names the file for a missing section and for a changed byte; both plugins pass unchanged |
| a7 | pull request checks | every check passes |
Do not touch
- The behavior of
plan.mjs: this plan changes tests, the fake gh, and author tooling only. - The report-skill-issue skill: the plan that follows changes it.
- Review trust, the verdict words, and the landing rules.
Open questions
Verification Results
Commits on the linked branch: e24c49b9 (step 1), 2fe28f52 (step 2), f0a82ab7 (steps 3 and 4), 33c45f33 (step 5). Each commit passed its focused check in a temporary detached worktree: bun scripts/tests/test-contracts.mjs passed at e24c49b9; node scripts/tests/plan-cli.mjs passed 66 of 66 at 2fe28f52 and 68 of 68 at f0a82ab7; at 33c45f33 the skills guard passed for both plugins, the registry check passed, and bun run check:js passed.
- a1:
bun scripts/ci.mjspassed for the repo-wide checks and both plugins on the candidate before the commits. - a2:
node scripts/tests/plan-cli.mjs: 68 tests, 68 pass;bun run test:coverage:plan.mjsline coverage 99.68%. - a3 (step 1): with
bun-1removed fromrepo-plan-cliandnode-24added torepo-skill-trigger-collision,bun scripts/tests/test-contracts.mjsfailed withrepo-plan-cli.toolchains must list bun-1: scripts/tests/plan-cli/harness.mjs names scripts/tests/fixtures/gh-stub, where scripts/tests/fixtures/gh-stub/gh runs with bunandrepo-skill-trigger-collision.toolchains lists node-24, which no discovered runner needs. Before the fix, the same removal passed. - a4 (step 2): with
mergedAtandbaseRefNameremoved fromPULL_REQUEST_FIELDSin a worktree at33c45f33, the plan CLI suite failed 26 of 68 tests. Before the fix, the same mutation passed 66 of 66. - a5 (step 3): with the refusal replaced by a pass-through in a worktree at
33c45f33,node --test scripts/tests/plan-cli/reopen.test.mjsfailed exactly the two new tests ("reopen refuses a plan whose latest close changes during the settle wait" and "reopen refuses a plan that someone closes as not planned during the settle wait"); 15 passed. - Step 4: the fake gh records no result of a claim on an owned plan (the assignees stay the same), so the stored-issue and single-write assertions are the proof; no reopen test inspects request arguments.
- a6 (step 5): on a copy of
plugins/docks/skills, the guard failed with<copy>/skills/engineering/code-clarity/SKILL.md: missing the Report section; ...for a removed section, and with.../zoom-out/SKILL.md: the Report section differs from scripts/config/report-section.md; ...for one added byte. Both plugins pass unchanged.
Review repairs: 8d104d93 (author-tooling tests for the registry and Report section rejections; the registry check takes --registry <path>), 24396e04 and b6ebe167 (the fake gh reads aliases, merges repeated selections, projects each node from its own place in the query, and gives nested objects their type; scripts/tests/plan-cli/gh-stub.test.mjs tests this directly), ad9068d4 and 848a2863 (the shebang parser skips env options, operands, and assignments; the extra-toolchain diagnostic names the discovered runners).
- Red baselines of the review tests: the six first
gh-stub.test.mjstests failed 6 of 6 against the fake gh at163acb53and 3 of 6 at33c45f33; the five tests ofb6ebe167failed 5 of 5 against the fake gh at24396e04. - At
848a2863:bun scripts/ci.mjspassed for the repo-wide checks and both plugins;node scripts/tests/plan-cli.mjspassed 79 of 79;bun scripts/tests/author-tooling.mjsandbun scripts/tests/test-contracts.mjspassed;plan.mjsline coverage 99.68%.
Reviews (time from the first prompt to the final answer; tokens are input, cached input, cache writes, and output):
| Round | Mode | Reviewers (seconds; tokens M) | Verdict | Findings |
|---|---|---|---|---|
| Plan 1 | full | 72; 0.43 | repair | a label change cannot reach the settle-wait refusal |
| Plan 2 | delta | 38; 0.21 | pass | none |
Code 1 at 33c45f33 |
panel | correctness 55; 0.29 · spec-and-tests 146; 0.50 · text-and-safety 140; 0.67 | pass | 5 MEDIUM: untested rejection paths and projection, alias-blind projection |
| Code 1 | self-review, during round 1 | 225; 1.61 | - | none |
Code 2 at 24396e04 |
panel (repairs over half the diff) | correctness 63; 0.33 · spec-and-tests 182; 0.59 · text-and-safety 116; 0.77 | pass | 2 MEDIUM, 3 LOW: first-match projection, nested type, env options, diagnostic evidence, untested branches |
Code 3 at ad9068d4 |
panel (repairs over half the diff) | correctness 50; 0.27 · spec-and-tests 139; 0.89 · text-and-safety 79; 0.43 | pass | 2 LOW |
Code 4 at 848a2863 |
delta | 34; 0.14 | pass | none |
Activity
DocksDocks commentedon Oct 23, 2026
Plan review round 1
Plan-review: repair
settle_refusal_tests; Acceptance a5 - Changing a status label cannot trigger the specified refusal without violating “Do not touch: the behavior ofplan.mjs.” Inplugins/plan-lifecycle/skills/productivity/plan-manager/scripts/plan.mjs,statusFromIssue(lines 443–453) derives a closed issue’s status fromstateReason, ignoring labels;reopenPlan(lines 1227–1242) therefore still seesfinishedand the same close. The proposed label-change test expects exit 1 and no write, but the unchanged helper reopens the issue successfully. - Schedule a change fromstateReason: 'COMPLETED'to'NOT_PLANNED'during the settle wait, preserving the closed state and latest closure. Assert the specified refusal and no helper writes, and use that case for a5.DocksDocks commentedon Oct 23, 2026
Plan review round 2, delta
Plan-review: pass
None.
Previous findings:
settle_refusal_tests; Acceptance a5: resolved - Decision 3 now changesstateReasonfromCOMPLETEDtoNOT_PLANNED, preserving the closed state and latest closure. This reaches the existing refusal without changingplan.mjs; step 3 and a5 require the corresponding mutation proof.DocksDocks commentedon Oct 23, 2026
Code review round 1, lens correctness
Code-review: pass
Standards
None.
Spec
None.
Code review round 1, lens spec-and-tests
Code-review: pass
Standards
[MEDIUM] Maintainability - scripts/tests/test-contracts.mjs:550-576 - No committed regression test exercises missing fixture runtimes or extra toolchains; the gate runs this validator only against the valid registry. [INFERENCE] Removing the new checks could therefore restore D3 without failing automated tests. - Add gate-executed cases for the unchanged registry, removed
bun-1, and an unnecessary toolchain, asserting exit status and diagnostics. - class: untested toolchain rejection[MEDIUM] Maintainability - scripts/skills/guard.mjs:54-78 - Neither report-section rejection branch has automated coverage. The existing skills-guard test in
scripts/tests/author-tooling.mjs:241-252exits at the earlier spawn failure. [INFERENCE] Removing the report check would leave those tests green. - Add valid, missing-section, and changed-byte fixtures, asserting exit status and affected filenames; include the all-sections-missing case that motivated D7. - class: untested report-section rejection[MEDIUM] Maintainability - scripts/tests/fixtures/gh-stub/gh:289-304 - No committed test asserts that unselected PR fields disappear. The new reopen tests exercise settle-wait refusals using unchanged queries, not projection. [INFERENCE] Restoring unconditional fixture fields could therefore reintroduce D5 without failing ordinary gate runs. - Add response-level tests for omitted scalar and nested fields across direct PRs, closers, and commit associations. - class: untested fixture field projection
Spec
{mergedAt: url}, the word search mistakes the alias for a selected field. [INFERENCE] It returns the fixture timestamp asmergedAtplus an unrequestedurl, whereas GitHub returns onlymergedAtcontaining the URL. Current helper queries have no PR-field aliases, so present reach is bounded, but aliased queries retain the fabricated-field defect. - Distinguish field names from response aliases and project the selected value under its response key, with a regression test. - class: alias-blind field projectionCode review round 1, lens text-and-safety
Code-review: pass
Standards
Spec
None.
DocksDocks commentedon Oct 23, 2026
Self-review
Ran while code review round 1 ran, on the full candidate diff at
33c45f33, with the dockscode-reviewandcode-clarityskills.No findings.
DocksDocks commentedon Oct 23, 2026
Code review round 2, lens correctness
Code-review: pass
Standards
None.
Spec
None.
Code review round 2, lens spec-and-tests
Code-review: pass
Standards
[MEDIUM] Bug - scripts/tests/fixtures/gh-stub/gh:345-356, scripts/tests/fixtures/gh-stub/gh:729 - For a PR query selecting
repository{__typename ... on Repository{nameWithOwner}}, inference: projection returns an empty repository because its fixture object lacks__typename. GitHub returns both selected fields. Current helper queries do not use this form, so reach is bounded. - Give the nested repository its type metadata and add a response-level regression test. - class: missing nested projection type metadata[LOW] Bug - scripts/tests/test-contracts.mjs:451-460 - For a referenced executable fixture with
#!/usr/bin/env -S -u NODE_OPTIONS bun, inference: discovery identifiesNODE_OPTIONSas the interpreter and rejects a valid Bun toolchain declaration. The current simple shebang is unaffected. - Consume operand-takingenvoptions correctly and cover this valid shebang form. - class: env option operand mistaken for interpreterSpec
[MEDIUM] Spec - scripts/tests/fixtures/gh-stub/gh:739-741, scripts/tests/fixtures/gh-stub/gh:812, .git/docks-review/plan-102.md:26 - With two aliased CLOSED_EVENT connections selecting different closer fields, inference: both PR closers receive the first connection’s selection, fabricating unselected fields in the second response. Likewise,
nodes{number} nodes{url}for associated PRs loses the second selection. Decision 2 requires “only the fields that the query selects at that level.” Current helper queries use single selections, limiting present reach. - Scope extraction to the current connection and merge repeated field selections; add regression cases for both instances. - class: first-match selection projection[LOW] Spec - scripts/tests/test-contracts.mjs:580-582, .git/docks-review/plan-102.md:25 - For an unnecessary toolchain, the diagnostic includes the contract id and token but no evidence path. Decision 1 explicitly requires “each with the contract id, the toolchain, and the evidence path,” leaving this diagnostic incomplete. - Include the discovered suite or runner evidence path and assert it in the extra-toolchain rejection test. - class: missing diagnostic evidence path
Code review round 2, lens text-and-safety
Code-review: pass
Standards
repository{a:nameWithOwner} repository{b:nameWithOwner}preserves both keys and that barerepositoryexits 1. - class: untested projection branchesSpec
None.
DocksDocks commentedon Oct 23, 2026
Code review round 3, lens correctness
Code-review: pass
Standards
None.
Spec
None.
Code review round 3, lens spec-and-tests
Code-review: pass
Standards
[LOW] Bug - scripts/tests/test-contracts.mjs:466 - If a referenced executable fixture uses
#!/usr/bin/env -S -- FORCE_COLOR=0 bun, inference: discovery identifiesFORCE_COLOR=0as its interpreter and rejects a valid Bun toolchain declaration.envstill processes assignments after--. The current fixture is unaffected. - End option parsing at--, but continue skipping assignments before selecting the interpreter; add a regression case. - class: env assignment mistaken for interpreter[LOW] Bug - scripts/tests/plan-cli/gh-stub.test.mjs:92-96 - The aliased-timeline regression test reads
issue.updatedAt, although its query never selects that field. It therefore relies on the fake issue handler’s over-returned fields; inference: faithful issue projection would break this otherwise valid test. - SelectupdatedAtexplicitly or obtain the expected timestamp from stored fixture state. - class: test dependency on unselected response fieldSpec
None.
Code review round 3, lens text-and-safety
Code-review: pass
Standards
None.
Spec
None.
DocksDocks commentedon Oct 23, 2026
Code review round 4, delta
Code-review: pass
Previous findings
--before identifying the interpreter; scripts/tests/author-tooling.mjs:332 adds the regression case.updatedAt, removing reliance on an unselected response field.Standards
None.
Spec
None.
test: require exact toolchains and discover fixture runtimes
test: return only the selected pull request fields from the fake gh
test: cover the settle-wait refusal and check reopen results only
fix: check the Report section of every skill in the skills guard
test: cover the rejection paths of the registry and Report section ch…
test: read GraphQL aliases in the fake gh and test its field projection
test: project each fake gh node from its own place in the query
test: parse env options in fixture shebangs and show extra-toolchain …
test: skip env assignments after -- and select every field a test reads
feat(plan-lifecycle): land a late pass whose open findings are unreac…