mirror of
https://github.com/prdlk/dotfiles.git
synced 2026-11-02 19:32:43 +00:00
chore(agents): update skill metadata timestamps, source URLs, and documentation wording across dot_agents files
This commit is contained in:
1 parent
12155ad58c
commit
c96c19c0f9
45 files changed
+1819
-1395
No files matched your search
@@ -9,6 +9,8 @@ Blume is a **markdown-first** documentation framework on Astro/Vite. You drop Ma
|
||||
|
||||
Your job is to convert a source docs repo into an **idiomatic** Blume project — not a 1:1 transliteration. Read this file, detect the source framework, open the matching `references/<framework>.md` for the exact mappings, and work the loop below. Report everything you drop or approximate.
|
||||
|
||||
Throughout this skill (including the `references/` files), **`<skill>` means the absolute path of the directory containing this SKILL.md** — resolve it from wherever you read this file (e.g. `node_modules/blume/skills/blume-migrate` or `.claude/skills/blume-migrate`). It is a placeholder to substitute, never a literal path.
|
||||
|
||||
## Migration philosophy
|
||||
|
||||
- **Target idiomatic Blume, not a mechanical port.** Prefer filesystem-derived navigation over an exhaustive explicit `navigation.sidebar`. Prefer `:::` directives over JSX callouts. Prefer Blume defaults over restating them in config.
|
||||
@@ -62,7 +64,7 @@ The single biggest shift for most sources — especially Mintlify — is that **
|
||||
`defineConfig({...})` — every field optional, all with defaults:
|
||||
|
||||
- **Site:** `title`, `description`, `logo` (string SVG, or `{ image: string | { light, dark, alt }, text, href }`), `banner` (`{ content, link, dismissible, id }` — no color/type). A logo renders beside `title` in the header, so a **wordmark logo doubles the brand** ("Acme Acme") — set `text: ""` to render the mark alone. **Prefer the string form over `{ light, dark }`:** if you have the logo SVG locally and it's monochrome (solid black or white), rewrite its `fill`/`stroke` to `currentColor` and use `logo: "/logo.svg"` — it then inherits the theme's text color and adapts to light/dark automatically, so you don't need separate light/dark files.
|
||||
- **`theme`:** `accent` (a color string for both modes, or `{ light, dark }` per mode), `action` (color), `mode` (`light`/`dark`/`system`), `radius`, `fonts` (`{ body, display, mono }` — curated Google-font slugs), `background` and `backgroundImage` (each a string, or `{ light, dark }` per mode). The old `accentDark`/`backgroundDark`/`backgroundImageDark` fields were **merged into these per-mode objects** — a bare string still applies to both modes, so only reach for `{ light, dark }` when the two modes differ. There is **no** `theme.strict` and **no** `theme.css` config field — custom CSS goes in a project-root **`theme.css` file** (auto-picked-up), and a source's "strict appearance" flags drop.
|
||||
- **`theme`:** `accent` (a color string for both modes, or `{ light, dark }` per mode), `action` (color), `mode` (`light`/`dark`/`system`), `radius`, `fonts` (`{ body, display, mono }` — each a curated Google-font slug, a `{ name, provider?, weights? }` object for any Google/Fontsource/Bunny/Fontshare family, or `{ name, variants: [{ src, weight?, style? }] }` for local font files), `background` and `backgroundImage` (each a string, or `{ light, dark }` per mode). The old `accentDark`/`backgroundDark`/`backgroundImageDark` fields were **merged into these per-mode objects** — a bare string still applies to both modes, so only reach for `{ light, dark }` when the two modes differ. There is **no** `theme.strict` and **no** `theme.css` config field — custom CSS goes in a project-root **`theme.css` file** (auto-picked-up), and a source's "strict appearance" flags drop.
|
||||
- **`content`:** `root` (default `"docs"`, relative to the project dir where `blume` runs), `include`/`exclude` (arrays of globs **relative to `content.root`**; defaults `["**/*.{md,mdx}"]` / `["**/_*", "**/.*"]`), `sources` (staged sources: `filesystem`, `github-releases`, `notion`, `sanity`, `mdx-remote`, `custom` — OpenAPI is **not** one of these; it's the top-level `openapi` field), `pages` (custom `.astro` dir), `defaultType`. When docs sit directly under the project dir (no `docs/` subfolder), set `root` there and **scope `include` to the real content folders** instead of scanning everything — `references/monorepo.md` §1.
|
||||
- **`basePath`** (top-level): a site-wide mount point (e.g. `"/docs"`) prepended to **every** route while staying invisible to the sidebar (no wrapper group). This is the right target for a source that served all docs under a prefix (Docusaurus `routeBasePath`, a Fumadocs `baseUrl` of `/docs`) — distinct from a per-source `prefix` (which adds a nav group) and from `deployment.base` (host subdirectory).
|
||||
- **`navigation`:** `tabs`, `selectors`, `featured` (links pinned above the sidebar on every route), `sidebar` (`{ display, items }` — `display` is the global render mode above; `items` is an explicit tree), `repo`. **Avoid an explicit `navigation.sidebar` unless you have to** — lean on the filesystem-derived sidebar. It only works when the file tree roughly matches the intended sidebar layout, so reshape folders to match first; reach for `sidebar.items` only for a shape files genuinely can't express (see "Config-declared nesting" above).
|
||||
@@ -70,7 +72,7 @@ The single biggest shift for most sources — especially Mintlify — is that **
|
||||
- **Don't set `deployment.site`.** Blume auto-fills it: the dev server's `localhost` URL in dev, and the deployment URL (`VERCEL_PROJECT_PRODUCTION_URL`/`VERCEL_URL`) on Vercel. Hardcoding it in `blume.config.ts` overrides that auto-detection and pins the wrong absolute URL (canonical links, sitemap, OG, llms.txt) everywhere but the one host you typed — so leave it unset even when a source config had a `url`/`site` field. (Sitemap still generates in production because the deploy URL is present there.)
|
||||
- **Favicon is a filename convention, not config.** Drop `icon`/`favicon.{svg,png,ico}` (and `apple-icon.png`) in the project root or `public/` — Blume auto-detects it. There is **no** `favicon` config field. A source favicon given as `{ light, dark }` **collapses to one** — pick a single file and report the loss.
|
||||
|
||||
The schema is exported from `blume/schema`; the full field reference is in `node_modules/blume/docs/configuration/`.
|
||||
The schema is exported from `blume/schema`; the full field reference is in the `docs/configuration/` directory of the installed `blume` package (see "Full documentation" below for how to locate it).
|
||||
|
||||
### Icons are Lucide, period
|
||||
|
||||
@@ -160,7 +162,7 @@ A `redirects: [{ from, to, status? }]` array **in `blume.config.ts`** maps old U
|
||||
|
||||
## Full documentation
|
||||
|
||||
The mapping details live in `references/`: one file per source framework (`mintlify.md`, `docusaurus.md`, `fumadocs.md`, `nextra.md`, `starlight.md`), plus **`monorepo.md`** for host-repo integration (content-layout detection, pnpm `minimumReleaseAge`, frozen-lockfile regeneration, the Vercel monorepo recipe, and the Astro/Vite patch). The Mintlify icon + frontmatter pass is automated by **`scripts/mintlify-codemod.mjs`** (zero-dependency, deterministic, idempotent; `--write` to apply). The authoritative Blume docs are bundled in the installed package at **`node_modules/blume/docs`** (or `apps/docs/content/docs` in a repo checkout). The most relevant pages:
|
||||
The mapping details live in `references/`: one file per source framework (`mintlify.md`, `docusaurus.md`, `fumadocs.md`, `nextra.md`, `starlight.md`), plus **`monorepo.md`** for host-repo integration (content-layout detection, pnpm `minimumReleaseAge`, frozen-lockfile regeneration, the Vercel monorepo recipe, and the Astro/Vite patch). The Mintlify icon + frontmatter pass is automated by **`scripts/mintlify-codemod.mjs`** (zero-dependency, deterministic, idempotent; `--write` to apply). The authoritative Blume docs are bundled in the installed package's **`docs/` directory** — not necessarily at the repository root: in a workspace monorepo (pnpm especially) the package lives in the depending workspace's `node_modules` (e.g. `apps/docs/node_modules/blume/docs`); `node -e "console.log(require.resolve('blume/package.json'))"` run from the depending package prints the exact location. (In a repo checkout of Blume itself, the docs source is `apps/docs/content/docs`.) The most relevant pages:
|
||||
|
||||
- `configuration/index.mdx` — every `blume.config.ts` field.
|
||||
- `content/navigation.mdx` — the sidebar/tabs/selectors model.
|
||||
|
||||
@@ -27,7 +27,7 @@ Resolve `$ref` includes first (Mintlify splits config across files). Map only wh
|
||||
| `background.color.{light,dark}` | `theme.background` (`{ light, dark }`) | one field now: `background: { light: …, dark: … }` (a bare string applies to both) |
|
||||
| `background.image` | `theme.backgroundImage` (`{ light, dark }`) | same per-mode shape as `background` |
|
||||
| `background.decoration` | **drop** | no Blume equivalent |
|
||||
| `fonts.family` / `fonts.{heading,body}.family` | `theme.fonts.{display,body}` | only if the family is a curated Google-font slug (kebab-case, e.g. `space-grotesk`); otherwise drop and tell the user to add `@font-face` in `theme.css` |
|
||||
| `fonts.family` / `fonts.{heading,body}.family` | `theme.fonts.{display,body}` | a curated slug (kebab-case, e.g. `space-grotesk`) maps directly; any other Google family maps to the object form `{ name: "Family Name" }`; a self-hosted font (`fonts.*.src` URLs) maps to `{ name, variants: [{ src, weight }] }` after downloading the files into the project |
|
||||
| `banner` | `banner` (`{ content, dismissible, id, link }`) | **only** those keys — drop `banner.color`/`banner.type` |
|
||||
| `styling.latex: true` | **drop the field** — block math `$$…$$` renders in `.mdx` with no config | there is **no** `markdown.math` field; inline `$…$` is **not** supported — convert inline math to `$$…$$` or drop it (report) |
|
||||
| `styling.codeblocks.theme` | `markdown.codeBlocks.theme` (`{ light, dark }`) | |
|
||||
@@ -50,9 +50,9 @@ Mintlify defaults to **FontAwesome**; Blume is **Lucide-only**. Convert every ic
|
||||
**Automate the frontmatter pass — don't hand-edit it.** `scripts/mintlify-codemod.mjs` (in this skill, zero-dependency) remaps every **frontmatter** `icon:` using the table below, drops brand/no-equivalent icons, and — in the same pass — drops/renames unsupported frontmatter keys (see [Frontmatter](#frontmatter)). It's deterministic and idempotent, and reports what it changed per file plus what it couldn't (unknown icons, OpenAPI-stub flags):
|
||||
|
||||
```bash
|
||||
# dry run first (report only), then apply:
|
||||
node <this-skill>/scripts/mintlify-codemod.mjs <content-dir>
|
||||
node <this-skill>/scripts/mintlify-codemod.mjs --write <content-dir>
|
||||
# <skill> = this skill's directory (the one containing SKILL.md); dry run first (report only), then apply:
|
||||
node <skill>/scripts/mintlify-codemod.mjs <content-dir>
|
||||
node <skill>/scripts/mintlify-codemod.mjs --write <content-dir>
|
||||
```
|
||||
|
||||
The codemod touches **only frontmatter**. Icons in MDX **body** (`<Icon icon="…">`, `<Card icon="…">`, nav-group icons in `docs.json`) it does not see — convert those by hand using the same table. Common mappings:
|
||||
@@ -153,4 +153,4 @@ Mintlify serves every top-level dir (e.g. `/images`) at the site root. Blume ser
|
||||
- **`footer.socials`** → suggest the `github` config, or a Footer override.
|
||||
- **Per-language banners** (`navigation.languages[].banner`) → no equivalent.
|
||||
- **Dynamic redirects** (`:slug*`/`:id` params) → can't be static path-to-path; move to host rules (`_redirects`, `vercel.json`).
|
||||
- **`<Update>`** changelog components, `iconType`, `background.decoration`, `search.prompt`, `seo.metatags`, non-curated fonts.
|
||||
- **`<Update>`** changelog components, `iconType`, `background.decoration`, `search.prompt`, `seo.metatags`.
|
||||
@@ -193,7 +193,8 @@ If the repo uses (or the user wants to adopt) **[Ultracite](https://www.ultracit
|
||||
The fix is a committed **pnpm patch** (`patches/oxfmt@0.55.0.patch`), shipped with this skill at `assets/oxfmt@0.55.0.patch`. Unlike the Astro/Vite patch, this one is a **known, deterministic diff** — copy it in, don't regenerate it:
|
||||
|
||||
```bash
|
||||
# 1. Copy the shipped patch into the target repo's patches/ dir (keep the exact filename):
|
||||
# 1. Copy the shipped patch into the target repo's patches/ dir (keep the exact filename;
|
||||
# <skill> = this skill's directory, the one containing SKILL.md):
|
||||
mkdir -p patches
|
||||
cp "<skill>/assets/oxfmt@0.55.0.patch" patches/oxfmt@0.55.0.patch
|
||||
```
|
||||
|
||||
@@ -64,10 +64,12 @@ The Blume CLI discovers your content, builds a content graph, and generates a hi
|
||||
|
||||
## Full documentation
|
||||
|
||||
This is a high-level overview. For complete, authoritative docs — configuration reference, every CLI command and flag, component APIs, content authoring, navigation, search, SEO, AI features, theming, and deployment — read the bundled docs in the installed package:
|
||||
This is a high-level overview. For complete, authoritative docs — configuration reference, every CLI command and flag, component APIs, content authoring, navigation, search, SEO, AI features, theming, and deployment — read the `docs/` directory bundled inside the installed `blume` package.
|
||||
|
||||
```
|
||||
node_modules/blume/docs
|
||||
**Locate the package first — it is not always at the repository root.** In a workspace monorepo (pnpm especially), `blume` is installed in the depending workspace's `node_modules` (e.g. `apps/docs/node_modules/blume/docs`), not the root. From the package that depends on `blume`, this prints the exact location:
|
||||
|
||||
```bash
|
||||
node -e "console.log(require.resolve('blume/package.json'))"
|
||||
```
|
||||
|
||||
Start with `node_modules/blume/docs/index.mdx` (Introduction) and `node_modules/blume/docs/01-quickstart.mdx`, then browse the `configuration/`, `content/`, `reference/`, and `advanced/` sections for specifics.
|
||||
The docs sit in `docs/` next to that `package.json`. Start with `docs/index.mdx` (Introduction) and `docs/01-quickstart.mdx`, then browse the `configuration/`, `content/`, `reference/`, and `advanced/` sections for specifics.
|
||||
@@ -18,7 +18,7 @@ Show this to the user, then immediately proceed to Step 2. The user reads and th
|
||||
|
||||
### 2. Spawn sub-agents
|
||||
|
||||
Spawn 3+ sub-agents in parallel using the Agent tool. Each must produce a **radically different** interface for the deepened module.
|
||||
Spawn 3+ sub-agents in parallel. Each must produce a **radically different** interface for the deepened module.
|
||||
|
||||
Prompt each sub-agent with a separate technical brief (file paths, coupling details, dependency category from [DEEPENING.md](DEEPENING.md), what sits behind the seam). The brief is independent of the user-facing problem-space explanation in Step 1. Give each agent a different design constraint:
|
||||
|
||||
|
||||
@@ -9,6 +9,12 @@ A discipline for hard bugs. Skip phases only when explicitly justified.
|
||||
|
||||
When exploring the codebase, read `CONTEXT.md` (if it exists) to get a clear mental model of the relevant modules, and check ADRs in the area you're touching.
|
||||
|
||||
## Redact
|
||||
|
||||
This skill has you show commands, outputs and captured artifacts. **Redact every secret first** — write `<REDACTED>` in its place. Build loops against env vars, so the credential stays in the environment rather than in what you show. Captured artifacts carry auth headers: quote only the lines that carry the signal.
|
||||
|
||||
If the redacted output is not enough to diagnose the bug, say so and ask the user.
|
||||
|
||||
## Phase 1 — Build a feedback loop
|
||||
|
||||
**This is the skill.** Everything else is mechanical. If you have a **tight** pass/fail signal for the bug — one that goes red on _this_ bug — you will find the cause; bisection, hypothesis-testing, and instrumentation all just consume it. If you don't have one, no amount of staring at code will save you.
|
||||
@@ -46,11 +52,11 @@ The goal is not a clean repro but a **higher reproduction rate**. Loop the trigg
|
||||
|
||||
### When you genuinely cannot build a loop
|
||||
|
||||
Stop and say so explicitly. List what you tried. Ask the user for: (a) access to whatever environment reproduces it, (b) a captured artifact (HAR file, log dump, core dump, screen recording with timestamps), or (c) permission to add temporary production instrumentation. Do **not** proceed to hypothesise without a loop.
|
||||
Stop and say so explicitly. List what you tried. Ask the user for: (a) access to whatever environment reproduces it, (b) a redacted captured artifact (HAR file, log dump, core dump, screen recording with timestamps), or (c) permission to add temporary production instrumentation. Do **not** proceed to hypothesise without a loop.
|
||||
|
||||
### Completion criterion — a tight loop that goes red
|
||||
|
||||
Phase 1 is done when the loop is **tight** and **red-capable**: you can name **one command** — a script path, a test invocation, a curl — that you have **already run at least once** (paste the invocation and its output), and that is:
|
||||
Phase 1 is done when the loop is **tight** and **red-capable**: you can name **one command** — a script path, a test invocation, a curl — that you have **already run at least once** (show the invocation and its output, redacted), and that is:
|
||||
|
||||
- [ ] **Red-capable** — it drives the actual bug code path and asserts the **user's exact symptom**, so it can go red on this bug and green once fixed. Not "runs without erroring" — it must be able to _catch this specific bug_.
|
||||
- [ ] **Deterministic** — same verdict every run (flaky bugs: a pinned, high reproduction rate, per above).
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
# capture VAR "<question>" → show question, read response into VAR
|
||||
#
|
||||
# At the end, captured values are printed as KEY=VALUE for the agent to parse.
|
||||
#
|
||||
# `capture` prints its value back to the terminal, where the agent reads it — so
|
||||
# capture observations, and leave signing in to the user as a `step`.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
---
|
||||
name: grilling
|
||||
description: Grill the user relentlessly about a plan or design. Use when the user wants to stress-test a plan before building, or uses any 'grill' trigger phrases.
|
||||
description: Grill the user relentlessly about a plan, decision, or idea. Use when the user wants to stress-test their thinking, or uses any 'grill' trigger phrases.
|
||||
---
|
||||
|
||||
Interview me relentlessly about every aspect of this plan until we reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one-by-one. For each question, provide your recommended answer.
|
||||
Interview the user relentlessly until you reach a shared understanding. Map this as a **design tree**: every decision branches into the decisions that hang off it.
|
||||
|
||||
Ask the questions one at a time, waiting for feedback on each question before continuing. Asking multiple questions at once is bewildering.
|
||||
Work the tree in **rounds**. The **frontier** is every decision whose prerequisites are already settled — the questions you can ask _now_ without guessing at answers you haven't heard yet. Ask the whole frontier in one round: number each question and give your recommended answer. Then wait for the user's answers before the next round.
|
||||
|
||||
If a *fact* can be found by exploring the codebase, look it up rather than asking me. The *decisions*, though, are mine — put each one to me and wait for my answer.
|
||||
Each question should be formatted like so:
|
||||
|
||||
Do not enact the plan until I confirm we have reached a shared understanding.
|
||||
```
|
||||
❓ **Q1** - **<question title>**: <question body, might be multiple paragraphs, including multiple choices>
|
||||
|
||||
➡️ <your recommended answer>
|
||||
```
|
||||
|
||||
Each round the user answers reshapes the tree — settled decisions push the frontier outward and unblock questions that depended on them. Recompute the frontier and ask the next round. A question whose answer depends on another question still open in this round belongs to a _later_ round, not this one.
|
||||
|
||||
Finding _facts_ is your job, never the user's. When a frontier question needs a fact from the environment (filesystem, tools, etc.), dispatch a sub-agent to find it — don't ask the user for anything you could look up yourself. Don't block on it: a running exploration is an unsettled prerequisite, so only the questions downstream of it wait for the sub-agent to report — ask the rest of the frontier now. The _decisions_ are the user's — put each to them and wait.
|
||||
|
||||
The session is done when the frontier is empty: every branch of the design tree visited, nothing left silently assumed. Do not act on it until the user confirms you have reached a shared understanding.
|
||||
@@ -17,9 +17,14 @@ This command is _informed_ by the project's domain model and built on a shared d
|
||||
|
||||
### 1. Explore
|
||||
|
||||
**Scope before you scan — YAGNI.** Deepening a module pays off by making future changes to it easier, so put extra weight on the parts of the codebase that have recently changed. Decide *where* to look before you look:
|
||||
|
||||
- If the user named a direction — a module, a subsystem, a pain point — take it, and skip the inference below.
|
||||
- Otherwise, walk back a good stretch of the commit history (`git log --oneline`) to find the codebase's hot spots — the files and areas that keep coming up — and let those paths pull your attention first. If the changes are scattered with no clear hot spot, widen the net.
|
||||
|
||||
Read the project's domain glossary (`CONTEXT.md`) and any ADRs in the area you're touching first.
|
||||
|
||||
Then use the Agent tool with `subagent_type=Explore` to walk the codebase. Don't follow rigid heuristics — explore organically and note where you experience friction:
|
||||
Then spawn a sub-agent to walk the codebase. Don't follow rigid heuristics — explore organically and note where you experience friction:
|
||||
|
||||
- Where does understanding one concept require bouncing between many small modules?
|
||||
- Where are modules **shallow** — interface nearly as complex as the implementation?
|
||||
@@ -56,7 +61,7 @@ Do NOT propose interfaces yet. After the file is written, ask the user: "Which o
|
||||
|
||||
### 3. Grilling loop
|
||||
|
||||
Once the user picks a candidate, run the `/grilling` skill to walk the design tree with them — constraints, dependencies, the shape of the deepened module, what sits behind the seam, what tests survive.
|
||||
Once the user picks a candidate, run the `/grilling` skill to walk the decision tree with them — constraints, dependencies, the shape of the deepened module, what sits behind the seam, what tests survive.
|
||||
|
||||
Side effects happen inline as decisions crystallize — run the `/domain-modeling` skill to keep the domain model current as you go:
|
||||
|
||||
|
||||
@@ -25,6 +25,70 @@ npx @schpet/linear-cli --version
|
||||
All subsequent commands can be prefixed with `npx @schpet/linear-cli` in place of `linear`. Otherwise, follow the install instructions at:\
|
||||
https://github.com/schpet/linear-cli?tab=readme-ov-file#install
|
||||
|
||||
## Common Tasks
|
||||
|
||||
Copy-pasteable recipes for the most frequent flows. Prefer these dedicated commands over `linear api` — reach for the GraphQL fallback only when no dedicated command or flag covers the operation.
|
||||
|
||||
### Query issues with filters
|
||||
|
||||
`issue query` searches across all assignees and supports structured filters that can be combined:
|
||||
|
||||
```bash
|
||||
linear issue query --team ENG --state started --json
|
||||
linear issue query --project "Mobile App" --state backlog --state triage --unassigned
|
||||
linear issue query --assignee sam --label bug --updated-after 2026-01-01
|
||||
```
|
||||
|
||||
Note: `linear issue list` is an alias of `issue mine` and only shows _your_ issues — use `issue query` for anything scoped to other people or a whole team/project.
|
||||
|
||||
### List my issues
|
||||
|
||||
```bash
|
||||
linear issue mine --state started --sort priority
|
||||
```
|
||||
|
||||
### Create an issue
|
||||
|
||||
```bash
|
||||
linear issue create --team ENG --title "Fix login redirect" \
|
||||
--description-file ./description.md --no-interactive
|
||||
```
|
||||
|
||||
Write multi-line markdown to a file and pass `--description-file` (see the markdown section below); `--no-interactive` avoids prompts in scripted use.
|
||||
|
||||
### Update an issue's state, assignee, or labels
|
||||
|
||||
```bash
|
||||
linear issue update ENG-123 --state "In Review" --assignee sam
|
||||
linear issue update ENG-123 --unassign
|
||||
linear issue update ENG-123 --add-label security # add, keep existing labels
|
||||
linear issue update ENG-123 --remove-label sprint-42 # detach; does not delete the label
|
||||
linear issue update ENG-123 --remove-label sprint-42 --add-label sprint-43 # atomic swap
|
||||
linear issue update ENG-123 --label infra --label security # replaces the label set
|
||||
```
|
||||
|
||||
### Add a comment
|
||||
|
||||
```bash
|
||||
linear issue comment add ENG-123 --body-file ./comment.md
|
||||
```
|
||||
|
||||
### Attach an image or screenshot so it is visible inline
|
||||
|
||||
```bash
|
||||
linear issue comment add ENG-123 --attach ./screenshot.png
|
||||
```
|
||||
|
||||
This uploads the image and embeds it in a comment, where Linear renders it inline. Do not use `linear issue attach` when the image must be visible: that command creates a sidebar link attachment and does not render images inline.
|
||||
|
||||
### View an issue / get its URL
|
||||
|
||||
```bash
|
||||
linear issue view ENG-123 # details incl. comments
|
||||
linear issue view ENG-123 --json # structured output
|
||||
linear issue url ENG-123 # print just the URL
|
||||
```
|
||||
|
||||
## Best Practices for Markdown Content
|
||||
|
||||
When working with issue descriptions or comment bodies that contain markdown, **always prefer using file-based flags** instead of passing content as command-line arguments:
|
||||
@@ -68,124 +132,129 @@ linear issue comment add ENG-123 --body-file /tmp/comment.md
|
||||
Compact command list, generated from `linear --help`:
|
||||
|
||||
```bash
|
||||
linear api
|
||||
|
||||
linear auth
|
||||
linear auth default
|
||||
linear auth list
|
||||
linear auth login
|
||||
linear auth logout
|
||||
linear auth list
|
||||
linear auth default
|
||||
linear auth migrate
|
||||
linear auth token
|
||||
linear auth whoami
|
||||
linear auth migrate
|
||||
|
||||
linear issue
|
||||
linear issue id
|
||||
linear issue mine
|
||||
linear issue query
|
||||
linear issue title
|
||||
linear issue start
|
||||
linear issue view
|
||||
linear issue url
|
||||
linear issue describe
|
||||
linear issue commits
|
||||
linear issue pull-request
|
||||
linear issue delete
|
||||
linear issue create
|
||||
linear issue update
|
||||
linear issue comment
|
||||
linear issue comment add
|
||||
linear issue comment delete
|
||||
linear issue comment update
|
||||
linear issue comment list
|
||||
linear issue attach
|
||||
linear issue link
|
||||
linear issue relation
|
||||
linear issue relation add
|
||||
linear issue relation delete
|
||||
linear issue relation list
|
||||
linear issue agent-session
|
||||
linear issue agent-session list
|
||||
linear issue agent-session view
|
||||
|
||||
linear team
|
||||
linear team create
|
||||
linear team delete
|
||||
linear team list
|
||||
linear team id
|
||||
linear team autolinks
|
||||
linear team members
|
||||
|
||||
linear project
|
||||
linear project list
|
||||
linear project view
|
||||
linear project create
|
||||
linear project update
|
||||
linear project delete
|
||||
|
||||
linear project-update
|
||||
linear project-update create
|
||||
linear project-update list
|
||||
linear config
|
||||
|
||||
linear cycle
|
||||
linear cycle list
|
||||
linear cycle view
|
||||
|
||||
linear milestone
|
||||
linear milestone list
|
||||
linear milestone view
|
||||
linear milestone create
|
||||
linear milestone update
|
||||
linear milestone delete
|
||||
linear document
|
||||
linear document create
|
||||
linear document delete
|
||||
linear document list
|
||||
linear document update
|
||||
linear document view
|
||||
|
||||
linear initiative
|
||||
linear initiative list
|
||||
linear initiative view
|
||||
linear initiative create
|
||||
linear initiative archive
|
||||
linear initiative update
|
||||
linear initiative unarchive
|
||||
linear initiative delete
|
||||
linear initiative add-project
|
||||
linear initiative archive
|
||||
linear initiative create
|
||||
linear initiative delete
|
||||
linear initiative list
|
||||
linear initiative remove-project
|
||||
linear initiative unarchive
|
||||
linear initiative update
|
||||
linear initiative view
|
||||
|
||||
linear initiative-update
|
||||
linear initiative-update create
|
||||
linear initiative-update list
|
||||
|
||||
linear issue
|
||||
linear issue agent-session
|
||||
linear issue agent-session list
|
||||
linear issue agent-session view
|
||||
linear issue attach
|
||||
linear issue comment
|
||||
linear issue comment add
|
||||
linear issue comment delete
|
||||
linear issue comment list
|
||||
linear issue comment update
|
||||
linear issue commits
|
||||
linear issue create
|
||||
linear issue delete
|
||||
linear issue describe
|
||||
linear issue id
|
||||
linear issue link
|
||||
linear issue mine
|
||||
linear issue pull-request
|
||||
linear issue query
|
||||
linear issue relation
|
||||
linear issue relation add
|
||||
linear issue relation delete
|
||||
linear issue relation list
|
||||
linear issue start
|
||||
linear issue title
|
||||
linear issue update
|
||||
linear issue url
|
||||
linear issue view
|
||||
|
||||
linear label
|
||||
linear label list
|
||||
linear label create
|
||||
linear label delete
|
||||
linear label list
|
||||
|
||||
linear document
|
||||
linear document list
|
||||
linear document view
|
||||
linear document create
|
||||
linear document update
|
||||
linear document delete
|
||||
linear milestone
|
||||
linear milestone create
|
||||
linear milestone delete
|
||||
linear milestone list
|
||||
linear milestone update
|
||||
linear milestone view
|
||||
|
||||
linear config
|
||||
linear project
|
||||
linear project create
|
||||
linear project delete
|
||||
linear project list
|
||||
linear project update
|
||||
linear project view
|
||||
|
||||
linear project-update
|
||||
linear project-update create
|
||||
linear project-update list
|
||||
|
||||
linear schema
|
||||
|
||||
linear api
|
||||
linear team
|
||||
linear team autolinks
|
||||
linear team create
|
||||
linear team delete
|
||||
linear team id
|
||||
linear team list
|
||||
linear team members
|
||||
linear team states
|
||||
|
||||
linear user
|
||||
linear user list
|
||||
```
|
||||
|
||||
## Reference Documentation
|
||||
|
||||
- [api](references/api.md) - Make a raw GraphQL API request
|
||||
- [auth](references/auth.md) - Manage Linear authentication
|
||||
- [issue](references/issue.md) - Manage Linear issues
|
||||
- [team](references/team.md) - Manage Linear teams
|
||||
- [project](references/project.md) - Manage Linear projects
|
||||
- [project-update](references/project-update.md) - Manage project status updates
|
||||
- [config](references/config.md) - Interactively generate .linear.toml configuration
|
||||
- [cycle](references/cycle.md) - Manage Linear team cycles
|
||||
- [milestone](references/milestone.md) - Manage Linear project milestones
|
||||
- [document](references/document.md) - Manage Linear documents
|
||||
- [initiative](references/initiative.md) - Manage Linear initiatives
|
||||
- [initiative-update](references/initiative-update.md) - Manage initiative status updates (timeline posts)
|
||||
- [issue](references/issue.md) - Manage Linear issues
|
||||
- [label](references/label.md) - Manage Linear issue labels
|
||||
- [document](references/document.md) - Manage Linear documents
|
||||
- [config](references/config.md) - Interactively generate .linear.toml configuration
|
||||
- [milestone](references/milestone.md) - Manage Linear project milestones
|
||||
- [project](references/project.md) - Manage Linear projects
|
||||
- [project-update](references/project-update.md) - Manage project status updates
|
||||
- [schema](references/schema.md) - Print the GraphQL schema to stdout
|
||||
- [api](references/api.md) - Make a raw GraphQL API request
|
||||
- [team](references/team.md) - Manage Linear teams
|
||||
- [user](references/user.md) - Manage Linear users
|
||||
|
||||
For curated examples of organization features (initiatives, labels, projects, bulk operations), see [organization-features](references/organization-features.md).
|
||||
|
||||
@@ -204,7 +273,7 @@ Each command has detailed help output describing all available flags and options
|
||||
|
||||
Some commands have required flags that aren't obvious. Notable examples:
|
||||
|
||||
- `issue list` requires a sort order — provide it via `--sort` (valid values: `manual`, `priority`), the `issue_sort` config option, or the `LINEAR_ISSUE_SORT` env var. Also requires `--team <key>` unless the team can be inferred from the directory — if unknown, run `linear team list` first.
|
||||
- `issue list` sorts by priority by default — override via `--sort` (valid values: `manual`, `priority`), the `issue_sort` config option, or the `LINEAR_ISSUE_SORT` env var. Requires `--team <key>` unless the team can be inferred from the directory — if unknown, run `linear team list` first.
|
||||
- `--no-pager` is only supported on `issue list` — passing it to other commands like `project list` will error.
|
||||
|
||||
## Using the Linear GraphQL API Directly
|
||||
|
||||
@@ -25,6 +25,70 @@ npx @schpet/linear-cli --version
|
||||
All subsequent commands can be prefixed with `npx @schpet/linear-cli` in place of `linear`. Otherwise, follow the install instructions at:\
|
||||
https://github.com/schpet/linear-cli?tab=readme-ov-file#install
|
||||
|
||||
## Common Tasks
|
||||
|
||||
Copy-pasteable recipes for the most frequent flows. Prefer these dedicated commands over `linear api` — reach for the GraphQL fallback only when no dedicated command or flag covers the operation.
|
||||
|
||||
### Query issues with filters
|
||||
|
||||
`issue query` searches across all assignees and supports structured filters that can be combined:
|
||||
|
||||
```bash
|
||||
linear issue query --team ENG --state started --json
|
||||
linear issue query --project "Mobile App" --state backlog --state triage --unassigned
|
||||
linear issue query --assignee sam --label bug --updated-after 2026-01-01
|
||||
```
|
||||
|
||||
Note: `linear issue list` is an alias of `issue mine` and only shows _your_ issues — use `issue query` for anything scoped to other people or a whole team/project.
|
||||
|
||||
### List my issues
|
||||
|
||||
```bash
|
||||
linear issue mine --state started --sort priority
|
||||
```
|
||||
|
||||
### Create an issue
|
||||
|
||||
```bash
|
||||
linear issue create --team ENG --title "Fix login redirect" \
|
||||
--description-file ./description.md --no-interactive
|
||||
```
|
||||
|
||||
Write multi-line markdown to a file and pass `--description-file` (see the markdown section below); `--no-interactive` avoids prompts in scripted use.
|
||||
|
||||
### Update an issue's state, assignee, or labels
|
||||
|
||||
```bash
|
||||
linear issue update ENG-123 --state "In Review" --assignee sam
|
||||
linear issue update ENG-123 --unassign
|
||||
linear issue update ENG-123 --add-label security # add, keep existing labels
|
||||
linear issue update ENG-123 --remove-label sprint-42 # detach; does not delete the label
|
||||
linear issue update ENG-123 --remove-label sprint-42 --add-label sprint-43 # atomic swap
|
||||
linear issue update ENG-123 --label infra --label security # replaces the label set
|
||||
```
|
||||
|
||||
### Add a comment
|
||||
|
||||
```bash
|
||||
linear issue comment add ENG-123 --body-file ./comment.md
|
||||
```
|
||||
|
||||
### Attach an image or screenshot so it is visible inline
|
||||
|
||||
```bash
|
||||
linear issue comment add ENG-123 --attach ./screenshot.png
|
||||
```
|
||||
|
||||
This uploads the image and embeds it in a comment, where Linear renders it inline. Do not use `linear issue attach` when the image must be visible: that command creates a sidebar link attachment and does not render images inline.
|
||||
|
||||
### View an issue / get its URL
|
||||
|
||||
```bash
|
||||
linear issue view ENG-123 # details incl. comments
|
||||
linear issue view ENG-123 --json # structured output
|
||||
linear issue url ENG-123 # print just the URL
|
||||
```
|
||||
|
||||
## Best Practices for Markdown Content
|
||||
|
||||
When working with issue descriptions or comment bodies that contain markdown, **always prefer using file-based flags** instead of passing content as command-line arguments:
|
||||
@@ -92,7 +156,7 @@ Each command has detailed help output describing all available flags and options
|
||||
|
||||
Some commands have required flags that aren't obvious. Notable examples:
|
||||
|
||||
- `issue list` requires a sort order — provide it via `--sort` (valid values: `manual`, `priority`), the `issue_sort` config option, or the `LINEAR_ISSUE_SORT` env var. Also requires `--team <key>` unless the team can be inferred from the directory — if unknown, run `linear team list` first.
|
||||
- `issue list` sorts by priority by default — override via `--sort` (valid values: `manual`, `priority`), the `issue_sort` config option, or the `LINEAR_ISSUE_SORT` env var. Requires `--team <key>` unless the team can be inferred from the directory — if unknown, run `linear team list` first.
|
||||
- `--no-pager` is only supported on `issue list` — passing it to other commands like `project list` will error.
|
||||
|
||||
## Using the Linear GraphQL API Directly
|
||||
|
||||
@@ -29,6 +29,40 @@ Commands:
|
||||
|
||||
## Subcommands
|
||||
|
||||
### default
|
||||
|
||||
> Set the default workspace
|
||||
|
||||
```
|
||||
Usage: linear auth default [workspace]
|
||||
|
||||
Description:
|
||||
|
||||
Set the default workspace
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
```
|
||||
|
||||
### list
|
||||
|
||||
> List configured workspaces
|
||||
|
||||
```
|
||||
Usage: linear auth list
|
||||
|
||||
Description:
|
||||
|
||||
List configured workspaces
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
```
|
||||
|
||||
### login
|
||||
|
||||
> Add a workspace credential
|
||||
@@ -66,33 +100,16 @@ Options:
|
||||
-f, --force - Skip confirmation prompt
|
||||
```
|
||||
|
||||
### list
|
||||
### migrate
|
||||
|
||||
> List configured workspaces
|
||||
> Migrate plaintext credentials to system keyring
|
||||
|
||||
```
|
||||
Usage: linear auth list
|
||||
Usage: linear auth migrate
|
||||
|
||||
Description:
|
||||
|
||||
List configured workspaces
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
```
|
||||
|
||||
### default
|
||||
|
||||
> Set the default workspace
|
||||
|
||||
```
|
||||
Usage: linear auth default [workspace]
|
||||
|
||||
Description:
|
||||
|
||||
Set the default workspace
|
||||
Migrate plaintext credentials to system keyring
|
||||
|
||||
Options:
|
||||
|
||||
@@ -133,20 +150,3 @@ Options:
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
```
|
||||
|
||||
### migrate
|
||||
|
||||
> Migrate plaintext credentials to system keyring
|
||||
|
||||
```
|
||||
Usage: linear auth migrate
|
||||
|
||||
Description:
|
||||
|
||||
Migrate plaintext credentials to system keyring
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
```
|
||||
@@ -2,20 +2,21 @@
|
||||
|
||||
## Commands
|
||||
|
||||
- [api](./api.md) - Make a raw GraphQL API request
|
||||
- [auth](./auth.md) - Manage Linear authentication
|
||||
- [issue](./issue.md) - Manage Linear issues
|
||||
- [team](./team.md) - Manage Linear teams
|
||||
- [project](./project.md) - Manage Linear projects
|
||||
- [project-update](./project-update.md) - Manage project status updates
|
||||
- [config](./config.md) - Interactively generate .linear.toml configuration
|
||||
- [cycle](./cycle.md) - Manage Linear team cycles
|
||||
- [milestone](./milestone.md) - Manage Linear project milestones
|
||||
- [document](./document.md) - Manage Linear documents
|
||||
- [initiative](./initiative.md) - Manage Linear initiatives
|
||||
- [initiative-update](./initiative-update.md) - Manage initiative status updates (timeline posts)
|
||||
- [issue](./issue.md) - Manage Linear issues
|
||||
- [label](./label.md) - Manage Linear issue labels
|
||||
- [document](./document.md) - Manage Linear documents
|
||||
- [config](./config.md) - Interactively generate .linear.toml configuration
|
||||
- [milestone](./milestone.md) - Manage Linear project milestones
|
||||
- [project](./project.md) - Manage Linear projects
|
||||
- [project-update](./project-update.md) - Manage project status updates
|
||||
- [schema](./schema.md) - Print the GraphQL schema to stdout
|
||||
- [api](./api.md) - Make a raw GraphQL API request
|
||||
- [team](./team.md) - Manage Linear teams
|
||||
- [user](./user.md) - Manage Linear users
|
||||
|
||||
## Quick Reference
|
||||
|
||||
|
||||
@@ -27,48 +27,6 @@ Commands:
|
||||
|
||||
## Subcommands
|
||||
|
||||
### list
|
||||
|
||||
> List documents
|
||||
|
||||
```
|
||||
Usage: linear document list
|
||||
|
||||
Description:
|
||||
|
||||
List documents
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
--project <project> - Filter by project (slug or name)
|
||||
--issue <issue> - Filter by issue (identifier like TC-123)
|
||||
--json - Output as JSON
|
||||
--limit <limit> - Limit results (Default: 50)
|
||||
```
|
||||
|
||||
### view
|
||||
|
||||
> View a document's content
|
||||
|
||||
```
|
||||
Usage: linear document view <id>
|
||||
|
||||
Description:
|
||||
|
||||
View a document's content
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
--raw - Output raw markdown without rendering
|
||||
-w, --web - Open document in browser
|
||||
--json - Output full document as JSON
|
||||
--no-download - Keep remote URLs instead of downloading files
|
||||
```
|
||||
|
||||
### create
|
||||
|
||||
> Create a new document
|
||||
@@ -82,40 +40,17 @@ Description:
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-t, --title <title> - Document title (required)
|
||||
-c, --content <content> - Markdown content (inline)
|
||||
-f, --content-file <path> - Read content from file
|
||||
--project <project> - Attach to project (slug or ID)
|
||||
--issue <issue> - Attach to issue (identifier like TC-123)
|
||||
--icon <icon> - Document icon (emoji)
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-t, --title <title> - Document title (required)
|
||||
-c, --content <content> - Markdown content (inline)
|
||||
-f, --content-file <path> - Read content from file
|
||||
--project <project> - Attach to project (UUID, slug ID, or name)
|
||||
--issue <issue> - Attach to issue (identifier like TC-123)
|
||||
--icon <icon> - Document icon (emoji)
|
||||
-i, --interactive - Interactive mode with prompts
|
||||
```
|
||||
|
||||
### update
|
||||
|
||||
> Update an existing document
|
||||
|
||||
```
|
||||
Usage: linear document update <documentId>
|
||||
|
||||
Description:
|
||||
|
||||
Update an existing document
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-t, --title <title> - New title for the document
|
||||
-c, --content <content> - New markdown content (inline)
|
||||
-f, --content-file <path> - Read new content from file
|
||||
--icon <icon> - New icon (emoji)
|
||||
-e, --edit - Open current content in $EDITOR for editing
|
||||
--force - Update content even when document comments may lose inline anchors
|
||||
```
|
||||
|
||||
### delete
|
||||
|
||||
> Delete a document (moves to trash)
|
||||
@@ -136,3 +71,69 @@ Options:
|
||||
--bulk-file <file> - Read document slugs/IDs from a file (one per line)
|
||||
--bulk-stdin - Read document slugs/IDs from stdin
|
||||
```
|
||||
|
||||
### list
|
||||
|
||||
> List documents
|
||||
|
||||
```
|
||||
Usage: linear document list
|
||||
|
||||
Description:
|
||||
|
||||
List documents
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
--project <project> - Filter by project (slug or name)
|
||||
--issue <issue> - Filter by issue (identifier like TC-123)
|
||||
--json - Output as JSON
|
||||
--limit <limit> - Limit results (Default: 50)
|
||||
```
|
||||
|
||||
### update
|
||||
|
||||
> Update an existing document
|
||||
|
||||
```
|
||||
Usage: linear document update <documentId>
|
||||
|
||||
Description:
|
||||
|
||||
Update an existing document
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-t, --title <title> - New title for the document
|
||||
-c, --content <content> - New markdown content (inline)
|
||||
-f, --content-file <path> - Read new content from file
|
||||
--icon <icon> - New icon (emoji)
|
||||
--project <project> - Attach to project (UUID, slug ID, or name)
|
||||
-e, --edit - Open current content in $EDITOR for editing
|
||||
--force - Update content even when document comments may lose inline anchors
|
||||
```
|
||||
|
||||
### view
|
||||
|
||||
> View a document's content
|
||||
|
||||
```
|
||||
Usage: linear document view <id>
|
||||
|
||||
Description:
|
||||
|
||||
View a document's content
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
--raw - Output raw markdown without rendering
|
||||
-w, --web - Open document in browser
|
||||
--json - Output full document as JSON
|
||||
--no-download - Keep remote URLs instead of downloading files
|
||||
```
|
||||
@@ -31,48 +31,43 @@ Commands:
|
||||
|
||||
## Subcommands
|
||||
|
||||
### list
|
||||
### add-project
|
||||
|
||||
> List initiatives
|
||||
> Link a project to an initiative
|
||||
|
||||
```
|
||||
Usage: linear initiative list
|
||||
Usage: linear initiative add-project <initiative> <project>
|
||||
|
||||
Description:
|
||||
|
||||
List initiatives
|
||||
Link a project to an initiative
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-s, --status <status> - Filter by status (active, planned, completed)
|
||||
--all-statuses - Show all statuses (default: active only)
|
||||
-o, --owner <owner> - Filter by owner (username or email)
|
||||
-w, --web - Open initiatives page in web browser
|
||||
-a, --app - Open initiatives page in Linear.app
|
||||
-j, --json - Output as JSON
|
||||
--archived - Include archived initiatives
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
--sort-order <sortOrder> - Sort order within initiative
|
||||
```
|
||||
|
||||
### view
|
||||
### archive
|
||||
|
||||
> View initiative details
|
||||
> Archive a Linear initiative
|
||||
|
||||
```
|
||||
Usage: linear initiative view <initiativeId>
|
||||
Usage: linear initiative archive [initiativeId]
|
||||
|
||||
Description:
|
||||
|
||||
View initiative details
|
||||
Archive a Linear initiative
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-w, --web - Open in web browser
|
||||
-a, --app - Open in Linear.app
|
||||
-j, --json - Output as JSON
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-y, --force - Skip confirmation prompt
|
||||
--bulk <ids...> - Archive multiple initiatives by ID, slug, or name
|
||||
--bulk-file <file> - Read initiative IDs from a file (one per line)
|
||||
--bulk-stdin - Read initiative IDs from stdin
|
||||
```
|
||||
|
||||
### create
|
||||
@@ -100,27 +95,87 @@ Options:
|
||||
-i, --interactive - Interactive mode (default if no flags provided)
|
||||
```
|
||||
|
||||
### archive
|
||||
### delete
|
||||
|
||||
> Archive a Linear initiative
|
||||
> Permanently delete a Linear initiative
|
||||
|
||||
```
|
||||
Usage: linear initiative archive [initiativeId]
|
||||
Usage: linear initiative delete [initiativeId]
|
||||
|
||||
Description:
|
||||
|
||||
Archive a Linear initiative
|
||||
Permanently delete a Linear initiative
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-y, --force - Skip confirmation prompt
|
||||
--bulk <ids...> - Archive multiple initiatives by ID, slug, or name
|
||||
--bulk-file <file> - Read initiative IDs from a file (one per line)
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-y, --force - Skip confirmation prompt
|
||||
--bulk <ids...> - Delete multiple initiatives by ID, slug, or name
|
||||
--bulk-file <file> - Read initiative IDs from a file (one per line)
|
||||
--bulk-stdin - Read initiative IDs from stdin
|
||||
```
|
||||
|
||||
### list
|
||||
|
||||
> List initiatives
|
||||
|
||||
```
|
||||
Usage: linear initiative list
|
||||
|
||||
Description:
|
||||
|
||||
List initiatives
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-s, --status <status> - Filter by status (active, planned, completed)
|
||||
--all-statuses - Show all statuses (default: active only)
|
||||
-o, --owner <owner> - Filter by owner (username or email)
|
||||
-w, --web - Open initiatives page in web browser
|
||||
-a, --app - Open initiatives page in Linear.app
|
||||
-j, --json - Output as JSON
|
||||
--archived - Include archived initiatives
|
||||
```
|
||||
|
||||
### remove-project
|
||||
|
||||
> Unlink a project from an initiative
|
||||
|
||||
```
|
||||
Usage: linear initiative remove-project <initiative> <project>
|
||||
|
||||
Description:
|
||||
|
||||
Unlink a project from an initiative
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-y, --force - Skip confirmation prompt
|
||||
```
|
||||
|
||||
### unarchive
|
||||
|
||||
> Unarchive a Linear initiative
|
||||
|
||||
```
|
||||
Usage: linear initiative unarchive <initiativeId>
|
||||
|
||||
Description:
|
||||
|
||||
Unarchive a Linear initiative
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-y, --force - Skip confirmation prompt
|
||||
```
|
||||
|
||||
### update
|
||||
|
||||
> Update a Linear initiative
|
||||
@@ -146,77 +201,22 @@ Options:
|
||||
-i, --interactive - Interactive mode for updates
|
||||
```
|
||||
|
||||
### unarchive
|
||||
### view
|
||||
|
||||
> Unarchive a Linear initiative
|
||||
> View initiative details
|
||||
|
||||
```
|
||||
Usage: linear initiative unarchive <initiativeId>
|
||||
Usage: linear initiative view <initiativeId>
|
||||
|
||||
Description:
|
||||
|
||||
Unarchive a Linear initiative
|
||||
View initiative details
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-y, --force - Skip confirmation prompt
|
||||
```
|
||||
|
||||
### delete
|
||||
|
||||
> Permanently delete a Linear initiative
|
||||
|
||||
```
|
||||
Usage: linear initiative delete [initiativeId]
|
||||
|
||||
Description:
|
||||
|
||||
Permanently delete a Linear initiative
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-y, --force - Skip confirmation prompt
|
||||
--bulk <ids...> - Delete multiple initiatives by ID, slug, or name
|
||||
--bulk-file <file> - Read initiative IDs from a file (one per line)
|
||||
--bulk-stdin - Read initiative IDs from stdin
|
||||
```
|
||||
|
||||
### add-project
|
||||
|
||||
> Link a project to an initiative
|
||||
|
||||
```
|
||||
Usage: linear initiative add-project <initiative> <project>
|
||||
|
||||
Description:
|
||||
|
||||
Link a project to an initiative
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
--sort-order <sortOrder> - Sort order within initiative
|
||||
```
|
||||
|
||||
### remove-project
|
||||
|
||||
> Unlink a project from an initiative
|
||||
|
||||
```
|
||||
Usage: linear initiative remove-project <initiative> <project>
|
||||
|
||||
Description:
|
||||
|
||||
Unlink a project from an initiative
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-y, --force - Skip confirmation prompt
|
||||
-w, --web - Open in web browser
|
||||
-a, --app - Open in Linear.app
|
||||
-j, --json - Output as JSON
|
||||
```
|
||||
@@ -18,28 +18,294 @@ Options:
|
||||
|
||||
Commands:
|
||||
|
||||
id - Print the issue based on the current git branch
|
||||
mine, list, l - List your issues
|
||||
query, q - Query issues with structured filters
|
||||
title [issueId] - Print the issue title
|
||||
start [issueId] - Start working on an issue
|
||||
view, v [issueId] - View issue details (default) or open in browser/app
|
||||
url [issueId] - Print the issue URL
|
||||
describe [issueId] - Print the issue title and Linear-issue trailer
|
||||
commits [issueId] - Show all commits for a Linear issue (jj only)
|
||||
pull-request, pr [issueId] - Create a GitHub pull request with issue details
|
||||
delete, d [issueId] - Delete an issue
|
||||
create - Create a linear issue
|
||||
update [issueId] - Update a linear issue
|
||||
comment - Manage issue comments
|
||||
attach <issueId> <filepath> - Attach a file to an issue
|
||||
link <urlOrIssueId> [url] - Link a URL to an issue
|
||||
relation - Manage issue relations (dependencies)
|
||||
id - Print the issue based on the current git branch
|
||||
mine, list, l - List your issues
|
||||
query, q - Query issues with structured filters
|
||||
title [issueId] - Print the issue title
|
||||
start [issueId] - Start working on an issue
|
||||
view, v [issueId] - View issue details (default) or open in browser/app
|
||||
url [issueId] - Print the issue URL
|
||||
describe [issueId] - Print the issue title and Linear-issue trailer
|
||||
commits [issueId] - Show all commits for a Linear issue (jj only)
|
||||
pull-request, pr [issueId] - Create a GitHub pull request with issue details
|
||||
delete, d [issueId] - Delete an issue
|
||||
create - Create a linear issue
|
||||
update [issueId] - Update a linear issue
|
||||
comment - Manage issue comments
|
||||
attach <issueId> <filepath> - Create a sidebar link attachment on an issue (images do not render inline)
|
||||
link <urlOrIssueId> [url] - Link a URL to an issue
|
||||
relation - Manage issue relations (dependencies)
|
||||
agent-session - Manage agent sessions for an issue
|
||||
```
|
||||
|
||||
## Subcommands
|
||||
|
||||
### agent-session
|
||||
|
||||
> Manage agent sessions for an issue
|
||||
|
||||
```
|
||||
Usage: linear issue agent-session
|
||||
|
||||
Description:
|
||||
|
||||
Manage agent sessions for an issue
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
|
||||
Commands:
|
||||
|
||||
list [issueId] - List agent sessions for an issue
|
||||
view, v <sessionId> - View agent session details
|
||||
```
|
||||
|
||||
#### agent-session subcommands
|
||||
|
||||
##### list
|
||||
|
||||
```
|
||||
Usage: linear issue agent-session list [issueId]
|
||||
|
||||
Description:
|
||||
|
||||
List agent sessions for an issue
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-j, --json - Output as JSON
|
||||
--status <status> - Filter by session status (Values: "pending", "active", "complete", "awaitingInput",
|
||||
"error", "stale")
|
||||
```
|
||||
|
||||
##### view
|
||||
|
||||
```
|
||||
Usage: linear issue agent-session view <sessionId>
|
||||
|
||||
Description:
|
||||
|
||||
View agent session details
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-j, --json - Output as JSON
|
||||
```
|
||||
|
||||
### attach
|
||||
|
||||
> Create a sidebar link attachment on an issue (images do not render inline)
|
||||
|
||||
```
|
||||
Usage: linear issue attach <issueId> <filepath>
|
||||
|
||||
Description:
|
||||
|
||||
Create a sidebar link attachment on an issue (images do not render inline)
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-t, --title <title> - Custom title for the attachment
|
||||
-c, --comment <body> - Create a linked comment with this body; the file remains a sidebar attachment
|
||||
--public - Upload images to a public, unauthenticated URL (default: private,
|
||||
workspace-members only)
|
||||
```
|
||||
|
||||
### comment
|
||||
|
||||
> Manage issue comments
|
||||
|
||||
```
|
||||
Usage: linear issue comment
|
||||
|
||||
Description:
|
||||
|
||||
Manage issue comments
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
|
||||
Commands:
|
||||
|
||||
add [issueId] - Add a comment or reply; images uploaded with --attach render inline
|
||||
delete <commentId> - Delete a comment
|
||||
update <commentId> - Update an existing comment
|
||||
list [issueId] - List comments for an issue
|
||||
```
|
||||
|
||||
#### comment subcommands
|
||||
|
||||
##### add
|
||||
|
||||
```
|
||||
Usage: linear issue comment add [issueId]
|
||||
|
||||
Description:
|
||||
|
||||
Add a comment or reply; images uploaded with --attach render inline
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-b, --body <text> - Comment body text
|
||||
--body-file <path> - Read comment body from a file (preferred for markdown content)
|
||||
-p, --parent <id> - Parent comment ID for replies
|
||||
-a, --attach <filepath> - Upload a file and add its Markdown link to the comment (images render inline;
|
||||
repeatable)
|
||||
--public - Upload attached images to a public, unauthenticated URL (default: private,
|
||||
workspace-members only)
|
||||
```
|
||||
|
||||
##### delete
|
||||
|
||||
```
|
||||
Usage: linear issue comment delete <commentId>
|
||||
|
||||
Description:
|
||||
|
||||
Delete a comment
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
```
|
||||
|
||||
##### list
|
||||
|
||||
```
|
||||
Usage: linear issue comment list [issueId]
|
||||
|
||||
Description:
|
||||
|
||||
List comments for an issue
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-j, --json - Output as JSON
|
||||
```
|
||||
|
||||
##### update
|
||||
|
||||
```
|
||||
Usage: linear issue comment update <commentId>
|
||||
|
||||
Description:
|
||||
|
||||
Update an existing comment
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-b, --body <text> - New comment body text
|
||||
--body-file <path> - Read comment body from a file (preferred for markdown content)
|
||||
```
|
||||
|
||||
### commits
|
||||
|
||||
> Show all commits for a Linear issue (jj only)
|
||||
|
||||
```
|
||||
Usage: linear issue commits [issueId]
|
||||
|
||||
Description:
|
||||
|
||||
Show all commits for a Linear issue (jj only)
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
```
|
||||
|
||||
### create
|
||||
|
||||
> Create a linear issue
|
||||
|
||||
```
|
||||
Usage: linear issue create
|
||||
|
||||
Description:
|
||||
|
||||
Create a linear issue
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
--start - Start the issue after creation
|
||||
-a, --assignee <assignee> - Assign the issue to 'self' or someone (by username or name)
|
||||
--due-date <dueDate> - Due date of the issue
|
||||
--parent <parent> - Parent issue (if any) as a team_number code
|
||||
-p, --priority <priority> - Priority of the issue (1-4, descending priority)
|
||||
--estimate <estimate> - Points estimate of the issue
|
||||
-d, --description <description> - Description of the issue
|
||||
--description-file <path> - Read description from a file (preferred for markdown content)
|
||||
-l, --label <label> - Issue label associated with the issue. May be repeated.
|
||||
--team <team> - Team associated with the issue (if not your default team)
|
||||
--project <project> - Project for the issue (UUID, slug ID, or name)
|
||||
-s, --state <state> - Workflow state for the issue (by name or type)
|
||||
--milestone <milestone> - Project milestone (UUID, or name when --project is set)
|
||||
--cycle <cycle> - Cycle name, number, 'active'/'now', 'next', 'previous', or a relative offset
|
||||
like +1 (use --cycle=-1 for negatives)
|
||||
--no-use-default-template - Do not use default template for the issue
|
||||
--no-interactive - Disable interactive prompts
|
||||
-t, --title <title> - Title of the issue
|
||||
```
|
||||
|
||||
### delete
|
||||
|
||||
> Delete an issue
|
||||
|
||||
```
|
||||
Usage: linear issue delete [issueId]
|
||||
|
||||
Description:
|
||||
|
||||
Delete an issue
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-y, --confirm - Skip confirmation prompt
|
||||
--bulk <ids...> - Delete multiple issues by identifier (e.g., TC-123 TC-124)
|
||||
--bulk-file <file> - Read issue identifiers from a file (one per line)
|
||||
--bulk-stdin - Read issue identifiers from stdin
|
||||
```
|
||||
|
||||
### describe
|
||||
|
||||
> Print the issue title and Linear-issue trailer
|
||||
|
||||
```
|
||||
Usage: linear issue describe [issueId]
|
||||
|
||||
Description:
|
||||
|
||||
Print the issue title and Linear-issue trailer
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-r, --references, --ref - Use 'References' instead of 'Fixes' for the Linear issue link
|
||||
```
|
||||
|
||||
### id
|
||||
|
||||
> Print the issue based on the current git branch
|
||||
@@ -57,6 +323,30 @@ Options:
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
```
|
||||
|
||||
### link
|
||||
|
||||
> Link a URL to an issue
|
||||
|
||||
```
|
||||
Usage: linear issue link <urlOrIssueId> [url]
|
||||
|
||||
Description:
|
||||
|
||||
Link a URL to an issue
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-t, --title <title> - Custom title for the link
|
||||
|
||||
Examples:
|
||||
|
||||
Link a URL to issue detected from branch linear issue link https://github.com/org/repo/pull/123
|
||||
Link a URL to a specific issue linear issue link ENG-123 https://github.com/org/repo/pull/123
|
||||
Link with a custom title linear issue link ENG-123 https://example.com --title "Design doc"
|
||||
```
|
||||
|
||||
### mine
|
||||
|
||||
> List your issues
|
||||
@@ -70,26 +360,49 @@ Description:
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-s, --state <state> - Filter by issue state (can be repeated for multiple states) (Default: [ "unstarted" ], Values: "triage", "backlog",
|
||||
"unstarted", "started", "completed", "canceled")
|
||||
--all-states - Show issues from all states
|
||||
--sort <sort> - Sort order (can also be set via LINEAR_ISSUE_SORT) (Values: "manual", "priority")
|
||||
--team <team> - Team to list issues for (if not your default team)
|
||||
--project <project> - Filter by project name
|
||||
--project-label <projectLabel> - Filter by project label name (shows issues from all projects with this label)
|
||||
--cycle <cycle> - Filter by cycle name, number, or 'active'
|
||||
--milestone <milestone> - Filter by project milestone name (requires --project)
|
||||
-l, --label <label> - Filter by label name (can be repeated for multiple labels)
|
||||
--limit <limit> - Maximum number of issues to fetch (default: 50, use 0 for unlimited) (Default: 50)
|
||||
--created-after <date> - Filter issues created after this date (ISO 8601 or YYYY-MM-DD)
|
||||
--updated-after <date> - Filter issues updated after this date (ISO 8601 or YYYY-MM-DD)
|
||||
-w, --web - Open in web browser
|
||||
-a, --app - Open in Linear.app
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-s, --state <state> - Filter by issue state (can be repeated for multiple states) (Default: [ "unstarted" ], Values: "triage", "backlog",
|
||||
"unstarted", "started", "completed", "canceled")
|
||||
--all-states - Show issues from all states
|
||||
--sort <sort> - Sort order (default: priority, can also be set via LINEAR_ISSUE_SORT) (Values: "manual", "priority")
|
||||
--team <team> - Team to list issues for (if not your default team)
|
||||
--project <project> - Filter by project (UUID, slug ID, or name)
|
||||
--project-label <projectLabel> - Filter by project label name (shows issues from all projects with this label)
|
||||
--cycle <cycle> - Filter by cycle name, number, 'active'/'now', 'next', 'previous', or a relative
|
||||
offset like +1
|
||||
--milestone <milestone> - Filter by project milestone (UUID, or name when --project is set)
|
||||
-l, --label <label> - Filter by label name (can be repeated for multiple labels)
|
||||
--limit <limit> - Maximum number of issues to fetch (default: 50, use 0 for unlimited) (Default: 50)
|
||||
--created-after <date> - Filter issues created after this date (ISO 8601 or YYYY-MM-DD)
|
||||
--updated-after <date> - Filter issues updated after this date (ISO 8601 or YYYY-MM-DD)
|
||||
-w, --web - Open in web browser
|
||||
-a, --app - Open in Linear.app
|
||||
--no-pager - Disable automatic paging for long output
|
||||
```
|
||||
|
||||
### pull-request
|
||||
|
||||
> Create a GitHub pull request with issue details
|
||||
|
||||
```
|
||||
Usage: linear issue pull-request [issueId]
|
||||
|
||||
Description:
|
||||
|
||||
Create a GitHub pull request with issue details
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
--base <branch> - The branch into which you want your code merged
|
||||
--draft - Create the pull request as a draft
|
||||
-t, --title <title> - Optional title for the pull request (Linear issue ID will be prefixed)
|
||||
--web - Open the pull request in the browser after creating it
|
||||
--head <branch> - The branch that contains commits for your pull request
|
||||
```
|
||||
|
||||
### query
|
||||
|
||||
> Query issues with structured filters
|
||||
@@ -116,10 +429,11 @@ Options:
|
||||
-A, --all-assignees - Show issues for all assignees (this is the default)
|
||||
-U, --unassigned - Show only unassigned issues
|
||||
--sort <sort> - Sort order: manual or priority (default: priority, not available with --search) (Values: "manual", "priority")
|
||||
--project <project> - Filter by project name
|
||||
--project <project> - Filter by project (UUID, slug ID, or name)
|
||||
--project-label <projectLabel> - Filter by project label name (shows issues from all projects with this label)
|
||||
--cycle <cycle> - Filter by cycle name, number, or 'active'
|
||||
--milestone <milestone> - Filter by project milestone name (requires --project)
|
||||
--cycle <cycle> - Filter by cycle name, number, 'active'/'now', 'next', 'previous', or a relative
|
||||
offset like +1
|
||||
--milestone <milestone> - Filter by project milestone (UUID, or name when --project is set)
|
||||
-l, --label <label> - Filter by label name (can be repeated for multiple labels)
|
||||
--limit <limit> - Maximum number of issues to fetch (default: 50, use 0 for unlimited) (Default: 50)
|
||||
--created-after <date> - Filter issues created after this date (ISO 8601 or YYYY-MM-DD)
|
||||
@@ -129,368 +443,6 @@ Options:
|
||||
--no-pager - Disable automatic paging for long output
|
||||
```
|
||||
|
||||
### title
|
||||
|
||||
> Print the issue title
|
||||
|
||||
```
|
||||
Usage: linear issue title [issueId]
|
||||
|
||||
Description:
|
||||
|
||||
Print the issue title
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
```
|
||||
|
||||
### start
|
||||
|
||||
> Start working on an issue
|
||||
|
||||
```
|
||||
Usage: linear issue start [issueId]
|
||||
|
||||
Description:
|
||||
|
||||
Start working on an issue
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-A, --all-assignees - Show issues for all assignees
|
||||
-U, --unassigned - Show only unassigned issues
|
||||
-f, --from-ref <fromRef> - Git ref to create new branch from
|
||||
-b, --branch <branch> - Custom branch name to use instead of the issue identifier
|
||||
```
|
||||
|
||||
### view
|
||||
|
||||
> View issue details (default) or open in browser/app
|
||||
|
||||
```
|
||||
Usage: linear issue view [issueId]
|
||||
|
||||
Description:
|
||||
|
||||
View issue details (default) or open in browser/app
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-w, --web - Open in web browser
|
||||
-a, --app - Open in Linear.app
|
||||
--no-comments - Exclude comments from the output
|
||||
--show-resolved-threads - Include resolved comment threads in the output
|
||||
--no-pager - Disable automatic paging for long output
|
||||
-j, --json - Output issue data as JSON
|
||||
--no-download - Keep remote URLs instead of downloading files
|
||||
```
|
||||
|
||||
### url
|
||||
|
||||
> Print the issue URL
|
||||
|
||||
```
|
||||
Usage: linear issue url [issueId]
|
||||
|
||||
Description:
|
||||
|
||||
Print the issue URL
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
```
|
||||
|
||||
### describe
|
||||
|
||||
> Print the issue title and Linear-issue trailer
|
||||
|
||||
```
|
||||
Usage: linear issue describe [issueId]
|
||||
|
||||
Description:
|
||||
|
||||
Print the issue title and Linear-issue trailer
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-r, --references, --ref - Use 'References' instead of 'Fixes' for the Linear issue link
|
||||
```
|
||||
|
||||
### commits
|
||||
|
||||
> Show all commits for a Linear issue (jj only)
|
||||
|
||||
```
|
||||
Usage: linear issue commits [issueId]
|
||||
|
||||
Description:
|
||||
|
||||
Show all commits for a Linear issue (jj only)
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
```
|
||||
|
||||
### pull-request
|
||||
|
||||
> Create a GitHub pull request with issue details
|
||||
|
||||
```
|
||||
Usage: linear issue pull-request [issueId]
|
||||
|
||||
Description:
|
||||
|
||||
Create a GitHub pull request with issue details
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
--base <branch> - The branch into which you want your code merged
|
||||
--draft - Create the pull request as a draft
|
||||
-t, --title <title> - Optional title for the pull request (Linear issue ID will be prefixed)
|
||||
--web - Open the pull request in the browser after creating it
|
||||
--head <branch> - The branch that contains commits for your pull request
|
||||
```
|
||||
|
||||
### delete
|
||||
|
||||
> Delete an issue
|
||||
|
||||
```
|
||||
Usage: linear issue delete [issueId]
|
||||
|
||||
Description:
|
||||
|
||||
Delete an issue
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-y, --confirm - Skip confirmation prompt
|
||||
--bulk <ids...> - Delete multiple issues by identifier (e.g., TC-123 TC-124)
|
||||
--bulk-file <file> - Read issue identifiers from a file (one per line)
|
||||
--bulk-stdin - Read issue identifiers from stdin
|
||||
```
|
||||
|
||||
### create
|
||||
|
||||
> Create a linear issue
|
||||
|
||||
```
|
||||
Usage: linear issue create
|
||||
|
||||
Description:
|
||||
|
||||
Create a linear issue
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
--start - Start the issue after creation
|
||||
-a, --assignee <assignee> - Assign the issue to 'self' or someone (by username or name)
|
||||
--due-date <dueDate> - Due date of the issue
|
||||
--parent <parent> - Parent issue (if any) as a team_number code
|
||||
-p, --priority <priority> - Priority of the issue (1-4, descending priority)
|
||||
--estimate <estimate> - Points estimate of the issue
|
||||
-d, --description <description> - Description of the issue
|
||||
--description-file <path> - Read description from a file (preferred for markdown content)
|
||||
-l, --label <label> - Issue label associated with the issue. May be repeated.
|
||||
--team <team> - Team associated with the issue (if not your default team)
|
||||
--project <project> - Name or slug ID of the project with the issue
|
||||
-s, --state <state> - Workflow state for the issue (by name or type)
|
||||
--milestone <milestone> - Name of the project milestone
|
||||
--cycle <cycle> - Cycle name, number, or 'active'
|
||||
--no-use-default-template - Do not use default template for the issue
|
||||
--no-interactive - Disable interactive prompts
|
||||
-t, --title <title> - Title of the issue
|
||||
```
|
||||
|
||||
### update
|
||||
|
||||
> Update a linear issue
|
||||
|
||||
```
|
||||
Usage: linear issue update [issueId]
|
||||
|
||||
Description:
|
||||
|
||||
Update a linear issue
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-a, --assignee <assignee> - Assign the issue to 'self' or someone (by username or name)
|
||||
--due-date <dueDate> - Due date of the issue
|
||||
--parent <parent> - Parent issue (if any) as a team_number code
|
||||
-p, --priority <priority> - Priority of the issue (1-4, descending priority)
|
||||
--estimate <estimate> - Points estimate of the issue
|
||||
-d, --description <description> - Description of the issue
|
||||
--description-file <path> - Read description from a file (preferred for markdown content)
|
||||
-l, --label <label> - Issue label associated with the issue. May be repeated.
|
||||
--team <team> - Team associated with the issue (if not your default team)
|
||||
--project <project> - Name or slug ID of the project with the issue
|
||||
-s, --state <state> - Workflow state for the issue (by name or type)
|
||||
--milestone <milestone> - Name of the project milestone
|
||||
--cycle <cycle> - Cycle name, number, or 'active'
|
||||
-t, --title <title> - Title of the issue
|
||||
```
|
||||
|
||||
### comment
|
||||
|
||||
> Manage issue comments
|
||||
|
||||
```
|
||||
Usage: linear issue comment
|
||||
|
||||
Description:
|
||||
|
||||
Manage issue comments
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
|
||||
Commands:
|
||||
|
||||
add [issueId] - Add a comment to an issue or reply to a comment
|
||||
delete <commentId> - Delete a comment
|
||||
update <commentId> - Update an existing comment
|
||||
list [issueId] - List comments for an issue
|
||||
```
|
||||
|
||||
#### comment subcommands
|
||||
|
||||
##### add
|
||||
|
||||
```
|
||||
Usage: linear issue comment add [issueId]
|
||||
|
||||
Description:
|
||||
|
||||
Add a comment to an issue or reply to a comment
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-b, --body <text> - Comment body text
|
||||
--body-file <path> - Read comment body from a file (preferred for markdown content)
|
||||
-p, --parent <id> - Parent comment ID for replies
|
||||
-a, --attach <filepath> - Attach a file to the comment (can be used multiple times)
|
||||
--public - Upload attached images to a public, unauthenticated URL (default: private,
|
||||
workspace-members only)
|
||||
```
|
||||
|
||||
##### delete
|
||||
|
||||
```
|
||||
Usage: linear issue comment delete <commentId>
|
||||
|
||||
Description:
|
||||
|
||||
Delete a comment
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
```
|
||||
|
||||
##### update
|
||||
|
||||
```
|
||||
Usage: linear issue comment update <commentId>
|
||||
|
||||
Description:
|
||||
|
||||
Update an existing comment
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-b, --body <text> - New comment body text
|
||||
--body-file <path> - Read comment body from a file (preferred for markdown content)
|
||||
```
|
||||
|
||||
##### list
|
||||
|
||||
```
|
||||
Usage: linear issue comment list [issueId]
|
||||
|
||||
Description:
|
||||
|
||||
List comments for an issue
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-j, --json - Output as JSON
|
||||
```
|
||||
|
||||
### attach
|
||||
|
||||
> Attach a file to an issue
|
||||
|
||||
```
|
||||
Usage: linear issue attach <issueId> <filepath>
|
||||
|
||||
Description:
|
||||
|
||||
Attach a file to an issue
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-t, --title <title> - Custom title for the attachment
|
||||
-c, --comment <body> - Add a comment body linked to the attachment
|
||||
--public - Upload images to a public, unauthenticated URL (default: private,
|
||||
workspace-members only)
|
||||
```
|
||||
|
||||
### link
|
||||
|
||||
> Link a URL to an issue
|
||||
|
||||
```
|
||||
Usage: linear issue link <urlOrIssueId> [url]
|
||||
|
||||
Description:
|
||||
|
||||
Link a URL to an issue
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-t, --title <title> - Custom title for the link
|
||||
|
||||
Examples:
|
||||
|
||||
Link a URL to issue detected from branch linear issue link https://github.com/org/repo/pull/123
|
||||
Link a URL to a specific issue linear issue link ENG-123 https://github.com/org/repo/pull/123
|
||||
Link with a custom title linear issue link ENG-123 https://example.com --title "Design doc"
|
||||
```
|
||||
|
||||
### relation
|
||||
|
||||
> Manage issue relations (dependencies)
|
||||
@@ -568,60 +520,121 @@ Options:
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
```
|
||||
|
||||
### agent-session
|
||||
### start
|
||||
|
||||
> Manage agent sessions for an issue
|
||||
> Start working on an issue
|
||||
|
||||
```
|
||||
Usage: linear issue agent-session
|
||||
Usage: linear issue start [issueId]
|
||||
|
||||
Description:
|
||||
|
||||
Manage agent sessions for an issue
|
||||
Start working on an issue
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-A, --all-assignees - Show issues for all assignees
|
||||
-U, --unassigned - Show only unassigned issues
|
||||
-f, --from-ref <fromRef> - Git ref to create new branch from
|
||||
-b, --branch <branch> - Custom branch name to use instead of the issue identifier
|
||||
```
|
||||
|
||||
### title
|
||||
|
||||
> Print the issue title
|
||||
|
||||
```
|
||||
Usage: linear issue title [issueId]
|
||||
|
||||
Description:
|
||||
|
||||
Print the issue title
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
|
||||
Commands:
|
||||
|
||||
list [issueId] - List agent sessions for an issue
|
||||
view, v <sessionId> - View agent session details
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
```
|
||||
|
||||
#### agent-session subcommands
|
||||
### update
|
||||
|
||||
##### list
|
||||
> Update a linear issue
|
||||
|
||||
```
|
||||
Usage: linear issue agent-session list [issueId]
|
||||
Usage: linear issue update [issueId]
|
||||
|
||||
Description:
|
||||
|
||||
List agent sessions for an issue
|
||||
Update a linear issue
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-j, --json - Output as JSON
|
||||
--status <status> - Filter by session status (Values: "pending", "active", "complete", "awaitingInput",
|
||||
"error", "stale")
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-a, --assignee <assignee> - Assign the issue to 'self' or someone (by username or name)
|
||||
--unassign - Clear the issue's assignee (cannot be combined with --assignee)
|
||||
--due-date <dueDate> - Due date of the issue
|
||||
--parent <parent> - Parent issue (if any) as a team_number code
|
||||
-p, --priority <priority> - Priority of the issue (1-4, descending priority)
|
||||
--estimate <estimate> - Points estimate of the issue
|
||||
-d, --description <description> - Description of the issue
|
||||
--description-file <path> - Read description from a file (preferred for markdown content)
|
||||
-l, --label <label> - Issue label associated with the issue; replaces the issue's entire label set.
|
||||
May be repeated. Use --add-label/--remove-label to change labels incrementally.
|
||||
--add-label <label> - Add a label to the issue, keeping its existing labels. May be repeated.
|
||||
--remove-label <label> - Remove a label from the issue, keeping its other labels (does not delete the
|
||||
label from the team). May be repeated.
|
||||
--team <team> - Team associated with the issue (if not your default team)
|
||||
--project <project> - Project to assign the issue to (UUID, slug ID, or name)
|
||||
-s, --state <state> - Workflow state for the issue (by name or type)
|
||||
--milestone <milestone> - Project milestone (UUID, or name when --project is set or the issue already has
|
||||
a project)
|
||||
--cycle <cycle> - Cycle name, number, 'active'/'now', 'next', 'previous', or a relative offset
|
||||
like +1 (use --cycle=-1 for negatives). Use --clear-cycle to remove the issue
|
||||
from its cycle
|
||||
--clear-cycle - Remove the issue from its cycle
|
||||
-t, --title <title> - Title of the issue
|
||||
```
|
||||
|
||||
##### view
|
||||
### url
|
||||
|
||||
> Print the issue URL
|
||||
|
||||
```
|
||||
Usage: linear issue agent-session view <sessionId>
|
||||
Usage: linear issue url [issueId]
|
||||
|
||||
Description:
|
||||
|
||||
View agent session details
|
||||
Print the issue URL
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-j, --json - Output as JSON
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
```
|
||||
|
||||
### view
|
||||
|
||||
> View issue details (default) or open in browser/app
|
||||
|
||||
```
|
||||
Usage: linear issue view [issueId]
|
||||
|
||||
Description:
|
||||
|
||||
View issue details (default) or open in browser/app
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-w, --web - Open in web browser
|
||||
-a, --app - Open in Linear.app
|
||||
--no-comments - Exclude comments from the output
|
||||
--show-resolved-threads - Include resolved comment threads in the output
|
||||
--no-pager - Disable automatic paging for long output
|
||||
-j, --json - Output issue data as JSON
|
||||
--no-download - Keep remote URLs instead of downloading files
|
||||
```
|
||||
@@ -25,26 +25,6 @@ Commands:
|
||||
|
||||
## Subcommands
|
||||
|
||||
### list
|
||||
|
||||
> List issue labels
|
||||
|
||||
```
|
||||
Usage: linear label list
|
||||
|
||||
Description:
|
||||
|
||||
List issue labels
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--team <teamKey> - Filter by team (e.g., TC). Shows team-specific labels only.
|
||||
--workspace - Show only workspace-level labels (not team-specific)
|
||||
--all - Show all labels (both workspace and team)
|
||||
-j, --json - Output as JSON
|
||||
```
|
||||
|
||||
### create
|
||||
|
||||
> Create a new issue label
|
||||
@@ -85,3 +65,23 @@ Options:
|
||||
-t, --team <teamKey> - Team key to disambiguate labels with same name
|
||||
-f, --force - Skip confirmation prompt
|
||||
```
|
||||
|
||||
### list
|
||||
|
||||
> List issue labels
|
||||
|
||||
```
|
||||
Usage: linear label list
|
||||
|
||||
Description:
|
||||
|
||||
List issue labels
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--team <teamKey> - Filter by team (e.g., TC). Shows team-specific labels only.
|
||||
--workspace - Show only workspace-level labels (not team-specific)
|
||||
--all - Show all labels (both workspace and team)
|
||||
-j, --json - Output as JSON
|
||||
```
|
||||
@@ -18,56 +18,22 @@ Options:
|
||||
|
||||
Commands:
|
||||
|
||||
list - List milestones for a project
|
||||
view, v <milestoneId> - View milestone details
|
||||
create - Create a new project milestone
|
||||
update <id> - Update an existing project milestone
|
||||
delete <id> - Delete a project milestone
|
||||
list - List milestones for a project
|
||||
view, v <milestone> - View milestone details. By default lists the first 10 attached issues from the
|
||||
first page of 50; use --all to paginate the full set.
|
||||
create - Create a new project milestone
|
||||
update <id> - Update an existing project milestone
|
||||
delete <id> - Delete a project milestone
|
||||
```
|
||||
|
||||
## Subcommands
|
||||
|
||||
### list
|
||||
|
||||
> List milestones for a project
|
||||
|
||||
```
|
||||
Usage: linear milestone list --project <projectId>
|
||||
|
||||
Description:
|
||||
|
||||
List milestones for a project
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
--project <projectId> - Project ID (required)
|
||||
```
|
||||
|
||||
### view
|
||||
|
||||
> View milestone details
|
||||
|
||||
```
|
||||
Usage: linear milestone view <milestoneId>
|
||||
|
||||
Description:
|
||||
|
||||
View milestone details
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
```
|
||||
|
||||
### create
|
||||
|
||||
> Create a new project milestone
|
||||
|
||||
```
|
||||
Usage: linear milestone create --project <projectId> --name <name>
|
||||
Usage: linear milestone create --project <project> --name <name>
|
||||
|
||||
Description:
|
||||
|
||||
@@ -77,34 +43,12 @@ Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
--project <projectId> - Project ID (required)
|
||||
--project <project> - Project (UUID, slug ID, or name) (required)
|
||||
--name <name> - Milestone name (required)
|
||||
--description <description> - Milestone description
|
||||
--target-date <date> - Target date (YYYY-MM-DD)
|
||||
```
|
||||
|
||||
### update
|
||||
|
||||
> Update an existing project milestone
|
||||
|
||||
```
|
||||
Usage: linear milestone update <id>
|
||||
|
||||
Description:
|
||||
|
||||
Update an existing project milestone
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
--name <name> - Milestone name
|
||||
--description <description> - Milestone description
|
||||
--target-date <date> - Target date (YYYY-MM-DD)
|
||||
--sort-order <value> - Sort order relative to other milestones
|
||||
--project <projectId> - Move to a different project
|
||||
```
|
||||
|
||||
### delete
|
||||
|
||||
> Delete a project milestone
|
||||
@@ -122,3 +66,62 @@ Options:
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-f, --force - Skip confirmation prompt
|
||||
```
|
||||
|
||||
### list
|
||||
|
||||
> List milestones for a project
|
||||
|
||||
```
|
||||
Usage: linear milestone list --project <project>
|
||||
|
||||
Description:
|
||||
|
||||
List milestones for a project
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
--project <project> - Project (UUID, slug ID, or name) (required)
|
||||
```
|
||||
|
||||
### update
|
||||
|
||||
> Update an existing project milestone
|
||||
|
||||
```
|
||||
Usage: linear milestone update <id>
|
||||
|
||||
Description:
|
||||
|
||||
Update an existing project milestone
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
--name <name> - Milestone name
|
||||
--description <description> - Milestone description
|
||||
--target-date <date> - Target date (YYYY-MM-DD)
|
||||
--sort-order <value> - Sort order relative to other milestones
|
||||
--project <project> - Move to a different project (UUID, slug ID, or name)
|
||||
```
|
||||
|
||||
### view
|
||||
|
||||
> View milestone details. By default lists the first 10 attached issues from the first page of 50; use --all to paginate the full set.
|
||||
|
||||
```
|
||||
Usage: linear milestone view <milestone>
|
||||
|
||||
Description:
|
||||
|
||||
View milestone details. By default lists the first 10 attached issues from the first page of 50; use --all to paginate the full set.
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
--all - Fetch and list every issue attached to the milestone (paginates the Linear API).
|
||||
--project <project> - Project for resolving a milestone name (UUID, slug ID, or name)
|
||||
```
|
||||
@@ -74,12 +74,18 @@ echo -e "DEV-123\nDEV-124" | linear issue delete --bulk-stdin
|
||||
linear initiative archive --bulk <id1> <id2>
|
||||
```
|
||||
|
||||
## Adding Labels to Issues
|
||||
## Updating Labels on Issues
|
||||
|
||||
```bash
|
||||
# Add label to issue
|
||||
linear issue update DEV-123 --label "Bug"
|
||||
# Add a label, keeping the issue's existing labels
|
||||
linear issue update DEV-123 --add-label "Bug"
|
||||
|
||||
# Add multiple labels
|
||||
# Remove a label from this issue only (label delete removes it team-wide)
|
||||
linear issue update DEV-123 --remove-label "sprint-42"
|
||||
|
||||
# Swap labels atomically in one update
|
||||
linear issue update DEV-123 --remove-label "sprint-42" --add-label "sprint-43"
|
||||
|
||||
# Replace the entire label set with exactly these labels
|
||||
linear issue update DEV-123 --label "Bug" --label "High Priority"
|
||||
```
|
||||
@@ -27,6 +27,60 @@ Commands:
|
||||
|
||||
## Subcommands
|
||||
|
||||
### create
|
||||
|
||||
> Create a new Linear project
|
||||
|
||||
```
|
||||
Usage: linear project create
|
||||
|
||||
Description:
|
||||
|
||||
Create a new Linear project
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-n, --name <name> - Project name (required)
|
||||
-d, --description <description> - Project description (max 255 characters, enforced by Linear's API)
|
||||
-f, --description-file <path> - Read project description from file (still subject to the 255-character API
|
||||
limit)
|
||||
--content <markdown> - Project overview markdown
|
||||
--content-file <path> - Read project overview markdown from a file
|
||||
-t, --team <team> - Team key (required, can be repeated for multiple teams)
|
||||
-l, --lead <lead> - Project lead (username, email, or @me)
|
||||
-s, --status <status> - Project status (planned, started, paused, completed, canceled, backlog)
|
||||
--start-date <startDate> - Start date (YYYY-MM-DD)
|
||||
--target-date <targetDate> - Target completion date (YYYY-MM-DD)
|
||||
--priority <priority> - Project priority (none, urgent, high, medium, low)
|
||||
--label <label> - Project label associated with the project. May be repeated.
|
||||
--member <user> - Project member (username, email, display name, or @me). May be repeated.
|
||||
--icon <icon> - Project icon
|
||||
--color <color> - Project color as a HEX string
|
||||
--initiative <initiative> - Add to initiative immediately (ID, slug, or name)
|
||||
-i, --interactive - Interactive mode (default if no flags provided)
|
||||
-j, --json - Output created project as JSON
|
||||
```
|
||||
|
||||
### delete
|
||||
|
||||
> Delete (trash) a Linear project
|
||||
|
||||
```
|
||||
Usage: linear project delete <projectId>
|
||||
|
||||
Description:
|
||||
|
||||
Delete (trash) a Linear project
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-f, --force - Skip confirmation prompt
|
||||
```
|
||||
|
||||
### list
|
||||
|
||||
> List projects
|
||||
@@ -50,6 +104,33 @@ Options:
|
||||
-j, --json - Output as JSON
|
||||
```
|
||||
|
||||
### update
|
||||
|
||||
> Update a Linear project
|
||||
|
||||
```
|
||||
Usage: linear project update <projectId>
|
||||
|
||||
Description:
|
||||
|
||||
Update a Linear project
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-n, --name <name> - Project name
|
||||
-d, --description <description> - Project description (max 255 characters, enforced by Linear's API)
|
||||
-f, --description-file <path> - Read project description from file (still subject to the 255-character API
|
||||
limit)
|
||||
-s, --status <status> - Status (planned, started, paused, completed, canceled, backlog)
|
||||
-l, --lead <lead> - Project lead (username, email, or @me)
|
||||
--start-date <startDate> - Start date (YYYY-MM-DD)
|
||||
--target-date <targetDate> - Target date (YYYY-MM-DD)
|
||||
-t, --team <team> - Team key (can be repeated for multiple teams)
|
||||
--label <label> - Replace the project's labels. May be repeated to set multiple labels.
|
||||
```
|
||||
|
||||
### view
|
||||
|
||||
> View project details
|
||||
@@ -68,72 +149,3 @@ Options:
|
||||
-w, --web - Open in web browser
|
||||
-a, --app - Open in Linear.app
|
||||
```
|
||||
|
||||
### create
|
||||
|
||||
> Create a new Linear project
|
||||
|
||||
```
|
||||
Usage: linear project create
|
||||
|
||||
Description:
|
||||
|
||||
Create a new Linear project
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-n, --name <name> - Project name (required)
|
||||
-d, --description <description> - Project description
|
||||
-t, --team <team> - Team key (required, can be repeated for multiple teams)
|
||||
-l, --lead <lead> - Project lead (username, email, or @me)
|
||||
-s, --status <status> - Project status (planned, started, paused, completed, canceled, backlog)
|
||||
--start-date <startDate> - Start date (YYYY-MM-DD)
|
||||
--target-date <targetDate> - Target completion date (YYYY-MM-DD)
|
||||
--initiative <initiative> - Add to initiative immediately (ID, slug, or name)
|
||||
-i, --interactive - Interactive mode (default if no flags provided)
|
||||
-j, --json - Output created project as JSON
|
||||
```
|
||||
|
||||
### update
|
||||
|
||||
> Update a Linear project
|
||||
|
||||
```
|
||||
Usage: linear project update <projectId>
|
||||
|
||||
Description:
|
||||
|
||||
Update a Linear project
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-n, --name <name> - Project name
|
||||
-d, --description <description> - Project description
|
||||
-s, --status <status> - Status (planned, started, paused, completed, canceled, backlog)
|
||||
-l, --lead <lead> - Project lead (username, email, or @me)
|
||||
--start-date <startDate> - Start date (YYYY-MM-DD)
|
||||
--target-date <targetDate> - Target date (YYYY-MM-DD)
|
||||
-t, --team <team> - Team key (can be repeated for multiple teams)
|
||||
```
|
||||
|
||||
### delete
|
||||
|
||||
> Delete (trash) a Linear project
|
||||
|
||||
```
|
||||
Usage: linear project delete <projectId>
|
||||
|
||||
Description:
|
||||
|
||||
Delete (trash) a Linear project
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-f, --force - Skip confirmation prompt
|
||||
```
|
||||
@@ -23,11 +23,29 @@ Commands:
|
||||
list - List teams
|
||||
id - Print the configured team id
|
||||
autolinks - Configure GitHub repository autolinks for Linear issues with this team prefix
|
||||
members [teamKey] - List team members
|
||||
members [teamKey] - List team members
|
||||
states [teamKey] - List workflow states for a team
|
||||
```
|
||||
|
||||
## Subcommands
|
||||
|
||||
### autolinks
|
||||
|
||||
> Configure GitHub repository autolinks for Linear issues with this team prefix
|
||||
|
||||
```
|
||||
Usage: linear team autolinks
|
||||
|
||||
Description:
|
||||
|
||||
Configure GitHub repository autolinks for Linear issues with this team prefix
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
```
|
||||
|
||||
### create
|
||||
|
||||
> Create a linear team
|
||||
@@ -69,6 +87,23 @@ Options:
|
||||
-y, --force - Skip confirmation prompt
|
||||
```
|
||||
|
||||
### id
|
||||
|
||||
> Print the configured team id
|
||||
|
||||
```
|
||||
Usage: linear team id
|
||||
|
||||
Description:
|
||||
|
||||
Print the configured team id
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
```
|
||||
|
||||
### list
|
||||
|
||||
> List teams
|
||||
@@ -88,40 +123,6 @@ Options:
|
||||
-a, --app - Open in Linear.app
|
||||
```
|
||||
|
||||
### id
|
||||
|
||||
> Print the configured team id
|
||||
|
||||
```
|
||||
Usage: linear team id
|
||||
|
||||
Description:
|
||||
|
||||
Print the configured team id
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
```
|
||||
|
||||
### autolinks
|
||||
|
||||
> Configure GitHub repository autolinks for Linear issues with this team prefix
|
||||
|
||||
```
|
||||
Usage: linear team autolinks
|
||||
|
||||
Description:
|
||||
|
||||
Configure GitHub repository autolinks for Linear issues with this team prefix
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
```
|
||||
|
||||
### members
|
||||
|
||||
> List team members
|
||||
@@ -137,5 +138,24 @@ Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-a, --all - Include inactive members
|
||||
-a, --all - Include inactive members
|
||||
-j, --json - Output as JSON
|
||||
```
|
||||
|
||||
### states
|
||||
|
||||
> List workflow states for a team
|
||||
|
||||
```
|
||||
Usage: linear team states [teamKey]
|
||||
|
||||
Description:
|
||||
|
||||
List workflow states for a team
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
-j, --json - Output as JSON
|
||||
```
|
||||
@@ -26,20 +26,43 @@ interface CommandInfo {
|
||||
subcommands: CommandInfo[]
|
||||
}
|
||||
|
||||
async function run(
|
||||
cmd: string[],
|
||||
): Promise<{ success: boolean; stdout: string; stderr: string }> {
|
||||
const command = new Deno.Command(cmd[0], {
|
||||
args: cmd.slice(1),
|
||||
stdout: "piped",
|
||||
stderr: "piped",
|
||||
env: { NO_COLOR: "1" }, // Disable ANSI colors
|
||||
})
|
||||
const result = await command.output()
|
||||
return {
|
||||
success: result.success,
|
||||
stdout: new TextDecoder().decode(result.stdout).trim(),
|
||||
stderr: new TextDecoder().decode(result.stderr).trim(),
|
||||
interface RunResult {
|
||||
success: boolean
|
||||
stdout: string
|
||||
stderr: string
|
||||
}
|
||||
|
||||
interface HelpResult {
|
||||
help: string
|
||||
failure: string | null
|
||||
}
|
||||
|
||||
interface DiscoveryResult {
|
||||
command: CommandInfo
|
||||
failures: string[]
|
||||
}
|
||||
|
||||
async function run(cmd: string[]): Promise<RunResult> {
|
||||
try {
|
||||
const command = new Deno.Command(cmd[0], {
|
||||
args: cmd.slice(1),
|
||||
stdout: "piped",
|
||||
stderr: "piped",
|
||||
env: { NO_COLOR: "1" }, // Disable ANSI colors
|
||||
})
|
||||
const result = await command.output()
|
||||
return {
|
||||
success: result.success,
|
||||
stdout: new TextDecoder().decode(result.stdout).trim(),
|
||||
stderr: new TextDecoder().decode(result.stderr).trim(),
|
||||
}
|
||||
} catch (error) {
|
||||
// Deno.Command throws (e.g. NotFound) when the binary is missing
|
||||
return {
|
||||
success: false,
|
||||
stdout: "",
|
||||
stderr: error instanceof Error ? error.message : String(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,251 +77,161 @@ function stripVersion(str: string): string {
|
||||
return str.replace(/^Version:.*\n?/gm, "").replace(/\n+$/, "")
|
||||
}
|
||||
|
||||
function parseCommands(helpText: string): string[] {
|
||||
const commands: string[] = []
|
||||
const lines = helpText.split("\n")
|
||||
let inCommands = false
|
||||
let foundAnyCommand = false
|
||||
function byName(a: { name: string }, b: { name: string }): number {
|
||||
if (a.name < b.name) return -1
|
||||
if (a.name > b.name) return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith("Commands:")) {
|
||||
inCommands = true
|
||||
continue
|
||||
}
|
||||
if (inCommands) {
|
||||
// Command lines look like: " command, alias - Description"
|
||||
// or: " command <arg> - Description"
|
||||
// Capture command name (stopping at comma or whitespace)
|
||||
function lastSegment(name: string): string {
|
||||
return name.split(" ").at(-1) ?? name
|
||||
}
|
||||
|
||||
function parseCommands(helpText: string): string[] {
|
||||
const lines = helpText.split("\n")
|
||||
const start = lines.findIndex((line) => line.startsWith("Commands:"))
|
||||
if (start === -1) {
|
||||
return []
|
||||
}
|
||||
|
||||
// Command lines look like " command, alias - Description". Collect names
|
||||
// until the first blank line after the section starts; blank lines before
|
||||
// the first command are ignored.
|
||||
return lines.slice(start + 1).reduce<{ names: string[]; done: boolean }>(
|
||||
(state, line) => {
|
||||
if (state.done) {
|
||||
return state
|
||||
}
|
||||
const match = line.match(/^\s{2}([a-z][-a-z]*)(?:,|\s)/)
|
||||
if (match) {
|
||||
commands.push(match[1])
|
||||
foundAnyCommand = true
|
||||
} else if (foundAnyCommand && line.trim() === "") {
|
||||
// Empty line after we've found commands means end of section
|
||||
break
|
||||
return { names: [...state.names, match[1]], done: false }
|
||||
}
|
||||
// Skip empty lines before first command (common in help output)
|
||||
}
|
||||
}
|
||||
|
||||
return commands
|
||||
if (state.names.length > 0 && line.trim() === "") {
|
||||
return { names: state.names, done: true }
|
||||
}
|
||||
return state
|
||||
},
|
||||
{ names: [], done: false },
|
||||
).names
|
||||
}
|
||||
|
||||
async function getCommandHelp(cmdPath: string[]): Promise<string> {
|
||||
async function getCommandHelp(cmdPath: string[]): Promise<HelpResult> {
|
||||
const result = await run(["linear", ...cmdPath, "--help"])
|
||||
if (!result.success) {
|
||||
return result.stderr || "Command help not available"
|
||||
const label = ["linear", ...cmdPath].join(" ")
|
||||
return {
|
||||
help: "",
|
||||
failure: `\`${label} --help\` failed: ${result.stderr || "no output"}`,
|
||||
}
|
||||
}
|
||||
return stripVersion(stripAnsi(result.stdout))
|
||||
return { help: stripVersion(stripAnsi(result.stdout)), failure: null }
|
||||
}
|
||||
|
||||
async function discoverCommand(cmdPath: string[]): Promise<CommandInfo> {
|
||||
const help = await getCommandHelp(cmdPath)
|
||||
async function discoverCommand(cmdPath: string[]): Promise<DiscoveryResult> {
|
||||
const { help, failure } = await getCommandHelp(cmdPath)
|
||||
const name = cmdPath.join(" ")
|
||||
|
||||
// Extract description from help text
|
||||
const descMatch = help.match(/Description:\s*\n\s*(.+)/)
|
||||
const description = descMatch ? descMatch[1].trim() : ""
|
||||
|
||||
// Find subcommands
|
||||
// Discover subcommands recursively
|
||||
const subcommandNames = parseCommands(help)
|
||||
const subcommands: CommandInfo[] = []
|
||||
const childResults = await Promise.all(
|
||||
subcommandNames.map((subcmd) => discoverCommand([...cmdPath, subcmd])),
|
||||
)
|
||||
|
||||
for (const subcmd of subcommandNames) {
|
||||
const subInfo = await discoverCommand([...cmdPath, subcmd])
|
||||
subcommands.push(subInfo)
|
||||
// Sort by name so output order is stable regardless of help order
|
||||
const subcommands = childResults.map((child) => child.command).sort(byName)
|
||||
const failures = [
|
||||
...(failure ? [failure] : []),
|
||||
...childResults.flatMap((child) => child.failures),
|
||||
]
|
||||
|
||||
return {
|
||||
command: { name, description, help, subcommands },
|
||||
failures,
|
||||
}
|
||||
|
||||
return { name, description, help, subcommands }
|
||||
}
|
||||
|
||||
function formatCommandMarkdown(cmd: CommandInfo, depth = 0): string {
|
||||
const lines: string[] = []
|
||||
const cmdName = cmd.name.replace(/^linear /, "")
|
||||
const heading = depth === 0 ? "#" : "##"
|
||||
function fencedBlock(content: string): string[] {
|
||||
return ["```", content, "```"]
|
||||
}
|
||||
|
||||
lines.push(`${heading} linear ${cmdName}`)
|
||||
lines.push("")
|
||||
lines.push("```")
|
||||
lines.push(cmd.help)
|
||||
lines.push("```")
|
||||
lines.push("")
|
||||
function renderSubSubcommand(subsub: CommandInfo): string[] {
|
||||
return [
|
||||
"",
|
||||
`##### ${lastSegment(subsub.name)}`,
|
||||
"",
|
||||
...fencedBlock(subsub.help),
|
||||
]
|
||||
}
|
||||
|
||||
// Add subcommands as separate sections
|
||||
for (const sub of cmd.subcommands) {
|
||||
lines.push(formatCommandMarkdown(sub, depth + 1))
|
||||
}
|
||||
function renderSubcommand(sub: CommandInfo): string[] {
|
||||
const subName = lastSegment(sub.name)
|
||||
const descriptionLines = sub.description ? [`> ${sub.description}`, ""] : []
|
||||
// Handle 3-level deep commands (e.g., issue comment add)
|
||||
const nestedLines = sub.subcommands.length > 0
|
||||
? [
|
||||
"",
|
||||
`#### ${subName} subcommands`,
|
||||
...sub.subcommands.flatMap(renderSubSubcommand),
|
||||
]
|
||||
: []
|
||||
|
||||
return lines.join("\n")
|
||||
return [
|
||||
"",
|
||||
`### ${subName}`,
|
||||
"",
|
||||
...descriptionLines,
|
||||
...fencedBlock(sub.help),
|
||||
...nestedLines,
|
||||
]
|
||||
}
|
||||
|
||||
function generateCommandDoc(cmd: CommandInfo): string {
|
||||
const lines: string[] = []
|
||||
const cmdName = cmd.name.replace(/^linear /, "")
|
||||
const subcommandLines = cmd.subcommands.length > 0
|
||||
? ["", "## Subcommands", ...cmd.subcommands.flatMap(renderSubcommand)]
|
||||
: []
|
||||
|
||||
lines.push(`# ${cmdName}`)
|
||||
lines.push("")
|
||||
lines.push(`> ${cmd.description}`)
|
||||
lines.push("")
|
||||
lines.push("## Usage")
|
||||
lines.push("")
|
||||
lines.push("```")
|
||||
lines.push(cmd.help)
|
||||
lines.push("```")
|
||||
|
||||
// Add subcommand details
|
||||
if (cmd.subcommands.length > 0) {
|
||||
lines.push("")
|
||||
lines.push("## Subcommands")
|
||||
|
||||
for (const sub of cmd.subcommands) {
|
||||
const subName = sub.name.split(" ").pop()!
|
||||
lines.push("")
|
||||
lines.push(`### ${subName}`)
|
||||
lines.push("")
|
||||
if (sub.description) {
|
||||
lines.push(`> ${sub.description}`)
|
||||
lines.push("")
|
||||
}
|
||||
lines.push("```")
|
||||
lines.push(sub.help)
|
||||
lines.push("```")
|
||||
|
||||
// Handle 3-level deep commands (e.g., issue comment add)
|
||||
if (sub.subcommands.length > 0) {
|
||||
lines.push("")
|
||||
lines.push(`#### ${subName} subcommands`)
|
||||
|
||||
for (const subsub of sub.subcommands) {
|
||||
const subsubName = subsub.name.split(" ").pop()!
|
||||
lines.push("")
|
||||
lines.push(`##### ${subsubName}`)
|
||||
lines.push("")
|
||||
lines.push("```")
|
||||
lines.push(subsub.help)
|
||||
lines.push("```")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join("\n")
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log("Generating Linear CLI documentation...")
|
||||
|
||||
// Check linear is available
|
||||
const versionResult = await run(["linear", "--version"])
|
||||
if (!versionResult.success) {
|
||||
console.error("Error: linear CLI not found. Is it installed?")
|
||||
Deno.exit(1)
|
||||
}
|
||||
console.log(`Linear CLI: ${stripAnsi(versionResult.stdout)}`)
|
||||
|
||||
// Auto-discover top-level commands from `linear --help`
|
||||
console.log("Discovering commands...")
|
||||
const topLevelHelp = await getCommandHelp([])
|
||||
const topLevelCommands = parseCommands(topLevelHelp).filter(
|
||||
(cmd) => !SKIP_COMMANDS.includes(cmd),
|
||||
)
|
||||
console.log(`Found ${topLevelCommands.length} top-level commands`)
|
||||
|
||||
const commands: CommandInfo[] = []
|
||||
|
||||
for (const cmd of topLevelCommands) {
|
||||
console.log(` Discovering: ${cmd}`)
|
||||
const info = await discoverCommand([cmd])
|
||||
commands.push(info)
|
||||
}
|
||||
|
||||
// Generate markdown files
|
||||
console.log("Generating markdown files...")
|
||||
|
||||
// Ensure references directory exists
|
||||
await Deno.mkdir(REFERENCES_DIR, { recursive: true })
|
||||
|
||||
// Get list of preserved files to keep
|
||||
const preservedPaths = new Set(
|
||||
PRESERVED_FILES.map((f) => join(REFERENCES_DIR, f)),
|
||||
)
|
||||
|
||||
// Clean up old generated files (but preserve manual files)
|
||||
for await (const entry of Deno.readDir(REFERENCES_DIR)) {
|
||||
const filePath = join(REFERENCES_DIR, entry.name)
|
||||
if (!preservedPaths.has(filePath) && entry.name.endsWith(".md")) {
|
||||
await Deno.remove(filePath)
|
||||
}
|
||||
}
|
||||
|
||||
// Write command documentation
|
||||
for (const cmd of commands) {
|
||||
const filename = `${cmd.name.replace(/^linear /, "")}.md`
|
||||
const filepath = join(REFERENCES_DIR, filename)
|
||||
const content = generateCommandDoc(cmd)
|
||||
await Deno.writeTextFile(filepath, content + "\n")
|
||||
console.log(` Generated: ${filename}`)
|
||||
}
|
||||
|
||||
// Generate index file
|
||||
const indexContent = generateIndex(commands)
|
||||
await Deno.writeTextFile(join(REFERENCES_DIR, "commands.md"), indexContent)
|
||||
console.log(" Generated: commands.md")
|
||||
|
||||
// Generate SKILL.md from template
|
||||
console.log("Generating SKILL.md from template...")
|
||||
const skillContent = await generateSkillMd(commands)
|
||||
await Deno.writeTextFile(SKILL_MD, skillContent)
|
||||
console.log(" Generated: SKILL.md")
|
||||
|
||||
// Format all generated files
|
||||
console.log("\nFormatting generated files...")
|
||||
const fmtResult = await run([
|
||||
"deno",
|
||||
"fmt",
|
||||
SKILL_DIR,
|
||||
])
|
||||
if (!fmtResult.success) {
|
||||
console.error("Warning: Failed to format generated files")
|
||||
console.error(fmtResult.stderr)
|
||||
}
|
||||
|
||||
console.log(`\nDone! Generated ${commands.length + 2} files.`)
|
||||
return [
|
||||
`# ${cmdName}`,
|
||||
"",
|
||||
`> ${cmd.description}`,
|
||||
"",
|
||||
"## Usage",
|
||||
"",
|
||||
...fencedBlock(cmd.help),
|
||||
...subcommandLines,
|
||||
].join("\n")
|
||||
}
|
||||
|
||||
function generateIndex(commands: CommandInfo[]): string {
|
||||
const lines: string[] = []
|
||||
|
||||
lines.push("# Linear CLI Command Reference")
|
||||
lines.push("")
|
||||
lines.push("## Commands")
|
||||
lines.push("")
|
||||
|
||||
for (const cmd of commands) {
|
||||
const commandLines = commands.map((cmd) => {
|
||||
const cmdName = cmd.name.replace(/^linear /, "")
|
||||
lines.push(`- [${cmdName}](./${cmdName}.md) - ${cmd.description}`)
|
||||
}
|
||||
return `- [${cmdName}](./${cmdName}.md) - ${cmd.description}`
|
||||
})
|
||||
|
||||
lines.push("")
|
||||
lines.push("## Quick Reference")
|
||||
lines.push("")
|
||||
lines.push("```bash")
|
||||
lines.push("# Get help for any command")
|
||||
lines.push("linear <command> --help")
|
||||
lines.push("linear <command> <subcommand> --help")
|
||||
lines.push("```")
|
||||
|
||||
return lines.join("\n") + "\n"
|
||||
return [
|
||||
"# Linear CLI Command Reference",
|
||||
"",
|
||||
"## Commands",
|
||||
"",
|
||||
...commandLines,
|
||||
"",
|
||||
"## Quick Reference",
|
||||
"",
|
||||
"```bash",
|
||||
"# Get help for any command",
|
||||
"linear <command> --help",
|
||||
"linear <command> <subcommand> --help",
|
||||
"```",
|
||||
].join("\n") + "\n"
|
||||
}
|
||||
|
||||
function flattenCommandPaths(cmd: CommandInfo): string[] {
|
||||
const lines = [`linear ${cmd.name}`]
|
||||
|
||||
for (const sub of cmd.subcommands) {
|
||||
lines.push(...flattenCommandPaths(sub))
|
||||
}
|
||||
|
||||
return lines
|
||||
return [`linear ${cmd.name}`, ...cmd.subcommands.flatMap(flattenCommandPaths)]
|
||||
}
|
||||
|
||||
function generateCommandsSection(commands: CommandInfo[]): string {
|
||||
@@ -308,24 +241,122 @@ function generateCommandsSection(commands: CommandInfo[]): string {
|
||||
}
|
||||
|
||||
function generateReferenceToc(commands: CommandInfo[]): string {
|
||||
const lines: string[] = []
|
||||
|
||||
for (const cmd of commands) {
|
||||
lines.push(
|
||||
`- [${cmd.name}](references/${cmd.name}.md) - ${cmd.description}`,
|
||||
return commands
|
||||
.map((cmd) =>
|
||||
`- [${cmd.name}](references/${cmd.name}.md) - ${cmd.description}`
|
||||
)
|
||||
}
|
||||
|
||||
return lines.join("\n")
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
async function generateSkillMd(
|
||||
commands: CommandInfo[],
|
||||
): Promise<string> {
|
||||
async function generateSkillMd(commands: CommandInfo[]): Promise<string> {
|
||||
const template = await Deno.readTextFile(SKILL_TEMPLATE)
|
||||
return template
|
||||
.replace("{{COMMANDS}}", generateCommandsSection(commands))
|
||||
.replace("{{REFERENCE_TOC}}", generateReferenceToc(commands))
|
||||
}
|
||||
|
||||
main()
|
||||
async function writeReferences(commands: CommandInfo[]): Promise<void> {
|
||||
await Deno.mkdir(REFERENCES_DIR, { recursive: true })
|
||||
|
||||
const generated = new Map<string, string>([
|
||||
...commands.map((cmd): [string, string] => {
|
||||
const filename = `${cmd.name.replace(/^linear /, "")}.md`
|
||||
return [filename, generateCommandDoc(cmd) + "\n"]
|
||||
}),
|
||||
["commands.md", generateIndex(commands)],
|
||||
])
|
||||
|
||||
// Write every generated file before removing anything so a partial failure
|
||||
// cannot leave the references directory gutted.
|
||||
for (const [filename, content] of generated) {
|
||||
await Deno.writeTextFile(join(REFERENCES_DIR, filename), content)
|
||||
console.log(` Generated: ${filename}`)
|
||||
}
|
||||
|
||||
// Remove stale generated docs: markdown no longer produced and not preserved.
|
||||
const keep = new Set([...generated.keys(), ...PRESERVED_FILES])
|
||||
for await (const entry of Deno.readDir(REFERENCES_DIR)) {
|
||||
if (entry.isFile && entry.name.endsWith(".md") && !keep.has(entry.name)) {
|
||||
await Deno.remove(join(REFERENCES_DIR, entry.name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log("Generating Linear CLI documentation...")
|
||||
|
||||
// Check linear is available
|
||||
const versionResult = await run(["linear", "--version"])
|
||||
if (!versionResult.success) {
|
||||
throw new Error(
|
||||
`linear CLI not found or failed to run: ${
|
||||
versionResult.stderr || "is it installed?"
|
||||
}`,
|
||||
)
|
||||
}
|
||||
console.log(`Linear CLI: ${stripAnsi(versionResult.stdout)}`)
|
||||
|
||||
// Auto-discover top-level commands from `linear --help`
|
||||
console.log("Discovering commands...")
|
||||
const topLevelHelp = await getCommandHelp([])
|
||||
if (topLevelHelp.failure) {
|
||||
throw new Error(topLevelHelp.failure)
|
||||
}
|
||||
const topLevelNames = parseCommands(topLevelHelp.help).filter(
|
||||
(cmd) => !SKIP_COMMANDS.includes(cmd),
|
||||
)
|
||||
console.log(`Found ${topLevelNames.length} top-level commands`)
|
||||
|
||||
const discovered = await Promise.all(
|
||||
topLevelNames.map((cmd) => discoverCommand([cmd])),
|
||||
)
|
||||
|
||||
// Abort before writing if any help fetch failed, so broken output is never
|
||||
// committed to the docs.
|
||||
const failures = discovered.flatMap((result) => result.failures)
|
||||
if (failures.length > 0) {
|
||||
throw new Error(
|
||||
`Aborting: ${failures.length} command help fetch(es) failed:\n${
|
||||
failures.join("\n")
|
||||
}`,
|
||||
)
|
||||
}
|
||||
|
||||
const commands = discovered.map((result) => result.command).sort(byName)
|
||||
|
||||
// Render SKILL.md from its template before writing anything, so a missing or
|
||||
// broken template aborts before writeReferences prunes any stale docs.
|
||||
console.log("Generating SKILL.md from template...")
|
||||
const skillContent = await generateSkillMd(commands)
|
||||
|
||||
// Generate markdown files
|
||||
console.log("Generating markdown files...")
|
||||
await writeReferences(commands)
|
||||
|
||||
await Deno.writeTextFile(SKILL_MD, skillContent)
|
||||
console.log(" Generated: SKILL.md")
|
||||
|
||||
// Format all generated files
|
||||
console.log("\nFormatting generated files...")
|
||||
const fmtResult = await run(["deno", "fmt", SKILL_DIR])
|
||||
if (!fmtResult.success) {
|
||||
// Fail hard: unformatted docs would break `deno fmt --check` in CI once committed.
|
||||
throw new Error(
|
||||
`Failed to format generated files: ${
|
||||
fmtResult.stderr || "unknown error"
|
||||
}`,
|
||||
)
|
||||
}
|
||||
|
||||
console.log(`\nDone! Generated ${commands.length + 2} files.`)
|
||||
}
|
||||
|
||||
if (import.meta.main) {
|
||||
main().catch((error: unknown) => {
|
||||
// Print a concise message, not a raw stack trace, on any abort path.
|
||||
console.error(
|
||||
`Error: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
Deno.exit(1)
|
||||
})
|
||||
}
|
||||
@@ -5,7 +5,7 @@ disable-model-invocation: true
|
||||
argument-hint: "A workflow to design, or nothing to go find one"
|
||||
---
|
||||
|
||||
Run a stateful `/grilling` session whose only output is **workflow** specs. Use the grilling discipline — relentless, one question at a time, a recommended answer attached to each — aimed at the vocabulary and goal below. Create, edit, and delete specs as the grilling resolves things.
|
||||
Run a stateful `/grilling` session whose only output is **workflow** specs. Use the grilling discipline — relentless, a round of questions at a time, a recommended answer attached to each — aimed at the vocabulary and goal below. Create, edit, and delete specs as the grilling resolves things.
|
||||
|
||||
## The loop lens
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
---
|
||||
name: native-sdk
|
||||
description: Discovery skill for the Native SDK, the complete toolkit for building native desktop applications - views are declarative Native markup (.native), logic is plain Zig, and the toolkit's own engine renders every pixel, with WebView surfaces as the optional web-content path. Use when the user asks what the Native SDK is, how to build a Native SDK app, author native UI, scaffold an app, configure app.zon, add bridge commands, embed web content, package an app, test a running app, or automate a Native SDK app.
|
||||
description: Discovery skill for the Native SDK, the complete toolkit for building native desktop applications. Apps are authored in TypeScript + declarative Native markup (.native) by default and compiled to native code with no JS runtime in the binary; Zig cores are an explicit alternative, and WebViews are the optional web-content path. Use when the user asks what the Native SDK is, how to build a Native SDK app, author native UI, scaffold an app, configure app.zon, add bridge commands, embed web content, package an app, test a running app, or automate a Native SDK app.
|
||||
allowed-tools: Bash(native:*), Bash(npx @native-sdk/cli:*)
|
||||
hidden: true
|
||||
---
|
||||
|
||||
# Native SDK
|
||||
|
||||
The Native SDK is the complete toolkit for building native desktop applications. Views are declarative markup in `.native` files, logic is plain Zig, and the toolkit's own engine draws every pixel into real OS windows — no browser, no WebView, no interpreter in the binary. Every app embeds a deterministic automation server, so agents can snapshot, drive, and screenshot the running window. Desktop is the mature surface (macOS deepest, Linux and Windows exercised in CI); mobile embedding is experimental. WebView surfaces coexist as the optional path for embedding web content or hosting an existing web frontend.
|
||||
The Native SDK is the complete toolkit for building native desktop applications. **The primary authoring path is TypeScript app logic in `src/core.ts` plus declarative Native markup in `.native` files.** The TypeScript core is checked and compiled ahead of time to native code, so the shipped binary contains no browser, WebView, JS runtime, or interpreter. Zig is how the toolkit itself works and is a first-class, explicitly chosen app-core alternative (`--template zig-core`); it is not the default inferred from the SDK's implementation. Every app embeds a deterministic automation server, so agents can snapshot, drive, and screenshot the running window. Desktop is the mature surface (macOS deepest, Linux and Windows exercised in CI); mobile embedding is experimental. WebView surfaces coexist as the optional path for embedding web content or hosting an existing web frontend.
|
||||
|
||||
## Start here
|
||||
|
||||
@@ -16,10 +16,11 @@ This file is a discovery stub for agents that installed the Native SDK once with
|
||||
```bash
|
||||
native skills list
|
||||
native skills get core
|
||||
native skills get core --full
|
||||
native skills get native-ui
|
||||
native skills get ts-core
|
||||
```
|
||||
|
||||
Use `native skills get core` for initial orientation. Use `native skills get core --full` for implementation tasks because it includes the reference files for project anatomy, runtime, frontend assets, bridge/security/native capabilities, packaging, and debugging. Use `native skills get automation` when testing a running app, taking snapshots, requesting reloads, or using the built-in automation server. Use `native skills get zig` when `zig build` fails with "no member named" errors on std APIs — the SDK requires Zig 0.16.0, and that skill maps each pre-0.16 idiom's compile error to the current one.
|
||||
Use `native skills get core` for initial orientation. For the default app-authoring path, load **both** `native-ui` (views, bindings, the app loop) and `ts-core` (the TypeScript subset, effects, subscriptions, and modules) before implementing. Use `native skills get core --full` when work reaches lower-level runtime wiring, WebViews, bridge/security, native capabilities, packaging, or debugging. Use `native skills get automation` when testing a running app, taking snapshots, requesting reloads, or using the built-in automation server. Use `native skills get zig` only for an existing Zig core, a toolkit extension, SDK implementation work, or a Zig 0.16 compile error.
|
||||
|
||||
## Quick orientation
|
||||
|
||||
@@ -30,4 +31,4 @@ cd my_app
|
||||
native dev
|
||||
```
|
||||
|
||||
Generated apps center on `app.zon`, `src/app.native` (the markup view), and `src/main.zig` (Model, Msg, update). Inspect those files before editing an existing app; web-frontend shells additionally carry `frontend/` and a `build.zig`.
|
||||
`native init my_app` generates the primary three-file app: `app.zon`, `src/app.native` (the markup view), and `src/core.ts` (`Model`, `Msg`, `update`). Inspect the tree before editing an existing app and preserve the core language it already uses. `src/main.zig` means the app explicitly uses the Zig-core template; web-frontend shells additionally carry `frontend/` and usually owned build/runtime wiring.
|
||||
@@ -1,13 +1,15 @@
|
||||
# Logic Prototype
|
||||
|
||||
A tiny interactive terminal app that lets the user drive a state model by hand. Use this when the question is about **business logic, state transitions, or data shape** — the kind of thing that looks reasonable on paper but only feels wrong once you push it through real cases.
|
||||
A single, self-contained HTML file — a **shareable demo** — that lets anyone drive a state model by clicking buttons. Use this when the question is about **business logic, state transitions, or data shape** — the kind of thing that looks reasonable on paper but only feels wrong once you push it through real cases.
|
||||
|
||||
Because it's one file with nothing to install, you can hand it to a non-developer — a designer, a PM, a domain expert — and let them feel the model for themselves. So it speaks their language, not the code's.
|
||||
|
||||
## When this is the right shape
|
||||
|
||||
- "I'm not sure if this state machine handles the edge case where X then Y."
|
||||
- "Does this data model actually let me represent the case where..."
|
||||
- "I want to feel out what the API should look like before writing it."
|
||||
- Anything where the user wants to **press buttons and watch state change**.
|
||||
- Anything where someone wants to **press buttons and watch state change**.
|
||||
|
||||
If the question is "what should this look like" — wrong branch. Use [UI.md](UI.md).
|
||||
|
||||
@@ -15,17 +17,11 @@ If the question is "what should this look like" — wrong branch. Use [UI.md](UI
|
||||
|
||||
### 1. State the question
|
||||
|
||||
Before writing code, write down what state model and what question you're prototyping. One paragraph, in the prototype's README or a comment at the top of the file. A logic prototype that answers the wrong question is pure waste — make the question explicit so it can be checked later, whether the user is watching now or returning to it AFK.
|
||||
Before writing code, write down what state model and what question you're prototyping. One paragraph, at the top of the demo (in a visible intro, not just a comment). A logic prototype that answers the wrong question is pure waste — make the question explicit so it can be checked later, whether the user is watching now or returning to it AFK.
|
||||
|
||||
### 2. Pick the language
|
||||
### 2. Isolate the logic in a portable module
|
||||
|
||||
Use whatever the host project uses. If the project has no obvious runtime (e.g. a docs repo), ask.
|
||||
|
||||
Match the project's existing conventions for tooling — don't add a new package manager or runtime just for the prototype.
|
||||
|
||||
### 3. Isolate the logic in a portable module
|
||||
|
||||
Put the actual logic — the bit that's answering the question — behind a small, pure interface that could be lifted out and dropped into the real codebase later. The TUI around it is throwaway; the logic module shouldn't be.
|
||||
Put the actual logic — the bit that's answering the question — in a single `<script>` block written as a small, pure module that could be lifted out and dropped into the real codebase later. The page around it is throwaway; this module isn't.
|
||||
|
||||
The right shape depends on the question:
|
||||
|
||||
@@ -34,46 +30,38 @@ The right shape depends on the question:
|
||||
- **A small set of pure functions** over a plain data type. Good when there's no implicit current state — just transformations.
|
||||
- **A class or module with a clear method surface** when the logic genuinely owns ongoing internal state.
|
||||
|
||||
Pick whichever shape best fits the question being asked, *not* whichever is easiest to wire to a TUI. Keep it pure: no I/O, no terminal code, no `console.log` for control flow. The TUI imports it and calls into it; nothing flows the other direction.
|
||||
Pick whichever shape best fits the question being asked, *not* whichever is easiest to wire to a page. Keep it pure: no DOM, no `document`, no button handlers reaching inside it. The page calls into it; nothing flows the other direction. This is what makes the prototype useful past its own lifetime: once the question's answered, the validated reducer / machine / function set lifts into the real module on its own.
|
||||
|
||||
This is what makes the prototype useful past its own lifetime: when the question's been answered, the validated reducer / machine / function set can be lifted into the real module on its own.
|
||||
### 3. Build the shareable HTML file
|
||||
|
||||
### 4. Build the smallest TUI that exposes the state
|
||||
One file, plain HTML/CSS/JS — no framework, no bundler, no server, everything inline so it opens by double-click and survives being emailed around. Anyone should be able to run it by opening it.
|
||||
|
||||
Build it as a **lightweight TUI** — on every tick, clear the screen (`console.clear()` / `print("\033[2J\033[H")` / equivalent) and re-render the whole frame. The user should always see one stable view, not an ever-growing scrollback.
|
||||
Write it for a non-developer. Every label is in **domain language**, not code — buttons and state read like the business, not the reducer. Explain in plain words what's happening.
|
||||
|
||||
Each frame has two parts, in this order:
|
||||
Lay it out with a clean hierarchy, top to bottom:
|
||||
|
||||
1. **Current state**, pretty-printed and diff-friendly (one field per line, or formatted JSON). Use **bold** for field names or section headers and **dim** for less important context (timestamps, IDs, derived values). Native ANSI escape codes are fine — `\x1b[1m` bold, `\x1b[2m` dim, `\x1b[0m` reset. No need to pull in a styling library unless one is already in the project.
|
||||
2. **Keyboard shortcuts**, listed at the bottom: `[a] add user [d] delete user [t] tick clock [q] quit`. Bold the key, dim the description, or vice-versa — whatever reads cleanly.
|
||||
1. **Title and one-line explanation** of what this demo lets you explore (the question from step 1).
|
||||
2. **Current state** — the full relevant state, rendered as a readable panel (labelled fields, not a raw JSON dump), re-rendered after every click so the change is visible. Where it helps a non-developer follow, call out what just changed.
|
||||
3. **Free-play buttons** — one button per action, always available, so anyone can poke at the model in any order. Each click dispatches its action and re-renders the state.
|
||||
4. **Guided walkthroughs** — a set of **scenarios**, one per tab. Each tab holds a short plain-language description of the scenario — the situation it sets up and what to watch for — and underneath it, the ordered **buttons to press** for that scenario. Each step is a real button: clicking it performs that action and moves to the next step. Starting a walkthrough resets to a known initial state so the scenario runs the same way every time.
|
||||
|
||||
Behaviour:
|
||||
Choose scenarios that demonstrate the awkward cases — the happy path, a tricky edge case, an attempt at something that should be illegal — the ones hard to reason about on paper.
|
||||
|
||||
1. **Initialise state** — a single in-memory object/struct. Render the first frame on start.
|
||||
2. **Read one keystroke (or one line)** at a time, dispatch to a handler that mutates state.
|
||||
3. **Re-render** the full frame after every action — don't append, replace.
|
||||
4. **Loop until quit.**
|
||||
Keep it beautiful but restrained: clean typography, generous spacing, one accent colour. No animations, no gimmicks — nothing that competes with the state and the buttons.
|
||||
|
||||
The whole frame should fit on one screen.
|
||||
### 4. Hand it over
|
||||
|
||||
### 5. Make it runnable in one command
|
||||
Send them the file, or open it for them. They'll click through the walkthroughs and free-play whenever they get to it; the interesting moments are when they say "wait, that shouldn't be possible" or "huh, I assumed X would be different" — those are the bugs in the _idea_, which is the whole point. If they want new actions or a new scenario, add them. Prototypes evolve.
|
||||
|
||||
Add a script to the project's existing task runner (`package.json` scripts, `Makefile`, `justfile`, `pyproject.toml`). The user should run `pnpm run <prototype-name>` or equivalent — never need to remember a path.
|
||||
### 5. Capture the answer and the prototype
|
||||
|
||||
If the host project has no task runner, just put the command at the top of the prototype's README.
|
||||
|
||||
### 6. Hand it over
|
||||
|
||||
Give the user the run command. They'll drive it themselves; the interesting moments are when they say "wait, that shouldn't be possible" or "huh, I assumed X would be different" — those are the bugs in the _idea_, which is the whole point. If they want new actions added, add them. Prototypes evolve.
|
||||
|
||||
### 7. Capture the answer and the prototype
|
||||
|
||||
Once the prototype has answered its question, capture the answer, then capture the prototype the way the [SKILL](SKILL.md) describes. The logic-specific mapping: the validated reducer / machine / function set lifts into the real module (the decision, absorbed); the TUI shell rides along to the throwaway branch that keeps the prototype as a primary source.
|
||||
Once the prototype has answered its question, capture the answer, then capture the prototype the way the [SKILL](SKILL.md) describes. The logic-specific mapping: the validated reducer / machine / function set lifts into the real module (the decision, absorbed); the HTML shell rides along to the throwaway branch that keeps the prototype as a primary source — and being one self-contained file, it stays trivially re-runnable there.
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
- **Don't add tests.** A prototype that needs tests is no longer a prototype.
|
||||
- **Don't wire it to the real database.** Use an in-memory store unless the question is specifically about persistence.
|
||||
- **Don't wire it to the real database.** Use in-memory state unless the question is specifically about persistence.
|
||||
- **Don't generalise.** No "what if we wanted to support X later." The prototype answers one question.
|
||||
- **Don't blur the logic and the TUI together.** If the reducer / state machine references `console.log`, prompts, or terminal escape codes, it's no longer portable. Keep the TUI as a thin shell over a pure module.
|
||||
- **Don't ship the TUI shell into production.** The shell is optimised for being driven by hand from a terminal. The logic module behind it is the bit worth keeping.
|
||||
- **Don't blur the logic and the page together.** If the pure module references the DOM, `document`, or button handlers, it's no longer liftable. Keep the page as a thin shell over a pure module.
|
||||
- **Don't reach for a framework, bundler, or server.** One file the recipient double-clicks; a React app or a dev server defeats "shareable".
|
||||
- **Don't ship the HTML shell into production.** The page is optimised for being clicked through by hand. The logic module behind it is the bit worth keeping.
|
||||
@@ -11,7 +11,7 @@ A prototype is **throwaway code that answers a question**. The question decides
|
||||
|
||||
Identify which question is being answered — from the user's prompt, the surrounding code, or by asking if the user is around:
|
||||
|
||||
- **"Does this logic / state model feel right?"** → [LOGIC.md](LOGIC.md). Build a tiny interactive terminal app that pushes the state machine through cases that are hard to reason about on paper.
|
||||
- **"Does this logic / state model feel right?"** → [LOGIC.md](LOGIC.md). Build a single shareable HTML file — free-play buttons plus tabbed guided walkthroughs — that pushes the state machine through cases that are hard to reason about on paper, and that a non-developer can drive.
|
||||
- **"What should this look like?"** → [UI.md](UI.md). Generate several radically different UI variations on a single route, switchable via a URL search param and a floating bottom bar.
|
||||
|
||||
The two branches produce very different artifacts — getting this wrong wastes the whole prototype. If the question is genuinely ambiguous and the user isn't reachable, default to whichever branch better matches the surrounding code (a backend module → logic; a page or component → UI) and state the assumption at the top of the prototype.
|
||||
@@ -19,7 +19,7 @@ The two branches produce very different artifacts — getting this wrong wastes
|
||||
## Rules that apply to both
|
||||
|
||||
1. **Throwaway from day one, and clearly marked as such.** Locate the prototype code close to where it will actually be used (next to the module or page it's prototyping for) so context is obvious — but name it so a casual reader can see it's a prototype, not production. For throwaway UI routes, obey whatever routing convention the project already uses; don't invent a new top-level structure.
|
||||
2. **One command to run.** Whatever the project's existing task runner supports — `pnpm <name>`, `python <path>`, `bun <path>`, etc. The user must be able to start it without thinking.
|
||||
2. **Trivial to run.** A UI prototype starts from one command in the project's task runner — `pnpm <name>`, `python <path>`, `bun <path>`, etc. A logic demo is a single HTML file the user double-clicks. Either way, no thinking required to start it.
|
||||
3. **No persistence by default.** State lives in memory. Persistence is the thing the prototype is _checking_, not something it should depend on. If the question explicitly involves a database, hit a scratch DB or a local file with a clear "PROTOTYPE — wipe me" name.
|
||||
4. **Skip the polish.** No tests, no error handling beyond what makes the prototype _runnable_, no abstractions. The point is to learn something fast.
|
||||
5. **Surface the state.** After every action (logic) or on every variant switch (UI), print or render the full relevant state so the user can see what changed.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: remix
|
||||
description: Build and review Remix 3 applications using the `remix` npm package and subpath imports. Use when working on Remix app structure, routes, controllers, middleware, validation, data access, auth, sessions, file uploads, server setup, UI components, hydration, navigation, or tests.
|
||||
description: Build and review Remix 3 applications using the `remix` npm package and subpath imports. Use when working on Remix app structure, routes, controllers, middleware, validation, data access, auth, sessions, file uploads, server setup, UI components, hydration, HMR, navigation, or tests.
|
||||
---
|
||||
|
||||
# Build a Remix App
|
||||
@@ -42,19 +42,19 @@ Classify the task first, then load the smallest useful reference set. Each refer
|
||||
|
||||
Use the table below to find candidates. Loading more than two or three files at once is usually a sign that the task hasn't been narrowed enough yet.
|
||||
|
||||
| Task involves... | Start with |
|
||||
| ----------------------------------------------------------------------------- | ------------------------------------------- |
|
||||
| Defining URLs, writing controllers and actions, returning responses | `references/routing-and-controllers.md` |
|
||||
| Composing the request lifecycle, ordering middleware, bridging to a server | `references/middleware-and-server.md` |
|
||||
| Compiling and serving browser modules, asset URL namespaces, preloads | `references/assets-and-browser-modules.md` |
|
||||
| Parsing input, validating with schemas, defining tables, querying, migrations | `references/data-and-validation.md` |
|
||||
| Per-browser state, login flows, route protection, identity | `references/auth-and-sessions.md` |
|
||||
| Component setup, state, lifecycle, updates, `queueTask`, context | `references/component-model.md` |
|
||||
| Event handlers, styles, refs, click/key behavior, simple animations | `references/mixins-styling-events.md` |
|
||||
| `clientEntry`, `run`, `<Frame>`, navigation, `<head>` | `references/hydration-frames-navigation.md` |
|
||||
| Router tests, component tests, test isolation | `references/testing-patterns.md` |
|
||||
| Spring physics, tweens, layout transitions | `references/animate-elements.md` |
|
||||
| Authoring custom reusable mixins | `references/create-mixins.md` |
|
||||
| Task involves... | Start with |
|
||||
| ------------------------------------------------------------------------------------------- | ------------------------------------------- |
|
||||
| Defining URLs, writing controllers and actions, returning responses | `references/routing-and-controllers.md` |
|
||||
| Composing the request lifecycle, ordering middleware, bridging to a server, development HMR | `references/middleware-and-server.md` |
|
||||
| Compiling and serving browser modules, asset URL namespaces, preloads, browser HMR | `references/assets-and-browser-modules.md` |
|
||||
| Parsing input, validating with schemas, defining tables, querying, migrations | `references/data-and-validation.md` |
|
||||
| Per-browser state, login flows, route protection, identity | `references/auth-and-sessions.md` |
|
||||
| Component setup, state, lifecycle, updates, `queueTask`, context | `references/component-model.md` |
|
||||
| Event handlers, styles, refs, click/key behavior, simple animations | `references/mixins-styling-events.md` |
|
||||
| `clientEntry`, `run`, `<Frame>`, navigation, browser HMR update handling, `<head>` | `references/hydration-frames-navigation.md` |
|
||||
| Router tests, component tests, test isolation | `references/testing-patterns.md` |
|
||||
| Spring physics, tweens, layout transitions | `references/animate-elements.md` |
|
||||
| Authoring custom reusable mixins | `references/create-mixins.md` |
|
||||
|
||||
Common bundles:
|
||||
|
||||
@@ -62,6 +62,7 @@ Common bundles:
|
||||
- **Protected area** -> auth and sessions, routing, testing
|
||||
- **Interactive widget** -> component model, mixins and styling; add hydration only if it runs in the browser
|
||||
- **Browser asset pipeline** -> assets and browser modules, hydration, middleware and server
|
||||
- **Development HMR** -> middleware and server, assets and browser modules, hydration
|
||||
- **File upload** -> middleware and server, data and validation, testing
|
||||
- **Navigation or frames** -> hydration, frames, navigation
|
||||
|
||||
@@ -205,7 +206,11 @@ Use this map to find the right package quickly. Each entry says what the package
|
||||
- `remix/router` — the router itself. Use for `createRouter`, controllers, middleware types, and registering routes
|
||||
- `remix/routes` — declarative route builders. Use for `route`, `get`, `post`, `put`, `del`, `form`, `resources` when defining `app/routes.ts`
|
||||
- `remix/node-fetch-server` — default Node adapter for new apps. Use `createRequestListener` with `node:http`, `node:https`, or `node:http2` in `server.ts` when booting the template-style app
|
||||
- `remix/assets` — browser asset server. Use for `createAssetServer` when serving compiled scripts and styles, getting public hrefs, and emitting preloads. Configure a `basePath`, and keep `fileMap` URL patterns relative to it. Shared compiler options such as `target`, `sourceMaps`, `sourceMapSourcePaths`, and `minify` live at the top level
|
||||
- `remix/node-hmr` — optional development Node HMR runner for rapid UI edits. Use `run` in `hmr.ts` to supervise `server.ts` behind an `hmr` script, and use `createHmrReadyFetch` when a stable public proxy should wait for child server readiness during updates
|
||||
- `remix/node-hmr/runtime` — child-process runtime API for code running under `remix/node-hmr`. Use to create browser HMR channels for asset servers and to emit server readiness after the child server starts listening
|
||||
- `remix/node-hmr/types` — type-only entry for `import.meta.hot` in Node modules
|
||||
- `remix/assets` — browser asset server. Use for `createAssetServer` when serving compiled scripts and styles, getting public hrefs, emitting preloads, and wiring browser HMR. Configure a `basePath`, keep `fileMap` URL patterns relative to it, use `allowFiles`/`denyFiles` for path and glob rules, and use exact package names in `allowPackages` for package-level access. Shared compiler options such as `target`, `sourceMaps`, `sourceMapSourcePaths`, and `minify` live at the top level
|
||||
- `remix/assets/types/hmr` — type-only entry for `import.meta.hot` in browser modules compiled by `remix/assets`
|
||||
- `remix/headers` — `SuperHeaders` plus typed header parsers and builders. Use the default export when you want a `Headers` subclass with typed accessors like `headers.contentType`, `headers.cacheControl`, and `headers.setCookie`; use named classes such as `CacheControl`, `ContentDisposition`, and `Vary` when working with individual header values
|
||||
- `remix/response/redirect` — `redirect(href, status?)`. Use for the canonical "POST then redirect" pattern and other location changes
|
||||
- `remix/response/html` — `createHtmlResponse`. Use when you need an HTML `Response` from a string or stream without rendering through `remix/ui`
|
||||
@@ -244,6 +249,10 @@ Use this map to find the right package quickly. Each entry says what the package
|
||||
|
||||
- `remix/ui` — the component runtime: components, core mixins, `clientEntry`, `run`, `<Frame>`, navigation helpers, and `createRoot`. Use for app UI behavior
|
||||
- `remix/ui/server` — server rendering: `renderToStream`, `renderToString`. Use in the `app/actions/render.tsx` helper that returns HTML responses
|
||||
- `remix/ui-hmr` — direct Remix UI component HMR transforms. Use only when writing a custom module hook or build integration
|
||||
- `remix/ui-hmr/node` — Node import hook for Remix UI component HMR. Use with `--import remix/ui-hmr/node` in development servers that run through `remix/node-hmr`
|
||||
- `remix/ui-hmr/assets` — `remix/assets` loader for Remix UI component HMR. Use `uiHmr()` in `createAssetServer({ scripts: { loaders } })` during development
|
||||
- `remix/ui/dev/refresh` — development refresh support used by HMR tooling, not normal application code
|
||||
- `remix/ui/animation` — animation APIs: `animateEntrance`, `animateExit`, `animateLayout`, `spring`, `tween`, and `easings`
|
||||
- `remix/ui/<primitive>` — UI primitives, mixins, and component helpers. Current subpaths include `remix/ui/accordion`, `remix/ui/anchor`, `remix/ui/button`, `remix/ui/checkbox`, `remix/ui/combobox`, `remix/ui/input`, `remix/ui/listbox`, `remix/ui/menu`, `remix/ui/popover`, and `remix/ui/select`
|
||||
- `remix/ui/test` — component test rendering helpers such as `render`
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
|
||||
How to serve browser scripts and styles from source. Read this when the task involves:
|
||||
|
||||
- Configuring `createAssetServer` (`basePath`, `fileMap`, `allow`, `deny`, fingerprinting, compiler options)
|
||||
- Configuring `createAssetServer` (`basePath`, `fileMap`, `allowFiles`, `allowPackages`, `denyFiles`, fingerprinting, compiler options)
|
||||
- Choosing between `staticFiles()` for already-built files and `createAssetServer()` for source assets that need import rewriting, preloads, or fingerprinted URLs
|
||||
- Generating script URLs or `<link rel="modulepreload">` tags for a client entry
|
||||
- Keeping server-only files out of the browser via `deny` rules
|
||||
- Enabling browser HMR for source-served modules
|
||||
- Keeping server-only files out of the browser via `denyFiles` rules
|
||||
|
||||
For routing the URL namespace itself, see `routing-and-controllers.md`. For client entry hydration, see `hydration-frames-navigation.md`.
|
||||
For routing the URL namespace itself, see `routing-and-controllers.md`. For client entry hydration and browser update handling, see `hydration-frames-navigation.md`. For the Node HMR runner and browser HMR channel, see `middleware-and-server.md`.
|
||||
|
||||
## When To Reach For It
|
||||
|
||||
@@ -35,8 +36,9 @@ let assetServer = createAssetServer({
|
||||
'app/*path': 'app/*path',
|
||||
'node_modules/*path': 'node_modules/*path',
|
||||
},
|
||||
allow: ['app/assets/**', 'node_modules/**'],
|
||||
deny: ['app/**/*.server.*'],
|
||||
allowFiles: ['app/assets/**'],
|
||||
allowPackages: ['remix'],
|
||||
denyFiles: ['app/**/*.server.*'],
|
||||
target: { es: '2020', chrome: '109', safari: '16.4' },
|
||||
sourceMaps: process.env.NODE_ENV === 'development' ? 'external' : undefined,
|
||||
minify: process.env.NODE_ENV === 'production',
|
||||
@@ -58,8 +60,11 @@ export default createController(routes, {
|
||||
|
||||
## Rules
|
||||
|
||||
- Treat `allow` and `deny` as the security boundary for browser-reachable source files.
|
||||
- Add a `deny` list for server-only modules such as `*.server.*`, private config, or other files that should never be exposed.
|
||||
- Treat `allowFiles`/`allowPackages` and `denyFiles` as the security boundary for browser-reachable source files.
|
||||
- Use `allowFiles` and `denyFiles` for file paths and globs. Relative values resolve from `rootDir`.
|
||||
- Use `allowPackages` for exact package names, not globs or subpaths. Packages allowed by `allowPackages` also allow their installed `dependencies` and `optionalDependencies`; peer dependencies must be listed explicitly if they should be browser-reachable.
|
||||
- `denyFiles` takes precedence over both file and package allow rules.
|
||||
- Add `denyFiles` rules for server-only modules such as `*.server.*`, private config, or other files that should never be exposed.
|
||||
- Set `rootDir` explicitly in monorepos so relative paths resolve from the intended project root.
|
||||
- `basePath` is the public URL namespace handled by the asset server.
|
||||
- `fileMap` keys are URL patterns relative to `basePath`, and values are root-relative file path patterns. They use `route-pattern` syntax on both sides.
|
||||
@@ -86,6 +91,8 @@ In development:
|
||||
- Keep `watch` enabled so source changes are picked up without restarting the server
|
||||
- Prefer stable URLs with normal revalidation
|
||||
- Enable source maps when debugging browser code
|
||||
- Use `hmr` only when the app is running under `remix/node-hmr`
|
||||
- Use `scripts.loaders` for development-only browser transforms such as `uiHmr()`
|
||||
|
||||
In deployment:
|
||||
|
||||
@@ -95,6 +102,38 @@ In deployment:
|
||||
|
||||
Fingerprinting assumes files on disk are stable and requires `watch: false`.
|
||||
|
||||
## Browser HMR
|
||||
|
||||
Use browser HMR when source-served browser modules should update without a full page reload during development. Let `remix/node-hmr` own the browser HMR channel so browser updates stay coordinated with server restarts.
|
||||
|
||||
```typescript
|
||||
import { createAssetServer } from 'remix/assets'
|
||||
import { uiHmr } from 'remix/ui-hmr/assets'
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV === 'development'
|
||||
const isHmr = Boolean(isDevelopment && process.env.REMIX_NODE_HMR)
|
||||
|
||||
const assetServer = createAssetServer({
|
||||
basePath: '/assets',
|
||||
fileMap: { '/app/*path': 'app/*path' },
|
||||
allowFiles: ['app/assets/**'],
|
||||
watch: isDevelopment,
|
||||
hmr: isHmr
|
||||
? async () => (await import('remix/node-hmr/runtime')).createBrowserHmrChannel()
|
||||
: undefined,
|
||||
scripts: {
|
||||
loaders: isHmr ? [uiHmr()] : undefined,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Guard `remix/node-hmr/runtime` imports with `process.env.REMIX_NODE_HMR`; that runtime API is only available inside the supervised child process.
|
||||
- Keep browser HMR and loaders development-only.
|
||||
- Add `remix/assets/types/hmr` to `compilerOptions.types` only when browser source modules use `import.meta.hot` directly.
|
||||
- Write HMR accept calls directly as `import.meta.hot.accept(...)` with literal dependency specifiers.
|
||||
|
||||
## Useful Compiler Options
|
||||
|
||||
- `minify` for production minification of scripts and styles
|
||||
@@ -103,6 +142,7 @@ Fingerprinting assumes files on disk are stable and requires `watch: false`.
|
||||
- `target` as an object for shared browser targets and script-only ECMAScript output, such as `{ es: '2020', chrome: '109', safari: '16.4' }`
|
||||
- `scripts.define` to replace globals such as `process.env.NODE_ENV`
|
||||
- `scripts.external` to leave specific script imports untouched
|
||||
- `scripts.loaders` to transform browser modules during compilation
|
||||
|
||||
Do not nest shared compiler options under `scripts`. Use top-level `minify`, `sourceMaps`, `sourceMapSourcePaths`, and `target` so they apply to styles as well as scripts.
|
||||
|
||||
|
||||
@@ -7,11 +7,12 @@ How server-rendered UI becomes interactive in the browser, and how the page upda
|
||||
- Marking a component for client-side hydration with `clientEntry`
|
||||
- Booting the client runtime with `run`
|
||||
- Streaming server content into a region of the page with `<Frame>` and reloading those regions
|
||||
- Handling browser HMR updates for hydrated entries
|
||||
- Triggering Navigation API transitions with `navigate(...)` or `link(...)`
|
||||
- Server rendering with `renderToStream` or `renderToString`
|
||||
- Managing the document `<head>`
|
||||
|
||||
For component-local state and updates, see `component-model.md`. For host-element behavior and events, see `mixins-styling-events.md`.
|
||||
For component-local state and updates, see `component-model.md`. For host-element behavior and events, see `mixins-styling-events.md`. For browser asset HMR setup, see `assets-and-browser-modules.md`.
|
||||
|
||||
## Server First, Then Hydrate
|
||||
|
||||
@@ -96,7 +97,7 @@ Use `run` to start the client runtime. It scans the document for client entry ma
|
||||
```tsx
|
||||
import { run } from 'remix/ui'
|
||||
|
||||
let app = run({
|
||||
const app = run({
|
||||
async loadModule(moduleUrl, exportName) {
|
||||
let mod = await import(moduleUrl)
|
||||
return mod[exportName]
|
||||
@@ -129,6 +130,19 @@ await app.ready()
|
||||
|
||||
`app` is an `EventTarget` that emits `error` events from any hydrated component.
|
||||
|
||||
## Browser HMR Updates
|
||||
|
||||
When `remix/node-hmr` reports a server update, reload the top frame to apply the latest server-rendered document while preserving browser state:
|
||||
|
||||
```tsx
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.on('server:update', async () => {
|
||||
await app.ready()
|
||||
await app.frames.top.reload()
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Frames
|
||||
|
||||
A `<Frame>` renders server content into the page. Frames stream after the initial HTML, nest inside other frames, contain client entries, and can be reloaded without full page navigation.
|
||||
|
||||
@@ -8,8 +8,9 @@ How to compose the request lifecycle and bridge the router to a runtime. Read th
|
||||
- Writing custom middleware that sets typed context values
|
||||
- Adding fast-exit handling (static files, CORS preflights) versus request-enriching layers (sessions, auth, data loading)
|
||||
- Choosing when to keep the generated Node server versus switching server adapters
|
||||
- Running the app server under development HMR
|
||||
|
||||
For data and persistence specifics, see `data-and-validation.md`. For session and auth specifics, see `auth-and-sessions.md`.
|
||||
For data and persistence specifics, see `data-and-validation.md`. For session and auth specifics, see `auth-and-sessions.md`. For browser asset HMR, see `assets-and-browser-modules.md`.
|
||||
|
||||
## Middleware Stack
|
||||
|
||||
@@ -80,6 +81,7 @@ let router = createRouter({ middleware })
|
||||
- **Session-backed HTML app** -> `compression()`, `staticFiles()`, optional `cop()`, `formData()`, `methodOverride()`, `session()`, optional `csrf()`, `asyncContext()`, `auth({ schemes })`
|
||||
- **Cross-origin API** -> `compression()`, `cors()`, optional `asyncContext()`, optional `auth({ schemes })`
|
||||
- **Upload flow** -> `compression()`, `staticFiles()`, `formData({ uploadHandler })`, then sessions, auth, and data-loading middleware as needed
|
||||
- **Optional development HMR** -> keep `server.ts` as the child app server, add `hmr.ts` for `remix/node-hmr`, and proxy public requests through `createHmrReadyFetch()`
|
||||
|
||||
### Middleware with options
|
||||
|
||||
@@ -214,3 +216,62 @@ Prefer inline arrays for `middleware` options. Use `RouterContext<typeof router>
|
||||
New apps already include a `server.ts` that adapts the app router with `remix/node-fetch-server`. Keep that generated server unless the task specifically needs to change runtime behavior such as host/protocol handling, TLS, HTTP/2, WebSockets, deployment lifecycle, or test-only server setup.
|
||||
|
||||
Use `remix/node-fetch-server` when you want to keep owning a standard Node `http`, `https`, or `http2` server directly.
|
||||
|
||||
## Development HMR
|
||||
|
||||
Treat HMR as an optional mode for rapid UI edits. Keep HMR supervision out of normal app code:
|
||||
put the real app server in `server.ts`, keep the `dev` script running it directly (usually with
|
||||
Node's watch mode), then add a development-only `hmr.ts` behind an `hmr` script when the project
|
||||
benefits from HMR.
|
||||
|
||||
```typescript
|
||||
// hmr.ts
|
||||
import * as http from 'node:http'
|
||||
|
||||
import { createFetchProxy } from 'remix/fetch-proxy'
|
||||
import { createHmrReadyFetch, run } from 'remix/node-hmr'
|
||||
import { createRequestListener } from 'remix/node-fetch-server'
|
||||
|
||||
const hmrProxyPort = 44100
|
||||
const hmrEventPort = 44101
|
||||
const appPort = 44102
|
||||
|
||||
const hmrRunner = run('./server.ts', {
|
||||
env: {
|
||||
...process.env,
|
||||
PORT: String(appPort),
|
||||
HMR_PROXY_PORT: String(hmrProxyPort),
|
||||
},
|
||||
nodeArgs: ['--import', 'remix/node-tsx', '--import', 'remix/ui-hmr/node'],
|
||||
browserHmrChannel: { port: hmrEventPort },
|
||||
})
|
||||
|
||||
let proxyFetch = createFetchProxy(`http://127.0.0.1:${appPort}`, {
|
||||
xForwardedHeaders: true,
|
||||
})
|
||||
|
||||
let server = http.createServer(createRequestListener(createHmrReadyFetch(hmrRunner, proxyFetch)))
|
||||
|
||||
server.listen(hmrProxyPort, '127.0.0.1')
|
||||
```
|
||||
|
||||
Keep `browserHmrChannel.port` stable so browser HMR clients can reconnect to the same event channel if the dev server is manually restarted.
|
||||
|
||||
Use a stable public proxy when browser requests may happen while the child server is restarting. `createHmrReadyFetch()` waits for the active child generation before forwarding requests and retries safe unavailable responses when the child changes during a request.
|
||||
|
||||
In the child `server.ts`, report readiness after the server is listening:
|
||||
|
||||
```typescript
|
||||
server.listen(port, () => {
|
||||
if (process.env.REMIX_NODE_HMR) {
|
||||
import('remix/node-hmr/runtime').then((nodeHmr) => nodeHmr.emitServerReady())
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Guard `remix/node-hmr/runtime` imports with `process.env.REMIX_NODE_HMR`.
|
||||
- Use `--import remix/ui-hmr/node` only when server-rendered Remix UI component modules should hot update.
|
||||
- Keep the default development and production startup paths independent from `hmr.ts`.
|
||||
- Close the public server and `hmrRunner` during `SIGINT` and `SIGTERM` shutdown.
|
||||
@@ -70,28 +70,29 @@ try {
|
||||
|
||||
## Test Runner Config
|
||||
|
||||
Configure discovery and coverage in `remix-test.config.ts` or with CLI flags:
|
||||
Configure discovery and coverage in the `test` section of `remix.json` or with CLI flags:
|
||||
|
||||
```ts
|
||||
export default {
|
||||
glob: {
|
||||
test: '**/*.test{,.e2e}.{ts,tsx}',
|
||||
e2e: '**/*.test.e2e.{ts,tsx}',
|
||||
exclude: 'node_modules/**',
|
||||
},
|
||||
coverage: {
|
||||
dir: '.coverage',
|
||||
include: ['app/**/*.{ts,tsx}'],
|
||||
exclude: ['app/**/*.test.{ts,tsx}'],
|
||||
statements: 80,
|
||||
lines: 80,
|
||||
branches: 70,
|
||||
functions: 80,
|
||||
```jsonc
|
||||
{
|
||||
"$schema": "https://remix.run/schemas/remix.json",
|
||||
"test": {
|
||||
"files": ["**/*.test{,.e2e}.{ts,tsx}"],
|
||||
"e2eFiles": ["**/*.test.e2e.{ts,tsx}"],
|
||||
"exclude": ["node_modules/**"],
|
||||
"coverage": {
|
||||
"dir": ".coverage",
|
||||
"include": ["app/**/*.{ts,tsx}"],
|
||||
"exclude": ["app/**/*.test.{ts,tsx}"],
|
||||
"statements": 80,
|
||||
"lines": 80,
|
||||
"branches": 70,
|
||||
"functions": 80,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Use `remix test --coverage` to enable coverage with defaults. Use `glob.exclude` when discovery would otherwise enter generated output, symlinked workspaces, or other paths that should not produce tests.
|
||||
Use `remix test --coverage` to enable coverage with defaults. Use `exclude` when discovery would otherwise enter generated output, symlinked workspaces, or other paths that should not produce tests.
|
||||
|
||||
## Component Tests
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ license: MIT
|
||||
compatibility: Rust 1.70+, Cargo
|
||||
metadata:
|
||||
author: apollographql
|
||||
version: "1.1.0"
|
||||
version: "1.1.1"
|
||||
allowed-tools: Bash(cargo:*) Bash(rustc:*) Bash(rustfmt:*) Bash(clippy:*) Read Write Edit Glob Grep
|
||||
---
|
||||
|
||||
@@ -24,7 +24,7 @@ Apply these guidelines when writing or reviewing Rust code. Based on Apollo Grap
|
||||
|
||||
Before reviewing, familiarize yourself with Apollo's Rust best practices. Read ALL relevant chapters in the same turn in parallel. Reference these files when providing feedback:
|
||||
|
||||
- [Chapter 1 - Coding Styles and Idioms](references/chapter_01.md): Borrowing vs cloning, Copy trait, Option/Result handling, iterators, comments
|
||||
- [Chapter 1 - Coding Styles and Idioms](references/chapter_01.md): Borrowing vs cloning, Copy trait, Option/Result handling, iterators, comments, when to extract a function (duplication vs. wrong abstraction)
|
||||
- [Chapter 2 - Clippy and Linting](references/chapter_02.md): Clippy configuration, important lints, workspace lint setup
|
||||
- [Chapter 3 - Performance Mindset](references/chapter_03.md): Profiling, avoiding redundant clones, stack vs heap, zero-cost abstractions
|
||||
- [Chapter 4 - Error Handling](references/chapter_04.md): Result vs panic, thiserror vs anyhow, error hierarchies
|
||||
|
||||
@@ -76,6 +76,7 @@ let new_num = increment(num); // `num` still usable after this point
|
||||
* All fields are `Copy` themselves.
|
||||
* The struct is `small`, up to 2 (maybe 3) words of memory or 24 bytes (each word is 64 bits/8bytes).
|
||||
* The struct **represents a "plain data object"**, without resourcing to ownership (no heap allocations. Example: `Vec` and `Strings`).
|
||||
* ❗**The type does not also implement `Iterator`.** Even if every field is `Copy`, never put `Copy` and `Iterator` on the same type (see [§1.5](#15-iterator-iter-vs-for)).
|
||||
|
||||
❗**Rust Arrays are stack allocated.** Which means they can be copied if their underlying type is `Copy`, but this will be allocated in the program stack which can easily become a stack overflow. More on [Chapter 3 - Stack vs Heap](./chapter_03.md#33-stack-vs-heap-be-size-smart)
|
||||
|
||||
@@ -351,6 +352,7 @@ for value in vec.iter().enumerate()
|
||||
* Avoid needlessly collect/allocate of a collection (e.g. vector) just to throw it away later by some larger operation or by another iteration.
|
||||
* Prefer `iter` over `into_iter` unless you don't need the ownership of the collection.
|
||||
* Prefer `iter` over `into_iter` for collections that inner type implements `Copy`, e.g. `Vec<i32>`.
|
||||
* **Never implement (or derive) both `Copy` and `Iterator` on the same type.** Copying an iterator and advancing one copy leaves the other untouched, which silently yields wrong results -- it is a well-known footgun. The standard library hit exactly this: it is why `Range` historically could not be `Copy`, and why the new `core::range` types (stabilized in Rust 1.96) implement `IntoIterator` instead of `Iterator` so they *can* be `Copy`. If you need an iterator on a `Copy` type, implement `IntoIterator` and return a separate iterator struct.
|
||||
* For summing numbers prefer `.sum` over `.fold`. `.sum` is specialized for summing values, so the compiler knows it can make optimizations on that front, while fold has a blackbox closure that needs to be applied at every step. If you need to sum by an initial value, just added in the expression `let my_sum = [1, 2, 3].sum() + 3`.
|
||||
|
||||
## 1.6 Comments: Context, not Clutter
|
||||
@@ -459,6 +461,8 @@ mod tests {
|
||||
|
||||
Let **structure** and **naming** replace commentary, and enhance its documentation with **tests as living documentation**.
|
||||
|
||||
> ❗ Splitting is about **naming and clarity**, not deduplication. Before extracting a helper to remove *duplicated* lines, check [§1.8](#18-when-to-extract-a-function-and-when-not-to).
|
||||
|
||||
### 📝 TODOs are not comments - track them properly
|
||||
|
||||
Avoid leaving lingering `// TODO: Lorem Ipsum` comments in the code. Instead:
|
||||
@@ -550,3 +554,94 @@ group_imports = "StdExternalCrate"
|
||||
```
|
||||
|
||||
> As of Rust version 1.88, it is necessary to execute rustfmt in nightly to correctly reorder code `cargo +nightly fmt`.
|
||||
|
||||
## 1.8 When to Extract a Function (and When Not To)
|
||||
|
||||
This chapter ([§1.6](#-breaking-up-long-functions-over-commenting-them)) and [Chapter 8](./chapter_08.md#85-replace-comments-with-code) recommend splitting long functions and replacing narrative comments with named helpers. That advice is about **naming and clarity** -- it is *not* a license to hunt down every repeated line. Extraction has a cost: every helper adds indirection, and a **wrong abstraction is much harder to remove than a little duplication**.
|
||||
|
||||
> "Duplication is far cheaper than the wrong abstraction." -- [Sandi Metz, The Wrong Abstraction](https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction)
|
||||
|
||||
### 📏 The Rule of Three
|
||||
|
||||
From [Martin Fowler's *Refactoring*](https://martinfowler.com/books/refactoring.html), attributed to Don Roberts:
|
||||
|
||||
> The first time you do something, you just do it. The second time you do something similar, you wince at the duplication, but you do the duplicate thing anyway. The third time you do something similar, you refactor.
|
||||
|
||||
Two occurrences of a couple of lines are usually **fine**. Wait for the third before reaching for a helper -- by then you will know what the abstraction actually *is*, instead of guessing at it.
|
||||
|
||||
### 🧠 DRY is about knowledge, not text
|
||||
|
||||
[DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) (*The Pragmatic Programmer*) says every piece of **knowledge** should have a single representation. It does **not** say "no two code fragments may look alike". Two blocks that *happen* to look the same today, but represent **different decisions**, will evolve in different directions. Forcing them into one helper couples code that should be free to diverge -- this is **coincidental duplication**, and deduplicating it is how wrong abstractions are born.
|
||||
|
||||
### 🧪 Examples
|
||||
|
||||
How these principles play out in code:
|
||||
|
||||
#### ✅ Coincidental duplication -- leave it inline:
|
||||
```rust
|
||||
// Similar-looking lines, but the `.env` and `.toml` formats are
|
||||
// unrelated decisions that will evolve independently:
|
||||
writeln!(env_file, "{key}={value}")?;
|
||||
// ... elsewhere ...
|
||||
writeln!(toml_file, "{key} = \"{value}\"")?;
|
||||
```
|
||||
|
||||
#### ❌ Deduplicating it forces a flag parameter -- the classic smell:
|
||||
```rust
|
||||
// One helper, two behaviors, and a `bool` to pick between them:
|
||||
fn write_entry(out: &mut impl Write, key: &str, value: &str, quoted: bool) -> io::Result<()> {
|
||||
if quoted {
|
||||
writeln!(out, "{key} = \"{value}\"")
|
||||
} else {
|
||||
writeln!(out, "{key}={value}")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Every new variant will grow another parameter or branch. And the `bool` is a smell in its own right: `write_entry(out, key, value, true)` tells the reader nothing at the call site -- Martin Fowler calls this a [Flag Argument](https://martinfowler.com/bliki/FlagArgument.html). When a mode selector is genuinely warranted, a two-variant enum (`Quoting::Quoted`) keeps call sites readable, and Clippy's [`fn_params_excessive_bools`](https://rust-lang.github.io/rust-clippy/master/index.html#fn_params_excessive_bools) lints against accumulating them. No enum rescues *this* design, though: its variants would exist only to encode the callers' differences.
|
||||
|
||||
#### ✅ Shared knowledge -- extract it:
|
||||
```rust
|
||||
// The definition of "retryable" is ONE business decision used in many
|
||||
// places. If it changes, it must change everywhere at once:
|
||||
fn is_retryable(status: StatusCode) -> bool {
|
||||
status == StatusCode::TOO_MANY_REQUESTS || status.is_server_error()
|
||||
}
|
||||
```
|
||||
|
||||
### 🔧 Unwinding a wrong abstraction
|
||||
|
||||
If you find yourself maintaining an abstraction like `write_entry`, [Sandi Metz's advice](https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction) is to **unwind it, not patch it**:
|
||||
|
||||
1. **Re-inline** the helper's body into every caller, substituting each call site's concrete arguments.
|
||||
2. **Reduce** each call site -- with the parameters now literal, dead branches and unused generality fall away.
|
||||
3. **Reevaluate** what the callers *actually* share, now that every one of them is explicit.
|
||||
4. **Reconstruct** one or more abstractions from what remains -- or none, if the sharing turns out to be coincidental.
|
||||
|
||||
> 🤖 Doing this by hand is tedious, which is why wrong abstractions survive on sunk cost. For an AI coding agent, unwinding is fast, mechanical work -- ask for it explicitly instead of letting the agent keep patching a helper that fights back.
|
||||
|
||||
### ✅ Extract a function when:
|
||||
* The logic appears in **3+ places** *and* represents the **same decision** (Rule of Three).
|
||||
* The name **adds meaning** the code alone does not (`is_retryable`, `authorize`) -- rather than restating it (`add_one_to_counter`).
|
||||
* It produces a **unit worth testing** in isolation (see [§1.6](#-breaking-up-long-functions-over-commenting-them)).
|
||||
* It hides genuine complexity behind a **small, honest signature** -- few parameters, no flags.
|
||||
|
||||
### ❌ Don't extract when:
|
||||
* It is **1-2 lines used in fewer than 3 places** -- the helper is pure indirection.
|
||||
* The call sites are only *almost* identical, and unifying them requires **flag parameters** or extra branches.
|
||||
* The only motivation is **line count**. A single-caller helper pays off when its *name* clarifies intent ([§1.6](#-breaking-up-long-functions-over-commenting-them), [§8.5](./chapter_08.md#85-replace-comments-with-code)) -- not when it merely relocates code.
|
||||
* You are **guessing** at a future abstraction. Wait for the third usage to reveal its real shape.
|
||||
|
||||
### 🧫 Test code: readability beats DRY
|
||||
|
||||
In tests, be **even more tolerant of duplication**. The Google Testing Blog calls this [DAMP -- "Descriptive And Meaningful Phrases"](https://testing.googleblog.com/2019/12/testing-on-toilet-tests-too-dry-make.html) ([*Software Engineering at Google*, ch. 12](https://abseil.io/resources/swe-book/html/ch12.html)): each test should read as a **self-contained story** -- setup, action, assertion -- without the reader chasing helpers to reconstruct what actually happened.
|
||||
|
||||
* **Tests have no tests.** Logic moved into a shared helper (loops, branches, clever parametrization) is itself untested -- a bug there silently weakens every test built on it.
|
||||
* **A failing test should be diagnosable on sight.** The reader is usually debugging a broken build; make the expected behavior visible in the test body, not three helpers away.
|
||||
* **Shared test helpers couple unrelated tests.** Change one behavior and dozens of tests fail at once -- for the helper's sake, not the behavior's.
|
||||
|
||||
Where to draw the line:
|
||||
* ✅ Share **setup and fixtures** -- a shared setup function or `rstest` cases, as [Chapter 5](./chapter_05.md#51-tests-as-living-documentation) recommends. Constructing a test server twice is boilerplate, not knowledge.
|
||||
* ❌ Keep each test's **action and assertion inline**, even when they look repetitive across tests.
|
||||
|
||||
> 🚨 When in doubt, **prefer duplication**. A duplicated line is trivially fixed later; a wrong abstraction accretes parameters and conditionals, because each maintainer keeps patching it instead of undoing it.
|
||||
@@ -174,6 +174,8 @@ fn the_function_accepts_all_strings_with_a(#[case] input: &str) {
|
||||
> * It's harder for both IDEs and humans to run/locate specific tests.
|
||||
> * Expectation vs condition naming is now visually inverted (expectation first).
|
||||
|
||||
> ❗ Share **setup**, not the test itself: keep each test's action and assertion inline, even when repetitive. Tests tolerate duplication better than production code -- see [Chapter 1, §1.8](./chapter_01.md#-test-code-readability-beats-dry).
|
||||
|
||||
## 5.2 Add Test Examples to your Docs
|
||||
|
||||
We will deep dive into docs at a later stage, so in this section we will just briefly go over how to add tests to you docs. Rustdoc can turn examples into executable tests using `///` with a few advantages:
|
||||
@@ -282,9 +284,15 @@ Rust comes with 2 macros to make assertions:
|
||||
|
||||
### 🚨 `assert!` reminders
|
||||
* Rust asserts support formatted strings, like the previous examples, those strings will be printed in case of failure, so it is a good practice to add what the actual state was and how it differs from the expected.
|
||||
* If you don't care about the exact pattern matching value, using `matches!` combined with `assert!` might be a good alternative.
|
||||
* If you don't care about the exact pattern matching value, using `matches!` combined with `assert!` might be a good alternative. Note the message string is an argument to `assert!`, not to `matches!`:
|
||||
```rust
|
||||
assert!(matches!(error, MyError::BadInput(_), "Expected `BadInput`, found {error}"));
|
||||
assert!(matches!(error, MyError::BadInput(_)), "Expected `BadInput`, found {error}");
|
||||
```
|
||||
* Since Rust 1.96 prefer the stabilized [`assert_matches!`](https://doc.rust-lang.org/std/macro.assert_matches.html) (and [`debug_assert_matches!`](https://doc.rust-lang.org/std/macro.debug_assert_matches.html)) over `assert!(matches!(..))`. It prints the actual value on failure for free, so you don't have to write the message yourself:
|
||||
```rust
|
||||
use std::assert_matches::assert_matches;
|
||||
|
||||
assert_matches!(error, MyError::BadInput(_));
|
||||
```
|
||||
* Use `#[should_panic]` wisely. It should only be used when panic is the desired behavior, prefer result instead of panic.
|
||||
* There are some other that can enhance your testing experience like:
|
||||
|
||||
@@ -102,6 +102,8 @@ fn save_auth_user(&self) -> Result<PathBuf, MyError> {
|
||||
}
|
||||
```
|
||||
|
||||
> ❗ Extract when the **name adds meaning** -- not to deduplicate a couple of similar-looking lines. See [Chapter 1, §1.8](./chapter_01.md#18-when-to-extract-a-function-and-when-not-to) for when duplication is the better trade-off.
|
||||
|
||||
## 8.6 `TODO` should become issues
|
||||
|
||||
Don't leave `// TODO:` scattered around the codebase with no owner. Instead:
|
||||
|
||||
@@ -37,7 +37,7 @@ Lead each section with the recommended answer so the user can accept it in a wor
|
||||
|
||||
**Section A — Issue tracker.**
|
||||
|
||||
> Explainer: The "issue tracker" is where issues live for this repo. Skills like `to-tickets`, `triage`, `to-spec`, and `qa` read from and write to it — they need to know whether to call `gh issue create`, write a markdown file under `.scratch/`, or follow some other workflow you describe. Pick the place you actually track work for this repo.
|
||||
> Explainer: The "issue tracker" is where issues live for this repo. Skills like `to-tickets`, `triage`, and `to-spec` read from and write to it — they need to know whether to call `gh issue create`, write a markdown file under `.scratch/`, or follow some other workflow you describe. Pick the place you actually track work for this repo.
|
||||
|
||||
Default posture: these skills were designed for GitHub. If a `git remote` points at GitHub, propose that. If a `git remote` points at GitLab (`gitlab.com` or a self-hosted host), propose GitLab. Otherwise (or if the user prefers), offer:
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Issue tracker: GitHub
|
||||
|
||||
Issues and PRDs for this repo live as GitHub issues. Use the `gh` CLI for all operations.
|
||||
Issues and specs for this repo live as GitHub issues. Use the `gh` CLI for all operations.
|
||||
|
||||
## Conventions
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Issue tracker: GitLab
|
||||
|
||||
Issues and PRDs for this repo live as GitLab issues. Use the [`glab`](https://gitlab.com/gitlab-org/cli) CLI for all operations.
|
||||
Issues and specs for this repo live as GitLab issues. Use the [`glab`](https://gitlab.com/gitlab-org/cli) CLI for all operations.
|
||||
|
||||
## Conventions
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Issue tracker: Local Markdown
|
||||
|
||||
Issues and specs (you may know a spec as a PRD) for this repo live as markdown files in `.scratch/`.
|
||||
Issues and specs for this repo live as markdown files in `.scratch/`.
|
||||
|
||||
## Conventions
|
||||
|
||||
|
||||
@@ -64,7 +64,9 @@ These rules are **always enforced**. Each links to a file with Incorrect/Correct
|
||||
- **Use existing components before custom markup.** Check if a component exists before writing a styled `div`.
|
||||
- **Callouts use `Alert`.** Don't build custom styled divs.
|
||||
- **Empty states use `Empty`.** Don't build custom empty state markup.
|
||||
- **Toast via `sonner`.** Use `toast()` from `sonner`.
|
||||
- **Toast follows the project base.** Use `toast` from the `toast` component for
|
||||
Base UI projects. Use `toast()` from `sonner` for Radix and React Aria
|
||||
projects.
|
||||
- **Use `Separator`** instead of `<hr>` or `<div className="border-t">`.
|
||||
- **Use `Skeleton`** for loading placeholders. No custom `animate-pulse` divs.
|
||||
- **Use `Badge`** instead of custom styled spans.
|
||||
@@ -135,7 +137,7 @@ These are the most common patterns that differentiate correct shadcn/ui code. Fo
|
||||
| Data display | `Table`, `Card`, `Badge`, `Avatar` |
|
||||
| Navigation | `Sidebar`, `NavigationMenu`, `Breadcrumb`, `Tabs`, `Pagination` |
|
||||
| Overlays | `Dialog` (modal), `Sheet` (side panel), `Drawer` (bottom sheet), `AlertDialog` (confirmation) |
|
||||
| Feedback | `sonner` (toast), `Alert`, `Progress`, `Skeleton`, `Spinner` |
|
||||
| Feedback | `toast` (Base UI), `sonner` (Radix/Aria), `Alert`, `Progress`, `Skeleton`, `Spinner` |
|
||||
| Command palette | `Command` inside `Dialog` |
|
||||
| Charts | `Chart` (wraps Recharts) |
|
||||
| Layout | `Card`, `Separator`, `Resizable`, `ScrollArea`, `Accordion`, `Collapsible` |
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
- Items always inside their Group component
|
||||
- Callouts use Alert
|
||||
- Empty states use Empty component
|
||||
- Toast notifications use sonner
|
||||
- Toast notifications follow the project base
|
||||
- Choosing between overlay components
|
||||
- Dialog, Sheet, and Drawer always need a Title
|
||||
- Card structure
|
||||
@@ -88,7 +88,19 @@ Chat components nest in a fixed order (`MessageScrollerProvider` → `MessageScr
|
||||
|
||||
---
|
||||
|
||||
## Toast notifications use sonner
|
||||
## Toast notifications follow the project base
|
||||
|
||||
For Base UI projects, use the `toast` component:
|
||||
|
||||
```tsx
|
||||
import { toast } from "@/components/ui/toast"
|
||||
|
||||
toast.add({
|
||||
title: "Changes saved.",
|
||||
})
|
||||
```
|
||||
|
||||
For Radix and React Aria projects, use Sonner:
|
||||
|
||||
```tsx
|
||||
import { toast } from "sonner"
|
||||
|
||||
@@ -23,6 +23,8 @@ A **seam** is the public boundary you test at: the interface where you observe b
|
||||
|
||||
Ask: "What's the public interface, and which seams should we test?"
|
||||
|
||||
When the shape of that interface is itself in question — how deep the module is, where the seam belongs, what the interface should expose — use the `/codebase-design` skill for the vocabulary. It is the shared source of the module, interface, depth, seam, adapter, leverage and locality terms, and it is a reference to consult, not a session to run.
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
- **Implementation-coupled** — mocks internal collaborators, tests private methods, or verifies through a side channel (querying the database instead of using the interface). The tell: the test breaks when you refactor but behavior hasn't changed.
|
||||
|
||||
@@ -73,7 +73,7 @@ Show counts and a one-line summary per item. Let the maintainer pick.
|
||||
|
||||
3. **Verify the claim.** Before any grilling, check that the claim holds up. For a bug, reproduce it from the reporter's steps. For a PR, confirm the diff does what it claims — check it out, run the relevant tests or commands. Report what happened: confirmed (with code path), failed, or insufficient detail (a strong `needs-info` signal). A confirmed verification makes a much stronger agent brief.
|
||||
|
||||
4. **Grill (if needed).** If the request needs fleshing out, run the `/grilling` and `/domain-modeling` skills together — grill it into shape one question at a time, sharpening domain terms and updating `CONTEXT.md`/ADRs inline as decisions land.
|
||||
4. **Grill (if needed).** If the request needs fleshing out, run the `/grilling` and `/domain-modeling` skills together — grill it into shape a round of questions at a time, sharpening domain terms and updating `CONTEXT.md`/ADRs inline as decisions land.
|
||||
|
||||
5. **Apply the outcome:**
|
||||
- `ready-for-agent` — post an agent brief comment ([AGENT-BRIEF.md](AGENT-BRIEF.md)).
|
||||
|
||||
@@ -40,7 +40,7 @@ import { describe, it } from 'node:test'
|
||||
Remix test package script:
|
||||
|
||||
```json
|
||||
"test": "remix-test"
|
||||
"test": "remix test"
|
||||
```
|
||||
|
||||
Remix test imports:
|
||||
@@ -64,6 +64,8 @@ import { describe, it } from '@remix-run/test'
|
||||
- Avoid importing higher-level workspace packages just to build a fixture. For example, a fetch-handler test can branch on `new URL(request.url).pathname` instead of depending on `@remix-run/fetch-router`.
|
||||
- Prefer Web APIs and standards-aligned primitives when they express the fixture clearly.
|
||||
- For e2e tests, serve the smallest app or handler that exercises the user-observable behavior under test.
|
||||
- For HMR tests, use real file changes, child processes, watchers, and browser assertions when the public behavior depends on runtime coordination.
|
||||
- Close spawned processes, asset servers, watchers, and HTTP servers in test cleanup.
|
||||
|
||||
## Assertions
|
||||
|
||||
|
||||
Reference in new issue
Block a user