Repository navigation
Fix #90 and let agents report skill problems to this repository #96
Description
Goal
Fix the plan-lifecycle defect in #90: a reopen after a partial-merge close can race GitHub's own close and leave the plan issue closed with reason REOPENED, a state that GitHub refuses to change. Give agents that use a skill from this repository a documented way to report a problem with that skill as a GitHub issue here, with a template that explains the problem well and keeps private data out of the public issue. Close the open items from the advisor check of #89 and #95.
Scope: the plan-lifecycle helper, contract, and skills; the shipped docks and plan-lifecycle skills; the test-contract registry; and releases of the changed plugins after the merge, each with its own question.
Yolo: the user delegated every decision except the merge to the agent.
Mode: plan-and-implement
Research
Open issues
gh issue list --repo DocksDocks/docks --state openlists one issue: plan-lifecycle: reopen right after a partial merge can leave the issue closed with reason REOPENED #90 (labelbug). Issues Probe: plan.mjs GraphQL write path (#89) #91 to Probe: plan.mjs label removal by snapshot id (#89) #94 are the throwaway probe issues of Optimize the gate and the plan helper after the Bun switch #89; they are closed as not planned and carry no labels.- plan-lifecycle: reopen right after a partial merge can leave the issue closed with reason REOPENED #90 is an agent-filed report against plan-lifecycle 0.15.0. The manager merged a partial pull request and reopened the plan issue 1 s after GitHub's
ClosedEvent(merge 19:29:45, close 19:29:47, reopen 19:29:48 UTC). The issue endedCLOSEDwithstateReasonREOPENED. Since then GitHub refuses every reopen and close, also in the web page, and only GitHub Support can repair it.plan.mjsderivesunreadablefor that state (statusFromIssue), sostep,retire, andarchivestop. Waiting until the issue readCLOSEDwith itsClosedEvent, plus about 10 s, avoided the state on the next plan (plan-lifecycle: reopen right after a partial merge can leave the issue closed with reason REOPENED #90, "Workaround that worked").
How the reopen works today
- The contract (
plan-contract.md, Pull requests, partial pull request step 2) tells the manager to runshow <n>after the merge and, when it readsfinished,gh issue reopen <n> --repo <owner/repo> --comment "Reopened: partial pull request #<pr> closed this plan through its linked branch. Work continues.". Nothing waits for GitHub's close to settle, so an agent that readsfinishedreopens within a second of the close, as in plan-lifecycle: reopen right after a partial merge can leave the issue closed with reason REOPENED #90.plan-manager/SKILL.md(Landing and reporting, BAD/GOOD) repeats the same step. plan.mjsreadSnapshotreads the latestClosedEventwith its closer (CLOSED_EVENT_FIELDS) but not itscreatedAt. The fake gh (scripts/tests/fixtures/gh-stub/gh) renders the latest close withoutcreatedAtand has noissue reopenhandler.- Experiment (
gh pr view <pr> --json closingIssuesReferenceson merged pull requests Cut the gate to 7 s and a gh round trip from each plan write #95, Run the repository's author tooling on Bun 1.4.4 #88, and yolo: archive only on Merge now when the answer starts a new run #86): a merged pull request still lists the issues it closes ([89],[87],[85]). TheClosedEventof Optimize the gate and the plan helper after the Bun switch #89 has closerPullRequestCut the gate to 7 s and a gh round trip from each plan write #95,stateReasonCOMPLETED, andcreatedAt2 s after the pull request'smergedAt. gh issue reopentakes-c, --comment(gh 2.102.0 help; https://cli.github.com/manual/gh_issue_reopen).
How agents would report a skill problem
- No reporting path exists: no shipped skill names this repository, there is no
.github/ISSUE_TEMPLATE, noSECURITY.md, and private vulnerability reporting is off (gh api repos/DocksDocks/docks/private-vulnerability-reportingreturns{"enabled":false}; https://docs.github.com/en/code-security/security-advisories/working-with-repository-security-advisories/configuring-private-vulnerability-reporting-for-a-repository). - plan-lifecycle: reopen right after a partial merge can leave the issue closed with reason REOPENED #90 shows what a useful report holds: plugin version, the exact instruction, an observed timeline, every repair attempt, a working workaround, and suggested changes. It also names a private repository and a private plan title, which a public report should leave out.
- Skills load their body only on activation, and agents choose skills by description (
plugins/docks/skills/AGENTS.md, intro). An agent sees a pointer in a skill body exactly when it uses that skill. The repository already keeps one byte-identical paragraph in several skills, the plan-lifecycle prerequisite paragraph, checked by a grep (plugins/plan-lifecycle/skills/AGENTS.md, Fail-loud routing). - gh can file and search:
gh issue create --repo <owner/repo> --title <t> --body-file <file>andgh issue list --repo <owner/repo> --state all --search <query>(gh 2.102.0 help; https://cli.github.com/manual/gh_issue_create, https://cli.github.com/manual/gh_issue_list). Without gh, a prefilledissues/new?title=&body=URL opens the form (https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/creating-an-issue, "Creating an issue from a URL query"). - Shipped skills sit at
<plugin-root>/skills/<category>/<skill>/SKILL.md, and each plugin root carries.claude-plugin/plugin.jsonand.codex-plugin/plugin.jsonwithversion. Each skill's frontmatter carriesmetadata.updatedandmetadata.content_hash, which identify the exact skill text. - The scorer gives 2 points for a body of 80 to 310 lines (
skill-guard.mjs). The longest bodies stay inside their current band with 4 more lines:type-safety-discipline(310-line file, about 300-line body) stays at or under 310, andyolois already above it.
Advisor follow-ups from #89 and #95
scripts/config/test-contracts.jsonlists"toolchains": ["node-24"]for all 17 contracts. Since Run the repository's author tooling on Bun 1.4.4 #88, 16 of them run on Bun (bun()/bunOk()inscripts/ci.mjs,bun testfor the unit files). Onlyscripts/tests/plan-cli.mjsruns on Node, and its fake gh runs on Bun.scripts/tests/test-contracts.mjsonly checks that the field is a sorted string array, so nothing caught the drift.- Corrections to the Cut the gate to 7 s and a gh round trip from each plan write #95 run summary: the remote branch
89-optimize-the-gate-and-the-plan-helper-after-the-bun-switchstays on GitHub (the repository does not delete merged branches;delete_branch_on_merge=false);statuswrites now also run the post-write label check, and astatuswrite now creates a missing phase label where it used to fail insidegh issue edit; plugin users get the faster helper only with a plan-lifecycle release, which steps 5 and 6 of this plan schedule. - No official GitHub source states a 100-label cap per issue (web search), so Cut the gate to 7 s and a gh round trip from each plan write #95's fix that takes removal ids from the paged snapshot stays as merged.
Decisions
- Add
reopen <n> --pr <pr>toplan.mjsand make it the contract's only reopen path after a partial merge. It reads the pull request (merged,mergedAt,closingIssuesReferences) and the plan for write.- When the pull request is not merged, it refuses.
- When the pull request does not close the plan, it prints that nothing needs a reopen, exits 0, and writes nothing, so the manager can run it after every partial merge.
- Otherwise it polls until it observes both facts: the plan reads
CLOSED, and its latestClosedEventcomes from that pull request (or from a commit associated with it) and is not older thanmergedAt. The pause grows from 250 ms and doubles up to 2 s, for at most 34 reads (about 60 s); the bound counts reads, not wall time. - A plan that reads
OPENwith such a close event was reopened after that close: the command printsplan #<n> is open: <status>and writes nothing. A close by another closer after the merge is refused. - After both observations it waits a full 10 s on its own clock, the wait that avoided the state in plan-lifecycle: reopen right after a partial merge can leave the issue closed with reason REOPENED #90. It then reads ownership, state, and the close event again, right before the write. It writes only when the plan still reads
CLOSEDwith reasonCOMPLETEDand the same close event; a plan that now readsOPENprints the open status, and a foreign owner is refused. - The reopen is one
gh api graphqlrequest:reopenIssue,addCommentwith the contract's comment, and, for an unassigned plan,addAssigneesToAssignablewith the acting login, so the claim lands in the same write as the contract requires (Status, ownership, and frozen state). The last field returns the stored state and reason, and the command printsplan #<n> reopened: <status>. - A plan that reads
CLOSEDwith reasonREOPENEDis refused with the stuck-state message: before the reopen, after it, and when GitHub answers the reopen with an error (plan-lifecycle: reopen right after a partial merge can leave the issue closed with reason REOPENED #90: "Could not reopen the issue"). - The waits block the process with
Atomics.wait, which Node allows on the main thread (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait). The plan-CLI cases load a test-only--importhook that makes those waits return at once, asscripts/tests/unit/agent-score-vacuity.test.mjsloads its fixture hook, so the shipped helper carries no test switch, and the read bound makes the timeout case fast. - Rejected: contract prose only (plan-lifecycle: reopen right after a partial merge can leave the issue closed with reason REOPENED #90 suggestion 1), because an agent would hand-roll the polling each time and a missed wait is permanent. Rejected: a fixed sleep after the merge, because it does not prove that the close happened. Rejected: a settle time computed from the event's
createdAt, because the local clock can drift from GitHub's and shrink the wait to nothing. Rejected:gh issue reopen --comment, because it cannot claim an unassigned plan in the same write.
showprints advice for a plan that GitHub leftCLOSEDwith reasonREOPENED:GitHub left this issue closed with reason REOPENED and refuses every state change; ask GitHub Support to repair it, and record step results in an issue comment until then(plan-lifecycle: reopen right after a partial merge can leave the issue closed with reason REOPENED #90 suggestion 2). The derived status staysunreadable, so the contract's status table does not change.- The contract documents the fallback (plan-lifecycle: reopen right after a partial merge can leave the issue closed with reason REOPENED #90 suggestion 3): do not retry the reopen; record the remaining step results in one issue comment; report the plan as stuck; ask the repository owner to contact GitHub Support. The contract's command table, advice list, and refusal list gain the new command, advice, and refusals.
plan-manager/SKILL.mdnamesreopenwhere it now names a hand reopen. - Add the docks skill
report-skill-issue(productivity/,user-invocable: true). It confirms that the problem is in the skill, collects the plugin version and the skill'smetadata.updatedandcontent_hash, searches for a duplicate, drafts the title<plugin>:<skill>: <symptom>and a fixed-section body, gets the user's approval of the exact text, and files it withgh issue create --repo DocksDocks/docks --body-file. Without gh, it gives the prefilled URL.- Its constraints: the repository is public; file only after approval, which ends the turn in a headless run; leave out secrets (name the variable), private repository, organization, and customer names, hostnames, machine paths, proprietary code, and private plan titles. A security problem is never filed publicly; the user gets the draft to send to the repository owner privately.
- The template follows plan-lifecycle: reopen right after a partial merge can leave the issue closed with reason REOPENED #90's structure: Summary, Skill, What the skill says, What happened, Expected, How to reproduce, Workaround, Suggested change.
- Rejected: a
.github/ISSUE_TEMPLATEform, because agents file throughgh issue create, and a second copy of the template would drift.
- Every shipped skill of both plugins ends with one byte-identical
## Report a problem with this skillsection that points toreport-skill-issue. For a runtime without docks, the section says to give the user a draft only and to file nothing, because the publication safeguards (public destination, redaction, security reports, exact-text approval) live in the skill.plugins/docks/skills/AGENTS.mdowns the rule and its verify command, andplugins/plan-lifecycle/skills/AGENTS.mdpoints to it.- Rejected: description text, because it costs listing budget in every session. Rejected: a SessionStart hook, because it injects context into every session. Rejected: a new validator, because the repository checks its other byte-identical paragraph with a grep. Rejected: the reviewer agent wrappers and the project-local
.agents/skills, because the wrappers load the canonical skill and project-local skills are fixed in place.
- Rejected: description text, because it costs listing budget in every session. Rejected: a SessionStart hook, because it injects context into every session. Rejected: a new validator, because the repository checks its other byte-identical paragraph with a grep. Rejected: the reviewer agent wrappers and the project-local
- Correct the registry: each contract lists the toolchain of the runner that the gate uses for it. That is
bun-1for the 16 Bun suites andbun-1plusnode-24forrepo-plan-cli, whose fake gh runs on Bun.scripts/tests/test-contracts.mjsderives the expected token from howscripts/ci.mjsruns each suite (node()/nodeOk()givenode-<major>from thenode-versionin.github/workflows/ci.yml;bun()/bunOk()andbun testgivebun-<major>frompackageManager). It fails when a contract omits its runner's token, so a later runtime change cannot leave the registry stale. Rejected: renaming the selection kindsnode-scriptandnode-test-file, because they name the selection shape and a rename changes no behavior. - Release both plugins after the merge, each with its own question: docks 0.21.0 (new skill) and plan-lifecycle 0.16.0 (new command), as minor bumps under 0.x semantic versioning. A plan-lifecycle release also ships the Optimize the gate and the plan helper after the Bun switch #89 helper changes.
plugins/plan-lifecycle/compatibility.jsonstays atminimum_docks_major: 0, because the plan-lifecycle pointer works without docks.
Request corrections
Asked: "check open issues as we have some new issues that came". Did: fix #90, the only open issue, and explain #91 to #94 as #89's closed probe issues. Reason: gh issue list --state open lists only #90.
Engineering standards
- Step 1
reopen_spec:tdd-workflow(spec tests before the helper code; red baseline below),test-coverageprinciples,code-clarity,commit-discipline. - Step 2
reopen_helper:fix-workflow(plan-lifecycle: reopen right after a partial merge can leave the issue closed with reason REOPENED #90 is a reported defect; the reproduction is step 1's red baseline),code-clarity,commit-discipline. - Step 3
report_skill:write-skill(description rules, trigger collisions, score),code-clarity,commit-discipline. - Step 4
registry_toolchains:tdd-workflow(the cross-check is written first and fails on the stale registry),code-clarity,commit-discipline. - Before code review round 1:
code-reviewself-review withcode-clarity.
Steps
| # | Id | Task | Files | Depends | Effect | Status | Done when |
|---|---|---|---|---|---|---|---|
| 1 | reopen_spec | Write the plan-CLI spec cases for reopen and the stuck-state advice: waits for the pull request's close, then reopens with the contract comment and claims an unassigned plan; nothing to reopen when the pull request does not close the plan; refuses an unmerged pull request; refuses a close by another closer after the merge; refuses GitHub's CLOSED/REOPENED state before and after the reopen, and show advises on it; prints the open status when the plan was already reopened; refuses after the bounded reads when GitHub never closes the plan. Add the test-only --import hook that skips the waits. Teach the fake gh the reopenIssue and addComment mutations, a delayed close after a set number of reads, a reopen that GitHub leaves closed, createdAt on close events, and a pullRequest query with closingIssuesReferences |
scripts/tests/plan-cli/, scripts/tests/fixtures/gh-stub/gh | - | local | done | the new cases fail on the current helper because reopen and the advice do not exist (red baseline in Verification Results), and the other plan-CLI cases still pass |
| 2 | reopen_helper | Implement reopen <n> --pr <pr> and the show advice (decisions 1 and 2) in plan.mjs; update the contract's partial pull request steps, command table, advice list, refusal list, and fallback (decision 3); name reopen in plan-manager/SKILL.md; backfill the plan-lifecycle content hashes |
plugins/plan-lifecycle/skills/productivity/plan-manager/scripts/plan.mjs, plugins/plan-lifecycle/skills/productivity/plan-manager/references/plan-contract.md, plugins/plan-lifecycle/skills/productivity/plan-manager/SKILL.md | 1 | local | done | node scripts/tests/plan-cli.mjs passes with the step 1 spec files unchanged (git diff -- <spec files> prints nothing); bun run test:coverage reports at least 99% line coverage for plan.mjs |
| 3 | report_skill | Add plugins/docks/skills/productivity/report-skill-issue/SKILL.md (decision 4); end every shipped skill of both plugins with the byte-identical report section (decision 5); add the rule and verify command to plugins/docks/skills/AGENTS.md and a pointer to plugins/plan-lifecycle/skills/AGENTS.md; list the skill in both READMEs; backfill the content hashes of both plugins |
plugins/docks/skills/, plugins/plan-lifecycle/skills/, README.md, plugins/docks/README.md | 2 | local | done | the gaps of a fresh agent without the skill are recorded first (write-skill step 0); a5, a6, a11, and a12 hold; bun scripts/ci.mjs --plugin docks and bun scripts/ci.mjs --plugin plan-lifecycle exit 0 |
| 4 | registry_toolchains | Write the toolchain cross-check in scripts/tests/test-contracts.mjs first, then correct the 17 toolchains values (decision 6) |
scripts/tests/test-contracts.mjs, scripts/config/test-contracts.json | - | local | done | the cross-check fails on the stale registry (red baseline), and after the correction bun scripts/tests/test-contracts.mjs passes |
| 5 | release_docks | After the merge and only with its own question: run bun scripts/release.mjs --plugin docks minor from a clean main |
plugins/docks/.claude-plugin/plugin.json, plugins/docks/.codex-plugin/plugin.json, .claude-plugin/marketplace.json | 3 | release | done | the docks--v0.21.0 tag CI passes and gh release view docks--v0.21.0 shows the release, or the step is skipped with the user's answer recorded |
| 6 | release_plan_lifecycle | After the merge and only with its own question: run bun scripts/release.mjs --plugin plan-lifecycle minor from a clean main |
plugins/plan-lifecycle/.claude-plugin/plugin.json, plugins/plan-lifecycle/.codex-plugin/plugin.json, .claude-plugin/marketplace.json | 2, 3, 5 | release | done | the plan-lifecycle--v0.16.0 tag CI passes and gh release view plan-lifecycle--v0.16.0 shows the release, or the step is skipped with the user's answer recorded |
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 and multi-plugin changes) |
| a2 | bun scripts/tests/ci-plugin-targeting.mjs |
exits 0 (the non-unit targeting contract, .github/AGENTS.md) |
| a3 | node scripts/tests/plan-cli.mjs |
exits 0, including every reopen case |
| a4 | bun run test:coverage |
runs under Node and reports at least 99% line coverage for plan.mjs |
| a5 | for f in plugins/docks/skills/*/*/SKILL.md plugins/plan-lifecycle/skills/*/*/SKILL.md; do sed -n '/^## Report a problem with this skill$/,$p' "$f" | sha256sum; done | sort | uniq -c |
one line whose count equals the number of shipped skills (30), and the hash is not that of empty input |
| a6 | node plugins/docks/skills/productivity/write-skill/scripts/skill-guard.mjs score --per-file plugins/docks/skills |
productivity/report-skill-issue scores at least 14, and no skill falls below its category floor |
| a7 | In a temporary worktree of the branch (the yolo Commits rule for worktree paths), set repo-plan-cli to list only bun-1 in scripts/config/test-contracts.json and run bun scripts/tests/test-contracts.mjs there; then run it in the checkout |
the worktree run fails naming repo-plan-cli; the checkout run passes |
| a8 | On GitHub, with the branch helper: show 89 (archived; its closure read now includes createdAt), and reopen 94 --pr 95 on the closed probe issue #94, which #95 does not close |
show succeeds; reopen prints that pull request #95 does not close plan #94 and exits 0; #94 stays closed as not planned with no labels |
| a9 | A dry run of report-skill-issue on #90's facts, without filing |
the draft has every template section, the title plan-lifecycle:plan-manager: <symptom>, and no private repository name or plan title |
| a10 | pull request checks | every check passes |
| a11 | Trigger check from write-skill (Behavioral checks): 9 should-trigger and 9 near-miss queries, a third of each held back; for each query a fresh sonic subagent gets the names and descriptions of every docks and plan-lifecycle skill and names the skill it would load, or none |
every held-back near-miss routes away from report-skill-issue (the near-misses include the three sibling routes: fix-workflow, skill-maintenance, and write-skill), and the held-back should-trigger queries load it with at most one miss |
| a12 | Baseline check from write-skill: 3 realistic prompts about a wrong instruction in a docks or plan-lifecycle skill, each given to a fresh sonic subagent without the skill and to one with it; assertions written before the "with" outputs are read |
the "with" runs close the gaps that the "without" runs show (template sections, redaction, a security problem kept private, approval before filing) and break nothing that the "without" runs got right |
Do not touch
- The plan contract's other rules and refusal strings, the write path from Optimize the gate and the plan helper after the Bun switch #89, and the status table.
- The reviewer agent wrappers (
plugins/plan-lifecycle/agents/) and the project-local.agents/skills/. docs/point-in-time records.- The remote branches
87-evaluate-and-adopt-the-bun-runtime-for-the-repository-mjs-toolingand89-optimize-the-gate-and-the-plan-helper-after-the-bun-switch, which earlier plans created. - Repository settings, including private vulnerability reporting.
- plan-lifecycle: reopen right after a partial merge can leave the issue closed with reason REOPENED #90's text, which the user's account published.
Open questions
Verification Results
Local runs: Linux x64, Node v24.22.1, Bun 1.4.4. Node test runs set TMPDIR to the review scratch (the workstation limitation recorded in #87).
Step 4 registry_toolchains (commit 6bbf8b30)
- Red baseline, step registry_toolchains:
bun scripts/tests/test-contracts.mjs: 16 failed, 1 passed (the cross-check named the 16 Bun suites, for examplerepo-author-tooling.toolchains must list bun-1: scripts/ci.mjs runs scripts/tests/author-tooling.mjs with bun;repo-plan-clipassed). - After the registry change, with the check unchanged since the red baseline:
PASS test_contracts contracts=17 discovered=17 selected=17.
Steps 1 and 2 reopen_spec, reopen_helper (commit 347f0a58)
- Red baseline, step reopen_spec:
node --test scripts/tests/plan-cli/*.test.mjs: 13 failed, 48 passed. Every failure is an assertion on the helper's usage error (reopenis not a command yet); the 48 existing cases pass. The spec files are staged. - After the helper change, with the spec files unchanged since the red baseline (
git diffon the four files was empty):node scripts/tests/plan-cli.mjs: 61 passed, 0 failed. - Beyond the spec, the helper refuses two more cases, and the contract lists both refusals:
plan #<n> changed during the settle wait; run reopen again(the read after the 10 s wait shows a different close) andplan issue state differs after reopen(the stored state still reads closed after the write). The pull request query also checks the repository of each closing reference, so an issue with the same number in another repository does not count.
Review fixes to reopen (commits 6a3ef0e8, b7fc4fef)
- Self-review: a plan closed before its partial pull request merged got
GitHub has not closed plan #<n> for pull request #<pr> yet; run reopen again once it has, which loops forever, because GitHub cannot close a closed issue. After the last read, such a plan now getsplan #<n> was closed by <closer> before pull request #<pr> merged; reopen it by hand only if that close was a mistake, and the contract lists it. Red baseline: the new case failed with the old message, and the other 13reopencases passed. - Code review round 1:
writeReopendid not recheck the owner at its pre-write read. It now refuses an assignee other than the acting login there and claims only when that read shows no assignee. The fake gh now lands due scheduled changes onissue view, as its own comment says. Red baseline: the new case exited 0 and wrote, and the other 62 passed. After the fix: 63 passed.
Step 3 report_skill (commits 8542d798, 093c13f0, a55d88f5)
- Baseline, "without" arm (recorded before the skill existed): three fresh
sonicagents had no target repository or gh command, no plugin version or runtime, no quote, no fixed sections, no duplicate search, no approval step, and one would report a security problem publicly. Right already: private names and the secret value left out, rotation advised. - a12, "with" arm, round 1: A held every applicable assertion; B skipped the duplicate search (and so missed the open plan-lifecycle: reopen right after a partial merge can leave the issue closed with reason REOPENED #90) and named no command or account; C wrote its private draft without the template sections. The skill then gained: every stage in order and no skipped search, also in a text-only run; the private draft uses every section; the pre-filing check lists the search and the target, command, and account; the prefilled link files nothing by itself.
- a12, round 2 (fresh agents, same prompts). Correction: this round was first recorded as a pass, and that was wrong. B3 ran the search, found plan-lifecycle: reopen right after a partial merge can leave the issue closed with reason REOPENED #90, and drafted a comment there, but it quoted the already-fixed plan-manager text from the development checkout under the installed version 0.15.0, so the comment blamed the fix. C3 quoted the security skill's instruction not to output
.envand still drafted a defect report. A3 and B3 took the content hash from the checkout, not from the installed copy. The probe setup pointed the agents at the checkout, but the skill did not say which copy to read either. The skill then gained (commita55d88f5): read the skill, its version, and its hash from the copy that the runtime loaded (or the installed copy, said in the draft), never from a checkout or another copy; a quote that does not say what the user reports, or says the opposite, gets no defect report. - a12, round 3 (fresh agents, same prompts; the setup serves installed skills through
skill://and names the checkout as the development copy): A4 read the installed fix-workflow (0.20.0, updated 2026-09-24, hash655b1d447db2), ran the search, and drafted a new issue with every section, the command, and the prefilled link, with approval first. B4 read the installed plan-manager (0.15.0, updated 2026-10-21, hash37cfc75fba99), quoted its text ("After a partial merge, reopen the issue as the contract states."), found plan-lifecycle: reopen right after a partial merge can leave the issue closed with reason REOPENED #90 through the issue search, and drafted a redacted comment there with only the new fact; it gave the issue link and filed nothing. C4 read the installed security skill (0.20.0, hash11afe1a4f593), quoted the instruction that contradicts the claim, drafted no defect report, advised rotation first, and pointed to another source. Assertion 3 and the private draft of assertion 7 do not apply to C4, because the quoted skill text refutes the claim; every other applicable assertion holds. Pass. - a11 trigger check (one fresh
sonicagent per query, given only the 30 names and descriptions): 9 of 9 report requests loadedreport-skill-issue, including the 3 held back; 9 of 9 near-misses routed elsewhere, including the 3 held back (skill-maintenance, none,plan-manager) and the sibling routesfix-workflowandwrite-skill. Pass. - Two dry runs encoded the prefilled link by hand (one link body differed from the shown draft, one was empty), so the link section now gives a Node and a Python command (commit
093c13f0). Both commands round-trip a body with backticks,&,$, quotes,%,#, and non-ASCII text. A repeat dry run produced a link whose body equals the shown draft and its body file byte for byte (2178 characters).
Acceptance (at a55d88f5 unless noted)
- a1
bun scripts/ci.mjs: exit 0,All ci.mjs checks passed - 2 plugin(s) + repo-wide. - a2
bun scripts/tests/ci-plugin-targeting.mjs: exit 0. - a3
node scripts/tests/plan-cli.mjs: 63 passed, 0 failed (atb7fc4fef; no plan-lifecycle file or test changed since). - a4
bun run test:coverage:plan.mjs99.68% lines, 80.14% branches, 97.65% functions; the uncovered lines 94-95, 451, and 843 are older code (atb7fc4fef). - a5: one line,
30 a27c6a6c22683e7cf5e9837d2a5c3baf2ea3cf104d04e5a9b2a52e598124d4c4; 30 shipped skills; not the hash of empty input. - a6:
productivity/report-skill-issue 15; the scorer and the docks gate report no skill below its floor; body 247 lines. - a7: in a temporary worktree of
8542d798,repo-plan-cliwith onlybun-1made the check fail with exactly one finding,repo-plan-cli.toolchains must list node-24: scripts/ci.mjs runs scripts/tests/plan-cli.mjs with node; the checkout run passed. The worktree was removed afterwards. The registry and the check have not changed since. - a8, real GitHub with the branch helper:
show 89printed#89 · finished · …andreviews: plan=pass code=passwith one GraphQL query that includes the close read.reopen 94 --pr 95printedpull request #95 does not close plan #94; nothing to reopen, exit 0, after one GraphQL query and no write; Probe: plan.mjs label removal by snapshot id (#89) #94 stayedCLOSED/NOT_PLANNEDwith no labels, and its JSON (state, reason, labels, assignees, comments, updatedAt) was identical before and after. Run at8542d798and again ata55d88f5, with the same result. - a9: a fresh agent drafted plan-lifecycle: reopen right after a partial merge can leave the issue closed with reason REOPENED #90's report from the issue's facts with the title
plan-lifecycle:plan-manager: reopening immediately after a partial merge can leave the plan issue unreadableand every template section; the private repository name, the plan title, and the issue number from plan-lifecycle: reopen right after a partial merge can leave the issue closed with reason REOPENED #90 are absent (skill text of093c13f0). Pass. - a10: pull request Fix #90 and let agents report skill problems to this repository #97 at the reviewed head
a55d88f5: all 5 checks pass (CI plugin-targeting contracts, resolve validation shards, validation shard core, validation shard repo, and validate). The first body text "does not close Probe: plan.mjs label removal by snapshot id (#89) #94" made GitHub list Probe: plan.mjs label removal by snapshot id (#89) #94 as a closing reference; the body now avoids that keyword, andclosingIssuesReferenceslists only plan-lifecycle: reopen right after a partial merge can leave the issue closed with reason REOPENED #90 and Fix #90 and let agents report skill problems to this repository #96.
Activity
DocksDocks commentedon Oct 23, 2026
Plan review round 1
Plan-review: repair
research_gap - Research decision 1; plan-lifecycle: reopen right after a partial merge can leave the issue closed with reason REOPENED #90, “Workaround that worked” - The successful workaround waited approximately 10 seconds after observing both
CLOSEDand the matching close event. Decision 1 requires only the event and measures settling fromcreatedAt, which GitHub documents as event creation time, not stabilization. These are not equivalent safeguards - Require both observations, then a full elapsed settle interval before reopening; distinguish a confirmed subsequent reopen from a close still being observed.research_gap - Research decision 1;
plugins/plan-lifecycle/skills/productivity/plan-manager/references/plan-contract.md:138-140;plan.mjs—readPlanIssue,writeIssueIfUnchanged- “No claim, because the reopen writes no body” contradicts the contract: mutations claim unassigned plans, including bodyless writes. The proposed sequence also lacks a fresh ownership check after waiting - Preserve claim-on-mutation behavior and re-read ownership, state, and closure immediately before reopening. Keep genuine no-op outcomes mutation-free.security_risk - Research decisions 4–5; step 3 - The standalone reporting skill prohibits private-data disclosure and public security reports, but the no-docks fallback specifies only a minimum draft and approval rule. That fallback cannot load the missing skill’s safeguards - Make the byte-identical section explicitly carry the public-destination warning, redaction requirements, security-report prohibition, and exact-text approval gate; otherwise restrict that fallback to a private draft without publication.
goal_fit - Acceptance a9;
plugins/docks/skills/AGENTS.md:41,115- A single report dry run does not cover the mandatory manual sibling-collision check or baseline check on the smallest targeted model/runtime. The full gate’s mechanical collision audit does not replace these - Include the required three near-miss routing checks and fresh-instance without/with-skill baseline comparison in Acceptance.research_gap - Steps 5–6, Files;
scripts/lib/plugin-release.mjs—runGenericPluginRelease- Both release steps declarenone, although the command writes and commits the selected plugin’s Claude manifest, Codex manifest, and.claude-plugin/marketplace.json- List those three paths for each release. Retain the existing dependency from step 6 to step 5 for the shared catalog.research_gap - Acceptance a7;
scripts/tests/test-contracts.mjs—REPO,readJson, registry initialization - The validator always reads the registry relative to its own script location; the proposed command provides no way to select a standalone scratch registry - Specify running the copied validator from a complete scratch repository containing the altered registry, so the negative check actually exercises the intended input.DocksDocks commentedon Oct 23, 2026
Plan review round 2
Plan-review: pass
None.
DocksDocks commentedon Oct 23, 2026
Self-review
Scope: full candidate diff since the merge base with
main(e49e144d..6a3ef0e8, 6 commits), reviewed withcode-reviewandcode-clarity. Two-axis mode: the spec source is this issue.Standards
MEDIUM · Correctness ·
plugins/plan-lifecycle/skills/productivity/plan-manager/scripts/plan.mjs—reopenPlan(introduced; fixed in6a3ef0e8)Evidence: for a plan that was closed before its partial pull request merged,
reopenread the plan 34 times and then printedGitHub has not closed plan #<n> for pull request #<pr> yet; run reopen again once it has.Why it is a problem: GitHub cannot close an issue that is already closed, so that merge never adds a close. An agent that follows the message retries forever.
Fix: after the last read, a plan that still reads closed gets
plan #<n> was closed by <closer> before pull request #<pr> merged; reopen it by hand only if that close was a mistake, and the contract lists the refusal. The helper still polls first, so a short lag between the issue state and its timeline cannot cause a false refusal. Red baseline: the new case failed with the old message, and the other 13reopencases passed. After the fix:node scripts/tests/plan-cli.mjs62 passed,plan.mjsline coverage 99.68%, andbun scripts/ci.mjs --plugin plan-lifecyclepassed.LOW · Correctness (kept by design) ·
plugins/plan-lifecycle/skills/productivity/plan-manager/scripts/plan.mjs—writeReopenEvidence:
reopenIssue,addComment, andaddAssigneesToAssignableare root fields of one mutation, and GraphQL runs every root field even when an earlier one fails.Why it matters: if GitHub rejects the reopen, the
Reopened: ...comment and the claim still land on an issue that stays closed.Disposition: kept. Decision 1 and the spec require one request with the contract comment, so that the claim lands in the same write as the reopen. The write runs only after the plan reads closed by the pull request, a 10 s settle wait, and a fresh read, so this path needs a GitHub failure on top. The helper then prints the stuck-state refusal, which names the real state of the issue.
Spec
No findings.
showadvice, status staysunreadable), and decision 3 (contract fallback).plan-manager/SKILL.mdnamesreopen <n> --pr <pr>in Landing and reporting and in the BAD/GOOD pair.AGENTS.mdnodes carry the rule and its pointer. Both READMEs list the skill.6a3ef0e8the full gate runs again before the push.compatibility.json, the frozen pre-GitHub archive, other plugins' manifests, and plan-lifecycle: reopen right after a partial merge can leave the issue closed with reason REOPENED #90's text are unchanged.DocksDocks commentedon Oct 23, 2026
Code review round 1
Code-review: pass
Standards
preWriteIssuecan return that foreign assignee, butwriteReopendiscards everything exceptidand proceeds to reopen, comment, and claim using stale ownership. This bypasses the foreign-owner refusal. Carry the acting viewer ID into this function, recheck the returned assignees, and derive the claim from that latest read before constructing the mutation.Spec
None.
DocksDocks commentedon Oct 23, 2026
Code review round 2
Code-review: pass
Standards
None.
Spec
None.
DocksDocks commentedon Oct 23, 2026
Code review round 3
Code-review: pass
Standards
None.
Spec
None.
test: require each test contract to name its gate runner
fix(plan-lifecycle): wait for GitHub's close before a partial-merge r…
feat(docks): add report-skill-issue and a report section in every skill
fix(docks): build the report-skill-issue link with a script
fix(plan-lifecycle): refuse reopen for a plan closed before the merge
fix(plan-lifecycle): recheck the owner at reopen's pre-write read
fix(docks): report-skill-issue quotes the loaded copy, not a checkout
feat(plan-lifecycle): run code review as a lens panel, then delta rounds
feat: add a project-local benchmark for the plan-lifecycle reviewers
fix: unwrap structured reviewer output and add a planted plan delta case
Merge pull request #105 from DocksDocks/102-fix-the-test-tooling-and-…