feat(skills): add aicr-triage skill for project board triage - #1911
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds the Estimated code review effort: 3 (Moderate) | ~25 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.agents/skills/aicr-triage/skill.md:
- Around line 1-12: Rename the skill file from skill.md to SKILL.md so it
matches the repository’s required skill filename and remains discoverable on
case-sensitive filesystems.
- Around line 99-103: Update the issue lookup command in the skill instructions
to use the board item’s repository via content.repository.nameWithOwner instead
of the hardcoded NVIDIA/aicr value, while continuing to use content.number for
the issue number.
- Around line 4-11: The advertised behavior in the skill description does not
match the implemented workflow. Update the `/aicr-triage` skill by either adding
discovery and apply paths for P2-to-P1 promotion, Ready-to-Backlog demotion, and
stale-issue closure—including their priority option IDs—or narrowing the
description to the currently supported unclassified-item flow and its fixed P2
update behavior.
- Around line 33-36: Update the specific-issue triage path in the skill
instructions to look up the project item for the named repository issue before
classification. Resolve both the project item ID and its current Status by
querying the project using the repository and issue number, then pass those
values into Step 5 so the requested board edits can be applied.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Enterprise
Run ID: 9c0784ce-6820-416b-a15f-d44df5fec1fa
📒 Files selected for processing (1)
.agents/skills/aicr-triage/skill.md
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.agents/skills/aicr-triage/skill.md:
- Around line 95-102: The named-issue path must explicitly handle both existing
and missing project items: state whether an item with an existing Status is
reclassified, and when no matching board item exists, report “issue is not on
project” rather than “No unclassified issues found.” Update the surrounding Step
2 guidance to cover named issues separately from the unclassified-item scan,
using the existing item id, Status, and issue details.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Enterprise
Run ID: 346d3986-0859-4cb4-a602-7ff410eadbeb
📒 Files selected for processing (1)
.agents/skills/aicr-triage/skill.md
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.agents/skills/aicr-triage/SKILL.md:
- Around line 144-149: Update the triage workflow around the Status and Priority
gh project item-edit operations to stop immediately when either edit fails,
record which field was successfully updated, and avoid reporting the item as
completed. Before including an item in the final summary, verify that both
Status and Priority match the intended values; otherwise report the partial or
failed state explicitly.
- Around line 27-29: Update the priority guidance table in SKILL.md to define
distinct criteria for selecting P0 versus P1 before referencing IDs 79628723 and
0a877460. Specify the escalation threshold and impact characteristics for each
priority, while preserving the existing P2 guidance and identifiers.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Enterprise
Run ID: 87dc42d8-ec30-4b8a-9d01-01004ce513f7
📒 Files selected for processing (1)
.agents/skills/aicr-triage/SKILL.md
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.agents/skills/aicr-triage/SKILL.md:
- Around line 94-98: Update the specific-issue board query and the
existing-classification branch in the issue triage workflow to retrieve and
display both Status and Priority before asking whether to reclassify. Preserve
the no-board-item and unclassified-item behavior, and use the fetched values
when showing the current classification.
- Around line 94-98: The named-issue handling in the triage workflow must run
before the empty unclassified-items early return. Update the surrounding flow in
SKILL.md so named invocations perform the board lookup and handle missing or
already-classified items, while the “No unclassified issues found” stop applies
only when no issue number was provided.
- Line 94: Update the Step 1 GraphQL lookup for a specified issue number to also
match content.repository.nameWithOwner against the target repository, rather
than filtering only by content.number. If the repository cannot be determined or
multiple items remain, reject the request as ambiguous before proceeding to Step
5, while preserving retrieval of the board item id and current Status.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Enterprise
Run ID: 7cf82e82-3d3d-45eb-9f2b-474eae65c08a
📒 Files selected for processing (1)
.agents/skills/aicr-triage/SKILL.md
ArangoGutierrez
left a comment
There was a problem hiding this comment.
Found 1 issue.
- Skill placed in
.agents/skills/aicr-triage/SKILL.mdinstead of.claude/skills/aicr-triage/SKILL.md. All 6 existing project skills live under.claude/skills/, and that is the directory Claude Code scans for skill discovery. The.agents/skills/path does not exist in the repo, so this skill will never auto-load. (.agents/skills/aicr-triage/SKILL.md:1)
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.agents/skills/aicr-triage/SKILL.md:
- Around line 65-67: Update the named-issue classification path to read and
display the fetched priority alongside the existing status. Thread priority.name
through the named-item data handling and include it in the proposal table before
reclassification confirmation, preserving the current status behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Enterprise
Run ID: a429a8b1-4b5b-4f12-b295-737c1dae7be1
📒 Files selected for processing (2)
.agents/skills/aicr-triage/SKILL.mddocs/contributor/skills.md
mchmarny
left a comment
There was a problem hiding this comment.
Solid port — but worth noting up front that this is a port of an existing personal skill (~/.claude/skills/triage-issues/), and the port both gained and lost behaviors relative to its source.
Gained (real improvements, keep these): GraphQL pagination past the 200-item gh project item-list cap; set -o pipefail + empty-stdin rejection; GraphQL errors-checked-before-data; prompt-injection scoping of issue content; named-single-issue targeting; the project OAuth-scope prerequisite; an explicit P0 definition.
Lost — the three comments below are each a behavior the source skill has that this version drops, not new suggestions:
- Source: "IDs differ per project — re-fetch every run, never hardcode."
- Source Step 7: every triaged issue gets a
gh issue comment, including no-change verdicts. - Source Step 4: three buckets (promote P2→P1, demote Ready→Backlog, close), not just first-time classification.
A fourth minor one: the source confirms via AskUserQuestion with one multi-select per bucket so the user can accept a subset; this version's plain-text "Proceed?" is all-or-nothing. Fine if AskUserQuestion isn't available on the Codex path — worth saying so explicitly.
| - If the user requests changes to specific rows: update those rows, re-show the table, and ask again. | ||
| - If the user says no: stop without making any changes. | ||
|
|
||
| ### 5. Apply classification |
There was a problem hiding this comment.
Board field edits don't appear in the issue timeline, so an assignee or watcher has no signal that triage happened. The source skill mandates a comment on every triaged issue, explicitly including no-change verdicts, for exactly this reason.
Suggest adding to this step:
gh issue comment "$n" -R "$repo" \
--body "Triaged: set to <Status> / <Priority>. <1-2 sentences of reasoning>"…plus a no-change variant for the reclassify path in Step 1, where the verdict is "current placement is correct". Skip only when a triage comment from the same session already exists on the issue.
There was a problem hiding this comment.
Superseded by 94e1c25. The branch was rewritten and this reply no longer described it: there is no timestamp marker and no no-change ACK comment. Applied field changes and closures still get an issue comment. See the reply below.
There was a problem hiding this comment.
Mark, this revision intentionally omits no-change ACK comments. Applied field changes and closures still receive issue comments, while correctly placed issues appear only in the triage report.
This avoids dozens of low-signal comments and removes the timestamp-marker machinery needed because comments reset updatedAt. If ACKs are a required workflow behavior, I’ll restore them; otherwise I’d like to resolve this as an intentional simplification.
bd44f84 to
3f9ad83
Compare
ArangoGutierrez
left a comment
There was a problem hiding this comment.
Second pass, six findings. The Step 1 Python ones were reproduced by extracting the snippet from the file and running it against fixtures.
- Project 248 is an org-level board, so matching on
content.numberalone can select items from different repositories: with the same number in two repos this returns both, while the branch below only handles zero items or "the single returned item". The query already carriesrepository.nameWithOwnerfor Step 2, so filtering on it here too would disambiguate. (.agents/skills/aicr-triage/SKILL.md:116) - Step 1's GraphQL board read also requires
read:project, so a defaultgh auth logintoken (repo, read:org, gist) fails on the very first command rather than at Step 5. Moving this check ahead of Step 1 would match where it actually bites. (.agents/skills/aicr-triage/SKILL.md:33) - Bulk selection keys only on Status, so an item left with Status set and Priority empty is never re-selected and the run reports "No unclassified issues found". That is exactly the state Step 5's partial-failure stop can leave behind, so the recovery path cannot see its own failure mode. Selecting on missing Status or missing Priority would let a retry pick it up. (.agents/skills/aicr-triage/SKILL.md:120)
.get('data', {})returnsNonewhen the key is present with a JSON null, which is how GraphQL represents an unresolvable object, so this raisesAttributeErrorinstead of printing the intended "unexpected GraphQL response" diagnostic just below. The(x or {})idiom already used at line 116 handles that shape. (.agents/skills/aicr-triage/SKILL.md:107)export TARGET_ISSUE=123andunset TARGET_ISSUEare both live commands in this one block, so running it as written always ends in bulk mode and the named-issue path never triggers. Commenting out one of the two branches would keep both usable. (.agents/skills/aicr-triage/SKILL.md:58)- Issue titles are rendered into this table verbatim, and a title containing a pipe or a newline can reflow the rows the user is approving. Since this table is the only gate before writes to a shared board, escaping
|and stripping newlines when building it would keep the confirmation trustworthy. (.agents/skills/aicr-triage/SKILL.md:164)
|
Thanks — after reconciling the provenance, I had initially treated Will update this PR into a faithful shared-agent port:
Will retain the improvements added during this PR: pagination beyond 200 items, |
3f9ad83 to
45339ff
Compare
cb4bfca to
afaa9e0
Compare
2001666 to
620c224
Compare
|
Major revision — the skill was rewritten around simplification. Merged as @mchmarny @ArangoGutierrez, thanks for the reviews. |
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.agents/skills/aicr-triage/SKILL.md:
- Around line 72-75: Update the temporary-file setup near owner and num in
.agents/skills/aicr-triage/SKILL.md:72-75 to create a unique per-run directory
with restrictive permissions, then derive ITEMS and ACTIVE paths within it
instead of using fixed filenames. Update the repository snapshot paths in
.agents/skills/aicr-triage/SKILL.md:136-140 to use that same directory and
remove it after verification completes.
- Around line 368-373: Update the Step 2 “no active issues” early-return
guidance so it applies only to initial discovery, not the Step 8 verification
flow. Ensure Step 8 always checks changed closures in the full `$ITEMS` dump,
even when `$ACTIVE` is empty, and confirms their Status is Done or reports
Manual Review.
- Around line 285-287: Update the workflow’s approved-item application step to
re-fetch each item’s current Status and Priority immediately before editing,
then compare them with the confirmation-table values. Route any item whose
values changed to Manual Review and skip its write; apply edits only when both
values still match, while preserving the existing reporting behavior.
- Around line 51-66: Update the metadata reads in the project setup flow around
gh project view and gh project field-list to fail closed, using strict command
failure handling so either command aborts the run immediately. Ensure project,
field, and option IDs are captured or reused only after both reads succeed,
while preserving the existing missing-metadata validation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Enterprise
Run ID: d981fa61-fc59-47bb-98c4-fb037624543e
📒 Files selected for processing (2)
.agents/skills/aicr-triage/SKILL.mddocs/contributor/skills.md
|
Related: #1920 tracks a gap found while reviewing this PR — the repo defines "stale" three ways ( The docs half is fixed in #1921. The skill half is deliberately not in scope here — it would add behavior to a change scoped to simplification — and is left for a follow-up once this merges. |
Adds a shared agent skill for triaging the NVIDIA AICR project board (project 248): classify new issues, promote P2 to P1, demote Ready to Backlog, and propose closures, applying only user-confirmed changes. Ported from the personal triage-issues skill, keeping its structure and adding first-time and incomplete classification buckets. A verdict writes every field whose correct value differs from the current one; the bucket name labels that difference rather than capping it. Concurrency and connectivity failures are out of scope. The invariant is narrow: a nonzero gh exit in the apply step stops every subsequent write, and a closure is only reached after its comment posted successfully. Read failures differ by scope -- a failed per-issue comment fetch routes that item to Manual Review and triage continues, while a failed repository-level state fetch stops the run, since without it no item's state is known. A one-field partial write from an aborted run is repaired by the incomplete-classification bucket next run; a concurrent overwrite is not. Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
ArangoGutierrez
left a comment
There was a problem hiding this comment.
Careful skill — the failure contract in Step 7, the quoted heredocs, and the comment-before-close ordering are all the right calls. Four things, the first one worth a look.
- Step 6 asks the user to approve the exact field write (
owner/repo#N — Status → Backlog, Priority → P2), but Step 7 then posts a public comment under their identity whose body the agent authors at apply time and never showed them. The skill's invariant is scoped to fields and closures — "no field is edited and no issue is closed without this confirmation" — so the comment slips through it. For a skill whose whole premise is confirm-before-mutate, showing the comment body in the Step 5 table (or including it in the Step 6 option text) would close the gap. (.agents/skills/aicr-triage/SKILL.md:314) gh auth statusis run without an assertion, and the guidance "needs 'project' in Token scopes" is satisfied textually by the read-onlyread:projectscope. Steps 1-2 read fine underread:project, so the run gets all the way through classification and user confirmation beforegh project item-editfails on the first write — landing in the "outcome unknown" state Step 7 works hard to avoid. Grepping for the exactprojectscope token would fail it up front. (.agents/skills/aicr-triage/SKILL.md:29)- Step 8 re-runs the Step 2 fetch right after Step 7 and requires a closed issue's Status to already read Done, but Step 4 notes that transition comes from the board's built-in "item closed → Status: Done" workflow, which is asynchronous. A closure that actually succeeded can therefore show up under Manual Review on a fast re-fetch. Re-reading just the closed items after a short delay, or treating not-yet-Done as pending rather than manual, would avoid the false positive. (.agents/skills/aicr-triage/SKILL.md:374)
$ITEMSand$ACTIVEare fixed basenames carrying no board identity even thoughowner/numare run parameters, and the "One named issue" block re-reads$ITEMSin a fresh shell without re-fetching it.$TMPDIRalso resolves differently sandboxed vs unsandboxed, so a dump left by an earlier run against a different board can be picked up silently. Folding$owner/$numinto the filename the way the per-repo$OPENpath already does would make a stale read impossible. (.agents/skills/aicr-triage/SKILL.md:75)
| || { echo "$label edit failed for #$n — outcome unknown; stop and report"; exit 1; } | ||
| ``` | ||
|
|
||
| **Comment on every applied verdict** — board field edits leave no trace in the |
There was a problem hiding this comment.
Step 6 asks the user to approve the exact field write (owner/repo#N — Status → Backlog, Priority → P2), but Step 7 then posts a public comment under their identity whose body the agent authors at apply time and never showed them. The skill's invariant is scoped to fields and closures — "no field is edited and no issue is closed without this confirmation" — so the comment slips through it. For a skill whose whole premise is confirm-before-mutate, showing the comment body in the Step 5 table (or including it in the Step 6 option text) would close the gap.
| OAuth scope. Check first — otherwise the very first command fails: | ||
|
|
||
| ```bash | ||
| gh auth status # needs "project" in Token scopes |
There was a problem hiding this comment.
gh auth status is run without an assertion, and the guidance "needs 'project' in Token scopes" is satisfied textually by the read-only read:project scope. Steps 1-2 read fine under read:project, so the run gets all the way through classification and user confirmation before gh project item-edit fails on the first write — landing in the "outcome unknown" state Step 7 works hard to avoid. Grepping for the exact project scope token would fail it up front.
|
|
||
| ### 8. Verify and report | ||
|
|
||
| Re-run the Step 2 fetch and confirm the outcome for every changed item. Field |
There was a problem hiding this comment.
Step 8 re-runs the Step 2 fetch right after Step 7 and requires a closed issue's Status to already read Done, but Step 4 notes that transition comes from the board's built-in "item closed → Status: Done" workflow, which is asynchronous. A closure that actually succeeded can therefore show up under Manual Review on a fast re-fetch. Re-reading just the closed items after a short delay, or treating not-yet-Done as pending rather than manual, would avoid the false positive.
| ```bash | ||
| # From Step 1, NOT the defaults: re-applying them would triage the wrong board. | ||
| owner="<owner>"; num="<project-number>" | ||
| ITEMS="${TMPDIR:-/tmp}/aicr-triage-items.json" |
There was a problem hiding this comment.
$ITEMS and $ACTIVE are fixed basenames carrying no board identity even though owner/num are run parameters, and the "One named issue" block re-reads $ITEMS in a fresh shell without re-fetching it. $TMPDIR also resolves differently sandboxed vs unsandboxed, so a dump left by an earlier run against a different board can be picked up silently. Folding $owner/$num into the filename the way the per-repo $OPEN path already does would make a stale read impossible.
Summary
Adds
aicr-triageas a shared agent skill under.agents/skills/for triaging the NVIDIA AICR project board (project 248), and lists it in the Available Skills catalog indocs/contributor/skills.md.Motivation / Context
Mark asked contributors to have their agents classify new issues on the project board. This skill makes that workflow available to every agent in the repo (Claude and Codex) via explicit invocation or context-triggered triage.
It is a port of the personal
triage-issuesskill. Earlier revisions of this PR grew to 680 lines chasing edge cases surfaced in review; the branch has been rewritten to a 384-line version that implements the same workflow with an explicit, narrow failure model.Fixes: N/A
Related: N/A
Type of Change
Component(s) Affected
cmd/aicr,pkg/cli)cmd/aicrd,pkg/server)pkg/recipe)pkg/bundler,pkg/component/*)pkg/collector,pkg/snapshotter)pkg/validator)pkg/errors,pkg/k8s)docs/,examples/).agents/skills/(agent skill definitions),docs/contributor/skills.mdImplementation Notes
Workflow. Resolve the project and its field/option IDs, pull the board, slice to active (non-Done) issues, classify each one, present the recommendations, confirm per bucket, apply, then re-pull and report.
A verdict writes every field whose correct value differs from the current one — the bucket names below label the shape of that difference for reporting and confirmation, they do not cap which fields are written. An issue at
Ready+P2that belongs atBacklog+P1gets both edits under one Demote verdict.Buckets, in precedence order — each issue gets one verdict.
Manual Reviewoutranks them all and is terminal: an item goes there whenever an exact, executable write cannot be named for it, and is never offered for confirmation or written.Stalled is a flag rather than a bucket: an
In progressitem untouched for more than 30 days is surfaced in its own table. It neither creates nor suppresses a bucket verdict — a stalled item can still be a legitimate Close or Incomplete, and being listed as stalled does not withdraw it from selection; it is the Stalled table that carries no action, not the issues in it. Staleness comes from the issue'supdatedAt, which is approximate — a bot or a triage comment resets it — and the skill says so.Field and option IDs are resolved every run via
gh project viewandgh project field-list. Projects v2 regenerates single-select option IDs when an option is deleted and re-added, so hardcoded IDs go stale silently.The board argument is carried, not re-defaulted. Step 1 resolves
owner/numfrom the skill arg, and Step 2 repeats those resolved values rather than re-applyingNVIDIA/248. Re-defaulting would make an alternate-board run analyze the AICR board, and since an approved closure uses the repository from that dump, it could close an issue on a board the user never asked to triage.The named-issue path searches the full board dump, not the active slice, so an issue that is on the board but marked Done is reported as such rather than as missing, and it stops rather than silently reclassifying it. The lookup filters on
content.type == "Issue"as well as number: issues and pull requests share one number sequence, the board holds both, andgh issue viewaccepts a PR number without complaint, so a number-only match could walk a pull request into issue classification.Reads are batched per repository, not per issue. The board dump already carries each item's title, labels, and body; only open/closed state and
updatedAtare missing, and onegh issue listcall supplies both for every open issue in a repo. On the live board that is one call instead of 83, and it doubles as the closed-but-not-Done check. The snapshot filename is derived from the repo so a multi-repo board cannot overwrite itself, and a page at the--limitceiling stops the run rather than reporting later open issues as closed. Full comment threads are still fetched per issue, but only for the verdicts that require them.Board reads use
gh project item-listwith a--limitabove the board size, asserted withjq -e '(.items | length) == .totalCount'.item-listreports the true total even when--limittruncates the array, so this is a real completeness check rather than a printed count. The fetch itself is guarded so an auth or network failure is not misreported as truncation.Confirmation is mandatory, and it shows the exact write — the proposal table carries a proposed-action column ("Status → Backlog, Priority → P2"), and each confirmation option repeats it as
owner/repo#N — <action>(numbers are repository-scoped and an org board can hold several repos), since First-time and Incomplete can each produce several target combinations and current-state-plus-reasoning does not say which one is being approved. Per-bucket multi-select viaAskUserQuestion, batched to 4 options per call, with a numbered-list fallback where that tool is unavailable. Closures are individually selectable. No raw issue text is copied into the proposal table: pipes are escaped and newlines stripped from every rendered cell, reasoning included, since that table is the only gate before writes to a shared board.Close is available only on NVIDIA project 248, gated at classification so an ineligible candidate never reaches the proposal table. A Close verdict edits no fields, so the item reaches Done via the project's built-in "item closed → Done" workflow, not via the skill. That workflow is enabled on 248 (96 of 97 closed issues are Done;
#1623is the lone exception) but is per-project, so on an alternate board — or whenever the precondition cannot be confirmed — Close candidates are routed to Manual Review rather than closed. Reporting a closure the skill declines to make is cheaper than creating a state it then refuses to clean up. Issues found already closed in an active column are likewise reported for a human to fix.The close reason is checked against an enum before the comment posts — an empty or mistyped value would otherwise fall through and close the issue as "not planned".
--duplicate-ofavailability is checked in Prerequisites, alongside the token scope: it landed ingh2.88.0, and it is a static property of the environment rather than something to re-probe per item. The check stops the run rather than warning, since capability state cannot be carried across fresh shells. The missing-target check stays per-item, ahead of the comment.Every applied verdict gets an issue comment, since board field edits leave no trace in the issue timeline. Bodies are piped in on stdin from a quoted heredoc rather than assembled into a shell command. Closures comment first and only then close, so nothing can close without a visible reason, and the close is asserted against a re-fetched
CLOSEDstate.Issue content is treated as untrusted evidence. Text in a title, body, or comment that reads like instructions or a priority directive cannot override the classification rules, the confirmation step, or the commands run.
Fail-closed guards. The ones where the obvious phrasing would have failed open instead:
item-editstanzas were collapsed into one parameterized stanza, run once per field the verdict changes and only for those fields, so the guard and its error text exist once rather than as a copy-paste pair that can drift. The stanza asserts its option id is present before callinggh; an empty one aborts. A skip-if-empty guard was rejected for the opposite reason: it lets the block exit 0 having changed nothing, after which the run posts aTriaged:comment for a change that never happened.jqslice that builds the active list is guarded. Without it, a failed slice yields an empty file and the next instruction reports "no active issues" — a clean bill of health for a board that was never read.elsebranch and close the issue as "not planned".Every block assigns the values it uses, including ones an earlier block already set — each shell call is a fresh shell, so a carried-over variable is empty. This is why the file paths in Step 2 are fixed strings rather than
mktempoutput: they can be re-derived rather than threaded between calls.Placeholders are quoted strings. Unquoted
<...>is shell redirection, not a placeholder:n=<issue number>is a parse error and--duplicate-of <original>redirects instead of passing an argument.Failure model, stated plainly
Concurrency and connectivity are out of scope. There is no locking, no pre-write re-validation, no retry, and no reconstruction of ambiguous outcomes.
ghexit in the apply step stops the run, and the report separates three states: applied, outcome unknown (a timeout can land after GitHub accepted the write), and not attempted. Closures are verified in the full board dump, since the active slice drops Done items and a successful closure would vanish from it. Read-phase failures differ by scope: a failed per-issue comment fetch routes that item to Manual Review and triage continues, while a failed repository-level state fetch stops the run, since without it no item's state is known.The invariant is narrow, and stated as narrowly as it holds: an observed nonzero exit stops every subsequent write, and a closure is only reached after its comment posted successfully. That is all. It does not claim every resulting board state is repairable — a concurrent overwrite is not, which is why it is listed above as a consequence rather than a handled case.
Deliberate deviations from
triage-issuesNo-change ACK comments are dropped. The source skill comments on every triaged issue including no-change verdicts. Applied changes and closures still get comments; issues with no state change appear in the triage report only. This avoids a run producing dozens of comments and removes the timestamp-marker machinery that made stalled detection disproportionately complex. Flagging it here because it was requested directly in review — say so if ACKs are a required workflow behavior rather than a preference carried over from the original.
The project-URL argument is dropped.
owner/numberis accepted; no URL parsing is documented.Testing
Two Markdown files, so
make qualifyis the repository gate rather than functional verification of the skill. The behavior was checked directly against the live board, read-only:gh project item-list 248 --owner NVIDIA --limit 400returns 205/205 items; thejq -ecompleteness assertion exits 0 there and exits 1 at--limit 5, confirming it detects truncation rather than reporting a countPVTI_*item id,status,priority,labels, andcontent.{number,repository,title,body}the classification steps rely onjqfilter selects on type and number: a Done issue is returned so its state can be reported (not filtered out), a board pull request with the same number is excluded, and an absent number returns empty||guard fires on a nonzero exitbashblock was extracted and run throughbash -nand ShellCheck (-s bash); all 9 parse and are clean at warning level.bash -ncaught three blocks that did not parse: unquoted<...>placeholders are shell redirection, son=<issue number>and--duplicate-of <original>were parse errors. All placeholders are now quoted strings.optionaborts with exit 1 rather than editing or silently skipping; a duplicate close with no surviving issue number aborts before the comment is posted, and a populated one proceeds; and the close-reason enum acceptsduplicateandnot plannedwhile rejecting an empty or mistyped value.No mutating command was run against the board.
make qualifypasses: unit tests with-race, the coverage gate, shell tests, e2e,go vet, andlint-go. Two spurious failures were ruled out along the way and are worth recording, since neither is caused by this change and both are easy to mis-read as one:mktemp: Operation not permitted, taking 6 shell tests with it. The same tests pass outside the sandbox.lint-goreported 109 golangci-lint issues whose file paths all pointed at other local worktrees (/private/tmp/aicr/review-b4b39d10d/...), i.e. a poisoned sharedGOCACHE. Re-running withGOCACHEandGOLANGCI_LINT_CACHEset to fresh directories gives0 issues. This PR contains no Go files, so it cannot produce a type-based lint finding.Risk Assessment
Rollout notes: Adds two Markdown files and no code. The skill mutates a shared board and closes issues, but only after explicit per-bucket confirmation, so the blast radius is bounded by what a reviewer approves in the moment.
Checklist
make testwith-race)make lint) —0 issueswith isolated caches; see Testinggit commit -S) — GPG signing info