| portability | ol-coupled |
| reuse | ol-platform-only |
| requires | ["linear-mcp"] |
| name | linear-backlog-manager |
| description | Publish an approved feature spec (tasks.md) to a Linear project as a structured tree of labeled, nested issues (feature → story → task), each with a spec back-link, requirements traceability, skill-routing label, and point estimate. Use when: the project tracker is Linear and a feature spec has been approved and needs to become a tracked backlog, or when a new requirement needs to be added to an existing Linear feature-issue. Phase 2 of the ol-sdd-workflow orchestrator when the project's tracker is Linear. The Linear parallel of backlog-manager (JIRA), ado-backlog-manager (ADO), and local-backlog-manager (filesystem). Requires Linear MCP — if the MCP is unavailable, fall back to `local-backlog-manager`.
|
Linear Backlog Manager
Role
You publish approved feature specs into a Linear project. You do not decide what to build — that was settled by feature-spec-author in Phase 1. You translate the approved tasks.md into a tree of Linear issues and maintain the issue map that Phase 3 and 4 will use.
You are the Linear-tracker parallel of backlog-manager (which targets JIRA). Same role, same inputs, same outputs — only the tracker substrate differs. You are invoked by the ol-sdd-workflow orchestrator at Phase 2 when the project's tracker is linear, or directly when backlog changes are needed.
The Linear model (read this first — it is the whole skill)
Linear has no Epic/Story/Subtask issue types and no transition workflow. The hierarchy is carried by labels and issue nesting, and state is set directly. The convention this skill enforces:
| Concept | Linear realisation |
|---|
| Shared team | one Linear Team for all apps (e.g. Ontoledgy, global prefix ONT-) — owns states, labels, cycles |
| App / solution | one Linear Project under the shared team (pre-existing — never created per feature) |
| Feature (one per feature; Jira "epic") | an Issue labelled type:feature, in the app project |
| Story (one per requirement) | an Issue labelled type:story, parentId = the feature-issue |
| Task (one per atomic task) | an Issue labelled type:task, parentId = the story-issue |
| Estimate | numeric estimate (points, not hours) |
| Spec back-link | links: [{url, title}] on the issue (shows as an attachment) |
| Initial state | set state directly to Backlog — no transitions |
| Sprint | a Cycle (assigned later by the sprint planner, not here) |
| Release grouping | an optional project Milestone (milestone on the issue) |
Nesting is reliable for the 3 levels above (feature→story→task), proven live. Because some Linear views flatten deep trees, the type:* labels are the primary way to read the hierarchy and nesting is secondary structure.
Inputs
documentation/specs/{feature}/tasks.md — approved (Phase 1 gate 1c passed)
documentation/specs/{feature}/requirements.md — for story grouping
documentation/specs/{feature}/design.md — for the feature-issue description
- Docs URL for the spec page (Notion or Confluence) — for back-links
- Linear team name or ID (e.g.
Ontoledgy) — required to create issues
- Linear project name or ID — the app's project (e.g.
OL AI Context Library)
- Default assignee (optional)
- Optional: existing release milestone (from a release planner) — if present, task-issues are tagged with it
Project & feature handling: existing app project vs release milestone
Before creating anything, settle the placement:
-
Confirm the app's Linear Project exists under the shared team. Use list_projects (filter by name). If it does not exist, ask the user to confirm the project name and create it once with save_project on the shared team (it is the app's home, not a per-feature container). Do not create a project per feature, and do not create a team per app — all apps share one team.
-
Feature-issue strategy:
- Release-skeleton feature-issue exists (created by a release planner): fetch it (
get_issue), update its description with the full design overview + spec URL + repo spec-folder link, ensure it carries type:feature, and create stories/tasks beneath it. Do not create a second feature-issue.
- No release plan, or feature not yet represented: create a new feature-issue labelled
type:feature under the app project.
-
Release milestone (optional): if the release planner created a project milestone for this release, pass milestone on the task-issues so the roadmap reflects them. Otherwise omit.
Outputs
| Output | Where |
|---|
| Feature-issue (one) | ONT-NNN (global prefix), label type:feature, in the app project |
| Story-issues (several) | ONT-NNN, type:story, parentId = feature-issue |
| Task-issues (many) | ONT-NNN, type:task, parentId = story-issue |
| Issue map | documentation/specs/{feature}/issue-map.md |
Issue structure
Feature-issue (one per feature)
- Title:
{Feature Name} — {one-line description}
- Description: overview + link to spec page (Notion/Confluence) + link to repo spec folder
- Project: the app project · Team: the shared team (
Ontoledgy / ONT-)
- Labels:
type:feature, feature:{feature-name}
- State:
Backlog
Story-issue (one per requirement group)
- Title: the requirement user story ("As a {role}, I want {feature}, so that {benefit}")
- Description: acceptance criteria from requirements.md (EARS format preserved)
- parentId: the feature-issue · Project/Team: same as the feature-issue
- Labels:
type:story, feature:{feature-name}, req:{requirement-number}
- State:
Backlog
Task-issue (one per atomic task in tasks.md)
- Title: task title from tasks.md
- Description: task purpose · exact files to create/modify ·
_Leverage: references · _Requirements: back-link · spec section link · impl hints from design.md
- parentId: the story-issue covering the referenced requirement
- Labels:
type:task, feature:{feature-name}, skill:{skill-name} (from the _Skill: annotation), agent:{engine} (codex or claude — see Execution-engine assignment), req:{requirement-number}
- Estimate: points (see Estimate convention below)
- Links:
[{url: spec-section URL, title: "Spec — {section}"}]
- Milestone: release milestone if one exists
- Assignee: default assignee if configured
- State:
Backlog
Estimate convention
tasks.md carries _Estimate: in hours; Linear uses numeric points. Convert with the project's scale. Default heuristic when the team uses a Fibonacci scale: ≤2h→2, ≤4h→3, ≤1d→5, ≤2d→8, more→13. If the team's scale is configured as hours/linear, carry the hour value as the number. Record which convention was used in the issue map header so estimates stay comparable across features.
Execution-engine assignment
Each task-issue is labelled upfront with the engine that will implement it, so Phase 4's executor never decides at runtime. The engine is one of:
agent:claude — implemented by the routed Claude-native engineer skill (skill:{name}), with the full clean-code-reviewer loop.
agent:codex — implemented by delegated Codex via the Codex MCP.
Confirm the engine-routing label name against the live workspace first. This skill uses agent:* as the engine-routing convention (the pair that shipped and got adopted workspace-wide: agent:codex / agent:claude). Before assuming either name, run list_issue_labels (Step 3) and check which engine-routing pair already exists on the team. If the workspace already carries a different pair (e.g. a legacy exec:*), follow the live precedent rather than introducing a second, competing scheme — and flag the divergence to the user. Never create a new engine-routing pair when one already exists under a different name.
Default rule: classify each task simple/complex using task-executor's complexity-triage signals (estimate ≥ 5 pts / > 4h · more than 3 files · introduces/changes a public API, schema, or migration · domain is auth/payments/data-identity(BIE)/security · touches async/locking/transactions/ordering · requires new test files), then complex → agent:claude, simple → agent:codex. All derivable from tasks.md at backlog time. Set it as a default the user reviews — show it per task in the Step 4 preview and the issue map, and let the user override any task's engine before publishing. The agent:{engine} label is the single source of truth sprint-planner carries into the kickoff and sprint-executor reads. Like the other type:* / skill:* labels, the two agent:* labels must exist on the team before issues reference them (Step 3).
Workflow
Step 1 — Validate inputs
- Confirm tasks.md exists and is marked approved; confirm the three spec files exist.
- Confirm the shared Linear team and the app project are known (from
documentation/workflow-config.md or user input). Resolve the project with list_projects.
- Confirm the feature does not already have a feature-issue (search
list_issues with label:type:feature + feature:{name} in the project). If one exists, ask: update existing or abort?
Step 2 — Parse tasks.md
Extract top-level groupings (→ candidate stories), individual tasks (- [ ] N.) with their _Requirements: / _Leverage: / _Skill: / _Estimate: fields, and group tasks by requirement to form stories.
Step 3 — Ensure labels exist
The hierarchy and routing live in labels, so they must exist before issues reference them.
list_issue_labels for the team first, and read the result before creating anything. Create any missing ones with create_issue_label (scope to the team via teamId):
- the three hierarchy labels
type:feature / type:story / type:task (one-time, on the shared team)
- the engine-routing pair, but only after resolving which pair the workspace uses (see the guard below) — create
agent:codex / agent:claude only if no engine-routing pair already exists; if a pair is already present under any name, reuse it and create nothing
feature:{feature-name} (once per feature)
req:{n} and skill:{name} as referenced by the tasks
- Resolve the engine-routing pair before creating any engine label. From the
list_issue_labels result, check whether the team already carries an engine-routing pair (the agent:* default, or a legacy pair under a different name such as exec:codex / exec:claude). If one exists, use that existing pair on all task-issues and tell the user the workspace diverges from the agent:* default when it does. Only create agent:codex / agent:claude when no engine-routing pair exists yet. Never create a second, competing pair.
- Do not assume
save_issue auto-creates labels — create them explicitly.
Step 4 — Preview before publishing
Produce the tree and the totals; ask the user to approve before any issue is created:
Project: {app project} (Team {team})
Feature-issue: {feature} — {description} [type:feature]
├── Story: As a {role}... (sum: 13 pts) [type:story, req:1]
│ ├── Task: Add columns to model [type:task, skill:python-data-engineer, agent:codex, 5pt]
│ ├── Task: Write Alembic migration [type:task, skill:python-data-engineer, agent:claude, 5pt]
│ └── Task: Add unit tests [type:task, skill:clean-code-tests, agent:codex, 3pt]
└── Story: As a {role}... (sum: 8 pts) [type:story, req:2]
└── ...
Total: {N} issues, {P} points · engines: {n} claude / {m} codex
Ask the user to approve before creating issues — and to override any task's agent: engine if they disagree with the default classification.
Step 5 — Create issues (capture IDs for nesting)
Use the Linear MCP in this order — each call returns an identifier (e.g. ONT-42) you pass as the next level's parentId:
- Feature-issue —
save_issue (or save_issue with id to flesh out a release skeleton). Capture its identifier.
- Story-issues — one
save_issue each with parentId = the feature-issue identifier. Capture each.
- Task-issues — one
save_issue each with parentId = the parent story identifier, estimate, labels, links, and milestone if applicable.
Create in small batches and surface any API error immediately — do not continue on failure. The proven call shape (from the ONT-27 dogfood):
save_issue({
title: "Add licence columns to LegalEntities model",
team: "Ontoledgy",
project: "OL AI Context Library",
parentId: "ONT-41", // the story-issue
labels: ["type:task", "feature:licence", "skill:python-data-engineer", "agent:codex", "req:1"],
estimate: 5,
state: "Backlog",
links: [{ url: "<spec section URL>", title: "Spec — Data model" }],
description: "<purpose · files · _Leverage · _Requirements · hints>"
})
If operating on a release-skeleton feature-issue, also update the release roadmap (milestone / release page) to reflect that this feature moved from "specced" to "in backlog".
Step 6 — Write issue map
Create documentation/specs/{feature}/issue-map.md — the canonical mapping Phase 3/4 use. Commit it.
# Issue Map — {feature-name} (estimate convention: points, Fibonacci)
Feature-issue: [ONT-40](https://linear.app/ontoledgy/issue/ONT-40)
| Task (from tasks.md) | Linear ID | Type | Skill | Engine | Estimate | Status |
|----------------------|-----------|------|-------|--------|----------|--------|
| 1. Add columns to model | [ONT-42](...) | task | python-data-engineer | codex | 5 | Backlog |
| 2. Write Alembic migration | [ONT-43](...) | task | python-data-engineer | claude | 5 | Backlog |
...
Step 7 — Update the docs surface & return
Append a "Linear Issues" section to the spec page (Notion or Confluence, via the docs adapter) with a table matching the issue map, so reviewers can trace spec → issues. Then return to caller with: feature-issue identifier + URL, count of stories and tasks, total points, issue-map path, and the project board URL.
Mapping from backlog-manager (JIRA → Linear)
| backlog-manager (JIRA) | linear-backlog-manager |
|---|
createJiraIssue epic | save_issue labelled type:feature (Jira epics are features; no separate epic level) |
createJiraIssue story parent:{epic} | save_issue parentId:{feature} labelled type:story |
createJiraIssue subtask parent:{story} | save_issue parentId:{story} labelled type:task |
addCommentToJiraIssue for back-link | links:[{url,title}] on the issue (or save_comment) |
| estimate in hours | estimate in points (convert; record convention) |
| project key (one per app) | one shared team (global ONT- prefix) + one project per app |
| release-skeleton epic | release-skeleton feature-issue, or project milestone |
editJiraIssue to amend | save_issue with id to amend |
Updates and amendments
When a spec changes after issues are published:
- New task added:
save_issue a new task-issue under the right story; append to the issue map.
- Task removed: set state to
Canceled with a reason comment; strike it through in the issue map. Never delete issues — the audit trail matters.
- Task scope changed:
save_issue with id to edit description; update the issue map.
- Estimate changed:
save_issue with id to change estimate.
What this skill does NOT do
- Does not author specs (Phase 1 /
feature-spec-author)
- Does not plan sprints or assign cycles (Phase 3 / sprint planner)
- Does not execute tasks (Phase 4 / executor)
- Does not log implementation details (Phase 5 /
impl-logger)
- Does not create the app's Linear Project per feature — the project is the app's pre-existing home
References
skills/backlog-manager/SKILL.md — the JIRA original; identical role and contract
- Linear MCP:
save_issue, save_project, list_projects, list_issues, get_issue, list_issue_labels, create_issue_label, list_issue_statuses, save_milestone, save_comment
- Proven call sequence: feature-issue
ONT-27 → stories ONT-28/29 → tasks ONT-30..34 in project "OL AI Context Library" (the trial that produced this skill; originally created as type:epic, since relabelled to type:feature per the decided standard)
- Companion:
impl-logger (Phase 5, tracker: linear), the tracker-adapter generalisation of the executors
Feedback
If the user corrects this skill's output due to a misinterpretation or missing rule in the skill itself (not a one-off preference), invoke skill-feedback to capture structured feedback and optionally post a GitHub issue.
If skill-feedback is not installed, ask the user: "This looks like a skill defect. Would you like to install the skill-feedback skill to report it?" If the user declines, continue without feedback capture.