---
name: skill-coordinate
type: skill
description: Cross-board PM coordination for Aled's delivery projects. Use whenever running the coordinate routine, or when PM comments have landed on tickets across any state and need to be processed. Sweeps all delivery projects for new Aled/agent comments since the last coordinate run, applies decisions, advances handoffs, unblocks cleared dependencies and auto-promotes ready tickets to Todo, surfaces stranded In-Review tickets, and surfaces structural issues (duplicates, missing parents, orphaned milestones, mis-homed children, epic scope-bleed) as proposal comments only. Never merges, marks Done, or touches Pipeline. Trigger this rather than ad-hoc coordination so decisions applied to ticket bodies, routing changes, and structural proposals follow consistent conventions.
license: Proprietary — Aled Pritchard workspace use.
---

# coordinate

The cross-board PM coordination skill. Runs as a scheduled Cowork task on the Linear connector. Follows `convention-linear` throughout.

## Trigger

Scheduled poll across all delivery projects: careerOS (cOS.Build, cOS.System, cOS.Content, cOS.App), Apps (os.Claude, app.fitness, Luna MVP, bot.Trader), and A1 (all client projects including a1.MeirionPritchard). Never touches the Pipeline team (Network / Roles / Advisory / Pitches) — that is relationship work, not delivery.

For each ticket in scope, the run checks whether any comments warrant action. The **watermark rule** (below) ensures efficiency and idempotency: only genuinely new, unprocessed input is acted on.

## The trigger-boundary rule

This rule guards against two PM legs touching the same ticket. coordinate's scope is defined precisely to avoid collision with exec, qa, and merge.

**coordinate processes:**
- Comments from Aled on tickets in **any** state except In Progress under `agent:F0-RGE`.
- Comments from other PM legs (pm-intake, triage) that contain open questions Aled has since answered in a subsequent comment.

**coordinate does NOT process:**
- Tickets currently **In Progress** carrying `agent:F0-RGE` — exec owns those while being worked.
- Exec, qa, or merge handoff comments (those are state-transition records, not PM instructions).
- GitHub PR review comments — those belong to the qa or exec leg.
- Comments already processed and acknowledged in a prior run (the watermark prevents re-processing).

**Watermark:** for each ticket, find coordinate's most recent comment. Only comments posted *after* that watermark from Aled (or from any other pm leg with new content) are candidates. If no prior coordinate comment exists on the ticket, the beginning of the ticket's history is the watermark.

**Comment ordering — `list_comments` uses `updatedAt` desc by default.** Any skill that re-touches a comment (edits it, adds a reaction, or references it in a sweep) bumps its `updatedAt` — which resurfaces it at the top of the next query. `limit: 1` therefore does **not** reliably return the chronologically latest comment; it returns the most recently *touched* comment. When scanning for the watermark or Aled's latest instruction, use `orderBy: createdAt` and a sufficient limit so the correct comment is found.

**Candidate selection — bound the comment pull, don't pick by feel.** No connector primitive returns "tickets with a comment created after T", and `updatedAt` on `list_issues` is dominated by **bulk plan / triage / milestone writes** — dozens of tickets across projects routinely share one identical second-precise `updatedAt` stamp from a sweep, not from an operator comment. So a recency filter floods on bulk-touched tickets while still missing comment-only changes, and hand-picking "high-signal" candidates is judgement-by-feel that can silently skip a genuine operator instruction on a bulk-touched ticket. Until a comment-feed filter exists (see the connector ask below), select Pass 1 candidates by a **bounded, repeatable** rule:

- Scope to **non-terminal states** (exclude Done / Canceled / Duplicate).
- Within those, pull `list_comments` (orderBy `createdAt`) only for tickets whose `updatedAt` is later than coordinate's watermark **and** whose latest change is **not** a known bulk-sweep signature — many tickets sharing one identical `updatedAt` stamp is the tell of a bulk write, not an operator comment.
- **Always** include any ticket where the operator has @mentioned coordinate or the `claude-code` label since the watermark.

This keeps the per-ticket comment read bounded and the same across runs, so detection is repeatable rather than budget- and judgement-dependent. **Connector ask (non-canon, for the operator):** a workspace/team comment feed filterable by `createdAt > T` and author would remove the inference entirely — the same connector lever as the compact-`list_issues` ask (APP-288).

