groom
Skill1 starsChanged 3 months agoMITView on GitHub
Run a Backlog Grooming session for a milestone. Loads full project context deterministically (via the backend's GET /api/groom-context route), explores the code regions tasks touch with a git-fresh cache, presents π² Backlog tasks in batches for human sign-off, and marks them ποΈ Ready. Use when the user says "groom", "grooming session", "let's groom milestone X", "bring the backlog to Ready", or starts a Backlog Grooming session. Requires a grooming manifest in the central config tree (config/projects/<dir>/grooming.json).
8 other projects use a version of this.
Adopted by
Most recently taken up by
- protokol11/skillstheir own version, first seen Nov 8, 2026
- conforma/agent-skillstheir own version, first seen Nov 1, 2026
- conforma/claude-skillstheir own version, first seen Nov 1, 2026
- dsonders/claude-skillstheir own version, first seen Oct 20, 2026
- lucagattoni/ClaudeCodeTidytheir own version, first seen Oct 2, 2026
- GentBajko/capstonetheir own version, first seen Sep 6, 2026
Other versions8 projects use a version of this.
Word for word Β· 0
None found.
Edited Β· 0
None found.
Wrote their own version Β· 8
- GentBajko/capstoneβ 5same name
- AmanTahiliani/box-boxβ 2same name
- misty-step/laboratoryβ 1same name
- protokol11/skillsβ 1same name
- conforma/agent-skillsβ 0same name
- conforma/claude-skillsβ 0same name
- dsonders/claude-skillsβ 0same name
- lucagattoni/ClaudeCodeTidyβ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (β99% sameβ means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We canβt tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository10 other files it gives its agents.
- deploySkill
- designSkill
- docsSkill
- gateSkill
- investigateSkill
- milestone-seedSkill
- milestone-wrapSkill
- opsSkill
- sync-guidelinesSkill
- wrapSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
18 rules. 0 are common, 8 are distinctive, and 10 are very specific to this project.
0
Common
Most of the 7 edited versions we read keep these.
8
Distinctive
This author's own choices. Worth a look.
10
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 31
Read the repo's grooming manifest from the central config tree β <config>/projects/<repo-dir>/grooming.json (the loader resolves the config β¦
- Distinctive: line 46
Never improvise a board id β copy it from the board's Notion URL / context.md.
- Very specific: line 131
Read the non-architecture context-page bodies from contextPages[].markdown (project context, product design doc, dev setup, future scope β β¦
- Very specific: line 137
Read each target task's architecture from targetTasks[].archUnits, dual-read per archSource:
- Distinctive: line 208
Keep the package reads in subagents so the main window stays small (the procedure must survive β that is the original failure mode).
- Very specific: line 223
Follow reference/presentation.md.
- Distinctive: line 254
Do not batch feedback. For each item: stage the write β confirm the change in chat β continue.
- Distinctive: line 261
If it's a genuine architectural decision you can't resolve, route it (file a π Design task + hard-block edge), per Β§ Cite-or-route.
- Very specific: line 268
If grooming reveals a design choice that needs human judgment, present options with pros/cons and stop β do not pick unilaterally.
- Distinctive: line 343
When grooming surfaces body content that is wrong or misleading (a bad symbol, a stale anchor, a self-contradiction), correct it β the β¦
- Distinctive: line 353
Only after explicit sign-off on the batch (_"looks good"_, _"ship it"_, _"next"_):
- Distinctive: line 397
Gate accretion (π» Code tasks): Before flipping to Ready, mint the task's stripped runtime/launch-and-observe items onto the milestone gate β¦
- Very specific: line 432
Before flipping to Ready, classify each candidate line the pre-groom body's ## Operational seed section carries as operational-seed (a β¦
- Very specific: line 473
Declared writes validation (π§ Operational tasks): Before flipping a π§ Operational task to Ready, check its body for a ## Declared writes β¦
- Very specific: line 482
Do not invent a classification tag the author didn't write: an entry with no tag, or an ambiguous one, is left as-is (it still promotes β β¦
- Very specific: line 501
After conversational sign-off in this session, apply each staged intent with node ~/.claude/scripts/staged-intents-client.mjs apply β¦
- Very specific: line 508
If the task has hard-block deps (or had any and now has none), stage a task.setDependsOn intent: {"taskId": "<id>", "dependsOn": ["<id>", β¦
- Distinctive: line 600
Scope is the target milestone only: Do not modify tasks on other boards unless a dependency issue is explicitly identified and the human β¦
βCommonβ is measured: kept by at least 6 in 10 of the 7 edited versions of this file we could read. βVery specificβ is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Backlog Grooming
Grooming brings π² Backlog tasks up to ποΈ Ready: every open question resolved or explicitly owned, scope verified against the actual code, tests and manual-gate items enumerated β then the human signs off, batch by batch.
This skill exists because the procedure is load-bearing but routinely skipped: under context pressure a single agent compresses the expensive Step-1 load and "just decides." Here the deterministic parts are done by a script (nothing to skip) and the judgment parts are gated on human sign-off (nothing to self-grant).
Read reference/anti-patterns.md now β those failure modes are the whole
reason this skill exists. Read reference/presentation.md before Step 2.
Step 0 β Resolve manifest & mode
- Read the repo's grooming manifest from the central config tree β
<config>/projects/<repo-dir>/grooming.json(the loader resolves the config root via$ORCHESTRATOR_CONFIG_DIR/--config-dir/ a host-aware default;<repo-dir>is the repo basename, override with--project). If it is missing, stop and tell the human (point them atreference/manifest.example.json). Do not improvise. - Note
architectural_control:fullβ this skill's default behavior (decide-and-rewrite within the rules).lowβ readreference/low-control.mdand follow the investigate-propose-route adaptation instead. The groomer does not own the architecture; resolution biases to proposing and routing to owners.
- Determine the milestone (from the user, e.g. "groom M9"). If it isn't yet
registered in
manifest.milestones(routine right after a new milestone board is created), the loader no longer dead-ends β it prints a copy-pasteable entry with the neighbour auto-filled. Add that entry to the manifest, or pass--board <data-source-id>to run immediately and persist the printed snippet afterward. Never improvise a board id β copy it from the board's Notion URL / context.md. - Determine mode from the cache dir
.skill-cache/grooming/<milestone>/:- absent β fresh groom.
- present β resume: the loader reuses fresh digests and re-explores only what changed. Same procedure either way.
Grooming runs interactively in the main repo on dev β never in an ephemeral
implementation worktree (the cache must persist). If you find yourself on a feature
branch in a worktree, stop and tell the human.
Step 1 β Deterministic load (the backend route, not you)
Load through the backend, not by hand-fetching context pages or task bodies
yourself β that is exactly the step that gets skipped. Do not shell out to
Notion directly (no notion-query.mjs / notion-page.mjs, no vendored
groom-load.mjs) β the backend's GET /api/groom-context route (see
packages/backend/src/routes/groomContext.ts) is the sole loader now; it wraps
the same loadGroomContext() the dashboard's grooming panel calls, so the
skill and the panel always see identical context.
node ~/.claude/scripts/groom-context-client.mjs --milestone <M> [--project <project-id>]groom-context-client.mjs is the vendored sanctioned node client (curl/wget
are off the auto-dispatch allowlist, node is not β re-vendored via the
/sync-guidelines skill), which calls the loopback, device-authed route
with $ORCHESTRATOR_DEVICE_TOKEN (host/port default to
127.0.0.1:3000, overridable via $ORCHESTRATOR_BACKEND_HOST /
$ORCHESTRATOR_BACKEND_PORT) and prints the GroomLoadResult bundle as JSON
on stdout. If it exits non-zero, stop β a partial load means a
contaminated groom. Report the error.
The bundle has:
contextPagesβ the context pages ({id, title, markdown}), fetched fresh every load.boardβ every non-Done target task ({id, title, status, type, priority, url}), andneighbourBoardsβ the same shape for neighbour milestones (context-only).targetTasksβ the target board's tasks, each carrying its parsedfilesSection/rawMarkdown, plus already-computed judgment seeds:readinessViolations(the shared readiness gate run ahead of time),sizeCheckSeed({files, loc_method}β a deterministic size-check seed),typeCheck(the type/content-mismatch scan),regions(resolved package/file scope), and its dual-read architecture (archSource+archUnitsβ see below).codeWorklistβ per-package deduped file paths declared across target task bodies (object form: package path β file list).gitFreshnessβ per-package freshness (fresh/stale/missing) vs. the local integration branch.dependencyCandidatesβ per-task dependency candidates (region overlap βͺ declared Depends On) for the groomer to confirm β never auto-wired.
The bundle does not carry milestone_gate_task_id / milestone_seed_task_id
directly β derive them from board exactly as the old loader did: the single
row with type === 'π¦ Gate' (null if absent), and the single π§ Operational
row whose title contains "config-seed" case-insensitively (null if zero or
ambiguous if more than one match β surface either case rather than guessing).
Record both in context-bundle.json for the rest of the session to read.
Persist this session's working state locally exactly as before β the route is a
pure read, so the skill still owns the on-disk cache under
.skill-cache/grooming/<milestone>/: write context-bundle.json (context pages
- board + neighbours + target tasks + the derived gate/seed task ids) and
worklist.json(codeWorklist + gitFreshness) from the bundle with the Write tool, and seed/mergegrooming-state.json(per-Backlog-task skeleton, preserved across resumes β same merge rule as before: carry forward human-entered fields, always refreshstatus/title/typefrom the live bundle, prune entries whose task is no longer a live non-Done target) with the Edit tool. This is what makes resume mode work and is unchanged from before the cutover β only the source of the bundle moved from a shell-out to the route.
grooming-state.jsonis FLAT, keyed by task id β and every entry must carryproject,milestone,title. The shape is a top-level map{ "<taskId>": { β¦ } }(not a nested{ milestone, project, tasks: { β¦ } }wrapper) βgroom-flip-client.mjsreadsstate[taskId]directly and requiresproject,milestone,titleon that entry (plushard_block_deps,size_check,type_check,gate_contribution,seed_contributionat flip time). Stampproject,milestone,titleon every entry when you seed it β including tasks that arrive in a later wave and get merged in mid-session (a wave-added entry seeded without them fails the flip at payload-build withmissing required field(s): title, project, milestone).
Read the non-architecture context-page bodies from contextPages[].markdown
(project context, product design doc, dev setup, future scope β always
populated, regardless of archSource). Also read the universal
task-authoring standard at config/task-writing.md (it is not a Notion
context page β the skill reads it from local disk).
Read each target task's architecture from targetTasks[].archUnits, dual-read
per archSource:
archSource: 'notion'β the milestone's fixed Notion architecture pages (Technical Architecture, Coding Guidelines), each{id, title}here and its body available viacontextPages[].markdown.archSource: 'store'βarchUnits[]carries region-intersected arch_unit store units (plus every active invariant), each already{id, title, body}β the body is inlined directly in the bundle, no extra fetch needed. If a unit'sbodyis ever absent, dereference it with themcp__orchestrator__architecture_getUnittool ({id}), or broaden the selection withmcp__orchestrator__architecture_queryUnits.
This is non-negotiable: resolving a task without the architectural constraints loaded is how grooming produces confidently-wrong decisions.
Disposition the constraints β don't just "read the pages." The loader surfaces each
task's binding constraints (targetTasks[].bindingConstraints β the
CONSTRAINT_CATALOG Γ regions intersection). Before a task can promote, disposition
every one of its binding constraints as one of complies / n-a (+ a mandatory
why) / conflictβroute (naming the routed π Design task) β recorded in
grooming-state.json as constraints_dispositioned and re-derived + enforced
server-side (groomGate.ts, FM1). The mandatory architecture read is the
disposition, not a page-turn: a constraint you didn't disposition is a constraint you
didn't check.
Step 1b β Explore the code (cached, judgment where needed)
Grooming decisions made from task bodies alone routinely miss real gaps. Read the code the tasks touch β but only once per region, and only what changed.
Per package in worklist.json:
freshβ reuse the digest already incode-map.json. Do not re-read.staleormissingβ dispatch one Explore subagent scoped to that package. Have it return a structured digest: public surface (classes/functions/signatures another task would import), the conventions in play, and anything that would invalidate a Backlog task's stated assumption. Write the digest tocode-map.jsonkeyed by the package path, stamped withworklist.baseline_sha:
(The loader reads{ "src/polimarket_analyser/<pkg>": { "head_sha": "<baseline_sha>", "digest": "...", "explored_by_task": ["<id>"], "ts": "<iso>" } }code-map.jsonfor freshness; the skill owns writing it.)
Write the cache/state files with the Edit/Write tool β never a shell script.
code-map.json,grooming-state.json(and the design skill'sdesign-state.json) are ordinary on-disk JSON the loader already seeded. Mutate them with the Edit tool (a unique-string change) or Read + Write the whole file (a structural change). Never write a throwaway script and run it (node _foo.cjsthenrm), and never shell out (echo >,cat >, acd β¦ && β¦chain). The script route does in three permission-prompting Bash calls (node,rm, thecd &&chain) what the Edit tool does in one auto-approved call β it is the cause of the constant permission friction, not a workaround for it.
For each task in worklist.unresolved_tasks (declared no resolvable path β
common, because real tasks reference code by identifier or prose, not a Files /
paths affected section): this is the judgment half the loader deliberately
left to you. Read the task body, extract the named symbols / analyzers / tables it
mentions, grep the repo to find the real file(s), map to a package, and explore
that package as above. If a stable areaβpackage mapping emerges, propose adding
it to manifest.area_aliases so the loader resolves it next time β confirm
before editing.
Keep the package reads in subagents so the main window stays small (the procedure must survive β that is the original failure mode).
Fan out, and falsify the first read. For a milestone of any size, dispatching one read-only Explore agent per stale/missing package (and per unresolved task) in parallel is the default, not a heavyweight option β it earns its keep. And falsify before asserting: a first read of "this is already done" / "this is subsumed by X" / "the body's anchor is right" is exactly where grooming goes wrong. A real run caught an "already-done" that was a live ordering bug, a "subsumed" that was a real residual, an internal body contradiction, and a wrong anchor β each would have produced bad grooming if trusted. Confirm the claim against the code before you groom on it.
Step 2 β Present π² Backlog in batches
Follow reference/presentation.md. In short:
- Only π² Backlog tasks need decisions. Tasks at any other status are shown as context only (name + status), never promoted.
- Batch by dependency cluster (sequencing comes from the depends-on chain β do not invent external sequencing labels). One batch per message.
- For each Backlog task, give the 4-point summary: what it achieves / open questions (write None. if clean) / automated tests / manual verification. Ground every claim in the code-map digest and the context pages β not the task body alone.
- End the batch with: "Any changes or questions before I mark these Ready and continue?"
- One batch at a time. Never present the next before the current is signed off.
Presentation is type-dependent β a mixed milestone runs both flows:
- π» Code (auto-dispatched) β the per-batch, per-task flow above: each task gets its 4-point summary and its own human decision (a wrong Ready launches an unattended worktree, so the stakes demand it).
- π Design / π Planning (interactive, picked up by
/design) β approve-by-standard: present ONE consolidated triage, not per-item asks β a clean list (names-only), blocked rows (+ the blocking dep), and needs-attention rows (+ the reason). The human engages only the needs-attention + blocked rows; the clean set promotes by default (visible + veto-able) β no per-item or bulk positive stamp. The clean-verdict standard, its taxonomy, and the deterministic floor that can only ever downgrade a proposed clean verdict live inreference/presentation.mdΒ§ Consolidated triage.
Step 3 β Incorporate feedback (one item at a time)
Do not batch feedback. For each item: stage the write β confirm the change in chat β continue.
- Clarification / correction β patch the affected section (see Β§ Editing a task body below), confirm.
- New open question β while grooming, resolve it into the spec, don't add a standing
## Open Questionsheading to a π» Code task (a live OQ heading blocks promotion β Tier-1). If it's a genuine architectural decision you can't resolve, route it (file a π Design task + hard-block edge), per Β§ Cite-or-route. - Missing task β draft it inline for review first; stage a
task.createintent only after the human okays it. - Missing prerequisite (a sibling ingestion/storage/analyzer task the scope doesn't cover) β propose a separate new task. Never silently widen the existing scope.
If grooming reveals a design choice that needs human judgment, present options
with pros/cons and stop β do not pick unilaterally. Load-bearing constraints from
the architecture pages win over task wording (see reference/anti-patterns.md).
Editing a task body β task.patchBodySection first, task.updateBody as fallback
Most groom-time body edits touch one section β clearing a resolved ## Open Questions,
pinning a hedged ## Files / paths affected line, adding a missing Files section. Do these as a
targeted, non-lossy patch, not a whole-body rewrite. task.updateBody is whole-body-replace:
it forces you to reconstruct Summary + Context-as-BlockModel[] + criteria + Files from scratch β
slow, and a single bad reconstruction silently drops content. task.patchBodySection edits one
section in place.
Stage it like any other intent (staged-intents-client.mjs create task.patchBodySection <payload>
<projectId> <groupId>). Payload is a discriminated union on operation, keyed by the exact section
heading text:
- Clear a resolved Open Questions block β
{"taskId","section":"Open Questions","operation":"remove"}(orreplaceits heading with a one-line resolved summary). Remove it β don't launder it past the gate with areadiness_override(see Β§ Remove Open Questions, don't override). - Pin a hedged Files line β
{"taskId","section":"Files / paths affected","operation":"replace", "find":"<hedge line, e.g. corresponding __tests__ (update/new)>","replaceWith":"<the concrete path(s)>"}. - Add a missing Files section β
{"taskId","section":"Files / paths affected","operation":"append", "content":"<the section lines>"}(a missing Files section is an auto-dispatch-blocking omission β FM2).
Availability.
task.patchBodySectionis granted to thegroom/design/opsworkflows and merged todev(PRs #1099/#1102/#1103). It only works against a backend deployed with that support β check the deployed SHA (/investigate's loader, or the staged-intents known-kinds) if acreate task.patchBodySection400s as an unknown kind. When it isn't available (older deployed backend), fall back totask.updateBodyand reconstruct the whole body carefully β re-emit every existing section verbatim; the whole-body replace drops anything you omit.
Inline every value the isolated implementer can't research
The single most-repeated groom-time judgment. A promoted π» Code task is auto-dispatched into an
isolated worktree: that implementer cannot read the skill tree (~/.claude/skills/β¦), the prod
host, or the central config tree. So any value, format, or decision it would otherwise have to look
up elsewhere must be inlined into the task body. Before promoting, ask: "what does this body assume
the implementer can go read β and can they, from an isolated worktree?" If not, inline it. Concrete
recurring cases: a required output format (e.g. embed the verbatim five-part closing-synthesis
format rather than "follow the /design format"); prod/environment facts (exact schedule, retention,
env-var names, ExecStart/User conventions β the implementer can't inspect the host); a corrected
symbol/anchor/DDL where the body's guess is wrong. This is distinct from "resolve the open
questions" β a body with every question resolved can still strand an implementer if it points at
something only readable from outside the worktree.
Body-authoring gotchas (checklist)
- Notion code-block language must be from its enum (
toml/shell/markdown/ β¦).iniis rejected β usetoml. - The WAF blocks bare
<interpreter> β¦ --flagcommand lines (python -m β¦,systemctl restart β¦) β the create/patch call fails with a Cloudflare "you have been blocked" HTML error, not a validation error. Describe the command prosaically (backtick the verb/unit), or prefix it so it isn't a bare interpreter line (a fullExecStart=/usr/bin/node β¦unit line is fine). (Same trap asprocedures.mdΒ§ Notion access.) - richText only auto-backticks whole shell-command lines β inline a symbol/path that contains a Tier-2 lexical trigger (a deferral phrase like "defer", quoted in prose) and it can trip a lexical false-positive; backtick or rephrase it so the gate doesn't read prose as a live signal.
- Keep body and payload consistent by hand (FM2). The Files-section gate reads the caller-supplied
filesPathsEntries, not the live body β so a resolved-looking payload over a still-hedged body will pass the gate but mis-drive the implementer. When you pin a Files line in the body, mirror it in thefiles_paths_entriesyou carry into the flip. Don't rely on the gate to re-derive from the body.
Remove Open Questions, don't override
For a π» Code task, a live ## Open Questions heading is a grooming artifact, not part of the
spec β and it blocks promotion (readiness Tier-1). The fix is to resolve each question into the
body and remove the heading (patchBodySection remove), not to launder the task past the gate
with a readiness_override. Lean on the edit, not the escape. (Contrast the interactive π Design
path, where the OQ heading is legitimate β it is the /design worklist β and promotion carries the
auto-applied override reason; see Step 4 Β§ approve-by-standard. The override is for that case, not for
dodging an unresolved Code-task question.)
A wrong body is worse than a vague one β fix cost is not a reason to ship the error
When grooming surfaces body content that is wrong or misleading (a bad symbol, a stale anchor, a
self-contradiction), correct it β the reconstruction/patch cost is never a reason to leave an error
in place. A vague body makes an implementer ask; a confidently wrong one sends an isolated implementer
down a dead end with no way to notice. Now that patchBodySection makes the fix a one-line patch, the
old excuse ("fixing means rebuilding the whole body") is gone.
Step 4 β Mark Ready after sign-off
Only after explicit sign-off on the batch ("looks good", "ship it", "next"):
- First, record the sign-off in
grooming-state.jsonfor each task in that batch: fillachieves,open_questions,tests,manual, confirmregions, fillhard_block_deps(seepresentation.mdΒ§ Dependencies for the hard-block vs soft-order distinction), fillsize_check(the size classification β seepresentation.mdΒ§ Size check β shape{ loc, loc_method, files, decision, split_into?, reason? }, wheredecisionis one of:"no_split"(β€500 LoC β proceed),"split_now"withsplit_into: ["<task-id>", β¦](over the threshold and splittable β you nominate the split; the orchestrator confirms and routes it to a dedicated session, see Β§ Size check),"unsplittable"withreason: "<one-line>"(over the threshold but must land atomically), or{ "decision": "n/a" }for Design/Planning tasks) andtype_check(the present-and-dispositioned type/content-mismatch scan β advisory, never a hard block on its own), determine the Gate accretion and Seed accretion content below for π» Code tasks (write it into the entry asgate_contribution/seed_contributionβ these are transient staging fields the flip command below reads and submits; the durable record is still the gate/seed DB marker the flip call writes, keyed by this task's id), and setsignoff: { "by": "<human>", "at": "<iso>" }. (signoff,hard_block_deps, andsize_checkare gated by the promotion hook and must be written ingrooming-state.jsonbefore the status flip, or the gate blocks the update.)
One-shot Ready-flip: once the entry above is complete, promote the task with a single call β no task id (or dependency id) is ever hand-typed on the command line, every id is resolved from the entry itself:
node ~/.claude/scripts/groom-flip-client.mjs .skill-cache/grooming/<M>/grooming-state.json <taskId>This posts the whole entry to the backend's POST /api/groom/flip route
(packages/backend/src/routes/groomFlip.ts, TaskWriteCommands.flipToReady), which
runs gate accretion + seed accretion + setDependsOn + setStatus(Ready) as one
transaction β the same order and the same groomingGate / dependsOn-before-Ready
invariants as the per-call path below, but rolling back any accretion already
committed if a later step fails, so a failed flip never leaves an orphan gate/seed
item or a wrong status. This replaces the six separate calls (gate accrete, seed
accrete, stage + apply setDependsOn, stage + apply setStatus) described next β
read the per-call breakdown when you need to understand or troubleshoot what the
one-shot command does, or when the discrete staged-intent surface is needed for
something the one-shot command doesn't cover (e.g. reviewing a staged write before
applying it).
Gate accretion (π» Code tasks): Before flipping to Ready, mint the
task's stripped runtime/launch-and-observe items onto the milestone gate store. The
accretion source is the task body's pre-groom ### ποΈ Manual verification section,
when present β a Code task author writes it only when there are real runtime items to
list, so read whatever the section actually contains (never boilerplate β that
convention is retired). Call the accretion route through the vendored client, never
a body-append for the accretion itself:
node ~/.claude/scripts/gate-state-client.mjs accrete \
'{"project":"<project-id>","taskId":"<task-id>","title":"<task-title>",
"milestone":"<M>","classification":"<Read-Only|Prod-Mutating|Human-Observation|needs-triage>",
"items":[{"text":"<item 1>"}, "β¦"]}'For a task with no standalone runtime item, call it with "classification":"none" and
an empty (or omitted) items array instead. Either way this POSTs to the loopback,
device-authed /api/gate/accrete-contribution route (see
packages/backend/src/routes/gateState.ts), which mints one gate_item per item on the
milestone gate store and records the gate_accretion marker
checkGroomingPromotionGate reads before allowing the Ready flip β the call itself is
the durable record; nothing further needs writing to grooming-state.json.
Confirm the accretion in chat before the Ready-flip.
Once accreted, if the task body still carries a ### ποΈ Manual verification
section, stage a task.patchBodySection (operation: "remove") that removes it
from the body entirely β never leave it behind and never replace it with boilerplate
(Covered by the Manual Verification Gate.). Fall back to a whole-body
task.updateBody only where patchBodySection isn't available (see Β§ Editing a task
body). A post-groom π» Code task carries no manual-verification section at all;
runtime/manual verification for it is owned by the gate from that point on, and its
absence in the body is the correct post-groom state, not a gap.
Seed accretion (π» Code tasks): The operational twin of Gate accretion.
Before flipping to Ready, classify each candidate line the pre-groom body's ## Operational
seed section carries as operational-seed (a genuine prod-data row/flag/default
deliberately kept out of this task's own PR β e.g. an analyzer_configs row,
config-category defaults, alias/cohort flags), in-pr (mislabeled β it actually ships in
the PR and does not accrete), or needs-triage (unclear, deferred). Mint every
operational-seed-classified candidate onto the milestone seed store β never the
in-pr/needs-triage ones. Call the accretion route through the vendored client,
never a task.updateBody body-append:
node ~/.claude/scripts/seed-state-client.mjs accrete \
'{"project":"<project-id>","taskId":"<task-id>","title":"<task-title>",
"milestone":"<M>","decision":"seeds","seeds":[{"spec":"<seed 1>"}, "β¦"]}'For a task with no operational seed (or every candidate classifies in-pr/needs-triage),
call it with "decision":"none" and an empty (or omitted) seeds array instead. This POSTs to the loopback, device-authed
/api/seed/accrete-contribution route (see packages/backend/src/routes/seedState.ts),
which mints one seed_item per seed on the milestone seed store and records the
seed_accretion marker checkGroomingPromotionGate reads before allowing the Ready
flip β again, the call itself is the durable record.
Once accreted, stage a task.patchBodySection (operation: "replace",
find: "None.", replaceWith: "- <seed 1>\n- β¦") that writes the same seed
spec(s) into the task body's own ## Operational seed section, replacing the
None. placeholder every task body already carries there β the operational twin of
Gate accretion's removal, but in the opposite direction: the section gets filled in,
not stripped out. Stage this on its own, with no groupId, and let it commit
before staging the seed.stage / task.setStatus group β never batch it as a
same-instant sibling of the arming task.setStatus. This must land on the real page
before the Ready-flip's stage-time content-match check runs
(checkGroupArmingIntentCompleteness re-derives the section server-side from the
stored body and cross-checks it against the accreted seed_item rows); staged in the
same group as the arming intent, the check would see only the still-unapplied
None. placeholder and pass on an empty comparison β the exact self-verifying gap
this content-match exists to close. A post-groom π» Code task carrying seeds shows
those seeds' real specs in its own ## Operational seed section; a task with none
shows None. β both are correct post-groom states, not a gap.
Confirm the accretion in chat before the Ready-flip.
Declared writes validation (π§ Operational tasks): Before flipping a π§ Operational task
to Ready, check its body for a ## Declared writes section (see task-writing.md Β§
Declared writes for the authoring format). It is optional β most Operational tasks carry none,
and that is fine β but when present it is load-bearing: it is what lets a dispatched ops
session's session.requestCapability calls auto-approve instead of parking for a human
decision, so a malformed entry silently strands the session on writes that should have sailed
through. Validate each bullet parses to a non-empty, backtick-quoted capability string; an
empty or tag-only bullet is malformed and blocks the Ready-flip the same way an unresolved
## Open Questions line does β send it back for correction rather than promoting around it.
Do not invent a classification tag the author didn't write: an entry with no tag, or an
ambiguous one, is left as-is (it still promotes β readinessGate.ts treats a missing/ambiguous
tag as a valid, fail-closed Prod-Mutating entry, never a validation failure) β only flag
entries where no capability could be parsed at all. This check runs automatically at
stage-time too (checkReadiness runs unconditionally on every promotion), so a missed
malformed entry is caught server-side regardless β but catching it in review avoids a bounced
flip.
- Then, stage the write per task through the sanctioned device-authed
staged-intents CLI client (
node ~/.claude/scripts/staged-intents-client.mjs create <kind> <json-payload> <projectId> [groupId]β seepackages/backend/scripts/staged-intents-client.mjs), never a directnotion-update-pagecall. This runs in the trusted Remote-Control session, so it stages through the same device-authed surface the dashboard panels use (POST /api/staged-intents, authenticated by$ORCHESTRATOR_DEVICE_TOKENβ not the stage-onlyORCHESTRATOR_STAGE_TOKENtransport, which is reserved for unattended orchestrator-launched worktree sessions staging through the orchestrator MCP tool surface). After conversational sign-off in this session, apply each staged intent withnode ~/.claude/scripts/staged-intents-client.mjs apply <intentId>(POST /api/staged-intents/:id/apply) β the readiness/groomGate guards still enforce on apply. This is not a UI panel: the session stages and applies its own writes, but only after the human has signed off in chat.- Generate one
groupIdper task (any stable string, e.g. the task id) so its writes present and apply as a unit. - If the task has hard-block deps (or had any and now has none), stage a
task.setDependsOnintent:{"taskId": "<id>", "dependsOn": ["<id>", β¦]}β the rendered array of hard-block task IDs. Hard-block deps live in theDepends Onproperty, never in the task body β downstream sessions read the property; body sequencing is invisible to them. If existingDepends Onheld free-form prose (notes, hints, soft-order observations), the canonical array overwrites it β soft-order observations are not persisted; they were a batch-level conversation and live in the chat record. - Then stage a
task.setStatusintent with the samegroupId:{"taskId": "<id>", "status": "Ready", "groomingGate": {"size_check": <the recorded size_check>, "type_check": <the recorded type_check>}}. ThegroomingGatefield is required β it is the command layer's promotion-gate check (checkGroomingPromotionGateingroomGate.ts, wired intoTaskWriteCommands.setStatus), the successor to the retiredgroom-gate.mjsPreToolUse hook: a missing or undispositionedsize_check/type_checkblocks the apply with a 409 and an annotation on the staged intent, same failure mode as the old hook. - Apply the
task.setDependsOnintent before (or together with) thetask.setStatusintent β the Ready flip is blocked with a 409 unless its group already carries an applied (or concurrently-applied)task.setDependsOnfor the same task.
- Generate one
- Confirm in chat what was staged (Ready flip and
Depends Onvalue) for each task, and that it is now waiting for human apply. Then present the next batch.
Approve-by-standard (interactive π Design / π Planning types)
The numbered flow above is the per-task π» Code path. Interactive types promote by standard, not per item. After the consolidated triage (Step 2), the human engages only the needs-attention + blocked rows; every task left in the clean set promotes by default (visible + veto-able) β you do not collect a per-item positive stamp.
- Record a
triageobject per interactive task ingrooming-state.json(not a bare string) βgroomGate.applyTriageFloorreadsentry.triage.proposedVerdict, so the entry must be:
Setting"triage": { "proposedVerdict": "clean" | "blocked" | "needs-attention" }triage: "clean"(a string) makes.proposedVerdictundefinedand the task floors toneeds-attention.proposedVerdictis your judgment call; the required-heading fact is derived server-side from the composed body β do not send it. Record it alongsideconstraints_dispositioned(see Step 1 Β§ constraint disposition). The flip client carries the wholetriageobject ingroomingGate; the server re-derives the deterministic floor and can only ever downgrade a proposedclean(a hard-block dep not β Done βblocked; no## Open Questionsheading present βneeds-attention; a routed constraint-conflict βneeds-attention). It never upgrades a judgedblocked/needs-attentionback toclean. - The
readiness_overridereason is auto-applied β never hand-typed. For a π Design task promoted clean, the backend supplies the standard template reason ("Design task β open questions are the /design worklist, resolved at execution; triaged clean in the<milestone>consolidated Design triage"); you record the cleantriageverdict, not the reason string. size_checkis{"decision": "n/a"}for Design/Planning, and gate/seed accretion does not apply (those are π» Code artifacts). Per-task disposition records + audited applies are preserved β the ceremony removed is only the per-item positive stamp.
Gates last: the milestone's π¦ Gate task is the final batch, after all code
tasks are signed off. Accretion happens incrementally β as each π» Code
task is promoted (Gate accretion above), its stripped items are minted onto the
milestone gate store via the accretion route, not appended to the Gate task body. The
final Gate batch confirms readiness (node ~/.claude/scripts/gate-state-client.mjs
readiness --milestone <M>) and presents the Gate for sign-off. The milestone
config-seed π§ Operational task accretes the same way (Seed accretion above) β a
human runs the seeded items via /ops at milestone end, after the code is merged and
deployed.
Re-check the board before finishing β don't close on a stale snapshot. Step 1 loaded the board once at the start, but new π² Backlog tasks can arrive during the session (the operator or another session files them while grooming runs). So before declaring the board groomed, re-query the live board for any π² Backlog tasks not in the original worklist. If any appeared, continue β groom them in fresh batches (Steps 2β4) exactly like the rest; don't defer them to "next time." Repeat the re-query until a pass finds no new Backlog. Only then, when every batch is signed off, confirm the milestone board is fully groomed.
Expect waves when a
/designexecution session is running concurrently β it files follow-on π² Backlog Code tasks into this board while you groom (a real run saw two waves, 12 + 3 tasks, arrive mid-session). The re-query-until-clean loop is what makes this safe, so treat it as load-bearing, not a formality. Record the set of task ids from the Step-1 worklist so each re-query surfaces the delta (newly-arrived tasks) at a glance rather than by manual diff β and remember a wave-added entry needsproject/milestone/titlestamped like any other (Step 1).
Rules (hard)
See ../_shared/reference/hard-rules.md for the planning-procedure core this
skill shares with /design and /ops (deterministic load, the human as the
gate, no silent writes, git -C not cd, and cache/state files via the
Edit/Write tool) β canonical source
packages/backend/src/planning/procedureCore.ts. Grooming-specific rules:
- Source of truth: Notion for architectural rules, decisions, and task
definitions. For implemented detail (DDL, signatures, analyzer specs), the code
under
source_rootwins; on intent/rationale, Notion wins. - Scope is the target milestone only. Do not modify tasks on other boards unless a dependency issue is explicitly identified and the human approves it.
- Never mark a β Done or βοΈ Deferred task Ready. Never retroactively edit any ordinary task already at ποΈ Ready or beyond β file a sibling instead. A Ready task may be in-flight: auto-dispatched if π» Code, human-run if π§ Operational / π Investigation / π§ͺ Testing β editing it races a live session. This holds for every ordinary type, without exception. The π¦ Gate is the lone non-ordinary task: an accumulator that, by its type's definition, accretes manual-verification items while sitting at Ready β appending to it is its lifecycle, not a modify-a-Ready-task exception.
- No silent writes (shared rule, groom-specific mechanics): every change is
staged through
staged-intents-client.mjsβ never a directnotion-update-pagecall. - Investigate before resolving. Reading the code comes before deciding what's resolved. "Decide at implementation time" is a defer, not a resolve.
- A π» Code body must be self-contained for an isolated worktree. The auto-dispatched
implementer cannot read the skill tree, the prod host, or the config tree β so every value,
format, or decision it would have to fetch from any of those must be inlined into the body
before promotion. This is a readiness bar distinct from "open questions resolved," and the single
most-repeated groom-time judgment (see Step 3 Β§ Inline every value the isolated implementer can't
research). A wrong body is worse than a vague one β correct errors regardless of fix cost;
patchBodySectionmakes the fix one line. - Cite-or-route, never invent. An open question that is an architectural
decision is cleared only by (a) citing a locked decision (an arch-page rule or
an already-β
-Done Design task) or (b) routing to
/designβ file a π Design task and add a hard-blockDepends Onedge to it. Never invent the answer in-grooming. A task whoseDepends Onnames a not-yet-β -Done Design/Planning task is not promotable (the promotion gate blocks it, FM3; for interactive types the triage floor downgrades it toblocked). - Per-task sign-off is an invariant for π» Code. A batched or momentum operator action ("these all look fine") never stands in for the per-task disposition records; π» Code tasks are always promoted individually. (Interactive Design / Planning types promote approve-by-standard β the documented batch flow, not a shortcut around this invariant; see Step 4 Β§ approve-by-standard.)
- The human is the promotion gate (shared rule) β but its granularity is
type-dependent, because the stakes are. For auto-dispatched π» Code, a wrong
Ready launches an unattended worktree, so promotion is a per-task human
decision, taken individually β a batched/momentum action never stands in for it.
For interactive π Design / π Planning (which
/designpicks up, so a wrong Ready is a reversible glance, not a launch), promotion is approve-by-standard: under one consolidated triage the clean set promotes by default (visible + veto-able) β no per-item or bulk positive stamp (see Step 2 Β§ consolidated triage and Step 4 Β§ approve-by-standard). The gate is preserved either way; per-task disposition records + audited applies are preserved either way. Reduced ceremony is not reduced rigor β the investigation posture (arch-page reading, code exploration, anchor grounding) is the sole non-server backstop once the per-item decision is removed, and stays non-negotiable. - Code tasks default to < 500 LoC estimated. The size check is
load-bearing, not advisory β every Code task carries an explicit
Size: line in its presentation header, and
size_checkis a required field ingrooming-state.jsonthat the promotion gate enforces. The 500-LoC default is a split threshold, not a hard ceiling β a task over it is nominated for splitting (and a sub-500 task may split on a natural seam); there is no hard file-count ceiling; a task that must land atomically records"unsplittable"+reason. Splitting and merging are orchestrator-driven: a grooming session nominates a split/merge candidate (decision: "split_now"+split_into), never performs it in-session β the orchestrator confirms the nomination and routes the split to a dedicated session (detect β confirm β route; seepresentation.mdΒ§ Size check). Design and Planning tasks are sized in open-question count, not LoC; write{"decision": "n/a"}for them. - Hard-block dependencies live in the Notion
Depends Onproperty, never in the task body. Soft-order observations are batch-level conversation only β not persisted. Thetask.setDependsOnintent is staged with the samegroupIdas thetask.setStatusReady flip (see Step 4). Seepresentation.mdΒ§ Dependencies for the hard-block-vs-soft-order test.
See reference/anti-patterns.md for the failure modes these rules prevent.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

