init: centralized agent skills for Claude Code + Codex

- skills/ shared by both tools (open Agent Skills standard)
- portable cross-skill refs (root-relative, no ~/.claude hardcode)
- bin/link.sh bootstrap for non-Nix machines
- nix/home.nix + flake.nix for home-manager

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014SK5Lo7LQfwLRVdCv1A8uF
This commit is contained in:
naps62
2026-07-24 17:57:56 +00:00
commit f83a247de3
109 changed files with 48638 additions and 0 deletions
+245
View File
@@ -0,0 +1,245 @@
# 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`. Use `linear-acme` for the example workspace. |
| `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`; an example workspace uses `linear-acme`). Requires `org` + `team`. Tool calls use the `mcp__<linearMcp>__*` 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__<linearMcp>__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=<owner>/<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 "<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.
## 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.