# Linear Common - Shared Config & Setup This document is referenced by the `/work` and `/yolo` skills. Do not invoke it directly. ## Project config Look for the tracker config in the current git repo root (`git rev-parse --show-toplevel`), trying these paths in order and using the first that exists: 1. `.claude/tracker.json` (preferred, generic name) 2. `.claude/linear.json` (legacy name, still supported) If none exists, run **First-time setup** below, then continue. When creating the file in setup, prefer the generic `.claude/tracker.json` name. ### Schema Linear example (`tracker: linear`): ```json { "tracker": "linear", "org": "ern", "team": "Ern", "project": "Contracts v2", "defaultBranch": "main", "commitScope": "platform", "buildCommand": "forge test", "setupCommands": ["bun install"], "contextFiles": ["docs/README.md"], "prReviewers": [], "labels": [] } ``` Gitea-issues example (`tracker: gitea`): ```json { "tracker": "gitea", "defaultBranch": "main", "commitScope": "maestro", "remoteHost": "gitea", "remoteBaseUrl": "https://git.naps.pt", "buildCommand": "cargo test", "contextFiles": ["CLAUDE.md"], "prReviewers": [], "labels": [] } ``` GitHub-issues example (`tracker: github`): ```json { "tracker": "github", "defaultBranch": "main", "commitScope": "contracts", "buildCommand": "pnpm test", "contextFiles": ["AGENTS.md"], "prReviewers": [], "labels": [] } ``` | Field | Required | Description | |---|---|---| | `tracker` | no | Where issues live: `linear` (default), `gitea`, or `github`. See **Tracker backend** below. | | `linearMcp` | no | MCP server name when `tracker: linear`. Default `linear-server`. Set a per-workspace name (e.g. `linear-acme`) when a project has its own server. | | `org` | linear only | Linear organization slug | | `team` | linear only | Linear team name (for listing/creating issues) | | `project` | no | Linear project name (narrows issue search). Ignored when `tracker: gitea`. | | `defaultBranch` | no | Base branch, default `main` | | `commitScope` | no | Conventional commit scope, e.g. `platform` -> `feat(platform): ...` | | `buildCommand` | no | Command to verify the build. Run after implementation. | | `setupCommands` | no | Commands to run inside a new worktree (install deps, etc.) | | `contextFiles` | no | Files to read before coding (specs, architecture docs) | | `prReviewers` | no | Reviewer usernames to request reviews from (`/work` only) | | `labels` | no | Default issue labels for ad-hoc issues | | `remoteHost` | no | `github` (default) or `gitea`. Selects which API `/work` uses for PR creation and review polling. `/yolo` is unaffected. Implied `gitea` when `tracker: gitea`, `github` when `tracker: github`. | | `remoteBaseUrl` | no | Required when `remoteHost: gitea` or `tracker: gitea`. Base URL of the Gitea instance (e.g. `https://git.naps.pt`). | > The config file is named `.claude/tracker.json` (preferred) or `.claude/linear.json` (legacy); either configures whichever tracker `tracker` selects. ## Tracker backend `tracker` selects where issues live. Every instruction below that refers to "the issue" applies to the configured backend; where the two differ (selection, status changes, branch naming) the gitea-specific steps are called out explicitly. - **`linear`** (default): issues live in Linear, accessed via the MCP server named by `linearMcp` (default `linear-server`; a workspace with its own server sets its own name). Requires `org` + `team`. Tool calls use the `mcp____*` prefix. - **`gitea`**: issues live in the repo's own Gitea issue tracker — **no Linear MCP involved**. The repo (`owner/repo`) is derived from `git remote get-url origin`; the API uses `remoteBaseUrl` + `$GITEA_TOKEN` (load via `source ~/.env.claude` if needed), exactly like the `/work` Gitea PR variant. `org`/`team`/`project` are ignored. - **`github`**: issues live in the repo's own GitHub issue tracker — **no Linear MCP involved**. The repo (`owner/repo`) is derived from `git remote get-url origin`; all issue and PR operations use the `gh` CLI (must be authenticated — `gh auth status`). `org`/`team`/`project` are ignored. GitHub has no workflow states, so WIP is signalled by assigning the issue to yourself (like gitea); the PR's `Closes #N` closes the issue on merge. ### First-time setup If no tracker config exists: 1. Ask the user which `tracker` (`linear`, `gitea`, or `github`). For `linear`, also ask `org`, `team`, and optionally `project`; for `gitea`, ask `remoteBaseUrl`. 2. Ask which optional fields they want. Show the table above. 3. Write the file to `.claude/tracker.json` (preferred). Suggest they commit it or gitignore it depending on preference. 4. Continue with the task. ## Remote host (`/work` only) `/work` opens PRs and iterates on reviews. The forge it talks to is selected by `remoteHost`: - **`github`** (default): uses `gh` for `gh pr create`, `gh pr view`, and the GitHub GraphQL review-thread API. No extra config needed. Requires an authenticated `gh` (`gh auth status`). - **`gitea`**: uses Gitea's REST API via `curl`. Requires `remoteBaseUrl` (e.g. `https://git.naps.pt`) and `$GITEA_TOKEN` in the environment (typically loaded from `~/.env.claude`). See `/work`'s "Gitea variant" section for the specific endpoints. `/yolo` doesn't care about `remoteHost` — push and merge are plain git, which works against any remote. ## Linear MCP auth (`tracker: linear` only) Skip this section entirely when `tracker: gitea`. The Linear MCP server (named by `linearMcp`, default `linear-server`) supports one org at a time. Before making Linear API calls, verify the current auth matches the configured `org`. If it doesn't (or if auth fails), tell the user to re-authenticate via `mcp____authenticate` and stop. Don't try to work around auth issues silently. ## Task selection Based on `$ARGUMENTS`. The steps differ by `tracker`. ### tracker: linear #### Linear issue ID provided (e.g. `ERN-347`) 1. Fetch the issue via the configured Linear MCP (`get_issue`). 2. Read the full description, acceptance criteria, and comments. #### Ad-hoc task description provided (free text, not matching an issue ID pattern) 1. Create a new Linear issue in the configured team (and project if set). 2. Apply any configured default `labels`. 3. Use the provided text as the issue title. If it's long, summarize for the title and use the full text as description. #### No argument (auto-pick) 1. List issues in the configured team/project that are unstarted (Backlog, Todo, Ready, or equivalent). 2. Pick the highest-priority unblocked issue. 3. If none found, tell the user and stop. **Then:** - Move the issue to "In Progress" immediately. - Note the issue ID, title, and `gitBranchName` for later use. - Use `gitBranchName` from the Linear response for branch naming (auto-links in Linear). ### tracker: gitea Set `BASE=$remoteBaseUrl`, `REPO=/` (from `git remote get-url origin`), and send `Authorization: token $GITEA_TOKEN` on every call. #### Issue number provided (e.g. `23` or `#23`) 1. `GET $BASE/api/v1/repos/$REPO/issues/23`. 2. Read the title, body, and comments (`GET .../issues/23/comments`). #### Ad-hoc task description provided (free text, not a bare number) 1. Create the issue: ```bash curl -sS -X POST -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" \ "$BASE/api/v1/repos/$REPO/issues" \ -d "$(jq -nc --arg title "" --arg body "<full text>" '{title:$title, body:$body}')" ``` 2. Apply any configured default `labels` (resolve label names to IDs via `GET .../labels`, pass `labels:[ids]`). 3. Title from the text (summarize if long; full text → body). Save the returned `number`. #### No argument (auto-pick) 1. `GET $BASE/api/v1/repos/$REPO/issues?state=open&type=issues&limit=50` (the `type=issues` filter excludes PRs). 2. Prefer an unassigned issue; if priority labels exist (e.g. `priority/*`), pick the highest, else the oldest open one. 3. If none found, tell the user and stop. **Then:** - Gitea has **no workflow states** — signal WIP by assigning the issue to yourself: ```bash curl -sS -X PATCH -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" \ "$BASE/api/v1/repos/$REPO/issues/$N" -d '{"assignees":["<your-gitea-username>"]}' ``` Optionally add a `status/in-progress` label if one already exists in the repo. Don't invent label schemes. - Note the issue `number` and title; derive the branch name (see Worktree setup). ### tracker: github Uses the `gh` CLI against the origin repo (`REPO=<owner>/<repo>` from `git remote get-url origin`). Requires an authenticated `gh` — run `gh auth status` first; if it fails, tell the user to run `gh auth login -h github.com` and stop. #### Issue number provided (e.g. `23` or `#23`) 1. `gh issue view 23 --json number,title,body,comments`. 2. Read the title, body, and comments. #### Ad-hoc task description provided (free text, not a bare number) 1. Create the issue: ```bash gh issue create --title "<title>" --body "<full text>" [--label <l1> --label <l2>] ``` Apply any configured default `labels` (each as a `--label` flag; only labels that already exist in the repo). 2. Title from the text (summarize if long; full text → body). Save the returned issue `number` (parse from the URL `gh` prints). #### No argument (auto-pick) 1. `gh issue list --state open --limit 50 --json number,title,labels,assignees`. 2. Prefer an unassigned issue; if priority labels exist (e.g. `priority/*`), pick the highest, else the oldest open one. 3. If none found, tell the user and stop. **Then:** - GitHub has **no workflow states** — signal WIP by assigning the issue to yourself: ```bash gh issue edit $N --add-assignee @me ``` Optionally add a `status/in-progress` label if one already exists in the repo. Don't invent label schemes. - Note the issue `number` and title; derive the branch name (see Worktree setup). ## Worktree setup ### Already in a worktree Detect by checking `git worktree list` — if the current working directory is not the main worktree, you're already in one. If already in a worktree: stay here. Check out the issue branch if the current branch doesn't match. ### Not in a worktree 1. Create a worktree at `worktrees/<branch-name>` relative to the repo root. - **tracker: linear**: use `gitBranchName` from Linear (auto-links in Linear). If unavailable, derive a concise name from the issue title. - **tracker: gitea**: derive `<issue-number>-<slug>` from the issue title (lowercase, hyphenated, ~5 words), e.g. `8-daemon-activity-state`. - **tracker: github**: derive `<issue-number>-<slug>` from the issue title (lowercase, hyphenated, ~5 words), e.g. `12-onchainid-erc3643-fixture`. - Do NOT include "claude" in branch names. 2. `cd` into the worktree. 3. Run each command in `setupCommands` from the config. 4. Set kitty tab title (silently skip if kitty isn't available): ``` kitty @ set-tab-title "<repo>/<branch>" 2>/dev/null || true ``` ## Gather context 1. Read all `contextFiles` from the config. 2. Re-read the tracking issue description (with project context you'll understand it better now). 3. Read `CLAUDE.md` / `AGENTS.md` at the repo root or `.claude/` if they exist, for project conventions. 4. Check recent git history: `git log --oneline -20` to understand current patterns. 5. **Locating the code to change — delegate when the area is unfamiliar or spread out.** If the issue clearly points at one or two known files, just read them inline. But if you'd otherwise open many files to answer "where does X live / what calls Y / where are the similar cases", spawn the **`cavecrew-investigator`** subagent instead (Agent tool, `subagent_type: caveman:cavecrew-investigator`). It returns a compressed `file:line` map, keeping the main thread's context lean for the implementation and review that follow. Skip it for small, obvious, single-file tasks — the spawn overhead isn't worth it there. ## Implementation guidelines 1. Work methodically through the requirements. 2. Commit after each logical step using conventional commits: - With scope if configured: `feat(scope): description` - Without: `feat: description` - Prefixes: `feat`, `fix`, `refactor`, `test`, `docs`, `chore` 3. Follow existing code patterns. Match the style of surrounding code. 4. Write tests appropriate to the project (match existing test patterns and coverage level). 5. Never amend commits — always create new ones. 6. Keep the tracking issue updated (Linear, Gitea, or GitHub, per `tracker`) if scope changes significantly. ## Local verification budget (shared machine) Several autonomous sessions (`/yolo`, `/nightshift`, `/work`, `/blitz`) run on one box at once, each with subagents. If every one runs the full suite whenever it feels like it, the machine OOMs and all of them die. If instead everything gets pushed for CI to check, CI is clogged and feedback is slow. So: **scoped checks locally, full suite once per push, heavy commands through the gate.** ### Tiers | When | Run | How | |---|---|---| | Inner loop, after each change | typecheck + lint + tests **for the touched module only** | direct, no gate | | Once, right before push/PR | the project's `buildCommand` (full suite) | through `gate.sh` | | Anything else | nothing | let CI do it | Never run the full suite twice for the same push. Never run it "to be sure" after a green scoped run of the same code. ### The gate Any command that compiles the whole project or runs the whole suite goes through the machine-wide semaphore: ```bash <skills-root>/linear-common/scripts/gate.sh -- <buildCommand> ``` It bounds concurrency machine-wide (default `nproc/4` slots), caps the command's memory and CPU via a systemd scope, and pins test/build parallelism env vars (`CARGO_BUILD_JOBS`, `RUST_TEST_THREADS`, `VITEST_MAX_*`, `MAKEFLAGS`, `GOMAXPROCS`, node heap) so the suite doesn't fan out to every core. - **Exit 75** = no slot or too little free RAM within the wait budget. It did **not** run. That is a normal outcome, not an error: push and let CI cover it, and say so in the commit/PR body. Do not retry in a loop, do not bypass the gate by running the command directly. - **Exit 137** = killed by the memory cap, NOT a test failure. Do not go hunting for a bug that isn't there. Re-run once with `AGENT_GATE_MEM_MAX=6G` if the suite genuinely needs more. - `gate.sh --status` shows slots busy and free RAM. Cheap; check it before deciding to run anything heavy. - If the file isn't executable on this install, prefix with `bash`. - Knobs (env): `AGENT_GATE_SLOTS`, `AGENT_GATE_WAIT` (default 600s), `AGENT_GATE_MEM_MAX` (3G), `AGENT_GATE_MEM_FLOOR` (2000MB), `AGENT_GATE_JOBS`. ### Subagents - Subagents **never run the full suite**, ever. They run scoped checks on what they touched. The session that dispatched them runs the full suite once, at the end. - Cap concurrent subagents at **3** per session, **2** if their tasks build or test. `gate.sh --status` showing no free slots is a signal to dispatch fewer, not to wait. ### Pushing - One push per finished unit of work. Not per commit, not per milestone. - Don't push to trigger CI as a substitute for the local scoped checks — that is how CI gets clogged. - When the gate was skipped (exit 75), the push is doing real verification work: arm the one-shot CI watcher so a red build surfaces. ## Rules - **Never ask the user** during autonomous work unless you hit a genuine blocker (architectural contradiction, missing credentials, ambiguous requirements that could go very wrong). - **If the build fails**, fix it before pushing. - **Respect existing project conventions** from CLAUDE.md, AGENTS.md, etc.