/speckit.linear.pull
Summary
Linear-anchored cross-repo inventory of every spec Issue, grouped by
Project, never mutates Linear.
Cross-repo unified view of every spec Issue Linear knows about. The
partner to /speckit.linear.status: where status is filesystem-
anchored and drift-aware (one repo, comparing disk against Linear),
pull is Linear-anchored and inventory-aware (every spec across every
repo bound to the operator's workspace, grouped by Project).
Direction: read-only. Talks to Linear ONLY via graphql::query;
issues zero issueCreate / issueUpdate / commentCreate / any
other mutation. Even from a worktree that could write (under Principle
IV v2.0.0 any worktree can), this command MUST NOT write — it is an
inventory tool, full stop.
Authority: not gated. Runs from any worktree, on any branch
(detached HEAD included), and (in --workspace-wide mode) from any
directory inside the consumer repo. Reports Linear's view without
inspecting filesystem state.
Layer: out-of-band inspect command, not part of Layer D's write
cycle. Safe to run during a deploy, during a CI build, during a merge.
The deterministic work happens in src/pull.sh; this command is the
AI-agent entry point that runs the shell and surfaces its output. The
formal API contract is contracts/command-shapes.md
(speckit.linear.pull slice). Operators reading this file are looking
at the markdown the AI agent reads — the same operations are available
via bash src/pull.sh directly. For the operator-facing end-to-end
walkthrough see
quickstart.md.
Usage
| Argument |
Default |
Meaning |
workspace-wide |
false (implies --repo) |
Query every Project the operator's team owns, not just the locally bound Project. Useful when running from a directory not bound to a Linear project, or for cross-repo coordination. |
phase |
(none — implies --all-phases) |
Restrict to a single lifecycle phase (specifying, clarifying, planning, tasking, red_team, implementing, analyzing, ready_to_merge, merged). |
json |
false |
Emit a machine-readable JSON array on stdout, one object per spec Issue. Default is the coloured human table grouped by Project. |
no-color |
false |
Force monochrome output. Also honoured via the NO_COLOR env variable. |
workspace-wide and phase are orthogonal — every combination is
valid. json and no-color are orthogonal to scope and phase.
CLI shape
speckit.linear.pull [--repo | --workspace-wide]
[--phase PHASE | --all-phases]
[--json | --human] [--no-color]
Default: --repo --all-phases --human.
Algorithm (what the AI agent executes)
Verify prerequisites. Refuse to proceed if any of these fail.
- Bash 4 or newer is on
PATH. macOS ships bash 3.2 by default;
the operator must brew install bash and ensure
/opt/homebrew/bin/bash (Apple Silicon) or /usr/local/bin/bash
(Intel) is earlier on PATH than /bin/bash.
- The consumer repo's config is present at
.specify/extensions/linear/linear-config.yml. If absent,
surface "run /spec-kit-linear-install first" and exit 2; do NOT
attempt to run the inspector.
jq, curl, and git are installed.
Compose the invocation. Translate the user-facing arguments
into src/pull.sh flags:
workspace-wide=true → --workspace-wide
- no
workspace-wide → --repo (the default)
phase=NAME → --phase NAME
- no
phase → --all-phases
json=true → --json
no-color=true → --no-color
Execute the inspector. Shell out:
bash src/pull.sh <flags>
The script:
- Loads + validates
linear-config.yml (src/config.sh). Halts
with exit 2 on missing / malformed UUIDs (FR-022).
- Builds a single GraphQL
IssueFilter:
labels.name startsWith "speckit-spec:" — the FR-004b workspace
label family identifies every spec Issue regardless of repo.
--repo: AND project.id eq <linear.project.id>.
--workspace-wide: AND team.id eq <linear.team.id>.
--phase X: AND a second-clause labels.name eq "phase:X"
under the top-level and: field so both label conditions can
coexist.
- Issues one
graphql::query call returning every matching Issue
(capped at 250 nodes per Linear's pagination default — sufficient
for the bridge's design ceiling of a few dozen specs per team).
- For each node, extracts:
identifier, feature_number (parsed
from the speckit-spec:NNN label), title, project_name,
state_name + state_type, phase_label (parsed from the
phase:* label), branch + worktree (parsed from the
description's memory block, if present), last_activity
(Linear's updatedAt), assignee_name (FR-034), estimate
(FR-035 rollup), and url (composed from
linear.workspace.url_key + identifier).
- Sorts by Project name, then lifecycle phase (using the canonical
spec-kit order: specifying → clarifying → planning → tasking →
red_team → implementing → analyzing → ready_to_merge → merged),
then last_activity descending.
- Renders the report on stdout (JSON array or Project-grouped human
table) and the structured summary on stderr.
Render the report. Two output shapes:
--human (default) — Project-grouped coloured table on stdout:
▼ spec-kit-linear
ID NNN PHASE STATE EST ASSIGNEE LAST ACTIVITY TITLE
ACM-13 005 implementing Implementing 46 ash 2026-05-28T11:50:00Z 005-some-spec
ACM-12 002 tasking Tasking 6 ash 2026-05-27T09:21:00Z 002-multi-phase
ACM-5 001 merged Done 40 ash 2026-05-25T16:00:00Z 001-spec-kit-linear-bridge
▼ another-repo
ID NNN PHASE STATE EST ASSIGNEE LAST ACTIVITY TITLE
ACM-22 003 planning Planning 8 ash 2026-05-28T08:00:00Z 003-feature-x
PHASE cell is yellow for early phases, blue for mid-lifecycle,
green for late / merged. Honours NO_COLOR and --no-color.
--json — JSON array on stdout, one object per spec Issue,
sorted Project → phase → last_activity:
[
{
"identifier": "ACM-13",
"feature_number": "005",
"title": "005-some-spec",
"project_id": "...",
"project_name": "spec-kit-linear",
"state_name": "Implementing",
"state_type": "started",
"phase_label": "implementing",
"branch": "005-some-spec",
"worktree": "/path/to/wt",
"last_activity": "2026-05-28T11:50:00Z",
"assignee_name": "ash",
"estimate": 46,
"url": "https://linear.app/acme/issue/ACM-13"
}
]
Handle the exit code.
0 — success (possibly with warnings). The inventory is authoritative.
1 — partial failure: a sub-query failed but other rows surfaced.
Recommend re-running once network connectivity is restored.
2 — workspace config error (missing/malformed linear-config.yml).
The script halted before any query. Surface the remediation the
script printed (typically: run /spec-kit-linear-install).
3 — transport failure. Linear was unreachable; no rows surfaced.
When this command fires
- Operator-driven —
/speckit.linear.pull from the AI agent
chat. Primary path for "what specs are in flight across my
workspace?", cross-repo coordination, and Project-level inventory
checks before a release.
- Never auto-fired. This is NOT wired into any
after_* hook or
any git hook by /spec-kit-linear-install. Operator-invoked only.
Output channel discipline
stdout carries the per-Issue inventory (JSON array or human
table). This is the contract: pipe it to jq or column confidently.
stderr carries:
- per-step log lines (the scope + phase + format being applied)
- the final structured
summary::emit block (always)
- No filesystem writes (Principle I). No Linear writes (Principle I +
FR-026). The inspector reads Linear and prints; everything else is
operator action.
Failure surface
Each failure mode is surfaced as a named warning in the summary
(Principle VIII):
config load failed: PATH — linear-config.yml absent. Exit 2.
config validation failed — malformed UUIDs or missing fields. Exit 2.
linear.project.id missing for --repo scope — operator tried
--repo against a workspace-wide-only config. Suggest
--workspace-wide or running /spec-kit-linear-install to bind a
Project. Exit 2.
linear.team.id missing for --workspace-wide scope — config has no
team UUID. Exit 2.
Linear query failed; no rows surfaced — transport / GraphQL
failure. Exit 3.
no spec Issues matched the requested filter — empty inventory.
Exit 0; the report shows the active scope + phase filter so the
operator can verify the empty result is intentional.
Related commands
/speckit.linear.status — disk-vs-Linear drift report for the
current repo. Use this for "is THIS repo in sync?"; use pull for
"what's in flight across EVERY repo?".
/speckit.linear.push — write path. Reconciles filesystem state
into Linear. Use /speckit.linear.status or /speckit.linear.pull
first to see what WOULD change.
/speckit.linear.seed — one-shot workspace setup. Run once per
Linear workspace before the first push.
/speckit.linear.install — per-repo install ceremony. Run once
per consumer repo before the first push.
See contracts/command-shapes.md for the formal contract on each
and
quickstart.md
for the end-to-end operator walkthrough.
FRs surfaced
This command implements (in whole or in part):
- FR-004b —
speckit-spec:NNN workspace label as the cross-repo
spec-Issue lookup key.
- FR-022 — config-load halt with operator-actionable remediation.
- FR-023 — structured
summary::emit block on stderr.
- FR-026 / FR-060 — read-only direction; the bridge never writes
from this command, from any worktree (Principle IV v2.0.0).
- FR-034 — operator assignee surfaced in the inventory.
- FR-035 — Fibonacci
[N] estimate rollup surfaced in the inventory.
1---2name: speckit-linear-pull3description: Cross-repo unified spec view from Linear (READ-ONLY; never mutates Linear)4---56# `/speckit.linear.pull`78## Summary910Linear-anchored cross-repo inventory of every spec Issue, grouped by11Project, never mutates Linear.1213Cross-repo unified view of every spec Issue Linear knows about. The14partner to `/speckit.linear.status`: where `status` is filesystem-15anchored and drift-aware (one repo, comparing disk against Linear),16`pull` is Linear-anchored and inventory-aware (every spec across every17repo bound to the operator's workspace, grouped by Project).1819**Direction**: read-only. Talks to Linear ONLY via `graphql::query`;20issues zero `issueCreate` / `issueUpdate` / `commentCreate` / any21other mutation. Even from a worktree that could write (under Principle22IV v2.0.0 any worktree can), this command MUST NOT write — it is an23inventory tool, full stop.24**Authority**: not gated. Runs from any worktree, on any branch25(detached HEAD included), and (in `--workspace-wide` mode) from any26directory inside the consumer repo. Reports Linear's view without27inspecting filesystem state.28**Layer**: out-of-band inspect command, not part of Layer D's write29cycle. Safe to run during a deploy, during a CI build, during a merge.3031The deterministic work happens in `src/pull.sh`; this command is the32AI-agent entry point that runs the shell and surfaces its output. The33formal API contract is `contracts/command-shapes.md`34(`speckit.linear.pull` slice). Operators reading this file are looking35at the markdown the AI agent reads — the same operations are available36via `bash src/pull.sh` directly. For the operator-facing end-to-end37walkthrough see38[`quickstart.md`](../specs/001-spec-kit-linear-bridge/quickstart.md).3940## Usage4142| Argument | Default | Meaning |43|---|---|---|44| `workspace-wide` | false (implies `--repo`) | Query every Project the operator's team owns, not just the locally bound Project. Useful when running from a directory not bound to a Linear project, or for cross-repo coordination. |45| `phase` | (none — implies `--all-phases`) | Restrict to a single lifecycle phase (`specifying`, `clarifying`, `planning`, `tasking`, `red_team`, `implementing`, `analyzing`, `ready_to_merge`, `merged`). |46| `json` | false | Emit a machine-readable JSON array on stdout, one object per spec Issue. Default is the coloured human table grouped by Project. |47| `no-color` | false | Force monochrome output. Also honoured via the `NO_COLOR` env variable. |4849`workspace-wide` and `phase` are orthogonal — every combination is50valid. `json` and `no-color` are orthogonal to scope and phase.5152### CLI shape5354```text55speckit.linear.pull [--repo | --workspace-wide]56 [--phase PHASE | --all-phases]57 [--json | --human] [--no-color]58```5960Default: `--repo --all-phases --human`.6162## Algorithm (what the AI agent executes)63641. **Verify prerequisites.** Refuse to proceed if any of these fail.65 - Bash 4 or newer is on `PATH`. macOS ships bash 3.2 by default;66 the operator must `brew install bash` and ensure67 `/opt/homebrew/bin/bash` (Apple Silicon) or `/usr/local/bin/bash`68 (Intel) is earlier on `PATH` than `/bin/bash`.69 - The consumer repo's config is present at70 `.specify/extensions/linear/linear-config.yml`. If absent,71 surface "run `/spec-kit-linear-install` first" and exit 2; do NOT72 attempt to run the inspector.73 - `jq`, `curl`, and `git` are installed.74752. **Compose the invocation.** Translate the user-facing arguments76 into `src/pull.sh` flags:77 - `workspace-wide=true` → `--workspace-wide`78 - no `workspace-wide` → `--repo` (the default)79 - `phase=NAME` → `--phase NAME`80 - no `phase` → `--all-phases`81 - `json=true` → `--json`82 - `no-color=true` → `--no-color`83843. **Execute the inspector.** Shell out:8586 ```bash87 bash src/pull.sh <flags>88 ```8990 The script:91 - Loads + validates `linear-config.yml` (`src/config.sh`). Halts92 with exit 2 on missing / malformed UUIDs (FR-022).93 - Builds a single GraphQL `IssueFilter`:94 - `labels.name startsWith "speckit-spec:"` — the FR-004b workspace95 label family identifies every spec Issue regardless of repo.96 - `--repo`: AND `project.id eq <linear.project.id>`.97 - `--workspace-wide`: AND `team.id eq <linear.team.id>`.98 - `--phase X`: AND a second-clause `labels.name eq "phase:X"`99 under the top-level `and:` field so both label conditions can100 coexist.101 - Issues one `graphql::query` call returning every matching Issue102 (capped at 250 nodes per Linear's pagination default — sufficient103 for the bridge's design ceiling of a few dozen specs per team).104 - For each node, extracts: `identifier`, `feature_number` (parsed105 from the `speckit-spec:NNN` label), `title`, `project_name`,106 `state_name` + `state_type`, `phase_label` (parsed from the107 `phase:*` label), `branch` + `worktree` (parsed from the108 description's memory block, if present), `last_activity`109 (Linear's `updatedAt`), `assignee_name` (FR-034), `estimate`110 (FR-035 rollup), and `url` (composed from111 `linear.workspace.url_key` + identifier).112 - Sorts by Project name, then lifecycle phase (using the canonical113 spec-kit order: specifying → clarifying → planning → tasking →114 red_team → implementing → analyzing → ready_to_merge → merged),115 then last_activity descending.116 - Renders the report on stdout (JSON array or Project-grouped human117 table) and the structured summary on stderr.1181194. **Render the report.** Two output shapes:120121 - `--human` (default) — Project-grouped coloured table on stdout:122123 ```text124 ▼ spec-kit-linear125 ID NNN PHASE STATE EST ASSIGNEE LAST ACTIVITY TITLE126 ACM-13 005 implementing Implementing 46 ash 2026-05-28T11:50:00Z 005-some-spec127 ACM-12 002 tasking Tasking 6 ash 2026-05-27T09:21:00Z 002-multi-phase128 ACM-5 001 merged Done 40 ash 2026-05-25T16:00:00Z 001-spec-kit-linear-bridge129130 ▼ another-repo131 ID NNN PHASE STATE EST ASSIGNEE LAST ACTIVITY TITLE132 ACM-22 003 planning Planning 8 ash 2026-05-28T08:00:00Z 003-feature-x133 ```134135 `PHASE` cell is yellow for early phases, blue for mid-lifecycle,136 green for late / merged. Honours `NO_COLOR` and `--no-color`.137138 - `--json` — JSON array on stdout, one object per spec Issue,139 sorted Project → phase → last_activity:140141 ```json142 [143 {144 "identifier": "ACM-13",145 "feature_number": "005",146 "title": "005-some-spec",147 "project_id": "...",148 "project_name": "spec-kit-linear",149 "state_name": "Implementing",150 "state_type": "started",151 "phase_label": "implementing",152 "branch": "005-some-spec",153 "worktree": "/path/to/wt",154 "last_activity": "2026-05-28T11:50:00Z",155 "assignee_name": "ash",156 "estimate": 46,157 "url": "https://linear.app/acme/issue/ACM-13"158 }159 ]160 ```1611625. **Handle the exit code.**163 - `0` — success (possibly with warnings). The inventory is authoritative.164 - `1` — partial failure: a sub-query failed but other rows surfaced.165 Recommend re-running once network connectivity is restored.166 - `2` — workspace config error (missing/malformed `linear-config.yml`).167 The script halted before any query. Surface the remediation the168 script printed (typically: run `/spec-kit-linear-install`).169 - `3` — transport failure. Linear was unreachable; no rows surfaced.170171## When this command fires172173- **Operator-driven** — `/speckit.linear.pull` from the AI agent174 chat. Primary path for "what specs are in flight across my175 workspace?", cross-repo coordination, and Project-level inventory176 checks before a release.177- **Never auto-fired.** This is NOT wired into any `after_*` hook or178 any git hook by `/spec-kit-linear-install`. Operator-invoked only.179180## Output channel discipline181182- `stdout` carries the per-Issue inventory (JSON array or human183 table). This is the contract: pipe it to `jq` or `column` confidently.184- `stderr` carries:185 - per-step log lines (the scope + phase + format being applied)186 - the final structured `summary::emit` block (always)187- No filesystem writes (Principle I). No Linear writes (Principle I +188 FR-026). The inspector reads Linear and prints; everything else is189 operator action.190191## Failure surface192193Each failure mode is surfaced as a named warning in the summary194(Principle VIII):195196- `config load failed: PATH` — `linear-config.yml` absent. Exit 2.197- `config validation failed` — malformed UUIDs or missing fields. Exit 2.198- `linear.project.id missing for --repo scope` — operator tried199 `--repo` against a workspace-wide-only config. Suggest200 `--workspace-wide` or running `/spec-kit-linear-install` to bind a201 Project. Exit 2.202- `linear.team.id missing for --workspace-wide scope` — config has no203 team UUID. Exit 2.204- `Linear query failed; no rows surfaced` — transport / GraphQL205 failure. Exit 3.206- `no spec Issues matched the requested filter` — empty inventory.207 Exit 0; the report shows the active scope + phase filter so the208 operator can verify the empty result is intentional.209210## Related commands211212- `/speckit.linear.status` — disk-vs-Linear drift report for the213 current repo. Use this for "is THIS repo in sync?"; use `pull` for214 "what's in flight across EVERY repo?".215- `/speckit.linear.push` — write path. Reconciles filesystem state216 into Linear. Use `/speckit.linear.status` or `/speckit.linear.pull`217 first to see what WOULD change.218- `/speckit.linear.seed` — one-shot workspace setup. Run once per219 Linear workspace before the first push.220- `/speckit.linear.install` — per-repo install ceremony. Run once221 per consumer repo before the first push.222223See `contracts/command-shapes.md` for the formal contract on each224and225[`quickstart.md`](../specs/001-spec-kit-linear-bridge/quickstart.md)226for the end-to-end operator walkthrough.227228## FRs surfaced229230This command implements (in whole or in part):231232- **FR-004b** — `speckit-spec:NNN` workspace label as the cross-repo233 spec-Issue lookup key.234- **FR-022** — config-load halt with operator-actionable remediation.235- **FR-023** — structured `summary::emit` block on stderr.236- **FR-026 / FR-060** — read-only direction; the bridge never writes237 from this command, from any worktree (Principle IV v2.0.0).238- **FR-034** — operator assignee surfaced in the inventory.239- **FR-035** — Fibonacci `[N]` estimate rollup surfaced in the inventory.