Imported from jakestrouse00/opencode-toolkit (
skills/plan-triage/SKILL.md). Install upstream withnpx skills add jakestrouse00/opencode-toolkit --skill plan-triage. Copyright stays with the author.
Plan Triage
This skill sweeps the unfinished plan backlog at .opencode/plans/*.md
(top-level only — archive/ is excluded), judges whether each plan is
functionally complete against the live codebase, and archives the ones that
are. It is the batch counterpart to change-outline's per-plan RESUME +
archive hand-off: where RESUME reconciles one plan and asks the user whether
to archive, this skill assesses every non-archived plan and archives the
completed ones in one pass.
It is mutating but narrow: it only edits plan markdown and moves plan files
into .opencode/plans/archive/. It never executes a plan's edits (that is
change-outline EXECUTE mode's job), never touches source code, never deletes
a plan (archive-only), and never commits.
Step 1 — Read AGENTS.md first
Read AGENTS.md in full. Its conventions section (if present) documents that
plans live at .opencode/plans/<slug>.md, that change-outline owns their
format, and that completed plans move to .opencode/plans/archive/<slug>.md.
Per AGENTS.md, read what verification the project actually has — a test
command, a linter, a type-checker, a build step, or an explicit "no tests /
no linter" note. Do not re-derive what AGENTS.md already states, and do
not invent verification commands the project does not have. The only
sanctioned automated checks are whatever AGENTS.md documents; for
Python projects without a test/lint setup, that's the Python import sanity
check (python -c "from <module> import <symbol>; print('ok')").
Step 2 — Detect --dry-run
Scan the user's message for a --dry-run flag (as a bare token, e.g.
"plan-triage --dry-run", "sweep the plans dry-run", "triage --dry-run"). If
present, run in dry-run mode: perform every assessment step, print the
would-be-archived list with reasons, but make no edits and no file moves.
If absent, run in live mode: assess, then edit + archive the completed
plans. State the mode at the top of the final report so the user can tell a
preview from a real sweep.
Step 3 — Discover the candidate plans
List every *.md at the top level of .opencode/plans/ with glob (pattern
.opencode/plans/*.md). Do not recurse into .opencode/plans/archive/ —
archived plans are done by convention and must be excluded. If the directory
or pattern returns no files, tell the user the plans folder is empty (or
missing) and stop.
Batch with Step 1: issue this glob in the same message as the
read of AGENTS.md from Step 1 — they are independent, and the opencode
harness dispatches them concurrently. (Step 2's --dry-run detection is a
pure in-memory scan of the user's message, no tool call, so it does not
affect the batch.) Do not gate the glob behind the AGENTS.md read.
Step 4 — Read every candidate plan in parallel
read each discovered file in full. Every plan is small (<220 lines);
reading them all is cheap and avoids missing a field by truncating. Do not
guess file contents from filenames.
Parallelize the reads: issue every read call (one per discovered plan
file from Step 3) in a single message — they are independent of each
other and the opencode harness dispatches them concurrently. Do not read
plans one at a time.
Step 5 — Classify each plan
For each plan, classify into exactly one bucket by inspecting its H1 title
and Status: line (case-insensitive, trimmed value):
-
skip — the file is not a change checklist:
- H1 is
# Note:(a context marker, not a plan), OR Status:isreference/wontfix/superseded(explicitly not a change to complete). These are left untouched and reported as skipped. Never archive a# Note:file — it is a permanent reference, not a completed change.
- H1 is
-
stale-complete —
Status:iscompleteordonebut the file is still at the top level of.opencode/plans/(not yet archived). The work is already marked done; this skill just needs to file it. Do not re-verify the steps or re-run the## Verificationblock — the author already attested completion. In live mode, add aReconciled: <YYYY-MM-DD>line under theGenerated:line (or update an existingReconciled:line) noting "auto-archived by plan-triage (was already Status: complete)", then move the file to.opencode/plans/archive/<slug>.md(Step 7). In dry-run mode, report it as a would-be-archive with reason "stale-complete". -
assess — everything else:
Status: in_progress, noStatus:line at all (treat as open), or any other value not in the skip/stale-complete sets. These go to Step 6 for live verification against the codebase.
If a plan has neither # Change: nor # Note: but lives under
.opencode/plans/, treat it as a plan (use the filename stem as the slug)
and apply the Status-based classification above.
Step 6 — Assess each "assess" plan for functional completeness
For each plan in the assess bucket, decide whether it is functionally complete via two gates. Both must pass to archive.
Gate A — every step's described edit is present in the live tree
Parse the ## Steps section into rows. Each row has the shape
- [<check>] N. **<file>** — \` (lines ~L1–L2): . Reason: . Verify: .(seechange-outline` Step 3). For each row:
- If the checkbox is already
[x], trust it — do not re-verify. (The author or a prior RESUME pass already confirmed it.) - If the checkbox is
[ ](unchecked), re-verify the described edit against the live tree:grep/globfor the cited<symbol>in the cited<file>to confirm the symbol still exists where the plan says (line ranges drift — re-grep, don't trust the~L1–L2hint).readthe cited file around the symbol and judge whether the specific edit described in<what to change>is present. The judgment is semantic, not string-match: e.g. "addvalidate_csstoCodeValidationToolset" is present iffvalidate_cssappears in that toolset's member list; "changetool_sets=[*default_guest_agent_tools]totool_sets=[*a11y_guest_tools]" is present iff the line now readsa11y_guest_tools.- If the edit is present → the step passes (treat as
[x]). - If the edit is absent or partial → the step fails. Record the step
number and a one-line reason ("symbol not found", "edit not present at
<file>:<line>", "partial — only N of M sub-changes present"). The plan is not functionally complete; stop assessing its remaining unchecked steps (no point — one failure is enough) and mark it left-incomplete.
Only if every unchecked step passes does the plan proceed to Gate B. A
plan with zero unchecked steps (all already [x]) skips straight to Gate B.
Concurrency — batch the Gate A calls across plans: the Gate A grep/glob
calls for one plan's unchecked steps are independent of another plan's, so
issue every unchecked step's grep/glob across all assess-bucket
plans in a single message — the opencode harness dispatches them
concurrently. With many assess plans this is the difference between one round
trip and many sequential greps. This may run some greps for a plan that later
fails an earlier step (wasted-but-cheap work) — the parallelism win outweighs
the redundant greps; the per-plan short-circuit (stop at first failing step)
still applies when you collate the results, it just doesn't gate the
dispatch. After the greps return, the follow-up read of each cited file
around its symbol is also independent — batch all of them in one message
too.
Sub-agent fan-out (large assess buckets, N ≥ 15 plans): when the assess
bucket holds N ≥ 15 plans with many unchecked steps each, escalate the
per-plan Gate A verification to concurrent explore sub-agents via the
Task tool. The per-plan grep+read+judge work is self-contained and
expensive enough at scale to justify fresh contexts.
- When to fan out: N ≥ 15 assess-bucket plans with unchecked steps → fan out one sub-agent per plan (or per slice of ~3–5 plans if N is very large). N < 15 → keep the parallel-tool-calls path above (sub-agent overhead isn't worth it for small buckets).
- How to fan out: in a single message, issue one
Taskcall per plan (or per slice) withsubagent_type: "explore". Each sub-agent'spromptmust include: the plan's## Stepsrows (paste them verbatim), the instruction togrep/readeach unchecked step's cited symbol in the live tree and judge whether the described edit is present (semantic judgment, not string-match), and a return shape of a JSON object{"slug": "...", "steps": [{"n": N, "present": true|false, "reason": "..."}], "all_pass": true|false}. Collect the sub-agents' JSON objects and use them as the Gate A verdicts. Gate B (verification block) and Step 7 (archive) stay in the parent — they need cross-plan ordering and the parent's file-move authority.
Gate B — the plan's ## Verification block passes
Run the plan's own ## Verification block as a whole-change check. This is
the same verification change-outline EXECUTE mode runs after all steps
(Step 6 of that skill). Per AGENTS.md, run only the automated checks the
project actually has:
python -c "from <module> import <symbol>; print('ok')"— Python import sanity check. Run these directly; they are fast and non-blocking.npm test/pytest/cargo test— ifAGENTS.mddocuments a test command, run it.ruff/eslint/tsc --noEmit/mypy— ifAGENTS.mddocuments a linter or type-checker, run it.grep -n "<pattern>" <file>/git diff <file>/read <file>— presence/diff inspections. Rungrepvia thegreptool orbash'srg; rungit diff/readviabash/readas appropriate.
Run every command in the ## Verification block. If the block is empty or
absent, Gate B passes vacuously (Gate A is the real signal in that case). If
any command fails (non-zero exit, missing symbol, expected pattern absent),
the plan is not functionally complete — record which check failed and the
command's output. Do not swallow failures: a failed import or a failed test
is a real signal, not noise.
Concurrency — batch Gate B across Gate-A-passing plans: Gate B for a plan
runs only if that plan's Gate A passed, so you cannot pre-batch all Gate B
commands up front — but once Gate A results are in, the ## Verification
block commands for all Gate-A-passing plans are independent of each
other. Issue them in a single message — the opencode harness dispatches
them concurrently. Per-plan ordering inside the block still matters (run a
plan's checks in the order its ## Verification block lists them), but
across plans there is no ordering constraint.
Verdict
- Both gates pass → the plan is functionally complete. In live mode, proceed to Step 7 (tick + set Status + archive). In dry-run mode, report it as a would-be-archive with reason "steps verified + verification block passed".
- Either gate fails → the plan is left incomplete. Do not edit it, do not archive it. Report it with the failing gate and the specific step/check that failed (one line each) so the user knows what's left.
Be conservative. If you cannot confidently judge whether an edit is
present (e.g. the <what to change> prose is vague, the symbol moved beyond
a simple grep, the change is behavioral not textual), treat the step as
failed and leave the plan in place. A false "complete" that archives an
unfinished plan is worse than a false "incomplete" that leaves it for the
user to glance at. When in doubt, do not archive.
Step 7 — Archive the completed plans (live mode only)
For each plan that is functionally complete (Gate A + Gate B both pass) or stale-complete (Step 5), in live mode:
- Tick the checkboxes. Replace every
- [ ]in the## Stepssection with- [x] (done). Leave already-[x]rows alone. This mirrors whatchange-outlineEXECUTE mode writes after each step. - Set the status. Change the
Status:line toStatus: complete. If there is noStatus:line, add one immediately under theGenerated:line. - Add a reconcile note. Add or update a
Reconciled: <YYYY-MM-DD>line underGenerated:(use today's date). Append a short note:auto-archived by plan-triage (steps verified + verification block passed)for assessed plans, orauto-archived by plan-triage (was already Status: complete)for stale-complete plans. - Save the edited plan at its current top-level path first (so the ticked/Status edits land on disk before the move).
- Move to archive. Move the file to
.opencode/plans/archive/<slug>.md(create thearchive/directory if it somehow does not exist). On a name collision with an existing archived plan, suffix-2,-3, … rather than overwriting — this matcheschange-outline's archive-collision convention. Use a move (not a copy + delete): the plan's history travels with it; do not leave a stale top-level copy behind.
In dry-run mode, skip all five sub-steps — report the would-be-archived list only.
Step 8 — Report
Print a single summary, then stop. The report has four sections:
Archived (newly complete)
A markdown table, one row per plan archived by this run's assessment (Gate A
- Gate B both passed). Columns:
| Slug | Steps | Verification | Archived to |
Slug— the plan slug (filename stem).Steps—N/N verified(e.g.7/7), orall pre-tickedif there were zero unchecked steps.Verification—passedorn/a (no block).Archived to—archive/<slug>.md(orarchive/<slug>-2.mdon collision).
Archived (stale-complete)
A plain list of slugs that were already Status: complete and just got
filed into archive/. One slug per line. If none, print (none).
Left incomplete
A markdown table, one row per plan that failed Gate A or Gate B and was left in place. Columns:
| Slug | Failing gate | Detail |
Failing gate—A (steps)orB (verification).Detail— one line: the failing step number + short reason (Gate A), or the failing verification command + short reason (Gate B). Cap at one line; do not dump full command output into the table.
Skipped
A plain list of slugs skipped (the # Note: context markers and
reference/wontfix/superseded plans). One slug per line. If none, print
(none).
After the four sections, print exactly one summary line:
plan-triage: archived N newly-complete + S stale-complete, left M incomplete, skipped K. (dry-run — no files changed.)
for dry-run, or the same line without the (dry-run …) suffix for live mode.
N, S, M, K are the counts in each section. Then stop.
Concurrency
Four independent batch points in this skill:
-
Step 1 + Step 3 (AGENTS.md read + plan glob): issue both in a single message — the
readofAGENTS.mdand theglobof.opencode/plans/*.mdare independent. (Step 2's--dry-rundetection is a pure in-memory scan of the user's message, no tool call, so it does not affect the batch.) Do not gate the glob behind the read. -
Step 4 (read every candidate plan): issue every discovered plan file's
readcall in a single message — they are independent of each other and the harness dispatches them concurrently. -
Step 6 Gate A (verify unchecked steps): the
grep/globcalls for one plan's unchecked steps are independent of another plan's. Issue every unchecked step'sgrep/globacross all assess-bucket plans in a single message. This may run some greps for a plan that later fails an earlier step (wasted-but-cheap work); the per-plan short-circuit (stop at first failing step) still applies when you collate results, it just doesn't gate the dispatch. After the greps return, the follow-upreadof each cited file around its symbol is also independent — batch all of them in one message too. -
Step 6 Gate B (verification block): Gate B for a plan runs only if that plan's Gate A passed, so you cannot pre-batch all Gate B commands up front. But once Gate A results are in, the
## Verificationblock commands for all Gate-A-passing plans are independent across plans — batch them in a single message. Per-plan ordering inside a block still matters (run a plan's checks in the order its block lists them).
Step 7 serialization boundary: the Step 6 batching stops at Step 7.
Step 7 (tick checkboxes + set Status + save edited plan + move to
archive/) runs one plan at a time — a move must follow its own edit
(the move reads the just-saved file off disk), and two moves to the same
collision-suffixed name must not race. Do not batch Step 7 across plans.
Out of scope
- Do not execute any plan's source-code edits. This skill verifies
presence and archives; it never makes the edits a plan describes. Executing
a plan is
change-outlineEXECUTE mode's job, triggered only by the literal phrase "execute ". - Do not touch source code. The only commands this skill runs against the
repo are read-only inspections (
grep,glob,read,git diff,python -c "from … import …", and whatever test/lint/typecheck commandsAGENTS.mddocuments) drawn from each plan's own## Verificationblock. Never run an edit/write against a source file. - Do not delete any plan. Completed plans are moved to
archive/, never deleted. A plan that fails assessment is left in place, not removed. - Do not recurse into
.opencode/plans/archive/. Archived = done; this skill never re-assesses or re-moves an already-archived plan. - Do not modify the
change-outlineorplan-dashboardskills. This skill is a consumer of the plan format those skills own. - Do not commit anything. Moving a plan into
archive/is a working-tree change the user reviews and commits themselves (via thecommit-codeskill or manually). - Do not claim to run tests, linters, type-checkers, or build steps
unless
AGENTS.mddocuments them. Read available verification fromAGENTS.mdrather than inventing commands the project doesn't have. - Do not archive a plan you cannot confidently verify. When Gate A or Gate B is uncertain, leave the plan in place and report it as left-incomplete with "could not confidently verify" as the detail. A false-positive archive is worse than a false-negative leave.