## Behaviour

The run executes five passes in sequence.

### Pass 1 — Comment sweep

For each delivery ticket **not** In Progress under `agent:F0-RGE`, and where new eligible comments exist since the watermark (selected per *Candidate selection* above):

1. Read the new comments and the ticket body together.
2. **Closed open question** — if the comment answers an open question in the body: apply the answer to the body directly, then reply confirming ("Applied @aledpritchard's answer: [summary]. Body updated.").
3. **Routing instruction** — if the comment is an explicit routing signal (e.g. "route to exec", "this is ready", "promote to Todo"): set `agent:F0-RGE`, move to Todo, clear the assignee. Post a confirmation comment.
4. **State / label / priority change** — apply directly, then confirm in a reply.
5. **Cross-ticket decision** — if the comment implies a follow-on ticket, file it in Backlog and link; otherwise apply in-line.

Post a single summary reply per ticket, not one reply per comment. If a comment's intent is ambiguous, post a clarifying question in-thread and leave all state unchanged until Aled replies.

### Pass 2 — Dependency hygiene

Sweep all delivery tickets with blocked-by relations.

**Stale-relation cleanup (autonomous):** where a blocker is **Canceled** or **Duplicate**, remove the blocked-by relation and comment: e.g. "Removed stale blocker to APP-120 (Canceled). No action needed."

**All blockers cleared (Done/Canceled) — harvest, then auto-promote where ready.** When a ticket's last blocker resolves:

1. **Harvest first (lift, don't re-derive).** If a cleared blocker was a design or decision ticket, lift its locked output into the dependent ticket's body — the spec, tokens, icon name, Figma node, reference SVG, interaction, whatever the build needs — and remove the satisfied relation, so the build leaf is self-contained and Forge never has to chase the design ticket.
2. **Auto-promote (Refinement → Todo) when all of these hold:** the ticket is in Refinement and **fully refined** (Pattern A criteria embedded, no open gaps or questions); an **executor label is set** (e.g. `agent:F0-RGE`); **every blocker is Done/Canceled** and its relation cleared (genuinely unblocked); any required **harvest is complete**; and **no standing operator hold applies** (the disqualifier below). Promote to Todo, clear the assignee, and comment confirming. A blocker reaching **Done is the sign-off** for that blocker's output, so no separate operator judgement is needed — that is the whole point of the design/decision ticket existing.

   **Standing-hold disqualifier — literal readiness never overrides a deliberate park.** Even when every criterion above is met, do **not** auto-promote if the ticket carries either: (a) a **prior coordinate or operator comment that explicitly reserved promotion to the operator** ("promotion from Refinement remains yours", or the operator having assigned it to himself and left it parked); or (b) a **body-stated dependency that is not encoded as a `blocks` relation** (a soft "depends on / after X" the criteria cannot see). In both cases the face-value criteria understate the real state: the operator has already signalled the call is his, or a dependency exists the relation graph does not carry. Treat it as a hold — leave it in Refinement, stay silent under the comment-idempotency guard, and do not re-surface it every run. Distinguish **"unblocked and never surfaced"** (promote) from **"unblocked, surfaced, and left parked by the operator"** (hold): a ticket coordinate itself previously handed to the operator, or that the operator has deliberately sat on, is his to release, not coordinate's to auto-promote. (Worked case: COS-223 / COS-224 satisfied every Pass 2 criterion on 2026-07-04, but coordinate had recorded "promotion from Refinement remains yours" on 2026-06-16/17 and the operator had left both parked ~3 weeks; auto-promoting would have overridden a standing hold and re-contradicted coordinate's own prior statement — APP-436.)
3. **Otherwise, hold (as before).** If any criterion is unmet — gaps remain, no executor set, harvest outstanding, a standing operator hold applies, or a judgement is genuinely still open — do **not** promote. Post a comment @mentioning Aled with the recommended next move and assign him.

Auto-promotion is **case-by-case judgement against the criteria above**, not a blanket sweep of everything in Refinement. It is idempotent: once a ticket is promoted to Todo it leaves coordinate's Refinement scope, so there is no unblock → auto-promote → re-block loop. *(Future: coordinate will promote in concert with Atlas, weighing priority and goals as well as readiness; out of scope for now — for now, promote only on the readiness criteria above.)*

