agentleFS
stoa-platformstoa-platform/stoa/.claude/skills/sync-plan/SKILL.md

sync-plan

Skill9 starsChanged 7 months agoApache-2.0View on GitHub

Cycle-aware bidirectional sync between plan.md and Linear. Discovers all cycle tickets, detects drift, updates markers.

The file

Show its rules and text

We split this skill into the rules it gives an agent

17 rules. About 0 are common, 6 are distinctive, and 11 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈6

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 11

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Always start by discovering the active cycles:

    line 25
  2. Very specific:

    Read plan.md and extract all CAB-XXXX references with their current markers:

    line 59
  3. Very specific:

    Check claimed_at > 2 hours ago

    line 99
  4. Very specific:

    Check PID alive: kill -0 $PID (same machine only)

    line 101
  5. Distinctive:

    If ALL checks fail → STALE

    line 102
  6. Distinctive:

    If status is Done → keep in Done section (historical)

    line 113
  7. Distinctive:

    If status is Todo/In Progress → Warning: ticket not assigned to any cycle

    line 114
  8. Distinctive:

    Update markers for existing tickets (4a)

    line 120
  9. Very specific:

    NEVER delete sub-items under [~] tickets (they contain PR references and manual progress)

    line 183
  10. Distinctive:

    NEVER delete the Milestones, KPIs Demo, or Regles sections

    line 184
  11. Very specific:

    NEVER delete phase metadata ([owner: X]) from plan.md during regeneration

    line 185
  12. Very specific:

    If claim file shows phase complete (completed_at != null) → update plan.md marker to [x]

    line 187
  13. Very specific:

    When adding a new ticket, format: - [ ] CAB-XXXX: <title> (<estimate> pts, P<priority>)

    line 188
  14. Very specific:

    After processing the missing set, recompute rolling_avg_3 from the last 3 entries (averages of pts_per_day, issues_done/days, …

    line 237
  15. Very specific:

    If mode is --velocity, read velocity.json and display:

    line 248
  16. Very specific:

    After every sync (all modes except --velocity), compute utilization:

    line 301
  17. Very specific:

    Last sync timestamp: always update the > Last sync: line in plan.md header

    line 336

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Plan ↔ Linear Sync (Cycle-Aware)

Synchronize plan.md with Linear ticket statuses using cycle-driven discovery.

Target: $ARGUMENTS

Step 0: Determine Sync Mode

ArgumentModeDescription
(empty)Full syncFetch current + next cycle, detect all drift
CAB-XXXXSingle ticketFetch one ticket, update plan.md marker
--pushPush to LinearPush plan.md markers → Linear statuses
--cyclesCycle discoveryShow current + next cycle summary only
--velocityVelocity reportShow velocity history table + trend + capacity forecast

Step 1: Fetch Linear Cycles

Always start by discovering the active cycles:

linear.list_cycles(teamId: "<LINEAR_TEAM_ID>")

Identify:

  • Current cycle: type: "current" or dates containing today
  • Next cycle: type: "upcoming" or starts after current ends

Extract: cycle ID, name, start date, end date, scope (total issues), completed count.

Step 2: Fetch Cycle Issues

For each active cycle (current + next):

linear.list_issues(
  team: "<LINEAR_TEAM_ID>",
  cycle: "<cycle_id>",
  first: 50
)

For each issue, extract: identifier, title, status, priority (value + name), estimate (value), cycleId, completedAt.

Important: The status field is a string ("Done", "In Progress", "Todo", "Backlog", "Canceled", "Duplicate"), NOT a nested object.

Priority mapping: priority is {value: N, name: "..."} where 1=Urgent, 2=High, 3=Normal, 4=Low.

Estimate mapping: estimate is {value: N, name: "..."} where N is fibonacci points (1,2,3,5,8,13,21,34,55).

Step 3: Parse plan.md

Read plan.md and extract all CAB-XXXX references with their current markers:

MarkerMeaning
- [x]Done locally
- [~]Partially done
- [ ]Not started or pending

Extract ticket IDs using pattern: CAB-\d{3,4}

Also identify which cycle section each ticket is in (e.g., "Cycle 7 — CURRENT" vs "Cycle 8 — NEXT").

Step 4: Detect Drift (3 categories)

4a. Marker Drift (ticket in both plan.md and Linear)

plan.mdLinearDrift?Action
[x]DoneNoSkip
[x]In ProgressYesWarning: plan.md says done but Linear disagrees
[ ]DoneYesUpdate plan.md → [x]
[ ]In ProgressYesUpdate plan.md → [~]
[~]DoneYesUpdate plan.md → [x]
[~]TodoYesUpdate Linear → In Progress
anyCanceled/DuplicateYesStrikethrough in plan.md

4b. Missing from plan.md (ticket in Linear cycle but NOT in plan.md)

This is the most critical category — the root cause of past drift.

For each issue in the cycle that has NO matching CAB-XXXX in plan.md:

  • Report: CAB-XXXX: NOT IN PLAN (Linear: <status>, <points> pts, <priority>)
  • Propose: add to the appropriate cycle section in plan.md

4c. Orphan in plan.md (ticket in plan.md but NOT in any active cycle)

4d. Stale Claims

For each file in .claude/claims/*.json: For each phase with owner != null: 1. Check claimed_at > 2 hours ago 2. Grep operations.log for SESSION-START with matching instance ID in last 2h 3. Check PID alive: kill -0 $PID (same machine only) 4. If ALL checks fail → STALE 5. Report: CAB-XXXX Phase N: STALE CLAIM (claimed by <instance> at <time>, no activity) 6. Propose: auto-release (clear owner, claimed_at, pid in claim file) 7. Log: RELEASE | task=<ID> phase=<N> instance=<old_owner> reason=stale

For standalone claims (.claude/claims/<CAB-XXXX>.json): Same 3 checks. If stale → report + propose auto-release.

See phase-ownership.md for full stale detection protocol.

For each CAB-XXXX in plan.md that isn't in current or next cycle:

  • If status is Done → keep in Done section (historical)
  • If status is Todo/In Progress → Warning: ticket not assigned to any cycle

Step 5: Apply Updates

Direction: Linear → plan.md (default)

  1. Update markers for existing tickets (4a)
  2. Add missing tickets from cycle to plan.md (4b) — in the correct cycle section
  3. Move tickets between cycle sections if cycleId changed
  4. Reorder within each section by priority (P1 first, P4 last)

Direction: plan.md → Linear (when user says --push)

  • [x] in plan.md → linear.update_issue(status="Done")
  • [~] in plan.md → linear.update_issue(status="In Progress")
  • [ ] with priority change → linear.update_issue(priority=N)

Step 6: Regenerate plan.md Structure

plan.md follows this exact structure:

# Sprint Plan — STOA Platform

> Auto-synced with Linear via `/sync-plan`. Source of truth: Linear cycles.
> Last sync: YYYY-MM-DD

## Cycle N (dates) — CURRENT

**Scope**: X pts | **Done**: Y pts | **Velocity**: Z issues closed
**Theme**: <from cycle description or memory.md>

### In Progress
- [~] CAB-XXXX: Title (N pts, PN)
  - ✅ completed sub-item
  - [ ] remaining sub-item

### Todo
- [ ] CAB-XXXX: Title (N pts, PN)

### Done (N issues)
- [x] CAB-XXXX: Title (N pts) — PR #N

---

## Cycle N+1 (dates) — NEXT

**Theme**: <description>

### Todo
- [ ] CAB-XXXX: Title (N pts, PN)

### Backlog (parked in cycle, not committed)
- CAB-XXXX: Title (PN)

---

## Milestones
(preserved from existing plan.md)

## KPIs Demo
(preserved from existing plan.md)

## Regles
1. **Linear is source of truth** — plan.md is a view, not the master
2-N. (preserved from existing plan.md)

Preservation rules:

  • NEVER delete sub-items under [~] tickets (they contain PR references and manual progress)
  • NEVER delete the Milestones, KPIs Demo, or Regles sections
  • NEVER delete phase metadata ([owner: X]) from plan.md during regeneration
  • Phase ownership markers are NOT derived from Linear — preserve them as-is
  • If claim file shows phase complete (completed_at != null) → update plan.md marker to [x]
  • When adding a new ticket, format: - [ ] CAB-XXXX: <title> (<estimate> pts, P<priority>)
  • Done tickets with PR references: preserve the — PR #N suffix

Step 7: Compute Metrics + Velocity Tracking

Calculate and display:

  • Scope: sum of all estimates in current cycle
  • Done: sum of estimates for Done issues
  • Velocity: count of Done issues
  • Completion %: Done / Scope
  • Burn rate: issues completed per day (based on cycle start date)

7a. Auto-Update velocity.json (close + backfill)

Read .claude/state/velocity.json once.

Compute the set of cycles that should be recorded:

  1. last_recorded = max id in cycles[]
  2. previous_cycle = number of the most recent closed cycle (from list_cycles(type: "previous"))
  3. Missing set = every cycle number in range (last_recorded, previous_cycle] that has no entry in cycles[]
  4. Also include the current cycle if completion_pct == 100 OR today > end_date (the original close trigger)

For each cycle in the missing set + current-if-closed, in ascending order:

a. Fetch its issues:

linear.list_issues(team: "<LINEAR_TEAM_ID>", cycle: "<N>", limit: 250)

b. Build the entry (same schema as before):

{
  "id": <cycle_number>,
  "name": "Cycle <N>",
  "start": "<startsAt date>",
  "end": "<endsAt date>",
  "days": <duration_days>,
  "points_committed": <scope; prefer Linear's scopeHistory[-1] if it includes sub-issues>,
  "points_done": <sum of estimate.value for status=="Done"; cross-check with completedScopeHistory[-1]>,
  "issues_total": <total>,
  "issues_done": <done count>,
  "prs_merged": <gh pr list --search "merged:<start>..<end>" --json number | jq length, else null>,
  "pts_per_day": <done_pts / days>,
  "completion_pct": <done_pts / scope * 100>,
  "note": "<set if scopeHistory grew >1.2x mid-cycle, else omit>"
}

c. Append to cycles[] only if id not already present (idempotent).

After processing the missing set, recompute rolling_avg_3 from the last 3 entries (averages of pts_per_day, issues_done/days, completion_pct). Update last_synced to today. Write the file in a single atomic operation.

Rules:

  • Append-only — never modify historical entries (a stale value once written is preserved as a snapshot)
  • Idempotent — re-running the same day is a no-op once cycles are recorded
  • Cap backfill at 8 cycles per run to bound MCP cost; log a warning if more are missing
  • prs_merged is best-effort; null is acceptable
  • For sub-issue accuracy: trust Linear's scopeHistory[-1] / completedScopeHistory[-1] over list_issues sums when both are available

7b. Velocity Report (--velocity mode)

If mode is --velocity, read velocity.json and display:

Velocity History — STOA Platform
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

| Cycle | Dates | Pts | Done | Velocity | Completion |
|-------|-------|-----|------|----------|------------|
| C7    | Feb 9-15 | 505 | 505 | 72.1/day | 100% |
| C8    | Feb 16-22 | 86 | 5 | ... | 6% |

Rolling 3-cycle avg: XX.X pts/day
Target utilization: 80%
Capacity forecast (next cycle, 7 days): XXX pts target

Trend: [↑|↓|→] <description>

Then exit (no sync needed).

Step 8: Report

Sync Results (YYYY-MM-DD):
━━━━━━━━━━━━━━━━━━━━━━━━━

Cycle N (current): X/Y pts done (Z%)
Cycle N+1 (next): A issues planned

Drift detected: N items
  CAB-XXXX: [ ] → [x] (Linear: Done)
  CAB-YYYY: [~] → [x] (Linear: Done)

Missing from plan.md: N items
  CAB-ZZZZ: Added (Linear: Todo, 8 pts, P2)
  CAB-WWWW: Added (Linear: In Progress, 3 pts, P1)

Orphans (in plan, not in cycle): N items
  CAB-VVVV: In Done section (OK — historical)

Stale Claims: N
  CAB-XXXX Phase 2: claimed by t4821 at 14:00, no activity (auto-released)
  CAB-YYYY: claimed by t9012 at 13:30, PID dead (auto-released)

Updates applied: N (Linear → plan.md)
Updates pushed: N (plan.md → Linear) [only if --push]

plan.md updated. Review changes with `git diff plan.md`.

8a. Capacity Utilization Warning

After every sync (all modes except --velocity), compute utilization:

  1. Read .claude/state/velocity.json
  2. Compute: capacity = rolling_avg_3.pts_per_day × cycle_days
  3. Compute: target = capacity × target_utilization
  4. Compute: utilization = pts_committed / target × 100

If utilization < 70%:

⚠️  Cycle under-loaded: XXX/YYY pts (ZZ% of target capacity)
    Run `/fill-cycle` to analyze backlog and propose fill plan.

If utilization 70-100%:

✅  Cycle healthy: XXX/YYY pts (ZZ% of target capacity)

If utilization > 100%:

🔴  Cycle over-loaded: XXX/YYY pts (ZZ% of target capacity)
    Consider deferring lower-priority items to next cycle.

Rules

  • Never delete lines from plan.md — only update markers and add new entries
  • Preserve sub-items — manual progress notes under [~] tickets are sacred
  • Preserve sections — Milestones, KPIs, Regles sections are manually maintained
  • Log sync in operations.log: MCP-CALL | service=linear action=sync-plan tickets=N cycles=current+next
  • Conflict resolution: Linear wins by default (plan.md is a view)
  • Rate limiting: max 20 get_issue calls per sync (use list_issues batch)
  • No auto-commit — show diff to user, let them decide whether to commit
  • Cycle-first discovery — always fetch by cycle, not just by project. This ensures tickets added to a cycle in Linear automatically appear in plan.md
  • Backlog/Duplicate/Canceled: skip these statuses when adding to plan.md Todo section. Only include in Backlog subsection of next cycle (without checkbox).
  • Last sync timestamp: always update the > Last sync: line in plan.md header

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.