From b42a8314396730c786909b1e27c0ac933864bcc2 Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Tue, 28 Jul 2026 14:10:08 +0100 Subject: [PATCH 1/2] =?UTF-8?q?feat(skills):=20add=20/issue=5Fcleanup=20?= =?UTF-8?q?=E2=80=94=20issue-tracker=20reconciliation=20door?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No skill owned the GitHub issue trackers: /repo_cleanup sweeps git debris only, /community handles external users' issues, and /create_issue and /update_issue are single-issue primitives. The trackers had drifted to 82 open across 18 repos, 35 of them closable. Adds /issue_cleanup as the issue-tracker counterpart to /repo_cleanup, following the same audit-first shape: audit -> bucketed dashboard -> per-bucket human confirmation -> execute -> recap. Closing requires two independent evidence legs (a PyAutoMind record header plus a merged PR) and always a human confirmation. The reasoning is the deliverable. A naive "record references the issue -> close it" rule is wrong five ways, each found by verification: 1. Body mentions are not claims — only header lines are evidence, and only for a known set of keys (`- notes:` prose cites issue URLs freely). 2. The header KEY carries the meaning: `issue:` completes, but `followup-issue:`/`parent-issue:`/`plan:` mean the record SPAWNED a still-open issue. Completing keys are an allowlist so new spawn-style keys fail closed. 3. Inline `(open …)`/`(STAYS OPEN …)` annotations and `Status: issued` override — a record in complete/ can have merely FILED its issue. 4. A phase-scoped claim ("(Phase 5 item 4)") does not complete an umbrella. 5. Age is not evidence of obsolescence — probe whether the named API still exists. Six 2018-19 PyAutoCTI issues closed on a zero-hit grep; two PyAutoHands issues of the same vintage stayed open as still-valid asks. Rules 1 and 4 were caught by the skill's own regression bar rather than by inspection, which is why the bar is part of the skill: it pins the post-sweep state (47 open / A=0 / B=1 / C=7 / F=22) and names the two traps — PyAutoGalaxy#417 must land in the backlog, not a held bucket, and PyAutoBrain#130 must land in B, not A. Also wires the door into COMMANDS.md as a new "maintenance doors" tier alongside /repo_cleanup, and adds a read-only issue-drift step to /wake_up so drift surfaces daily instead of at 82-issue depth. The audit half is safe to run unattended; every close stays human-gated. Refs #174 Co-Authored-By: Claude Opus 5 --- skills/COMMANDS.md | 22 +++ skills/issue_cleanup/SKILL.md | 28 +++ skills/issue_cleanup/issue_cleanup.md | 141 +++++++++++++++ skills/issue_cleanup/reference.md | 251 ++++++++++++++++++++++++++ skills/wake_up/wake_up.md | 8 + 5 files changed, 450 insertions(+) create mode 100644 skills/issue_cleanup/SKILL.md create mode 100644 skills/issue_cleanup/issue_cleanup.md create mode 100644 skills/issue_cleanup/reference.md diff --git a/skills/COMMANDS.md b/skills/COMMANDS.md index af67b82..336977d 100644 --- a/skills/COMMANDS.md +++ b/skills/COMMANDS.md @@ -85,6 +85,28 @@ call, so the Brain is not bypassed): local-only steps. Interactive/terminal only (the automated morning webhooks are separate). +**5. Maintenance doors** — periodic sweeps that reason about accumulated debris +and then execute their own cleanup after per-bucket human confirmation. They own +no agent, but unlike the composition doors they *mutate*, so each is +audit-first and confirmation-gated: + +- **`/repo_cleanup`** — git debris across the canonical checkouts and worktrees: + stale feature branches, `[gone]` tracking refs, stashes, dirty checkouts, + orphan worktrees. Local-dev only (needs the checkouts). The Hygiene Agent's + `tidy` mode is its front door; this skill is the executor. +- **`/issue_cleanup`** — the GitHub **issue trackers**: audit every open issue + against the PyAutoMind completion records and merged PRs, bucket them + (shipped / weak evidence / deliberately open / in flight / external / + unreconciled), close only what a human confirms. Runs anywhere `gh` is + authenticated, including mobile/Codex. Closing needs **two independent + evidence legs**, and the record header *key* decides meaning — `issue:` + completes, `followup-issue:` / `parent-issue:` / `plan:` mean the record + *spawned* a still-open issue. `/wake_up` runs its audit half read-only. + +These two are complements, not overlaps: `/repo_cleanup` never touches issues, +`/issue_cleanup` never touches git. Neither handles **external** users' issues — +that is `/community`, whose drafts stay human-approved. + Codex skills also expose the remaining public CLI agents directly: the `clone` conductor, the `release` conductor, and the read-only `vitals`, `review`, `memory`, and `samplers` faculties. They do not gain new diff --git a/skills/issue_cleanup/SKILL.md b/skills/issue_cleanup/SKILL.md new file mode 100644 index 0000000..dce1b2c --- /dev/null +++ b/skills/issue_cleanup/SKILL.md @@ -0,0 +1,28 @@ +--- +name: issue-cleanup +description: Reconcile the PyAuto GitHub issue trackers — audit every open issue against the PyAutoMind completion records and merged PRs, bucket them (shipped / weak evidence / deliberately open / in flight / external / unreconciled), then close only what a human confirms. Use for tracker drift, stale issue backlogs, or "are any of these already done?". +--- + +# Issue Cleanup + +Thin discovery wrapper. The canonical command body is +[`issue_cleanup.md`](issue_cleanup.md) in this directory — follow it exactly; +the long-form detail (header taxonomy, evidence rules, dashboard layout, +per-bucket execution, regression bar) is in [`reference.md`](reference.md). + +The issue-tracker counterpart to `/repo_cleanup`'s git-debris sweep: audit → +bucketed dashboard → per-bucket human confirmation → execute → recap. + +**Two things to know before running it:** + +- **The record header *key* decides everything.** `issue:` means a PyAutoMind + record completed that issue; `followup-issue:`, `library-followup-issue:`, + `parent-issue:`, `upstream-issues-filed:` and `plan:` mean the record + *spawned* it and it is legitimately open. Treat the completing keys as an + allowlist — a loose `*issue*:` match closes live follow-ups. +- **Closing requires two independent evidence legs** (record header + merged + PR) and always a human confirmation. The audit half is read-only and safe to + run unattended; `/wake_up` does exactly that and reports counts only. + +Do not duplicate or reinterpret the workflow here — if it changes, edit +`issue_cleanup.md`. diff --git a/skills/issue_cleanup/issue_cleanup.md b/skills/issue_cleanup/issue_cleanup.md new file mode 100644 index 0000000..556b918 --- /dev/null +++ b/skills/issue_cleanup/issue_cleanup.md @@ -0,0 +1,141 @@ +# Issue Cleanup — reconcile the GitHub issue trackers + +Periodic reconciliation sweep across every PyAuto repo's **GitHub issue +tracker**. Finds issues whose work has demonstrably shipped but which were never +closed, separates them from issues that are deliberately open, still in flight, +or owned by the community, and closes only what a human confirms. + +A **PyAutoBrain dev-workflow** hygiene skill — the issue-tracker counterpart to +`/repo_cleanup`'s git-debris sweep. Like that skill it **reasons** about what is +safe to close and then runs its own `gh` mechanics: it reads the PyAutoMind +completion records to know what shipped, cross-checks GitHub for merged PRs, and +executes the closes. Cleanup is not release work, so it never touches +PyAutoHands. Organ boundary + execution-environment model: +[`../WORKFLOW.md`](../WORKFLOW.md). + +**Distinct from:** `/repo_cleanup` (branches, refs, stashes, worktrees — git +debris, never issues); `/community` (**external** users' issues awaiting *our +reply* — this skill never touches those, it routes them there); `/create_issue` +and `/update_issue` (single-issue primitives); `/hygiene` (code-quality debt, not +the tracker). + +## Why this needs care + +The obvious rule — *"a PyAutoMind `complete/` record references this issue, so +close it"* — is **wrong five different ways**. Each was found by verification, +not inspection: four during the 2026-07-28 sweep that took the trackers from 82 +open to 47, and the fifth when this skill's own regression bar was first run +against that result. Encode all five or the skill will close live work: + +1. **Body mentions are not claims.** Grepping record *files* for an issue URL + over-matches. `complete/2026/05/many-vis-prep-dft.md` discusses + `PyAutoArray#326` in its prose while its own `- issue:` header reads + `(CI-triage cluster G, no GitHub issue)`. **Only the header line is + evidence.** + +2. **The header *key* carries the meaning.** This is the load-bearing rule. + `issue:` means *this record completes that issue*. The spawn-style keys mean + the **opposite** — the record *created* that issue and it is legitimately + open. Full taxonomy in [`reference.md`](reference.md); matching loosely on + `*issue*:` closes live follow-ups. + +3. **Inline annotations override everything.** Records mark deliberate + exceptions in the text after the URL: `(open — findings census stays as + reference)`, `(STAYS OPEN — real finding + resumable fit)`. Any `open` token + means hands off — and these appear on `plan:` lines too, not just `issue:`. + +4. **A record in `complete/` does not mean the *work* completed.** + `ep-hierarchical-scale-collapse.md` carries `Status: issued` — it *filed* + `PyAutoFit#1405` and reported two defects on it. The record is complete; the + issue is a live bug. **Read the status field, not the directory.** + +5. **A phase-scoped claim does not complete an umbrella issue.** Four separate + records each claim a *Phase 5 item* of `PyAutoBrain#130` — `(Phase 5 item 4)`, + `(Phase 5 F5 item)`, and so on. Each finished its piece; none establishes the + issue is done. Scoped claims go to bucket B for a human, never to A. + +And a corroborating sixth: `PyAutoReduce#8`'s record already said `(CLOSED)` +while GitHub still had it open. Closes silently fail to land, which is part of +why reconciling is worth doing at all. + +## Safety principles (non-negotiable) + +1. **Audit first, act second.** The audit is read-only and safe to run + unattended. Nothing closes without an explicit per-bucket confirmation. +2. **Two independent evidence legs** before an issue is even *proposed* for + closing: a completing header key in a record, **and** a merged PR. +3. **Never close an external user's issue.** Author not in the maintainer set → + bucket E, routed to `/community`, never closed here. +4. **Never close anything claimed** in `PyAutoMind/active.md` / `parked.md`. +5. **Age is not evidence.** A 2700-day issue may still be valid; probe whether + the API or infrastructure it names still exists (see "Obsolescence" below). +6. **Every close leaves a comment naming its evidence** (record path + PR), so + the decision is auditable and reversible. + +## Scope + +**Swept:** every repo in `PyAutoMind/repos.yaml` with an issue tracker — the +organs, the libraries, the workspaces incl. `_test`/`_developer` variants, the +HowTo repos and the assistants. + +**Never touched:** pull requests (the issues endpoint returns them too — filter +`select(.pull_request == null)`); issues in bucket C, D or E. + +## Steps + +### 1. Setup + +Confirm `gh auth status`; if unauthenticated, stop. Resolve repo owners from +`PyAutoMind/repos.yaml` — do not hard-code legacy owner defaults. + +### 2. Audit (read-only) + +Collect every open issue, then build the evidence sets from the PyAutoMind +records. Commands, the header taxonomy, and the annotation rule: +[`reference.md`](reference.md) → "Collect open issues", "Parse the records", +"Evidence legs". + +### 3. Dashboard + +Present the audit grouped into fixed buckets A–F (omit empty buckets; always +print the Summary counts). Layout: [`reference.md`](reference.md) → "Dashboard +layout". + +### 4. Per-bucket confirmation + execution + +Work the buckets in the fixed order, printing what will be closed and getting +approval before each destructive step. Recipes: [`reference.md`](reference.md) +→ "Per-bucket execution". + +### 5. Recap + +Print what closed, what was held back and why, so the next sweep resumes from +there. Format: [`reference.md`](reference.md) → "Recap". + +## Obsolescence needs a real check, not an age threshold + +The eight oldest issues in the first sweep split on **evidence**, not age: + +- The six `PyAutoCTI` ones (2693–2785 days) named `FrameGeometry`, `CIFrame`, + `CIData`, `ci_data_analysis`, `ci_pattern`, and the `phase.py` / `pipeline` + module. A grep of `autocti/` returned **zero** for every one — the CTI + resurrection removed that surface entirely — so they closed `not_planned`. +- `PyAutoHands#16` (test against pre-release deps) and `#17` (fail the build if + the RTD docs build fails) are the same vintage (~1337 days) but were **left + open**: no `--pre` workflow and no RTD gating exist in + `PyAutoHands/.github/workflows/`, so they are still-valid unimplemented asks. + +Age correlates with obsolescence; it never establishes it. Probe whether the +named API or infrastructure still exists before proposing a close. + +## Notes + +- `gh issue close` is broken in this environment — comment, then + `gh api -X PATCH … -f state=closed`. Full recipe in [`reference.md`](reference.md). +- Bot-authored self-refreshing issues (`[url-check]`, `[heart-health]`) are + recognised and excluded from staleness ranking — they are a live signal, not + debris. +- `/wake_up` runs the **audit half only** and reports counts in its digest; + every close still comes back through this skill's confirmation. +- If a record's claim and GitHub disagree, trust GitHub for *state* and the + record for *intent* — then reconcile, and say which one was stale. diff --git a/skills/issue_cleanup/reference.md b/skills/issue_cleanup/reference.md new file mode 100644 index 0000000..393486f --- /dev/null +++ b/skills/issue_cleanup/reference.md @@ -0,0 +1,251 @@ +# issue_cleanup — reference detail + +Factored out of `issue_cleanup.md`. The body is authoritative for the flow; this +holds the collection commands, the record header taxonomy, the evidence rules, +the dashboard layout, the per-bucket execution recipes, the recap, and the +execution-environment fallback. + +## Collect open issues + +Resolve the repo list and owners from `PyAutoMind/repos.yaml`. For each repo: + +```bash +gh api "repos///issues?state=open&per_page=100" --paginate \ + --jq '[.[] | select(.pull_request == null) | {num:.number, title:.title, + author:.user.login, created:.created_at, updated:.updated_at, + labels:[.labels[].name], comments:.comments}]' +``` + +`select(.pull_request == null)` is **required** — the issues endpoint returns +pull requests too, and counting them inflates every total. + +Note: this `gh` build rejects several documented `--json` fields on +`gh issue list` / `gh run view` (`displayTitle`, `jobs`, …). Use `gh api` for +anything structured; it is the reliable path here. + +## Parse the records + +Evidence comes from `PyAutoMind/complete/**/*.md`. Two rules govern it. + +### Rule 1 — only *known* header lines count + +Match `^\s*-?\s*:\s*` and read the issue URL out of +``. A URL appearing anywhere else in the file is **prose**, not a +claim: `complete/2026/05/many-vis-prep-dft.md` discusses `PyAutoArray#326` in +its notes while its own `- issue:` header says `(CI-triage cluster G, no GitHub +issue)`. + +**`` must be in the known set below — an arbitrary `- :` is not a +header.** Records use `- notes:` for long prose paragraphs that routinely cite +issue URLs, so a bare "any `- key:` line" regex re-admits exactly the prose +Rule 1 exists to exclude. `PyAutoGalaxy#417` is the worked example: it appears +only inside another record's `- notes:` field, so nothing claims it and it +belongs in the unreconciled backlog. + +### Rule 2 — the header key decides the meaning + +| Key | Uses¹ | Meaning | Closable? | +|-----|------:|---------|-----------| +| `issue:` | 630 | This record **completes** that issue | **yes** | +| `issues:` | 6 | Same, several issues on one line | **yes** | +| `followup-issue:` | 2 | Record **spawned** it; still open | **no** | +| `follow-up-issue:` | 1 | Record **spawned** it; still open | **no** | +| `library-followup-issue:` | 1 | Record **spawned** it; still open | **no** | +| `parent-issue:` | 1 | Umbrella this record is one part of | **no** | +| `upstream-issues-filed:` | 1 | Filed upstream, not ours to close | **no** | +| `issued:` | 1 | Filed only — see Rule 3 | **no** | +| `plan:` | — | Implementation backlog, deliberately open | **no** | + +¹ counts from the 2026-07-28 census of 800 records; re-derive rather than trust +these, but the **ratio** is the point — the completing key dominates, so a loose +`*issue*:` match looks right on a spot-check and silently closes live +follow-ups. + +Treat the completing set as an **allowlist** (`issue:`, `issues:`), never the +spawn set as a denylist — a new spawn-style key added later must fail closed. + +The table above is also the **known-key set** for Rule 1: a `- :` line +whose key is not in it (`notes:`, `summary:`, `repos:`, …) is prose, and its +URLs are not claims of any kind. + +### Rule 3 — annotations and status override + +- Any `open` token (case-insensitive) in the text **after** the URL means hands + off: `(open)`, `(open — findings census stays as reference)`, `(STAYS OPEN — + real finding + resumable fit)`, `(both stay OPEN for parked design work + only)`. Scan `plan:` lines for these too. +- A record carrying `Status: issued` **filed** its issue rather than completing + it (`ep-hierarchical-scale-collapse.md` → `PyAutoFit#1405`, a live bug). + Read the status field; `complete/` is the directory, not the verdict. + +### Rule 4 — a phase-scoped claim does not complete an umbrella + +If the annotation scopes the claim to part of the work — `(Phase 5 F5 item)`, +`(Phase 5 item 4)`, `(WP 2)`, `(stage b)` — the record completed *that piece*, +not the issue. Several records may each claim a different phase of the same +umbrella without any of them finishing it. + +Route these to **bucket B** (weak evidence), never A. `PyAutoBrain#130` is the +worked example: four separate records claim Phase-5 items of it, and no +combination of them establishes that Phase 5 as a whole is done. Deciding that +needs a human reading the issue, which is exactly what bucket B is for. + +## Evidence legs + +An issue reaches bucket A only with **both**: + +1. **Record leg** — a completing header key claims it, with no `open` + annotation and no `Status: issued`. +2. **PR leg** — a merged PR. Check **both** paths; the timeline alone + under-reports (12 of 29 in the first sweep had only the second): + +```bash +# path 1 — GitHub's own cross-reference +gh api "repos///issues//timeline?per_page=100" \ + --jq '[.[] | select(.event=="cross-referenced" and .source.issue.pull_request != null) + | {pr:.source.issue.number, merged:(.source.issue.pull_request.merged_at != null)}]' + +# path 2 — a PR number named in the record body, verified merged +gh api "repos///pulls/" --jq 'if .merged_at then "MERGED" else .state end' +``` + +## Buckets + +| Bucket | Definition | Action | +|---|---|---| +| **A** shipped | both evidence legs pass | closable, on confirmation | +| **B** weak evidence | a record exists but a leg fails | report only | +| **C** deliberately open | annotation or `Status: issued` says so | never touch | +| **D** in flight | claimed in `active.md` / `parked.md` | never touch | +| **E** external | author not in the maintainer set | route to `/community` | +| **F** unreconciled | no record at all | the real backlog; sub-split by age | + +The maintainer set comes from `repos.yaml` ownership; everyone else is external. +Bot authors (`github-actions[bot]`) are their own sub-group: self-refreshing +issues like `[url-check]` and `[heart-health]` re-open themselves by design and +must be excluded from staleness ranking. + +## Dashboard layout + +``` +Issue Cleanup — Audit +===================== +Summary + 18 repos scanned · 82 open issues + A 29 closable · B 7 weak · C 5 held · D 6 in flight · E 8 external · F 27 unreconciled + +Bucket A — Shipped, closable (record header + merged PR) +Bucket B — Record exists, evidence incomplete (report only) +Bucket C — Deliberately open (annotated in the record) +Bucket D — In flight (active.md / parked.md) +Bucket E — External, awaiting our reply → /community +Bucket F — Unreconciled backlog (no record) — split: ancient / live / bot +``` + +One section per bucket, issues grouped by repo, each line carrying the evidence +that classified it (record path, PR number, or the annotation text). Omit empty +buckets; always print the Summary counts even if zero. + +## Per-bucket execution (fixed order) + +Print the exact list and get approval before each destructive step. + +1. **Bucket A** — batch confirm, then per issue: comment the evidence, then + close as completed. + + ```bash + gh api repos///issues//comments -f body="$(cat <<'EOF' + Closing as complete — swept by an issue-tracker reconciliation pass. + + This work shipped and is recorded in PyAutoMind: + - `PyAutoMind/complete///.md` + + Verified via two independent legs: the record claims this issue in its + `- issue:` header, and its pull request is confirmed merged. Reopen if + anything here is still outstanding. + EOF + )" + gh api -X PATCH repos///issues/ \ + -f state=closed -f state_reason=completed + ``` + + `gh issue close` is broken in this environment — it must be the comment + + `PATCH` pair. `--jq .state` on the PATCH gives a one-word confirmation per + issue; a run that prints anything other than `closed` needs investigating, + not retrying. + +2. **Bucket F, ancient sub-group** — never batch. Per issue: read the body, + grep the current source for the API/module/infrastructure it names, and + decide. + - Named surface is **gone** → close with `state_reason=not_planned` and a + comment saying what was greped and that it returned zero. + - Named surface is **absent but still wanted** (the ask was never + implemented) → **keep open**, and say so in the recap. + +3. **Buckets B, C, D, E** — no action. Report them so the next sweep does not + re-derive the same conclusions, and point bucket E at `/community`. + +## Recap + +``` +Issue Cleanup — Recap + Closed N as completed (bucket A) + Closed N as not_planned (obsolete, bucket F) + Held N deliberately-open · N in-flight · N external + Left N unreconciled — the real backlog + Kept open despite age: +``` + +List everything kept and why, so the next sweep resumes from there rather than +re-litigating it. + +## Regression bar + +The audit is deterministic given the same trackers and records, so it can be +checked rather than eyeballed. Against the post-sweep state of 2026-07-28 it +must reproduce exactly: + +| Bucket | Count | Notes | +|---|---:|---| +| open issues | 47 | across the 18 repos with trackers | +| **A** closable | **0** | all 29 were closed that day | +| **B** partial/umbrella | 1 | `PyAutoBrain#130` — Rule 4 | +| **C** deliberately open | 7 | see below | +| spawn-key held | 3 | Rule 2 | +| **D** in flight | 6 | `active.md` / `parked.md` | +| **E** external | 8 | community backlog | +| **F** unreconciled | 22 | the real backlog | + +Bucket C must contain exactly `PyAutoArray#377`, `PyAutoFit#1330`, +`PyAutoFit#1332`, `PyAutoFit#1338`, `PyAutoFit#1405`, `PyAutoReduce#13`, +`PyAutoReduce#17` — note `#1338` arrives via an annotated `plan:` line and +`#1405` via `Status: issued`, so this count also exercises Rule 3. + +The three spawn-key holds are `PyAutoArray#326` (`library-followup-issue`), +`autolens_workspace_test#106` (`follow-up-issue`) and `autolens_workspace_test#77` +(`followup-issue`) — the check that catches a regression to loose `*issue*:` +matching. + +Two traps this bar exists to catch, both of which a first implementation got +wrong: + +- **`PyAutoGalaxy#417` must land in F, not in a held bucket.** It is cited only + inside another record's `- notes:` prose. If it shows up as "held", the + header-key allowlist (Rule 1) has regressed to matching any `- word:` line — + right answer, wrong reason. +- **`PyAutoBrain#130` must land in B, not A.** If it appears as closable, Rule 4 + has regressed and umbrella issues are being closed on partial evidence. +- `PyAutoHands#16` and `#17` stay in F despite being ~1337 days old — match them + exactly, not by a `PyAutoHands#1*` prefix, which also catches `#127`/`#156`/`#161`. + +## Execution environments + +The audit is pure `gh` + reading `PyAutoMind/`, so it runs anywhere `gh` is +authenticated — including mobile Claude Code chat and Codex, where +`/repo_cleanup` cannot run at all. No local library checkout is needed. + +The one degraded piece is the obsolescence probe in bucket F, which greps repo +source: without a local checkout, use +`gh api repos///contents/` or the code-search API, and if +neither is available report the ancient issues as **candidates only** rather +than closing them. diff --git a/skills/wake_up/wake_up.md b/skills/wake_up/wake_up.md index d2e8613..76bb672 100644 --- a/skills/wake_up/wake_up.md +++ b/skills/wake_up/wake_up.md @@ -66,6 +66,12 @@ Run in order, then emit the digest. ranking. Surface who is waiting and for how long; respond via **`/community`** (drafts are human-approved there — never auto-reply from the digest). +6b. **Issue-tracker drift** — run the **audit half of `/issue_cleanup`** + (read-only, `gh` + `PyAutoMind/complete/`): how many issues are open, and how + many look shipped-but-still-open. Report the counts only — **never close + anything from the digest**; closing is `/issue_cleanup`'s own + confirmation-gated step. A rising shipped-but-open count means ship flows are + skipping their issue close. 7. **Resume context** — pick up where you left off: in-flight / parked / queued work (`PyAutoMind/active.md`, `parked.md`, `queue.md`) + open **pending-release PRs** (`gh`); **locally** also worktrees with unpushed commits. @@ -79,6 +85,8 @@ Emit one prioritized card: - ⚠️ **Drifted** — off-main / behind repos, version-pin mismatches. - 🔄 **Resume** — in-flight / parked tasks, open pending-release PRs. - 🧹 **Cleanable** — cleanup surfaced *for approval*: list it, never auto-act. + Includes the issue-tracker counts from step 6b (point at **`/issue_cleanup`**) + alongside the git debris (**`/repo_cleanup`**). - ✅ **Clear** — say so in one line. End with a one-line verdict — *"clear to work"* or *"N things need you"* — and, in From e20923b75c2ae478058d694b6083133fc0ee5888 Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Tue, 28 Jul 2026 14:17:20 +0100 Subject: [PATCH 2/2] docs(issue_cleanup): make the skill harness-neutral for Codex as well as Claude The body referenced sibling doors in Claude-only /verb notation. Switch to the established $verb form used elsewhere ($repo-cleanup, $community, $create-issue, $update-issue, $hygiene, $wake-up), keeping one explicit dual-notation line so the Claude spelling is still discoverable. Also records why this skill is the harness-portable half of the cleanup pair: it needs only an authenticated gh and a readable PyAutoMind/, so it runs on Codex and mobile where /repo_cleanup cannot. Adds the constraint that no step may depend on a local checkout, a Claude-only tool or a ~/.claude path, and notes that SKILL.md's `name:` must stay hyphenated because Codex takes its skill name from it. Co-Authored-By: Claude Opus 5 --- skills/issue_cleanup/SKILL.md | 4 ++-- skills/issue_cleanup/issue_cleanup.md | 19 ++++++++++++------- skills/issue_cleanup/reference.md | 16 ++++++++++++---- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/skills/issue_cleanup/SKILL.md b/skills/issue_cleanup/SKILL.md index dce1b2c..5051c90 100644 --- a/skills/issue_cleanup/SKILL.md +++ b/skills/issue_cleanup/SKILL.md @@ -10,7 +10,7 @@ Thin discovery wrapper. The canonical command body is the long-form detail (header taxonomy, evidence rules, dashboard layout, per-bucket execution, regression bar) is in [`reference.md`](reference.md). -The issue-tracker counterpart to `/repo_cleanup`'s git-debris sweep: audit → +The issue-tracker counterpart to `$repo-cleanup`'s git-debris sweep: audit → bucketed dashboard → per-bucket human confirmation → execute → recap. **Two things to know before running it:** @@ -22,7 +22,7 @@ bucketed dashboard → per-bucket human confirmation → execute → recap. allowlist — a loose `*issue*:` match closes live follow-ups. - **Closing requires two independent evidence legs** (record header + merged PR) and always a human confirmation. The audit half is read-only and safe to - run unattended; `/wake_up` does exactly that and reports counts only. + run unattended; `$wake-up` does exactly that and reports counts only. Do not duplicate or reinterpret the workflow here — if it changes, edit `issue_cleanup.md`. diff --git a/skills/issue_cleanup/issue_cleanup.md b/skills/issue_cleanup/issue_cleanup.md index 556b918..83499c6 100644 --- a/skills/issue_cleanup/issue_cleanup.md +++ b/skills/issue_cleanup/issue_cleanup.md @@ -5,18 +5,23 @@ tracker**. Finds issues whose work has demonstrably shipped but which were never closed, separates them from issues that are deliberately open, still in flight, or owned by the community, and closes only what a human confirms. +Run it as `$issue-cleanup` (`/issue_cleanup` in Claude) — it needs only an +authenticated `gh` and a readable `PyAutoMind/`, so unlike `$repo-cleanup` it +works on **any harness and any environment**, including mobile Claude Code chat +and Codex, with no local library checkout. + A **PyAutoBrain dev-workflow** hygiene skill — the issue-tracker counterpart to -`/repo_cleanup`'s git-debris sweep. Like that skill it **reasons** about what is +`$repo-cleanup`'s git-debris sweep. Like that skill it **reasons** about what is safe to close and then runs its own `gh` mechanics: it reads the PyAutoMind completion records to know what shipped, cross-checks GitHub for merged PRs, and executes the closes. Cleanup is not release work, so it never touches PyAutoHands. Organ boundary + execution-environment model: [`../WORKFLOW.md`](../WORKFLOW.md). -**Distinct from:** `/repo_cleanup` (branches, refs, stashes, worktrees — git -debris, never issues); `/community` (**external** users' issues awaiting *our -reply* — this skill never touches those, it routes them there); `/create_issue` -and `/update_issue` (single-issue primitives); `/hygiene` (code-quality debt, not +**Distinct from:** `$repo-cleanup` (branches, refs, stashes, worktrees — git +debris, never issues); `$community` (**external** users' issues awaiting *our +reply* — this skill never touches those, it routes them there); `$create-issue` +and `$update-issue` (single-issue primitives); `$hygiene` (code-quality debt, not the tracker). ## Why this needs care @@ -65,7 +70,7 @@ why reconciling is worth doing at all. 2. **Two independent evidence legs** before an issue is even *proposed* for closing: a completing header key in a record, **and** a merged PR. 3. **Never close an external user's issue.** Author not in the maintainer set → - bucket E, routed to `/community`, never closed here. + bucket E, routed to `$community`, never closed here. 4. **Never close anything claimed** in `PyAutoMind/active.md` / `parked.md`. 5. **Age is not evidence.** A 2700-day issue may still be valid; probe whether the API or infrastructure it names still exists (see "Obsolescence" below). @@ -135,7 +140,7 @@ named API or infrastructure still exists before proposing a close. - Bot-authored self-refreshing issues (`[url-check]`, `[heart-health]`) are recognised and excluded from staleness ranking — they are a live signal, not debris. -- `/wake_up` runs the **audit half only** and reports counts in its digest; +- `$wake-up` runs the **audit half only** and reports counts in its digest; every close still comes back through this skill's confirmation. - If a record's claim and GitHub disagree, trust GitHub for *state* and the record for *intent* — then reconcile, and say which one was stale. diff --git a/skills/issue_cleanup/reference.md b/skills/issue_cleanup/reference.md index 393486f..51f6bfe 100644 --- a/skills/issue_cleanup/reference.md +++ b/skills/issue_cleanup/reference.md @@ -117,7 +117,7 @@ gh api "repos///pulls/" --jq 'if .merged_at then "MERGED" else | **B** weak evidence | a record exists but a leg fails | report only | | **C** deliberately open | annotation or `Status: issued` says so | never touch | | **D** in flight | claimed in `active.md` / `parked.md` | never touch | -| **E** external | author not in the maintainer set | route to `/community` | +| **E** external | author not in the maintainer set | route to `$community` | | **F** unreconciled | no record at all | the real backlog; sub-split by age | The maintainer set comes from `repos.yaml` ownership; everyone else is external. @@ -138,7 +138,7 @@ Bucket A — Shipped, closable (record header + merged PR) Bucket B — Record exists, evidence incomplete (report only) Bucket C — Deliberately open (annotated in the record) Bucket D — In flight (active.md / parked.md) -Bucket E — External, awaiting our reply → /community +Bucket E — External, awaiting our reply → $community Bucket F — Unreconciled backlog (no record) — split: ancient / live / bot ``` @@ -183,7 +183,7 @@ Print the exact list and get approval before each destructive step. implemented) → **keep open**, and say so in the recap. 3. **Buckets B, C, D, E** — no action. Report them so the next sweep does not - re-derive the same conclusions, and point bucket E at `/community`. + re-derive the same conclusions, and point bucket E at `$community`. ## Recap @@ -242,7 +242,15 @@ wrong: The audit is pure `gh` + reading `PyAutoMind/`, so it runs anywhere `gh` is authenticated — including mobile Claude Code chat and Codex, where -`/repo_cleanup` cannot run at all. No local library checkout is needed. +`$repo-cleanup` cannot run at all. No local library checkout is needed. + +This is the harness-portable half of the pair, so keep it that way: no step may +depend on a local library checkout, a Claude-only tool, or a `~/.claude` path. +Commands in this file are plain `gh` + POSIX shell for exactly that reason. +`bin/install.sh` installs the skill into `~/.claude/skills/issue_cleanup`, +`~/.claude/commands/issue_cleanup.md` and `~/.codex/skills/issue-cleanup` (Codex +takes the hyphenated `name:` from `SKILL.md`, which is why that frontmatter must +stay hyphenated). The one degraded piece is the obsolescence probe in bucket F, which greps repo source: without a local checkout, use