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.
In this repository27 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- cli/CLAUDE.mdCLAUDE.md
- control-plane-api/CLAUDE.mdCLAUDE.md
- control-plane-ui/CLAUDE.mdCLAUDE.md
- e2e/CLAUDE.mdCLAUDE.md
- portal/CLAUDE.mdCLAUDE.md
- stoa-gateway/CLAUDE.mdCLAUDE.md
- analyticsSkill
- cartoSkill
- ci-debugSkill
- ci-fixSkill
- competitive-watchSkill
- councilSkill
- decomposeSkill
- extractSkill
- fill-cycleSkill
- generate-backlogSkill
- impactSkill
- memory-consolidateSkill
- monitor-cdSkill
- parallel-reviewSkill
- pr-guardianSkill
- rewriteSkill
- roadmapSkill
- spec-drivenSkill
- verify-megaSkill
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
- Distinctive: line 25
Always start by discovering the active cycles:
- Very specific: line 59
Read plan.md and extract all CAB-XXXX references with their current markers:
- Very specific: line 99
Check claimed_at > 2 hours ago
- Very specific: line 101
Check PID alive: kill -0 $PID (same machine only)
- Distinctive: line 102
If ALL checks fail → STALE
- Distinctive: line 113
If status is Done → keep in Done section (historical)
- Distinctive: line 114
If status is Todo/In Progress → Warning: ticket not assigned to any cycle
- Distinctive: line 120
Update markers for existing tickets (4a)
- Very specific: line 183
NEVER delete sub-items under [~] tickets (they contain PR references and manual progress)
- Distinctive: line 184
NEVER delete the Milestones, KPIs Demo, or Regles sections
- Very specific: line 185
NEVER delete phase metadata ([owner: X]) from plan.md during regeneration
- Very specific: line 187
If claim file shows phase complete (completed_at != null) → update plan.md marker to [x]
- Very specific: line 188
When adding a new ticket, format: - [ ] CAB-XXXX: <title> (<estimate> pts, P<priority>)
- Very specific: line 237
After processing the missing set, recompute rolling_avg_3 from the last 3 entries (averages of pts_per_day, issues_done/days, …
- Very specific: line 248
If mode is --velocity, read velocity.json and display:
- Very specific: line 301
After every sync (all modes except --velocity), compute utilization:
- Very specific: line 336
Last sync timestamp: always update the > Last sync: line in plan.md header
≈ “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.
Files
Plan ↔ Linear Sync (Cycle-Aware)
Synchronize plan.md with Linear ticket statuses using cycle-driven discovery.
Target: $ARGUMENTS
Step 0: Determine Sync Mode
| Argument | Mode | Description |
| (empty) | Full sync | Fetch current + next cycle, detect all drift |
CAB-XXXX | Single ticket | Fetch one ticket, update plan.md marker |
--push | Push to Linear | Push plan.md markers → Linear statuses |
--cycles | Cycle discovery | Show current + next cycle summary only |
--velocity | Velocity report | Show 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:
| Marker | Meaning |
- [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.md | Linear | Drift? | Action |
[x] | Done | No | Skip |
[x] | In Progress | Yes | Warning: plan.md says done but Linear disagrees |
[ ] | Done | Yes | Update plan.md → [x] |
[ ] | In Progress | Yes | Update plan.md → [~] |
[~] | Done | Yes | Update plan.md → [x] |
[~] | Todo | Yes | Update Linear → In Progress |
| any | Canceled/Duplicate | Yes | Strikethrough 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)
- Update markers for existing tickets (4a)
- Add missing tickets from cycle to plan.md (4b) — in the correct cycle section
- Move tickets between cycle sections if
cycleIdchanged - 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 #Nsuffix
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:
last_recorded= maxidincycles[]previous_cycle= number of the most recent closed cycle (fromlist_cycles(type: "previous"))- Missing set = every cycle number in range
(last_recorded, previous_cycle]that has no entry incycles[] - Also include the current cycle if
completion_pct == 100ORtoday > 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_mergedis best-effort; null is acceptable- For sub-issue accuracy: trust Linear's
scopeHistory[-1]/completedScopeHistory[-1]overlist_issuessums 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:
- Read
.claude/state/velocity.json - Compute:
capacity = rolling_avg_3.pts_per_day × cycle_days - Compute:
target = capacity × target_utilization - 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_issuecalls per sync (uselist_issuesbatch) - 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
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.