**Long-stale block:** if a blocker reached Done more than three days ago and the relation was never cleared, flag it as likely stale in a comment and assign Aled.

**Still genuinely blocked:** leave in place.

### Pass 3 — Surface-and-flag

Proposals only. Post a comment @mentioning Aled. Never auto-restructure, create tickets in bulk, delete, or merge.

**Candidate duplicates:** two tickets in the same project with highly similar titles. Post: "@aledpritchard Possible duplicate: [A] and [B] appear to cover the same ground. Recommend closing one — please confirm."

**Missing parent:** a leaf `type:task` or `type:story` in a project with epics that has no `parentId`. Post: "@aledpritchard [Ticket] has no parent. Consider grouping under [suggested epic] or confirm it stands alone."

**Missing milestone:** a leaf ticket in a project with milestones that carries no milestone. Post: "@aledpritchard [Ticket] has no milestone. Recommend [suggested milestone] — please confirm."

**Mis-homed child:** a child whose `domain:*` label (or evident subject) points to a different team or project than its parent's home — e.g. a `domain:MBN` business deliverable sitting under an os.Claude canon epic, where the canon siblings carry no such domain. Post: "@aledpritchard [Ticket] carries domain:MBN but its parent [epic] is an os.Claude canon epic. Recommend re-homing to [suggested project] — please confirm." Placement is checked one ticket at a time at refinement by triage, but a bulk-authored epic with all children created in one sitting bypasses that gate; this pass is the continuous net that catches it.

**Epic scope-bleed:** an epic whose children span two distinct outcomes or domains — typically capability-building (an OS canon capability) mixed with business-delivery (work that *uses* the capability). The tell is a child set where some carry a `domain:*` label and others don't, or two clearly different outcomes under one parent. Post: "@aledpritchard [Epic] mixes two outcomes — [A] (capability) and [B] (delivery, domain:X). Recommend splitting into separate epics so each closes on its own outcome — please confirm."

At most one proposal comment per ticket per run. If an open proposal from a prior run has had no reply, do not re-post — skip.

### Pass 4 — Stranded In Review (surface only)

In Review is where a ticket waits for the merge gate, and two failure modes strand it there with nothing surfacing it — In Review is otherwise outside triage's scope, so this gap has no other owner. Both are surfaced to the operator as proposals; coordinate never merges. Apply the comment-idempotency guard (below) so a stranded ticket is surfaced once, not re-flagged every run.

