> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. > Also consult **superpowers:writing-skills** for the repo's skill-authoring conventions (frontmatter, description triggers, references) while doing the doc-heavy tasks. **Goal:** Add a new dev-flow skill `linear-scrum` that pushes a project into Linear (via the connected L...
6 stars
0 votes
0 copies
0 views
Added October 19, 2026
developmentpythongobashgitapi
Works with
claude code
cli
api
mcp
Security analysis
A92/100
mediumInstalls packages at runtime which could introduce malicious dependencies
Installs into .claude/skills of the current project.
Are you the author of Plans?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/lukedj78-plans)
# `linear-scrum` Skill Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
> Also consult **superpowers:writing-skills** for the repo's skill-authoring conventions (frontmatter, description triggers, references) while doing the doc-heavy tasks.
**Goal:** Add a new dev-flow skill `linear-scrum` that pushes a project into Linear (via the connected Linear MCP) and runs every project with agile scrum (cycles, story-point estimates, sprint planning, velocity reports), with Linear as the source of truth after setup.
**Architecture:** A standalone skill folder `linear-scrum/` (family `core`, stack-agnostic) with a `SKILL.md`, three references (`scrum-model.md`, `linear-mcp.md`, a vendored `contracts.md`), and two small Python scripts (`task_key.py` for stable idempotent issue keys, `meta_linear.py` to upsert the `linear`/`scrum` blocks of `.workflow/meta.json`). Minimal edits to `dev-flow` add routing + a non-blocking policy and document the new `meta.json` blocks. The repo build scripts regenerate `skills.json` and `dist/linear-scrum.skill`.
**Tech Stack:** Markdown skills (dev-flow contract), Python 3 (stdlib only) for scripts + `pytest`-free unittest, the connected Linear MCP, the repo's `scripts/*.py` build/lint/registry tooling.
**Decisions locked (open questions from the spec, resolved here):**
- Reports: **chat-only** (no `.workflow/reports/` file — avoids a drift-prone artifact).
- Estimates without an `Estimated:` hint: **batch-prompt once** (list all unestimated tasks, ask for points in one message); default to unestimated if skipped.
- Skill name: **`linear-scrum`**.
---
## File Structure
**Create:**
- `linear-scrum/SKILL.md` — the skill: frontmatter + 3 modes (Setup / Adopt / Sync) + meta.json schema + `[VERIFY]` markers + DoD.
- `linear-scrum/references/scrum-model.md` — scrum conventions: cadence, Fibonacci estimates, state mapping, sprint-planning algorithm, velocity, `blocked` label.
- `linear-scrum/references/linear-mcp.md` — cookbook mapping each operation to Linear MCP tools; the headless API-key handoff to `docs/loop-engineering.md`.
- `linear-scrum/references/contracts.md` — vendored copy of `dev-flow/references/contracts.md` (house style: standalone skills carry a copy).
- `linear-scrum/scripts/task_key.py` — pure function: a `tasks.md` line → stable 12-char key for `issue_map`.
- `linear-scrum/scripts/meta_linear.py` — upsert `meta.json#linear` + `#scrum`, merge `issue_map` idempotently.
- `linear-scrum/scripts/test_scripts.py` — unittest for both scripts.
**Modify:**
- `dev-flow/SKILL.md` — routing at `prd_drafted`/`tasks_split`, horizontal-family mention, one policy paragraph.
- `dev-flow/references/contracts.md` — document the optional `linear`/`scrum` blocks.
- `install.sh` + `uninstall.sh` — add `linear-scrum`.
- `README.md` — add `linear-scrum` to the catalog (one row/paragraph).
- `skills.json` — regenerated by the build script (do not hand-edit).
- `dist/linear-scrum.skill` — regenerated by the build script.
---
### Task 1: Scaffold the skill folder + lint-valid frontmatter
**Files:**
- Create: `linear-scrum/SKILL.md`
- [ ] **Step 1: Create `linear-scrum/SKILL.md` with frontmatter + title only**
The `description` MUST contain the literal markers `Triggers` and `Not for:` (the repo linter enforces both). Write exactly:
```markdown
---
name: linear-scrum
description: 'Take a dev-flow project into Linear and run it with agile scrum. Creates/links a Linear Project under the workspace team, pushes `.workflow/tasks.md` into issues (story-point estimates, `area:web`/`area:agent` labels, milestones), sets up cycles/sprints (2-week cadence), and keeps Linear as the source of truth: sync pushes only new tasks, pulls status/velocity, plans the active sprint up to the velocity target, and reports. Triggers: "porta il progetto in Linear", "crea le issue in Linear", "setup scrum", "pianifica lo sprint", "report di velocity", "adotta questo progetto in Linear", or dev-flow routing from `prd_drafted`/`tasks_split`. Modes: Setup (new project), Adopt (existing Linear project), Sync (ongoing). Not for: writing or estimating the tasks themselves (use `prd-to-tasks`), building the app or agent (use design-md-to-app / eve-agent), or the headless server loop wiring (see docs/loop-engineering.md).'
---
# linear-scrum — Linear integration + agile scrum for a dev-flow project
(body written in Task 4)
```
- [ ] **Step 2: Run the linter and confirm the new skill passes frontmatter checks**
Run: `cd ~/my-skills && python3 scripts/lint_skills.py linear-scrum`
Expected: exit 0 (or 1 warnings-only), and NO error mentioning `linear-scrum` for `name`/`description`/`Triggers`/`Not for:`. If `pyyaml` is missing, run `pip install pyyaml` first.
- [ ] **Step 3: Commit**
```bash
git add linear-scrum/SKILL.md
git commit -m "feat(linear-scrum): scaffold skill folder + frontmatter"
```
---
### Task 2: `task_key.py` — stable idempotent issue keys (TDD)
**Files:**
- Create: `linear-scrum/scripts/task_key.py`
- Test: `linear-scrum/scripts/test_scripts.py`
- [ ] **Step 1: Write the failing test**
Create `linear-scrum/scripts/test_scripts.py`:
```python
import unittest
from task_key import task_key
class TestTaskKey(unittest.TestCase):
def test_stable_across_checkbox_state_and_whitespace(self):
a = task_key("- [ ] **Wire login flow** — connect the form to better-auth")
b = task_key("- [x] **Wire login flow** — connect the form to better-auth")
self.assertEqual(a, b)
def test_title_only_ignores_body(self):
a = task_key("- [ ] **Wire login flow** — body one")
b = task_key("- [ ] **Wire login flow** — a totally different body")
self.assertEqual(a, b)
def test_different_titles_differ(self):
a = task_key("- [ ] **Wire login flow**")
b = task_key("- [ ] **Wire logout flow**")
self.assertNotEqual(a, b)
def test_is_12_hex_chars(self):
k = task_key("- [ ] **Anything**")
self.assertRegex(k, r"^[0-9a-f]{12}$")
if __name__ == "__main__":
unittest.main()
```
- [ ] **Step 2: Run the test to verify it fails**
Run: `cd ~/my-skills/linear-scrum/scripts && python3 -m unittest test_scripts -v`
Expected: FAIL with `ModuleNotFoundError: No module named 'task_key'`.
- [ ] **Step 3: Write the minimal implementation**
Create `linear-scrum/scripts/task_key.py`:
```python
#!/usr/bin/env python3
"""task_key.py — derive a stable identity key for a tasks.md checkbox line.
The key goes into meta.json#linear.issue_map so re-running Setup/Sync never
creates a duplicate Linear issue for the same task. It is derived from the task
TITLE only (the text before the ` — ` body separator), normalized so that
checkbox state, bold markers, case, and whitespace do not change it.
"""
from __future__ import annotations
import hashlib
import re
import sys
def task_key(line: str) -> str:
s = line.strip()
s = re.sub(r"^-\s*\[[ xX]\]\s*", "", s) # drop "- [ ] " / "- [x] "
s = s.split(" — ")[0] # title before the em-dash body
s = s.replace("**", "").strip() # drop bold markers
s = re.sub(r"\s+", " ", s).lower() # collapse whitespace, case-fold
return hashlib.sha1(s.encode("utf-8")).hexdigest()[:12]
if __name__ == "__main__":
for arg in sys.argv[1:]:
print(task_key(arg))
```
- [ ] **Step 4: Run the test to verify it passes**
Run: `cd ~/my-skills/linear-scrum/scripts && python3 -m unittest test_scripts -v`
Expected: 4 tests, all PASS.
- [ ] **Step 5: Commit**
```bash
git add linear-scrum/scripts/task_key.py linear-scrum/scripts/test_scripts.py
git commit -m "feat(linear-scrum): task_key.py for stable idempotent issue keys"
```
---
### Task 3: `meta_linear.py` — upsert the linear/scrum blocks (TDD)
**Files:**
- Create: `linear-scrum/scripts/meta_linear.py`
- Modify: `linear-scrum/scripts/test_scripts.py`
- [ ] **Step 1: Add failing tests to `test_scripts.py`**
Append to `linear-scrum/scripts/test_scripts.py` (above the `if __name__` block):
```python
from meta_linear import ensure_scrum, upsert_linear, record_issues
class TestMetaLinear(unittest.TestCase):
def base(self):
return {"project_slug": "demo", "phase": "tasks_split", "history": []}
def test_upsert_linear_sets_block(self):
m = upsert_linear(self.base(), team_id="T", team_name="Lucadigerlando",
project_id="P", url="http://x")
self.assertEqual(m["linear"]["team_id"], "T")
self.assertEqual(m["linear"]["project_id"], "P")
self.assertEqual(m["linear"]["issue_map"], {})
def test_record_issues_merges_no_dup(self):
m = upsert_linear(self.base(), team_id="T", team_name="N",
project_id="P", url="u")
m = record_issues(m, {"abc": "LUC-1"})
m = record_issues(m, {"abc": "LUC-1", "def": "LUC-2"}) # re-run + new
self.assertEqual(m["linear"]["issue_map"], {"abc": "LUC-1", "def": "LUC-2"})
def test_ensure_scrum_defaults(self):
m = ensure_scrum(self.base())
self.assertEqual(m["scrum"]["cadence_weeks"], 2)
self.assertEqual(m["scrum"]["estimate_scale"], "fibonacci")
self.assertEqual(m["scrum"]["states"]["blocked_label"], "blocked")
def test_ensure_scrum_is_idempotent(self):
m = ensure_scrum(self.base())
m["scrum"]["velocity_target"] = 13 # user-set value must survive
m2 = ensure_scrum(m)
self.assertEqual(m2["scrum"]["velocity_target"], 13)
```
- [ ] **Step 2: Run to verify failure**
Run: `cd ~/my-skills/linear-scrum/scripts && python3 -m unittest test_scripts -v`
Expected: FAIL with `ModuleNotFoundError: No module named 'meta_linear'`.
- [ ] **Step 3: Write the minimal implementation**
Create `linear-scrum/scripts/meta_linear.py`:
```python
#!/usr/bin/env python3
"""meta_linear.py — upsert the `linear` and `scrum` blocks of .workflow/meta.json.
Pure dict transforms (unit-testable) plus a thin CLI. Linear is the source of
truth after setup; these helpers only track the mapping + config on our side.
issue_map is keyed by task_key(), so merges are naturally duplicate-free.
"""
from __future__ import annotations
import argparse
import json
from datetime import datetime, timezone
from pathlib import Path
DEFAULT_STATES = {
"backlog": "Backlog", "todo": "Todo", "in_progress": "In Progress",
"in_review": "In Review", "done": "Done", "blocked_label": "blocked",
}
def _now() -> str:
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
def upsert_linear(meta: dict, *, team_id: str, team_name: str,
project_id: str, url: str) -> dict:
block = meta.get("linear", {})
block.update({
"team_id": team_id, "team_name": team_name,
"project_id": project_id, "url": url,
"issue_map": block.get("issue_map", {}),
"last_synced_at": _now(),
})
meta["linear"] = block
return meta
def record_issues(meta: dict, mapping: dict) -> dict:
block = meta.setdefault("linear", {})
im = block.setdefault("issue_map", {})
im.update(mapping) # dict keyed by task_key → no duplicates
block["last_synced_at"] = _now()
return meta
def ensure_scrum(meta: dict, *, cadence_weeks: int = 2,
estimate_scale: str = "fibonacci") -> dict:
s = meta.get("scrum", {})
s.setdefault("cadence_weeks", cadence_weeks)
s.setdefault("estimate_scale", estimate_scale)
s.setdefault("velocity_target", None)
s.setdefault("states", dict(DEFAULT_STATES))
meta["scrum"] = s
return meta
def _load(p: Path) -> dict:
return json.loads(p.read_text())
def _save(p: Path, meta: dict) -> None:
p.write_text(json.dumps(meta, indent=2) + "\n")
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("meta_path")
sub = ap.add_subparsers(dest="op", required=True)
ln = sub.add_parser("upsert-linear")
for f in ("team-id", "team-name", "project-id", "url"):
ln.add_argument(f"--{f}", required=True)
ri = sub.add_parser("record-issues")
ri.add_argument("--mapping", required=True, help="JSON object key->identifier")
sub.add_parser("ensure-scrum")
args = ap.parse_args()
p = Path(args.meta_path)
meta = _load(p)
if args.op == "upsert-linear":
meta = upsert_linear(meta, team_id=args.team_id, team_name=args.team_name,
project_id=args.project_id, url=args.url)
meta = ensure_scrum(meta)
elif args.op == "record-issues":
meta = record_issues(meta, json.loads(args.mapping))
elif args.op == "ensure-scrum":
meta = ensure_scrum(meta)
_save(p, meta)
if __name__ == "__main__":
main()
```
- [ ] **Step 4: Run to verify pass**
Run: `cd ~/my-skills/linear-scrum/scripts && python3 -m unittest test_scripts -v`
Expected: 8 tests total (4 from Task 2 + 4 here), all PASS.
- [ ] **Step 5: Commit**
```bash
git add linear-scrum/scripts/meta_linear.py linear-scrum/scripts/test_scripts.py
git commit -m "feat(linear-scrum): meta_linear.py upsert of linear/scrum blocks"
```
---
### Task 4: Write the `SKILL.md` body (three modes)
**Files:**
- Modify: `linear-scrum/SKILL.md` (replace the `(body written in Task 4)` placeholder)
- [ ] **Step 1: Replace the placeholder line with the full body**
Replace the single line `(body written in Task 4)` with exactly this content:
````markdown
This skill owns the **Linear side** of a dev-flow project. It writes to Linear (via the connected Linear MCP) and to the `linear` / `scrum` blocks of `.workflow/meta.json`. It **never** touches `app/`, `apps/agent/`, or other framework files, and it **does not change the `phase` enum** — like `eve-agent` and the discipline skills it records state in `meta.json` and appends `history`, and is never a phase gate.
Read `references/contracts.md` for the `.workflow/` contract, `references/scrum-model.md` for the scrum conventions, and `references/linear-mcp.md` for the exact Linear MCP operations.
## Preconditions
- A **Linear MCP must be connected** in the session. Confirm with `list_teams` before any write. For the headless server loop use the API-key path in `docs/loop-engineering.md` instead — this skill is the interactive counterpart.
- `.workflow/meta.json` exists (dev-flow project). If not, tell the user and stop.
- `.workflow/tasks.md` exists for Setup mode (run `prd-to-tasks` first otherwise).
## Read state, then pick a mode
Read `.workflow/meta.json`:
- No `meta.json#linear` and `tasks.md` present → **Setup mode**.
- A Linear Project already exists for this project but `meta.json#linear` is absent/partial → **Adopt mode**.
- `meta.json#linear` present → **Sync mode**.
Do **one** logical operation per invocation, then stop. Every mode is idempotent.
## Setup mode (new project)
1. `list_teams` → resolve the team (single team today: `Lucadigerlando`). One dev-flow project = one **Linear Project** under that team, never a new team.
2. Ensure scrum config on the team: **cycles enabled** and a **Fibonacci estimate scale**. If the MCP cannot toggle these, stop and ask the user to enable "Cycles" + estimates once in Linear, then re-run. `[VERIFY]` against the installed MCP whether these are settable.
3. Create/link the Linear Project (`save_project`); capture its id + url.
4. Push `tasks.md` → issues (`save_issue`), one per `- [ ]` task:
- **estimate**: from the task's `Estimated:` hint mapped to the nearest Fibonacci point; for tasks with no hint, **batch-prompt once** (list them, ask for points in one message) and default to unestimated if skipped.
- **labels**: `area:web` / `area:agent` (from `meta.json#stack` + the task's `*(addressed by …)*` tag), plus `type:scaffold` for setup tasks.
- **milestone**: map epics/milestones via `save_milestone` when `tasks.md` groups by epic.
- record each `task_key(line) → issue identifier` for the next step.
5. Create the first cycle (`list_cycles` to check, then create; 2-week cadence).
6. Persist state:
```bash
python3 scripts/meta_linear.py <root>/.workflow/meta.json upsert-linear \
--team-id <id> --team-name <name> --project-id <pid> --url <url>
python3 scripts/meta_linear.py <root>/.workflow/meta.json record-issues \
--mapping '{"<task_key>":"LUC-123", ...}'
```
Then append a `history` entry via `dev-flow/scripts/update_meta.py`:
`{ "skill": "linear-scrum", "action": "setup", "ran_at": "<ISO8601>" }` (no phase bump).
## Adopt mode (existing project already partly in Linear)
1. Discover the existing Project and its issues (`list_projects`, `list_issues`).
2. Match issues to `tasks.md` lines by title (`task_key`) and **backfill** `issue_map` without creating anything (`meta_linear.py record-issues`).
3. Fill any missing scrum config (`meta_linear.py ensure-scrum`), then `upsert-linear` with the discovered project id/url.
4. Append history `{ "skill": "linear-scrum", "action": "adopt", … }`.
## Sync mode (ongoing, horizontal)
- **Push new tasks**: for each `tasks.md` line whose `task_key` is not in `issue_map`, `save_issue` + `record-issues`. Never edit or re-push existing issues — Linear owns status/ordering/estimates.
- **Pull status/velocity**: read issue states + completed points from Linear (`list_issues`, `list_cycles`).
- **Sprint planning**: move items from `Backlog`/`Todo` into the active cycle up to the **velocity target** (`meta.json#scrum.velocity_target`; learned from past cycles, asked on the first). See `references/scrum-model.md`.
- **Report**: produce a sprint/velocity summary **to chat** (points committed vs done, carry-over, blocked issues). Do not write a report file.
- Append history `{ "skill": "linear-scrum", "action": "sync", … }`.
## Definition of Done (per run)
- **Setup**: `list_issues` on the Project returns the pushed issues; `meta.json#linear` populated; a re-run creates **no duplicates** (verify by re-running Setup and confirming the issue count is unchanged).
- **Sync**: the active cycle holds planned issues up to the velocity target and a report was produced.
- Scripts stay green: `cd linear-scrum/scripts && python3 -m unittest test_scripts`.
## What this skill does NOT do
- Doesn't write/estimate tasks (that's `prd-to-tasks`).
- Doesn't build the app or agent (`design-md-to-app` / `eve-agent`).
- Doesn't run human ceremonies (standup/retro) or generate ceremony artifacts.
- Doesn't create a Linear team per project — one team, many Projects.
- Doesn't bump `phase`.
````
- [ ] **Step 2: Lint the completed skill**
Run: `cd ~/my-skills && python3 scripts/lint_skills.py linear-scrum`
Expected: exit 0/1, no errors for `linear-scrum`. Cross-references to sibling skills (`prd-to-tasks`, `eve-agent`, `design-md-to-app`) resolve — those folders exist.
- [ ] **Step 3: Commit**
```bash
git add linear-scrum/SKILL.md
git commit -m "feat(linear-scrum): SKILL.md body — Setup/Adopt/Sync modes + DoD"
```
---
### Task 5: `references/scrum-model.md`
**Files:**
- Create: `linear-scrum/references/scrum-model.md`
- [ ] **Step 1: Write the reference**
Create `linear-scrum/references/scrum-model.md`:
```markdown
# Scrum model for dev-flow projects
The conventions `linear-scrum` applies. Depth = **structure + planning + reports**; human ceremonies (standup, retro) stay human.
## Cadence
- Default **2-week** cycles (`meta.json#scrum.cadence_weeks`). One Linear cycle = one sprint.
## Estimates
- **Fibonacci** story points (1, 2, 3, 5, 8, 13). Map a task's `Estimated: <2-8h>` hint to the nearest point (≤2h→1, ~half-day→2, day→3, 2-3 days→5, ~week→8, more→13, and split if >13).
- Tasks with no hint: batch-prompt the user once; default to unestimated rather than guessing.
## Workflow states (this workspace)
`Backlog · Todo · In Progress · In Review · Done · Canceled`. There is **no `Blocked` state** — use a **`blocked` label** on the issue instead (`meta.json#scrum.states.blocked_label`).
## Sprint planning
1. Determine the velocity target: `meta.json#scrum.velocity_target` if set, else the average completed points of the last 2-3 cycles, else ask the user for the first cycle.
2. Pull issues from `Todo`/`Backlog` in priority order until the sum of estimates reaches the target (don't exceed by more than the smallest remaining item).
3. Assign them to the active cycle; leave the rest in the backlog.
## Velocity report (to chat)
For the active/last cycle report: committed points, completed points, carry-over, blocked issues (by label), and the rolling velocity (last 3 cycles). No file is written.
## Labels
- `area:web` / `area:agent` — deduced from `meta.json#stack` and each task's `*(addressed by …)*` owner.
- `type:scaffold` — setup/foundation tasks owned by another skill.
- `blocked` — the stand-in for a Blocked state.
```
- [ ] **Step 2: Lint + commit**
Run: `cd ~/my-skills && python3 scripts/lint_skills.py linear-scrum`
Expected: no new errors.
```bash
git add linear-scrum/references/scrum-model.md
git commit -m "docs(linear-scrum): scrum-model reference"
```
---
### Task 6: `references/linear-mcp.md`
**Files:**
- Create: `linear-scrum/references/linear-mcp.md`
- [ ] **Step 1: Write the reference**
Create `linear-scrum/references/linear-mcp.md`:
```markdown
# Linear MCP cookbook
How each `linear-scrum` operation maps to Linear MCP tools. **Verify names/params against the connected MCP** before relying on them — tool names are workspace-specific and may change. `[VERIFY]`
| Operation | MCP tool | Notes |
|---|---|---|
| Resolve team | `list_teams` | Single team today (`Lucadigerlando`). Always call first. |
| List / find project | `list_projects` | Adopt mode discovery. |
| Create / link project | `save_project` | One per dev-flow project. Capture id + url. |
| Create milestone | `save_milestone` | Map epics from `tasks.md`. |
| Create issue | `save_issue` | Pass title, description, estimate, labels, project, milestone. `[VERIFY]` that `estimate` is accepted (needs the team estimate scale enabled). |
| List issues | `list_issues` | Status/velocity pull; duplicate check in DoD. |
| Issue statuses | `list_issue_statuses` | Confirms the state names in `scrum-model.md`. |
| Cycles | `list_cycles` | Check/create the active sprint. `[VERIFY]` whether cycles are creatable via MCP or must be enabled in Linear settings first. |
| Comment | `save_comment` | Optional: post the sprint report as a project comment (still also to chat). |
## Idempotency
Never create an issue whose `task_key` is already in `meta.json#linear.issue_map`. Re-running Setup/Sync must be a no-op for already-pushed tasks. Verify by re-running and confirming `list_issues` count is unchanged.
## Headless loop handoff
The autonomous server loop (Linear → Claude Code → PR) uses a **Linear API key**, not this MCP — see `docs/loop-engineering.md` §1. The states, atomic-issue rule, and `area:*` labels there are the same conventions this skill sets up, so a project bootstrapped here drops straight into that loop. The Linear meter is separate from both billing meters in that runbook (it's just the PM tool).
```
- [ ] **Step 2: Lint + commit**
Run: `cd ~/my-skills && python3 scripts/lint_skills.py linear-scrum`
Expected: no new errors.
```bash
git add linear-scrum/references/linear-mcp.md
git commit -m "docs(linear-scrum): linear-mcp cookbook reference"
```
---
### Task 7: Vendor `references/contracts.md`
**Files:**
- Create: `linear-scrum/references/contracts.md` (copy of `dev-flow/references/contracts.md`)
- [ ] **Step 1: Copy the canonical contract**
Run: `cd ~/my-skills && cp dev-flow/references/contracts.md linear-scrum/references/contracts.md`
- [ ] **Step 2: Verify it copied and lint**
Run: `cd ~/my-skills && head -1 linear-scrum/references/contracts.md && python3 scripts/lint_skills.py linear-scrum`
Expected: first line is `# `.workflow/` — Contract between dev-flow skills`; lint has no new errors.
- [ ] **Step 3: Commit**
```bash
git add linear-scrum/references/contracts.md
git commit -m "docs(linear-scrum): vendor .workflow contract copy"
```
---
### Task 8: dev-flow routing + policy + contract blocks
**Files:**
- Modify: `dev-flow/SKILL.md`
- Modify: `dev-flow/references/contracts.md`
- [ ] **Step 1: Add the routing rows in `dev-flow/SKILL.md` Step 2 table**
In the `## Workflow` → Step 2 phase table, change the `prd_drafted` and `tasks_split` rows to append the Linear proposal. Replace:
```markdown
| `prd_drafted` | `prd-to-tasks` if user wants explicit task breakdown; OR `figma-to-design-md` if user has a Figma; OR `image-to-design-md` if user has 1+ raster images (PNG/JPG screenshots, mockups, Pinterest pins); OR jump to `design-md-to-app` if simple project + DESIGN.md will be hand-written. |
| `tasks_split` | `figma-to-design-md` or `image-to-design-md` or `design-md-to-app`. |
```
with:
```markdown
| `prd_drafted` | `prd-to-tasks` if user wants explicit task breakdown; OR `figma-to-design-md` if user has a Figma; OR `image-to-design-md` if user has 1+ raster images (PNG/JPG screenshots, mockups, Pinterest pins); OR jump to `design-md-to-app` if simple project + DESIGN.md will be hand-written. |
| `tasks_split` | **Propose `linear-scrum` (Setup)** to take the project into Linear + set up scrum (default, skip only on explicit opt-out); then `figma-to-design-md` or `image-to-design-md` or `design-md-to-app`. |
```
- [ ] **Step 2: Add a policy paragraph after the Step 2 table**
Immediately after the phase table (before "The orchestrator must propose, not impose."), insert:
```markdown
**Project-management policy (Linear + scrum).** Every project is run in **Linear with agile scrum** unless the user explicitly opts out. When `tasks.md` exists (phase `tasks_split`, or `prd_drafted` once tasks are generated), propose `linear-scrum` **Setup**. `linear-scrum` is also a **horizontal capability** — invoke it any time for **Sync** (push new tasks, plan the sprint, report velocity), regardless of `phase`. It records `meta.json#linear` + `#scrum` and appends `history`, but never bumps `phase` and never gates progression. For an existing project already partly in Linear, `linear-scrum` **Adopt** backfills the link.
```
- [ ] **Step 3: Document the meta.json blocks in `dev-flow/references/contracts.md`**
After the `### `artifacts` field` section (before `### `phase` enum (canonical)`), insert:
```markdown
### `linear` and `scrum` fields (optional, owned by `linear-scrum`)
Present once a project is taken into Linear. Written only by the `linear-scrum` skill; other skills must not touch them.
```json
"linear": {
"team_id": "…", "team_name": "…", "project_id": "…", "url": "…",
"issue_map": { "<task_key>": "LUC-123" },
"last_synced_at": "<ISO-8601 UTC>"
},
"scrum": {
"cadence_weeks": 2, "estimate_scale": "fibonacci", "velocity_target": null,
"states": { "backlog": "Backlog", "todo": "Todo", "in_progress": "In Progress",
"in_review": "In Review", "done": "Done", "blocked_label": "blocked" }
}
```
`issue_map` keys are `task_key()` digests of `tasks.md` lines (see `linear-scrum/scripts/task_key.py`) so re-syncing never duplicates issues. Linear is the source of truth for status/ordering/estimates after Setup; `tasks.md` stays the append-only intake for new work.
```
- [ ] **Step 4: Lint the whole repo**
Run: `cd ~/my-skills && python3 scripts/lint_skills.py`
Expected: exit 0/1; no errors. The new `linear-scrum` cross-references from `dev-flow` resolve.
- [ ] **Step 5: Commit**
```bash
git add dev-flow/SKILL.md dev-flow/references/contracts.md
git commit -m "feat(dev-flow): route to linear-scrum + document linear/scrum meta blocks"
```
---
### Task 9: Register the skill (install scripts, README, registry, bundle)
**Files:**
- Modify: `install.sh`, `uninstall.sh`
- Modify: `README.md`
- Regenerate: `skills.json`, `dist/linear-scrum.skill`
- [ ] **Step 1: Add `linear-scrum` to the install/uninstall skill lists**
Inspect how skills are enumerated: `cd ~/my-skills && grep -n "prd-to-tasks" install.sh uninstall.sh`. Add a `linear-scrum` entry in the exact same shape (array element / line) right after the `prd-to-tasks` entry in **both** files.
- [ ] **Step 2: Add a README catalog entry**
Find the core-skills section: `cd ~/my-skills && grep -n "prd-to-tasks" README.md | head`. Add a one-line/one-row `linear-scrum` entry mirroring the format of the neighboring `prd-to-tasks` entry (name + one-sentence description: "Take a project into Linear and run it with agile scrum — cycles, estimates, sprint planning, velocity reports; Linear as source of truth").
- [ ] **Step 3: Regenerate the registry and the bundle**
Run:
```bash
cd ~/my-skills
python3 scripts/build_skills_registry.py
python3 scripts/build_skill_bundles.py linear-scrum
```
Expected: `skills.json` updated to include `linear-scrum` (family `core`); `✓ dist/linear-scrum.skill` printed.
- [ ] **Step 4: Verify registry + bundle + full lint**
Run:
```bash
cd ~/my-skills
grep -c '"name": "linear-scrum"' skills.json # expect 1
test -f dist/linear-scrum.skill && echo bundle-ok
python3 scripts/lint_skills.py # expect exit 0/1, no errors
(cd linear-scrum/scripts && python3 -m unittest test_scripts) # expect all PASS
```
Expected: `1`, `bundle-ok`, clean lint, all unittests PASS.
- [ ] **Step 5: Commit**
```bash
git add install.sh uninstall.sh README.md skills.json dist/linear-scrum.skill
git commit -m "chore(linear-scrum): register skill in install/README/registry + bundle"
```
---
## Self-review notes (author)
- **Spec coverage**: Setup/Adopt/Sync (Task 4), Linear=source-of-truth (Task 4 Sync + contracts Task 8), scrum depth structure+planning+reports (Tasks 4-5), non-blocking policy (Task 8), meta.json blocks (Tasks 3, 8), one-team-many-projects + `blocked` label + `[VERIFY]` on cycles/estimates (Tasks 4-6). Open questions resolved in the header.
- **No new `phase` value** — consistent across Tasks 4 and 8.
- **Idempotency** is the tested correctness property (Tasks 2-3) and the DoD re-run check (Task 4).
- Names consistent: `task_key`, `upsert_linear`, `record_issues`, `ensure_scrum`, `issue_map`, `scrum.states.blocked_label` across all tasks.
## Manual verification (post-implementation, needs the live Linear MCP)
Not automatable offline — do once with the connected MCP on a throwaway `.workflow/` project:
1. Setup on a 3-task `tasks.md` → confirm a Linear Project + 3 issues + 1 cycle; `meta.json#linear.issue_map` has 3 entries.
2. Re-run Setup → issue count unchanged (idempotent).
3. Sync after appending a 4th task → exactly one new issue.