sync

Installation
SKILL.md

Error handling convention for ALL bash commands in this skill:

  • IF command exits non-zero → capture stderr, print diagnostic, then retry or fallback:
    ✗ {command} failed: {stderr summary}
      → Try: {suggested fix}
      → Fallback: {what the skill does instead}
    
  • IF command returns empty stdout when a result is expected → treat as ⚠ warn, note it, proceed with fallback
  • Never stop the entire skill for a single command failure — degrade gracefully and skip the affected section

0. Parse Arguments & Validate Environment

Parse mode from $ARGUMENTS:

  • IF empty or "full" → MODE=full
  • IF "quick" → MODE=quick
  • IF "--consolidate" → MODE=consolidate
  • IF unrecognized → show usage and stop: "Usage: /product:sync [quick|full|--consolidate]"

Check git repo:

  • Run git rev-parse --is-inside-work-tree 2>&1
  • IF exit code non-zero → stop: "✗ Not inside a git repo. Run git init or cd into your project."
  • Run git remote -v 2>/dev/null → store HAS_REMOTE (true if any remote configured)

Check Linear CLI:

  • Run linear auth whoami 2>&1
  • IF command not found → set LINEAR_AVAILABLE=false, warn:
    ✗ linear CLI not found
      → Try: Install from https://github.com/schpet/linear-cli
      → Fallback: Running git-only sync (Linear checks will be skipped)
    
  • ELIF output contains "not authenticated" or "unauthorized" or exit code non-zero → set LINEAR_AVAILABLE=false, warn:
    ✗ linear CLI not authenticated
      → Try: Run `linear auth` to log in
      → Fallback: Running git-only sync (Linear checks will be skipped)
    
  • ELSE → LINEAR_AVAILABLE=true

Read project scope:

  • IF .linear-project exists:
    • Read file and trim whitespace/newlines: PROJECT=$(cat .linear-project | tr -d '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
    • IF trimmed value is empty → warn: ".linear-project exists but is empty. Continuing unscoped."
    • ELIF LINEAR_AVAILABLE:
      • Validate project exists: linear project list 2>&1 | grep -qi "$PROJECT"
      • IF not found → warn:
        ⚠ Project '{PROJECT}' not found in Linear. It may have been renamed or archived.
          → Try: Update `.linear-project` or run `/product:init-project` to relink
          → Fallback: Continuing unscoped (showing all projects)
        
        Set PROJECT="" (clear it)
      • ELSE → note: "Scoped to Linear project: {PROJECT}"
    • Build PROJECT_FLAG: ${PROJECT:+--project '$PROJECT'}
  • IF .linear-project missing → warn: "No .linear-project found — showing issues from all projects. Run /product:init-project to link one."

Print header:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 PRODUCT ► SYNC [{PROJECT or "All Projects"}] ({MODE})
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

IF MODE=consolidate → skip to Step 3.

1. Gather State

Collect data from all available sources. Run independent commands in parallel where possible.

From Linear (skip entirely if LINEAR_AVAILABLE=false):

  • linear issue list -s started --limit 20 $PROJECT_FLAG 2>&1 → in-progress issues
    • IF fails → warn and set STARTED_ISSUES=empty
  • linear issue list -s unstarted --limit 20 $PROJECT_FLAG 2>&1 → backlog
    • IF fails → warn and set BACKLOG_ISSUES=empty
  • linear issue list -s completed --limit 10 $PROJECT_FLAG 2>&1 → recently completed
    • IF fails → warn and set COMPLETED_ISSUES=empty
  • linear issue list -s cancelled --limit 10 $PROJECT_FLAG 2>&1 → recently cancelled (for orphan detection)
    • IF fails → warn and set CANCELLED_ISSUES=empty

From Git:

  • git log --oneline -30 2>&1 → recent commits
    • IF repo has no commits → note "Empty repo, no commits yet" and skip git-based gap detection
  • git log --oneline --first-parent main -30 2>/dev/null || git log --oneline --first-parent master -30 2>/dev/null → commits on default branch only (for squash-merge detection)
    • IF fails (no main/master) → skip squash-merge detection, note which default branch was tried
  • git branch --list 2>&1 → local branches
  • git branch -r --merged main 2>/dev/null || git branch -r --merged master 2>/dev/null → remote branches merged into default (only if HAS_REMOTE)
  • git reflog --since="7 days ago" --no-decorate 2>/dev/null | head -50 → detect force-pushes/rebases
  • Current branch name: git branch --show-current 2>&1

From GSD (if .planning/ exists):

  • Read .planning/STATE.md for current phase context
  • Read .planning/ROADMAP.md for phase list and completion status
  • Read .planning/PROJECT.md for project objectives
  • IF any file missing → skip silently (GSD is optional)

2. Detect Gaps

IF LINEAR_AVAILABLE=false → only detect git-only gaps (working tree, stale branches). Skip all Linear-dependent categories.

Compare sources and classify each finding into exactly one bucket:

Status Definition
Synced Linear status matches git state (e.g., in-progress + recent commits on branch)
Stale in Linear Issue is "In Progress" but no commits referencing it in 48h+
Untracked work Commits reference an issue ID but Linear still shows "Unstarted"
Done but open Branch merged or squash-merged into default branch, but Linear issue not moved to Done
Orphan branch Local branch references a Linear issue that is cancelled, done, or doesn't exist
Missing branch Linear issue is "In Progress" but no matching local branch exists
Squash-merged Issue ID appears in default branch's first-parent commit messages, but Linear status is still "Started" — detected via git log --first-parent
Rebased/force-pushed Branch reflog shows rebase or force-push entries for a branch mapped to an active issue — informational, flagged as ⚠
Direct-to-main Commits on default branch reference an issue ID (e.g., ENG-\d+ pattern) but no feature branch exists for that issue
Unassigned in-progress Linear issue is "In Progress" but has no assignee

Detection logic:

  • Synced: For each in-progress Linear issue, check if a local branch contains the issue ID (e.g., eng-123 in branch name) AND has commits within the last 48h referencing it.

  • Stale in Linear: In-progress issues where git log --all --oneline --since="48 hours ago" | grep -i "$ISSUE_ID" returns empty.

  • Untracked work: git log --oneline -30 | grep -oiE '[A-Z]+-[0-9]+' extracts issue IDs from recent commits, cross-reference against Linear unstarted list.

  • Done but open: Check git branch --merged main (or master) for branches containing issue IDs that are still "Started" in Linear. Also check first-parent log for squash-merge references.

  • Orphan branch: Extract issue IDs from branch names (git branch --list | grep -oiE '[a-z]+-[0-9]+'), check against cancelled/done issues list.

  • Missing branch: In-progress Linear issues where no local branch contains the issue ID.

  • Squash-merged: Issue IDs found in git log --first-parent main --oneline that are still "Started" in Linear but have no open feature branch.

  • Rebased/force-pushed: Parse git reflog for lines containing "rebase" or "reset" on branches that match active issue IDs. Flag as informational.

  • Direct-to-main: Issue IDs in default branch commits that have no corresponding branch in git branch --list.

  • Unassigned in-progress: From Linear data, check started issues for missing assignee field (parse linear issue list output).

3. Consolidate Objectives

IF MODE=quick → skip this step.

Compare three sources of truth for project-level alignment:

Source 1 — Linear project (if LINEAR_AVAILABLE and PROJECT is set):

  • Run linear project view "$PROJECT" 2>&1 → extract milestones, description, target dates
  • IF command fails or project not set → note "Linear project metadata unavailable" and skip this source

Source 2 — GSD planning (if .planning/ exists):

  • Read .planning/ROADMAP.md → extract phase names and completion status
  • Read .planning/PROJECT.md → extract stated objectives and timeline
  • IF files missing → note "GSD planning state unavailable" and skip this source

Source 3 — CLAUDE.md (if exists at repo root):

  • Read CLAUDE.md → extract any goals, objectives, or project description sections
  • IF missing → skip this source

Cross-reference:

  • Does every Linear milestone have a corresponding GSD phase?
  • Does CLAUDE.md mention goals not tracked in Linear or GSD?
  • Are there GSD phases with no matching Linear milestone (future work is okay)?

Present alignment table:

Objective Alignment:
─────────────────────
  ✓ "Auth system" — Linear milestone ↔ GSD Phase 3 ↔ CLAUDE.md
  ⚠ "API redesign" — Linear milestone, no GSD phase planned
  ⚠ "Performance audit" — In CLAUDE.md goals, not in Linear
  ○ "Onboarding flow" — GSD Phase 5, no Linear milestone (future work)

IF misalignments found AND MODE != quick: Ask: "How do you want to resolve these misalignments?"

  • Create Linear issues for untracked objectives
  • Update CLAUDE.md to match current Linear state
  • Note them and move on (address later)
  • Skip — alignment is fine as-is

IF only one source is available → note: "Only one source of truth available — consolidation needs at least two of: Linear project, GSD planning, CLAUDE.md"

4. Present Summary

Print a formatted sync report:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 SYNC REPORT
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Synced (N):
  ✓ ENG-123 — Feature description

Gaps Found (N):
  ⚠ ENG-456 — Stale: in-progress but no commits in 3 days
  ⚠ ENG-789 — Untracked: commits exist but Linear shows unstarted
  ⚠ ENG-012 — Done but open: branch merged, issue still in-progress
  ⚠ ENG-345 — Squash-merged: appears on main, still marked Started
  ○ ENG-678 — Direct-to-main: commits on main, no feature branch
  ○ ENG-901 — Unassigned: in-progress but no assignee
  ○ eng-999-old-feature — Rebased: force-push detected (informational)

Backlog (N):
  ○ ENG-234 — Urgent — Unstarted
  ○ ENG-567 — High — Unstarted

{IF consolidation ran:}
Objective Alignment:
  ✓ N aligned  ⚠ N misaligned  ○ N untracked

IF MODE=quick → print summary and skip to Step 6 (route only, no interactive resolution).

5. Resolve Gaps (Interactive)

IF zero gaps found → skip to Step 6.

Walk through each gap and ask what to do:

For "Stale in Linear": Ask: "ENG-456 has been in-progress for N days with no commits. What happened?"

  • Still working on it → keep as-is
  • Blocked → add a blocker comment to Linear: linear issue comment add $ID -b "Blocked: {reason}" 2>&1
  • Abandoned → move back to backlog: linear issue update $ID -s "Triage" 2>&1
  • Actually done → move to Done: linear issue update $ID -s "Done" 2>&1

For "Untracked work": Ask: "Found commits referencing ENG-789 but it's still Unstarted in Linear. Update it?"

  • Yes, move to In Progress: linear issue update $ID -s "In Progress" 2>&1
  • Yes, it's actually Done: linear issue update $ID -s "Done" 2>&1
  • No, those commits were reverted → skip

For "Done but open": Ask: "ENG-012 looks complete (branch merged). Close it in Linear?"

  • Yes, move to Done with auto-generated summary: linear issue update $ID -s "Done" 2>&1
  • No, there's still remaining work → skip

For "Orphan branch": Ask: "Branch eng-999-old-feature references a cancelled/done issue. Clean up?"

  • Delete the branch: git branch -d $BRANCH 2>&1 (safe delete; if fails, note it may have unmerged work and ask about -D)
  • Keep it → skip

For "Missing branch": Ask: "ENG-345 is In Progress but has no local branch. What happened?"

  • Create one now → git checkout -b $BRANCH_NAME 2>&1 (generate slug from issue title)
  • It's on a different machine → skip
  • It's done, just not tracked → move to Done

For "Squash-merged": Ask: "ENG-345 appears squash-merged into main but is still In Progress. Close it?"

  • Yes, move to Done with merge reference
  • No, there's still remaining work on another branch

For "Direct-to-main": Ask: "Found commits on main referencing ENG-678 but no feature branch was used. Track this?"

  • Move to Done (work is on main)
  • Ignore (was a hotfix, already tracked)

For "Unassigned in-progress": Ask: "ENG-901 is In Progress but has no assignee. Assign to you?"

  • Yes, assign to me: linear issue update $ID --assignee me 2>&1
  • Assign to someone else → prompt for name
  • Move back to backlog: linear issue update $ID -s "Triage" 2>&1

For "Rebased/force-pushed": (informational only) Note: "Branch for ENG-XXX was rebased/force-pushed recently. Commit references in Linear comments may be stale."

  • No action needed unless user asks

For ALL Linear update commands: check exit code. If update fails, print:

✗ Failed to update ENG-XXX: {stderr}
  → Try: Run the command manually: linear issue update ...

6. Cleanup & Route

Cleanup (ask before each action)

Stale branch cleanup:

  • List branches from "Orphan branch" gaps that user agreed to delete
  • Also check for any local branches already merged into default branch: git branch --merged main 2>/dev/null | grep -v 'main\|master\|\*'
  • Ask: "Found N merged/orphan branches. Delete them all, pick which to delete, or skip?"

Suggest issue consolidation:

  • From the gathered Linear data, check for issues with very similar titles (compare words, look for near-duplicates)
  • IF potential duplicates found → "These issues might be duplicates:"
    ⚠ ENG-111 "Login page redesign" ↔ ENG-222 "Redesign login page"
    
    "Merge them in Linear? (This just flags them — you'll need to merge manually in Linear)"
  • IF none → skip silently

Prune remote refs (only if HAS_REMOTE):

  • Check for stale remote tracking branches: git remote prune origin --dry-run 2>&1
  • IF stale refs found → ask: "Found N stale remote refs. Prune them?"
    • Yes → git remote prune origin 2>&1
    • No → skip

Route to Next Action

Based on the final state after resolving gaps:

  • IF consolidation found misalignment → suggest /product:update for a full project status review
  • IF there are unstarted high/urgent issues → suggest /product:start-task
  • IF there's an active in-progress issue with no branch → suggest creating one
  • IF GSD state exists with an incomplete phase → suggest /gsd:progress
  • IF many gaps were found and resolved → suggest running /product:sync again to verify, or /product:maintain for a deeper health check
  • IF everything is synced and nothing urgent → "All clear. Pick up the next task when ready."

<success_criteria>

  • Environment validated with explicit error messages for every failure mode
  • Linear CLI auth checked (not just existence) — graceful fallback if unavailable
  • .linear-project trimmed, validated against Linear, special chars handled
  • All 10 gap categories detected (or skipped gracefully with reason)
  • Squash-merged and direct-to-main work correctly identified
  • Objectives consolidated across Linear, GSD, and CLAUDE.md (in full/consolidate mode)
  • Every gap surfaced and user made an explicit decision
  • All Linear update commands have exit code checking
  • Cleanup offered: stale branches, duplicate issues, remote ref pruning
  • Clear next action suggested based on final state </success_criteria>
Related skills
Installs
7
First Seen
Apr 2, 2026