**Approved-but-unmerged (Linear-detectable, actionable).** A ticket In Review carrying the **approval signal** — `agent:R3-LAY` set after review (the operator's approval action, evicting `agent:PR-1SM`), or an explicit operator `approved` comment — with **no merge after three days** has had its `@relay` merge signal lapse. Surface it: comment @mentioning the operator with the recommended `@relay` / merge action, and assign him. (Worked case: A1-72 approved 2026-06-25, the `@relay` signal never cleanly given, sat untouched until 2026-06-30.)

**False-green CI (named, not yet actionable here).** A PR whose branch predates a CI workflow newly merged to `main` reports "all checks passed" because the gating workflow never ran on it — so the ticket reads mergeable while its defining acceptance criterion was never actually verified (worked case: A1-72 / PR #27, the Playwright workflow added to `main` by A1-73 *after* #27's branch was cut). Detecting this needs **GitHub read access** — the branch-behind state and the workflow set on `main` — which this skill does not have: coordinate is **Linear-only**. It is named here as a known gap for the operator, not encoded as an instruction: either a GitHub-capable leg (qa / merge) owns the false-green guard, or coordinate's connector scope is widened to read PR/CI state. Do not bake in a check coordinate cannot execute.

### Pass 5 — Parent state rollup

A parent `type:epic` or `type:feature` frequently sits in **Backlog / Refinement / Todo** while its children are already **In Progress or Done** — a stale board signal: the outcome is clearly underway but does not read that way. Roll the parent's state up to reflect its children.

- **Trigger:** a parent (`type:epic` or `type:feature`) has at least one child in **In Progress or Done**, and the parent is itself in a not-started state (Backlog / Refinement / Todo).
- **Action:** move the parent to **In Progress**. Re-fetch the parent's current state with `get_issue` immediately before the write (per convention-linear *Reading Linear safely*), and apply the comment-idempotency guard — a parent already In Progress needs no action and no comment.
- **Hard boundary — never auto-advance a parent to Done.** An epic or feature is an outcome the **operator closes** when all its children are done (skill-triage *Behaviour*; convention-linear *Structure*). Only the "work started" transition (→ In Progress) is automated; completion stays the operator's. A parent whose children are *all* still Backlog stays put — only a child in In Progress or Done triggers the rollup, never a child merely in Todo.
- **Separate axes — no double-count.** Issue-level parent state and Linear *project* status are different axes: this rolls up the parent **issue** only, and never writes project status or touches the daily-report reporting gate (`statusType == started`). A re-opened child under an already-closed parent is out of scope (rare) — leave it.

(Captured 2026-07-07, APP-453; candidates on that run included A1-41, A1-110, COS-160 with children already In Progress/Done, versus A1-112 whose children were all Backlog and correctly stayed put.)

## Guardrails

- **Comment idempotency — no-new-information guard (every pass).** Before posting any comment — a Pass 1 confirmation, a Pass 2 hold or block note, a Pass 3 proposal, or a Pass 4 stranded-In-Review surface — read the ticket's last agent comment. If the note about to be posted is substantively the same as the last one (same hold reason, same blockers, same recommendation) and nothing material has changed since it (no new operator comment, no state / label / assignee / attachment change, no blocker cleared), **stay silent and leave the ticket as is.** Only post when there is genuinely new information: a blocker cleared, a state/label change, a new dependency, a changed recommendation, or a fresh operator comment to answer. A held ticket re-surfacing on consecutive runs with the same situation must not collect a near-identical comment each time — that costs the operator a read for zero gain and dilutes the comments that do carry new information. Where a ticket keeps re-surfacing only to be re-held, prefer a one-time structural fix that takes it out of the poll (e.g. a refined human-decision ticket → `human` + assign Aled) over re-commenting every run. (This generalises the Pass 3 "do not re-post an unanswered proposal" rule to all comment posting; the same guard lives in skill-triage.)
- Never merges. Merge belongs to merge, gated by an explicit Relay approval signal.
- Never marks Done. Done is Aled's sign-off.
- **Rolls a parent up to In Progress, never to Done.** Pass 5 moves an epic/feature to In Progress once a child is In Progress or Done, so the board reads honestly; it never closes a parent — completion of an outcome is the operator's sign-off (as above).
- **Auto-promotes Refinement → Todo only against the Pass 2 readiness criteria** (fully refined, executor set, all blockers Done, harvest complete) **and only when no standing operator hold applies** — a prior coordinate/operator comment reserving promotion to the operator, or a body-stated dependency not encoded as a `blocks` relation, disqualifies auto-promotion however ready the ticket looks (the Pass 2 standing-hold disqualifier). Never promotes a ticket with open gaps, no executor, or genuinely outstanding operator judgement; when in doubt, hold and assign Aled.
- **Surfaces stranded In-Review tickets, never resolves them.** Pass 4 surfaces an approved-but-unmerged ticket to the operator with the recommended merge action; it never gives the `@relay` signal or merges itself. The false-green CI guard is named, not executed — coordinate cannot read GitHub.
- Never touches the Pipeline team (Network / Roles / Advisory / Pitches).
- Never acts on tickets In Progress under `agent:F0-RGE`.
- Never makes destructive structural changes (delete tickets, remove epics, bulk-reassign) without Aled's explicit instruction. A mis-homed child or a scope-bled epic is surfaced as a proposal — never re-homed or split autonomously.
- Single-select agent group: when routing a ticket to Forge, the label update evicts any prior `agent:*` label. Never set two `agent:*` labels simultaneously.
- Consequential or ambiguous calls go to Aled in-thread; never a guess.

## Setup

- Deployed as a Claude Code cloud scheduled task (cloud, machine off).
- Connector: **Linear only** (GitHub write access not required for this skill).
- Routine prompt: `Run the coordinate skill.`
- Cadence: a few times a day (mind the ~1h floor).

On finish, run skill-ops-retro capture on this run's friction: file a FRICTION note to the Pulse queue for the drain pass to assess.
