diff --git a/openspec/changes/archive/2026-01-11-add-linear-mcp-workflow/design.md b/openspec/changes/archive/2026-01-11-add-linear-mcp-workflow/design.md new file mode 100644 index 00000000..e2f43f10 --- /dev/null +++ b/openspec/changes/archive/2026-01-11-add-linear-mcp-workflow/design.md @@ -0,0 +1,37 @@ +## Context +OpenSpec slash-command templates currently describe proposal/apply/archive workflows without any external backlog integration. The request is to wire those workflows to Linear via MCP so backlog selection, state transitions, and epic tracking happen automatically when a Linear MCP connection is present. + +## Goals / Non-Goals +- Goals: + - Detect Linear MCP availability and use it only when connected. + - Persist a preferred Linear team/project configuration inside `openspec/` and reflect it in `openspec/project.md`. + - Keep one Linear epic issue per source-of-truth spec under the preferred Linear project. + - Use Linear backlog stories as the starting point for proposals and move story states through the lifecycle. +- Non-Goals: + - Implement a non-MCP Linear API client. + - Replace OpenSpec change naming or proposal structure. + +## Decisions +- Decision: Store Linear preferences in `openspec/linear.yml` with `team_id` and `project_id` keys. + - Rationale: YAML is concise, easy to hand-edit, and fits alongside existing markdown without frontmatter ambiguity. +- Decision: Add a new `## Linear MCP` section to `openspec/project.md` containing the preferred team and project IDs. + - Rationale: Keeps the preferences visible in the primary project overview while still retaining the canonical config in `openspec/linear.yml`. +- Decision: Represent epics as regular Linear issues labeled `Epic` in the preferred project. + - Rationale: The MCP API exposes issues and labels directly; using a label avoids relying on ambiguous milestone semantics. +- Decision: Use Linear issue description as the destination for proposal content (proposal body without frontmatter). + - Rationale: Keeps the story aligned with the current proposal while avoiding YAML noise. +- Decision: Order backlog story selection by priority (fall back to recency if priority is unavailable) and show an interactive list. + - Rationale: Aligns with the idea of “top stories” while keeping the prompt deterministic. + +## Risks / Trade-offs +- Ambiguity around “epic” semantics in Linear could require adjustment if teams rely on milestones or issue types; the `Epic` label approach is a best-effort default. +- The Linear MCP may not be connected in all environments; instructions must degrade gracefully. + +## Migration Plan +1. Update slash-command templates to include Linear MCP detection and the new workflow steps. +2. Add the new `linear-mcp-workflow` spec delta and validate the change. +3. Ensure any prompt-generation tests that assert template text are updated. + +## Open Questions +- Should the `Epic` label name be configurable (e.g., `Epic`, `Milestone`, `Large Story`) via the Linear config file? +- Do we want to include Linear project names (in addition to IDs) in `openspec/project.md` for readability? diff --git a/openspec/changes/archive/2026-01-11-add-linear-mcp-workflow/proposal.md b/openspec/changes/archive/2026-01-11-add-linear-mcp-workflow/proposal.md new file mode 100644 index 00000000..707697cd --- /dev/null +++ b/openspec/changes/archive/2026-01-11-add-linear-mcp-workflow/proposal.md @@ -0,0 +1,14 @@ +## Why +Tie OpenSpec proposals and lifecycle updates directly to Linear so teams can pick backlog work, track status changes, and keep source-of-truth specs represented as epics in a single project view. + +## What Changes +- Detect when Linear MCP is available by confirming the MCP is connected and can list teams, and load preferred team/project IDs from an OpenSpec-local config, updating `openspec/project.md` with those preferences. +- Keep OpenSpec workflows operating as usual when Linear MCP is not available, skipping Linear-specific prompts and updates. +- Ensure each source-of-truth spec in `openspec/specs/` has a matching Linear epic issue in the preferred project. +- Proposal workflow lists top backlog stories from the Linear project, prompts for selection, writes the selected story ID into `proposal.md` frontmatter, and moves the story from Backlog to Todo. +- Apply workflow updates the Linear story with proposal content and moves it to In Progress. +- Archive workflow updates the Linear story with proposal content, moves it to Done, and refreshes spec epics. + +## Impact +- Affected specs: linear-mcp-workflow (new) +- Affected code: src/core/templates/slash-command-templates.ts, src/core/templates/slash-command-templates.ts consumers (init/update) diff --git a/openspec/changes/archive/2026-01-11-add-linear-mcp-workflow/specs/linear-mcp-workflow/spec.md b/openspec/changes/archive/2026-01-11-add-linear-mcp-workflow/specs/linear-mcp-workflow/spec.md new file mode 100644 index 00000000..a04f46b4 --- /dev/null +++ b/openspec/changes/archive/2026-01-11-add-linear-mcp-workflow/specs/linear-mcp-workflow/spec.md @@ -0,0 +1,54 @@ +## ADDED Requirements +### Requirement: Linear MCP Detection and Preferences +The proposal, apply, and archive workflows SHALL detect whether Linear MCP is available and only execute Linear actions when connected. Availability SHALL be confirmed by a successful connection and a successful team list query. When connected, the workflow SHALL load `openspec/linear.yml` to obtain `team_id` and `project_id`, and update `openspec/project.md` with the preferred Linear team and project. If the config file does not exist, the workflow SHALL prompt the user to select a Linear team and project, then persist the selections to `openspec/linear.yml` and `openspec/project.md`. + +#### Scenario: Linear MCP connected with stored preferences +- **WHEN** a workflow starts and Linear MCP is available +- **AND** `openspec/linear.yml` contains `team_id` and `project_id` +- **THEN** load those values and update `openspec/project.md` to reflect the preferred team and project + +#### Scenario: Linear MCP connected without stored preferences +- **WHEN** a workflow starts and Linear MCP is available +- **AND** `openspec/linear.yml` is missing +- **THEN** prompt for a Linear team and project +- **AND** persist the selections to `openspec/linear.yml` +- **AND** update `openspec/project.md` with the preferred team and project + +#### Scenario: Linear MCP not available +- **WHEN** a workflow starts and Linear MCP is not available +- **THEN** continue the OpenSpec workflow without Linear prompts or updates + +### Requirement: Spec Epic Synchronization +When Linear MCP is available, the workflows SHALL ensure each capability under `openspec/specs/` has a corresponding Linear epic issue in the preferred project. Missing epics SHALL be created and labeled `Epic`, and existing epics SHALL be updated to reflect the spec identifier and path. + +#### Scenario: Creating missing epics for specs +- **WHEN** a workflow runs with Linear MCP connected +- **AND** a spec does not have a matching epic issue +- **THEN** create a new Linear issue labeled `Epic` in the preferred project +- **AND** include the spec identifier and `openspec/specs//spec.md` path in the epic description + +### Requirement: Proposal Story Selection and State Transition +When creating a proposal with Linear MCP connected, the workflow SHALL fetch the top backlog stories from the preferred Linear project, present an interactive list for selection, store the selected story ID in `proposal.md` frontmatter, and move the story from Backlog to Todo. + +#### Scenario: Selecting a backlog story for a proposal +- **WHEN** the proposal workflow starts with Linear MCP connected +- **THEN** list backlog stories from the preferred project in an interactive prompt +- **AND** store the selected story ID as `linear_story_id` in `proposal.md` frontmatter +- **AND** move the selected story from Backlog to Todo + +### Requirement: Apply Story Update and In Progress Transition +When applying a change with a `linear_story_id` in `proposal.md`, the workflow SHALL update the Linear story description with the proposal content and move the story to In Progress. + +#### Scenario: Applying a change with a Linear story +- **WHEN** the apply workflow runs and `proposal.md` includes `linear_story_id` +- **THEN** update the Linear story description using the proposal content +- **AND** move the story to In Progress + +### Requirement: Archive Story Update, Completion, and Epic Refresh +When archiving a change with a `linear_story_id` in `proposal.md`, the workflow SHALL update the Linear story description with the proposal content, move the story to Done, and refresh spec epics in the preferred project. + +#### Scenario: Archiving a change with a Linear story +- **WHEN** the archive workflow runs and `proposal.md` includes `linear_story_id` +- **THEN** update the Linear story description using the proposal content +- **AND** move the story to Done +- **AND** refresh the spec epic issues for `openspec/specs/` diff --git a/openspec/changes/archive/2026-01-11-add-linear-mcp-workflow/tasks.md b/openspec/changes/archive/2026-01-11-add-linear-mcp-workflow/tasks.md new file mode 100644 index 00000000..66bada11 --- /dev/null +++ b/openspec/changes/archive/2026-01-11-add-linear-mcp-workflow/tasks.md @@ -0,0 +1,11 @@ +## 1. Specification Updates +- [x] 1.1 Add the linear MCP workflow requirements in `openspec/changes/add-linear-mcp-workflow/specs/linear-mcp-workflow/spec.md`. +- [x] 1.2 Capture configuration and epic-mapping decisions in `openspec/changes/add-linear-mcp-workflow/design.md`. + +## 2. Template Updates +- [x] 2.1 Update `src/core/templates/slash-command-templates.ts` to include Linear MCP steps in proposal/apply/archive workflows. +- [x] 2.2 Update any supporting docs or schema templates that mirror the slash-command content. + +## 3. Validation +- [x] 3.1 Update or add tests that assert the updated slash-command template content. +- [x] 3.2 Run `openspec validate add-linear-mcp-workflow --strict` and address any issues. diff --git a/openspec/specs/linear-mcp-workflow/spec.md b/openspec/specs/linear-mcp-workflow/spec.md new file mode 100644 index 00000000..26612a74 --- /dev/null +++ b/openspec/specs/linear-mcp-workflow/spec.md @@ -0,0 +1,75 @@ +# linear-mcp-workflow Specification + +## Purpose +TBD - created by archiving change add-linear-mcp-workflow. Update Purpose after archive. +## Requirements +### Requirement: Linear MCP Detection and Preferences +The proposal, apply, and archive workflows SHALL detect whether Linear MCP is available and only execute Linear actions when connected. Availability SHALL be confirmed by a successful connection and a successful team list query. When connected, the workflow SHALL load `openspec/linear.yml` to obtain `team_id` and `project_id`, and update `openspec/project.md` with the preferred Linear team and project. If the config file does not exist, the workflow SHALL prompt the user to select a Linear team and project, then persist the selections to `openspec/linear.yml` and `openspec/project.md`. + +#### Scenario: Linear MCP connected with stored preferences +- **WHEN** a workflow starts and Linear MCP is available +- **AND** `openspec/linear.yml` contains `team_id` and `project_id` +- **THEN** load those values and update `openspec/project.md` to reflect the preferred team and project + +#### Scenario: Linear MCP connected without stored preferences +- **WHEN** a workflow starts and Linear MCP is available +- **AND** `openspec/linear.yml` is missing +- **THEN** prompt for a Linear team and project +- **AND** persist the selections to `openspec/linear.yml` +- **AND** update `openspec/project.md` with the preferred team and project + +#### Scenario: Linear MCP not available +- **WHEN** a workflow starts and Linear MCP is not available +- **THEN** continue the OpenSpec workflow without Linear prompts or updates + +### Requirement: Spec Epic Synchronization +When Linear MCP is available, the workflows SHALL ensure each capability under `openspec/specs/` has a corresponding Linear epic issue in the preferred project. Missing epics SHALL be created and labeled `Epic`, and existing epics SHALL be updated to reflect the spec identifier and path. + +#### Scenario: Creating missing epics for specs +- **WHEN** a workflow runs with Linear MCP connected +- **AND** a spec does not have a matching epic issue +- **THEN** create a new Linear issue labeled `Epic` in the preferred project +- **AND** include the spec identifier and `openspec/specs//spec.md` path in the epic description + +### Requirement: Proposal Story Selection and State Transition +When creating a proposal with Linear MCP connected, the workflow SHALL fetch the top backlog stories from the preferred Linear project, present an interactive list for selection, store the selected story ID in `proposal.md` frontmatter, and move the story from Backlog to Todo. + +#### Scenario: Selecting a backlog story for a proposal +- **WHEN** the proposal workflow starts with Linear MCP connected +- **THEN** list backlog stories from the preferred project in an interactive prompt +- **AND** store the selected story ID as `linear_story_id` in `proposal.md` frontmatter +- **AND** move the selected story from Backlog to Todo + +### Requirement: Apply Story Update and In Progress Transition +When applying a change with a `linear_story_id` in `proposal.md`, the workflow SHALL update the Linear story description with the proposal content and move the story to In Progress. + +#### Scenario: Applying a change with a Linear story +- **WHEN** the apply workflow runs and `proposal.md` includes `linear_story_id` +- **THEN** update the Linear story description using the proposal content +- **AND** move the story to In Progress + +### Requirement: Archive Story Update, Completion, and Epic Refresh +When archiving a change with a `linear_story_id` in `proposal.md`, the workflow SHALL update the Linear story description with the proposal content, move the story to Done, and refresh spec epics in the preferred project. + +#### Scenario: Archiving a change with a Linear story +- **WHEN** the archive workflow runs and `proposal.md` includes `linear_story_id` +- **THEN** update the Linear story description using the proposal content +- **AND** move the story to Done +- **AND** refresh the spec epic issues for `openspec/specs/` + +### Requirement: Archived Spec Issue Tracking +When archiving updates to merged source-of-truth specs with Linear MCP connected, the workflow SHALL ensure each updated spec has a corresponding Linear issue in status `Spec`, and store the issue ID in spec front matter as `linear_spec_issue_id`. If the issue already exists, the workflow SHALL update it with the latest merged spec content. + +#### Scenario: Creating missing spec issues after archive +- **WHEN** the archive workflow runs with Linear MCP connected +- **AND** the archive updates one or more specs +- **AND** an updated spec lacks `linear_spec_issue_id` in front matter +- **THEN** search for an existing Linear issue for the spec and create one if missing +- **AND** set the issue status to `Spec` +- **AND** write the resulting issue ID to the spec front matter + +#### Scenario: Updating existing spec issues after archive +- **WHEN** the archive workflow runs with Linear MCP connected +- **AND** the archive updates one or more specs +- **AND** an updated spec includes `linear_spec_issue_id` in front matter +- **THEN** update the linked Linear issue with the latest merged spec content diff --git a/src/core/templates/slash-command-templates.ts b/src/core/templates/slash-command-templates.ts index 980e5bca..8666d3bb 100644 --- a/src/core/templates/slash-command-templates.ts +++ b/src/core/templates/slash-command-templates.ts @@ -11,11 +11,12 @@ const proposalGuardrails = `${baseGuardrails}\n- Identify any vague or ambiguous const proposalSteps = `**Steps** 1. Review \`openspec/project.md\`, run \`openspec list\` and \`openspec list --specs\`, and inspect related code or docs (e.g., via \`rg\`/\`ls\`) to ground the proposal in current behaviour; note any gaps that require clarification. 2. Choose a unique verb-led \`change-id\` and scaffold \`proposal.md\`, \`tasks.md\`, and \`design.md\` (when needed) under \`openspec/changes//\`. -3. Map the change into concrete capabilities or requirements, breaking multi-scope efforts into distinct spec deltas with clear relationships and sequencing. -4. Capture architectural reasoning in \`design.md\` when the solution spans multiple systems, introduces new patterns, or demands trade-off discussion before committing to specs. -5. Draft spec deltas in \`changes//specs//spec.md\` (one folder per capability) using \`## ADDED|MODIFIED|REMOVED Requirements\` with at least one \`#### Scenario:\` per requirement and cross-reference related capabilities when relevant. -6. Draft \`tasks.md\` as an ordered list of small, verifiable work items that deliver user-visible progress, include validation (tests, tooling), and highlight dependencies or parallelizable work. -7. Validate with \`openspec validate --strict --no-interactive\` and resolve every issue before sharing the proposal.`; +3. If Linear MCP is connected (confirm by listing teams), check for \`openspec/linear.yml\`. When missing, query Linear for available teams and projects, present the list, ask the user to select their preferred team and project, then persist to \`openspec/linear.yml\` and update \`openspec/project.md\`. When present, load \`team_id\` and \`project_id\` from \`openspec/linear.yml\` and update \`openspec/project.md\`. Then ensure each \`openspec/specs/\` capability has a matching Linear epic issue in the preferred project, list top backlog stories, prompt for selection, write \`linear_story_id\` into \`proposal.md\` frontmatter, and move the story from Backlog to Todo. If Linear MCP is not available, continue without Linear prompts or updates. +4. Map the change into concrete capabilities or requirements, breaking multi-scope efforts into distinct spec deltas with clear relationships and sequencing. +5. Capture architectural reasoning in \`design.md\` when the solution spans multiple systems, introduces new patterns, or demands trade-off discussion before committing to specs. +6. Draft spec deltas in \`changes//specs//spec.md\` (one folder per capability) using \`## ADDED|MODIFIED|REMOVED Requirements\` with at least one \`#### Scenario:\` per requirement and cross-reference related capabilities when relevant. +7. Draft \`tasks.md\` as an ordered list of small, verifiable work items that deliver user-visible progress, include validation (tests, tooling), and highlight dependencies or parallelizable work. +8. Validate with \`openspec validate --strict --no-interactive\` and resolve every issue before sharing the proposal.`; const proposalReferences = `**Reference** @@ -26,10 +27,11 @@ const proposalReferences = `**Reference** const applySteps = `**Steps** Track these steps as TODOs and complete them one by one. 1. Read \`changes//proposal.md\`, \`design.md\` (if present), and \`tasks.md\` to confirm scope and acceptance criteria. -2. Work through tasks sequentially, keeping edits minimal and focused on the requested change. -3. Confirm completion before updating statuses—make sure every item in \`tasks.md\` is finished. -4. Update the checklist after all work is done so each task is marked \`- [x]\` and reflects reality. -5. Reference \`openspec list\` or \`openspec show \` when additional context is required.`; +2. If Linear MCP is connected (confirm by listing teams), load \`openspec/linear.yml\` for \`team_id\` and \`project_id\` or prompt to select them, persist to \`openspec/linear.yml\`, update \`openspec/project.md\`, ensure each \`openspec/specs/\` capability has a matching Linear epic issue in the preferred project, and when \`proposal.md\` includes \`linear_story_id\`, update the Linear story description with the proposal content and move it to In Progress. If Linear MCP is not available, continue without Linear prompts or updates. +3. Work through tasks sequentially, keeping edits minimal and focused on the requested change. +4. Confirm completion before updating statuses—make sure every item in \`tasks.md\` is finished. +5. Update the checklist after all work is done so each task is marked \`- [x]\` and reflects reality. +6. Reference \`openspec list\` or \`openspec show \` when additional context is required.`; const applyReferences = `**Reference** - Use \`openspec show --json --deltas-only\` if you need additional context from the proposal while implementing.`; @@ -42,8 +44,9 @@ const archiveSteps = `**Steps** - If you still cannot identify a single change ID, stop and tell the user you cannot archive anything yet. 2. Validate the change ID by running \`openspec list\` (or \`openspec show \`) and stop if the change is missing, already archived, or otherwise not ready to archive. 3. Run \`openspec archive --yes\` so the CLI moves the change and applies spec updates without prompts (use \`--skip-specs\` only for tooling-only work). -4. Review the command output to confirm the target specs were updated and the change landed in \`changes/archive/\`. -5. Validate with \`openspec validate --strict --no-interactive\` and inspect with \`openspec show \` if anything looks off.`; +4. If Linear MCP is connected (confirm by listing teams), load \`openspec/linear.yml\` for \`team_id\` and \`project_id\` or prompt to select them, persist to \`openspec/linear.yml\`, update \`openspec/project.md\`, ensure each \`openspec/specs/\` capability has a matching Linear epic issue in the preferred project, and when \`proposal.md\` includes \`linear_story_id\`, update the Linear story description with the proposal content and move it to Done. After the archive completes and the merged source-of-truth specs are updated, ensure each updated spec has a single Linear issue in status \`Spec\`: if the spec front matter already includes \`linear_spec_issue_id\`, update that issue with the latest spec content; if not, search for an existing issue for the spec and create one if missing, then add \`linear_spec_issue_id\` to the spec front matter. Do this for every merged spec produced by the archive. If Linear MCP is not available, continue without Linear prompts or updates. +5. Review the command output to confirm the target specs were updated and the change landed in \`changes/archive/\`. +6. Validate with \`openspec validate --strict --no-interactive\` and inspect with \`openspec show \` if anything looks off.`; const archiveReferences = `**Reference** - Use \`openspec list\` to confirm change IDs before archiving. diff --git a/test/core/init.test.ts b/test/core/init.test.ts index 09f357ea..d4f936e8 100644 --- a/test/core/init.test.ts +++ b/test/core/init.test.ts @@ -197,6 +197,7 @@ describe('InitCommand', () => { expect(proposalContent).toContain('auto_execution_mode: 3'); expect(proposalContent).toContain(''); expect(proposalContent).toContain('**Guardrails**'); + expect(proposalContent).toContain('Linear MCP'); const applyContent = await fs.readFile(wsApply, 'utf-8'); expect(applyContent).toContain('---'); diff --git a/test/core/update.test.ts b/test/core/update.test.ts index 5df33dc2..04e66e92 100644 --- a/test/core/update.test.ts +++ b/test/core/update.test.ts @@ -354,6 +354,7 @@ Old body const updated = await fs.readFile(cursorPath, 'utf-8'); expect(updated).toContain('id: openspec-apply'); expect(updated).toContain('Work through tasks sequentially'); + expect(updated).toContain('Linear MCP'); expect(updated).not.toContain('Old body'); const [logMessage] = consoleSpy.mock.calls[0];