Skip to content

Fix the test, tooling, and skill defects that the #98 benchmark found in #96 #102

Closed

Description

@DocksDocks

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.json lists "toolchains": ["bun-1", "node-24"] for repo-plan-cli, because the gate runs scripts/tests/plan-cli.mjs with Node and the fake gh (scripts/tests/fixtures/gh-stub/gh) starts with #!/usr/bin/env bun. With bun-1 removed from that row, bun scripts/tests/test-contracts.mjs still printed PASS test_contracts contracts=18 discovered=18 selected=18. The check (scripts/tests/test-contracts.mjs, the loop over registry.contracts) asserts only that each runner that it discovers in scripts/ci.mjs is listed. It never discovers the fake gh's runtime, and it never rejects a listed toolchain that nothing needs.
  • D4: plan.mjs refuses with plan #<n> changed during the settle wait; run reopen again when the plan is still closed after the settle wait and either its status is no longer finished or the latest close differs from the one before the wait (reopen, after sleep(REOPEN_SETTLE_MS)). grep -rn 'changed during the settle wait' scripts/tests prints nothing: no test reaches either branch.
  • D5: the fake gh builds pull request nodes with merged, mergedAt, state, url, and baseRefName whether or not the query selects them (pullRequestNode and the Repository.pullRequest handler). With mergedAt and baseRefName removed from PULL_REQUEST_FIELDS in plan.mjs, node scripts/tests/plan-cli.mjs still passed 66 of 66, although GitHub would then return neither field and pullRequestNodes would 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.md gives the verify command for 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 printed 3 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

  1. D3: the registry check discovers fixture runtimes and requires an exact toolchain list. test-contracts.mjs also discovers the interpreter of each executable fixture that a suite runs: an executable file with a #! line under scripts/tests/fixtures/, when the suite's files name that fixture's folder. bun maps to bun-1 and node to node-24, as the gate runners do. Each contract's toolchains must 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 for repo-plan-cli (the next fixture would have the same gap).
  2. 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 in plan.mjs (the helper already validates; the gap is in the fake).
  3. 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 another createdAt, so the close differs; one test changes the issue's stateReason from COMPLETED to NOT_PLANNED during the wait while the issue stays closed with the same latest close, so statusFromIssue reads retired instead of finished. A label change cannot reach the refusal, because a closed issue's status comes from stateReason, 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.
  4. 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.
  5. D7: the skills guard checks the Report section. scripts/skills/guard.mjs, which the gate runs for each plugin's skills, fails when a SKILL.md does not end with the canonical Report section, byte for byte. The canonical text lives once, in scripts/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 in plugins/docks/skills/AGENTS.md then 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) and code-clarity.
  • Steps 3 and 4: test-coverage (tests for behavior that exists; no test of request text) and code-clarity.
  • Commits: commit-discipline. Self-review during code review round 1: code-review with code-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.mjs passed 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.mjs line coverage 99.68%.
  • a3 (step 1): with bun-1 removed from repo-plan-cli and node-24 added to repo-skill-trigger-collision, bun scripts/tests/test-contracts.mjs failed with repo-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 bun and repo-skill-trigger-collision.toolchains lists node-24, which no discovered runner needs. Before the fix, the same removal passed.
  • a4 (step 2): with mergedAt and baseRefName removed from PULL_REQUEST_FIELDS in a worktree at 33c45f33, 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.mjs failed 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.mjs tests failed 6 of 6 against the fake gh at 163acb53 and 3 of 6 at 33c45f33; the five tests of b6ebe167 failed 5 of 5 against the fake gh at 24396e04.
  • At 848a2863: bun scripts/ci.mjs passed for the repo-wide checks and both plugins; node scripts/tests/plan-cli.mjs passed 79 of 79; bun scripts/tests/author-tooling.mjs and bun scripts/tests/test-contracts.mjs passed; plan.mjs line 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

  1. self-assigned this
    on Oct 23, 2026
  2. DocksDocks commented on Oct 23, 2026

    @DocksDocks
    OwnerAuthor

    Plan review round 1

    Plan-review: repair

    • research_gap - Research decision 3; Steps settle_refusal_tests; Acceptance a5 - Changing a status label cannot trigger the specified refusal without violating “Do not touch: the behavior of plan.mjs.” In plugins/plan-lifecycle/skills/productivity/plan-manager/scripts/plan.mjs, statusFromIssue (lines 443–453) derives a closed issue’s status from stateReason, ignoring labels; reopenPlan (lines 1227–1242) therefore still sees finished and 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 from stateReason: '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.
  3. DocksDocks commented on Oct 23, 2026

    @DocksDocks
    OwnerAuthor

    Plan review round 2, delta

    Plan-review: pass

    None.

    Previous findings:

    • Round-1 finding on Research decision 3; Steps settle_refusal_tests; Acceptance a5: resolved - Decision 3 now changes stateReason from COMPLETED to NOT_PLANNED, preserving the closed state and latest closure. This reaches the existing refusal without changing plan.mjs; step 3 and a5 require the corresponding mutation proof.
  4. DocksDocks commented on Oct 23, 2026

    @DocksDocks
    OwnerAuthor

    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-252 exits 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

    • [MEDIUM] Spec - scripts/tests/fixtures/gh-stub/gh:289-296, .git/docks-review/plan-102.md:26 - Decision 2 requires each PR node to hold “only the fields that the query selects at that level.” For a valid selection such as {mergedAt: url}, the word search mistakes the alias for a selected field. [INFERENCE] It returns the fixture timestamp as mergedAt plus an unrequested url, whereas GitHub returns only mergedAt containing 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 projection

    Code review round 1, lens text-and-safety

    Code-review: pass

    Standards

    • [MEDIUM] Maintainability - scripts/skills/guard.mjs:59-62, scripts/tests/test-contracts.mjs:569-576 - Future changes relying on routine CI lack regression protection for these rejection branches: existing tests exercise only guard spawn failure and the unchanged registry. Inference: disabling these branches would leave CI green while restoring D3/D7. Add gate-run negative tests for missing/extra toolchains and missing/byte-modified Report sections, asserting failure and identifying diagnostics. - class: untested validator rejection branches

    Spec

    None.

  5. DocksDocks commented on Oct 23, 2026

    @DocksDocks
    OwnerAuthor

    Self-review

    Ran while code review round 1 ran, on the full candidate diff at 33c45f33, with the docks code-review and code-clarity skills.

    No findings.

  6. DocksDocks commented on Oct 23, 2026

    @DocksDocks
    OwnerAuthor

    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 identifies NODE_OPTIONS as the interpreter and rejects a valid Bun toolchain declaration. The current simple shebang is unaffected. - Consume operand-taking env options correctly and cover this valid shebang form. - class: env option operand mistaken for interpreter

    Spec

    • [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

    • [LOW] Maintainability - scripts/tests/fixtures/gh-stub/gh:345-364, scripts/tests/plan-cli/gh-stub.test.mjs:56-113 - Repeated nested selections and object fields without subfields exercise new branches that no committed test covers. Inference: replacing selection merging with last-write-wins, or removing the missing-subfields rejection, would leave current tests green while breaking GitHub fidelity. - Add direct fake-gh cases asserting that repository{a:nameWithOwner} repository{b:nameWithOwner} preserves both keys and that bare repository exits 1. - class: untested projection branches

    Spec

    None.

  7. DocksDocks commented on Oct 23, 2026

    @DocksDocks
    OwnerAuthor

    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 identifies FORCE_COLOR=0 as its interpreter and rejects a valid Bun toolchain declaration. env still 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. - Select updatedAt explicitly or obtain the expected timestamp from stored fixture state. - class: test dependency on unselected response field

    Spec

    None.

    Code review round 3, lens text-and-safety

    Code-review: pass

    Standards

    None.

    Spec

    None.

  8. DocksDocks commented on Oct 23, 2026

    @DocksDocks
    OwnerAuthor

    Code review round 4, delta

    Code-review: pass

    Previous findings

    • Round-3 finding on scripts/tests/test-contracts.mjs:466: fixed - Line 467 skips assignments after -- before identifying the interpreter; scripts/tests/author-tooling.mjs:332 adds the regression case.
    • Round-3 finding on scripts/tests/plan-cli/gh-stub.test.mjs:92-96: fixed - The query at line 92 explicitly selects updatedAt, removing reliance on an unselected response field.

    Standards

    None.

    Spec

    None.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions