linear-motion 0.2.0

A CLI tool for syncing between Linear and Motion
Documentation
{
  "productName": "Linear-Motion Sync Tool",
  "version": "1.0",
  "documentDate": "2025-09-07",
  "overview": "A command-line microservice to automate the synchronization of tasks from Linear projects into a user's Motion calendar, designed for individual developers to improve personal productivity.",
  "goals": [
    {
      "goalId": "G1",
      "description": "Eliminate manual effort in synchronizing Linear issues with a personal Motion calendar."
    },
    {
      "goalId": "G2",
      "description": "Provide a reliable, fault-tolerant, and configurable sync process that runs in the background."
    },
    {
      "goalId": "G3",
      "description": "Ensure bidirectional status updates for completed tasks to create a closed-loop workflow."
    }
  ],
  "targetAudience": {
    "primary": "Individual software developers, engineers, and product managers.",
    "characteristics": [
      "Uses Linear for team projects and Motion for personal task management.",
      "Comfortable with CLI tools and JSON configuration.",
      "Values automation and personal productivity."
    ]
  },
  "functionalRequirements": [
    {
      "reqId": "FR-CONF-01",
      "component": "Configuration",
      "description": "Tool must be configured via a single `config.json` file.",
      "acceptanceCriteria": [
        "A `linear-motion init` command exists and generates a valid JSON template.",
        "The tool correctly parses global and per-source settings."
      ]
    },
    {
      "reqId": "FR-CONF-02",
      "component": "Configuration",
      "description": "Configuration must support multiple sync sources, each with a Linear API key and project list.",
      "acceptanceCriteria": [
        "The `sync_sources` array is correctly parsed and processed.",
        "Each source can have its own sync strategy (webhook/poll)."
      ]
    },
    {
      "reqId": "FR-CONF-03",
      "component": "Configuration",
      "description": "Configuration must support global `sync_rules` that can be overridden on a per-source basis.",
      "acceptanceCriteria": [
        "Per-source rules correctly merge with and override global rules.",
        "Settings like `default_task_duration_mins` and `completed_linear_tag` are applied correctly."
      ]
    },
    {
      "reqId": "FR-SYNC-01",
      "component": "Sync Logic",
      "description": "On startup, an idempotent sync of all open, assigned Linear issues must be performed.",
      "acceptanceCriteria": [
        "Running the tool multiple times does not create duplicate tasks in Motion.",
        "All relevant existing issues are present in Motion after the first run."
      ]
    },
    {
      "reqId": "FR-SYNC-02",
      "component": "Sync Logic",
      "description": "Linear issue time estimates must be converted to Motion task durations based on a flexible strategy mapping.",
      "acceptanceCriteria": [
        "The tool correctly identifies the estimation type used by a Linear team.",
        "Fibonacci, T-Shirt, and points-based estimates are converted to the correct minute duration in Motion."
      ]
    },
    {
      "reqId": "FR-SYNC-03",
      "component": "Sync Logic",
      "description": "When a synced task is completed in Motion, a specified tag must be applied to the corresponding Linear issue.",
      "acceptanceCriteria": [
        "The tool polls Motion for completed tasks.",
        "The correct tag is applied in Linear.",
        "The ID mapping is removed from the local database upon successful tagging."
      ]
    },
    {
      "reqId": "FR-STATE-01",
      "component": "State Management",
      "description": "If the completion tag is removed from a Linear issue, the tool must re-sync it as a new task.",
      "acceptanceCriteria": [
        "The absence of the tag and the local DB mapping triggers a re-sync.",
        "A new task is created in Motion."
      ]
    },
    {
      "reqId": "FR-STATE-02",
      "component": "State Management",
      "description": "If a Linear issue is set to 'Done' or 'Canceled', the Motion task is marked as complete, not deleted.",
      "acceptanceCriteria": [
        "The task remains on the user's Motion calendar in a completed state."
      ]
    },
    {
      "reqId": "FR-CLI-01",
      "component": "CLI",
      "description": "The tool must support `init`, `sync`, `sync --watch`, and `status` commands.",
      "acceptanceCriteria": [
        "`sync` runs once and exits, printing errors inline.",
        "`sync --watch` starts a persistent daemon process.",
        "`status` communicates with the daemon via IPC to fetch and display status."
      ]
    }
  ],
  "nonFunctionalRequirements": [
    {
      "reqId": "NFR-REL-01",
      "category": "Reliability",
      "description": "The tool must be fault-tolerant. Failure to process one source must not impact others. All errors must be logged to a queryable status table."
    },
    {
      "reqId": "NFR-PERF-01",
      "category": "Performance",
      "description": "The tool must adhere to Linear and Motion API rate limits. Polling frequencies must be configurable."
    },
    {
      "reqId": "NFR-SEC-01",
      "category": "Security",
      "description": "API keys are stored in `config.json`. The user is responsible for file permissions. No OS keychain integration is required for v1.0."
    }
  ],
  "technicalAppendix": {
    "notes": "This appendix provides high-level pointers for implementation within a hypothetical Rust codebase.",
    "sections": [
      {
        "feature": "Configuration Loading",
        "description": "Logic for parsing `config.json`, including the global and per-source override for `sync_rules`.",
        "filePointers": [
          "src/config/mod.rs#L15-L40",
          "src/config/models.rs#L10-L80"
        ]
      },
      {
        "feature": "CLI Command Parsing",
        "description": "Implementation of the clap or similar library to handle the `init`, `sync`, `sync --watch`, and `status` commands.",
        "filePointers": [
          "src/main.rs#L25-L70",
          "src/cli.rs#L5-L50"
        ]
      },
      {
        "feature": "Sync Orchestrator",
        "description": "The main loop that iterates through sync sources and triggers the appropriate strategy (poll/webhook). Handles fault tolerance.",
        "filePointers": [
          "src/sync/orchestrator.rs#L50-L150"
        ]
      },
      {
        "feature": "Linear API Client",
        "description": "GraphQL client for fetching issues, updating tags, and auto-configuring webhooks.",
        "filePointers": [
          "src/clients/linear.rs#L30-L200"
        ]
      },
      {
        "feature": "Motion API Client",
        "description": "REST client for creating, updating, and polling tasks.",
        "filePointers": [
          "src/clients/motion.rs#L25-L180"
        ]
      },
      {
        "feature": "Database Interaction",
        "description": "Wrapper for `fjall` DB. Manages the ID mapping table and the error/status tracking table.",
        "filePointers": [
          "src/db/mod.rs#L10-L100"
        ]
      },
      {
        "feature": "IPC for Status Command",
        "description": "Implementation of the IPC server (in the daemon) and client (in the `status` command handler) for querying runtime state.",
        "filePointers": [
          "src/ipc/server.rs#L20-L80",
          "src/ipc/client.rs#L15-L60"
        ]
      }
    ]
  }
}