diff --git a/.gitignore b/.gitignore index aefb053..23d072d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,19 @@ +# Secret-safety net — none of these should ever be committed. +hosts.yml +*.rayconfig +.env +.env.* +*.pem +*.key +*token* +*credential* + +# Machine-local zsh overrides (see packages/zsh/dot-config/zsh/90-local.zsh.example). +packages/zsh/dot-config/zsh/90-local.zsh + +# Generated dump for Brewfile re-curation (make brew-dump); never committed. +Brewfile.dump + # Superpowers working docs (brainstorming specs, implementation plans) — # kept locally as design artifacts, not checked in. docs/superpowers/ diff --git a/Brewfile b/Brewfile new file mode 100644 index 0000000..dd4e8a8 --- /dev/null +++ b/Brewfile @@ -0,0 +1,164 @@ +# Curated package list — installed via `make brew-install` (brew bundle). +# Re-curation flow: `make brew-dump` writes Brewfile.dump (gitignored); diff it +# against this file and promote keepers by hand. The dump is the menu, not the +# Brewfile. `go`/`npm` entries from the dump are intentionally excluded (managed +# by go install / nvm, not brew). + +# --- taps ------------------------------------------------------------------- +tap "jesseduffield/lazydocker", trusted: true +tap "loft-sh/tap", trusted: true +tap "minio/stable", trusted: true +tap "openclaw/tap", trusted: true +tap "sanketsudake/tap" + +# --- core CLI --------------------------------------------------------------- +brew "bash" +brew "coreutils" +brew "findutils" +brew "gnu-sed" +brew "grep" +brew "make" +brew "moreutils" +brew "ripgrep" +brew "tree" +brew "wget" + +# --- AI-harness prereqs (stow, jq, gh, node-via-nvm, python) ---------------- +brew "stow" # dotfiles + harness symlink manager (needs >= 2.4.0) +brew "jq" +brew "gh" +brew "nvm" # provides node/npx for skill-vendoring tooling +brew "python@3.13" +brew "mas" # Mac App Store CLI (for the mas entries below) + +# --- shell & everyday tools -------------------------------------------------- +brew "atuin" +brew "btop" +brew "ffmpeg" +brew "pandoc" +brew "poppler" +brew "qpdf" # pdfunlock() +brew "git-lfs" +brew "zsh-autosuggestions" # ghost-text next-command suggestion from history +brew "zsh-syntax-highlighting" # invalid commands go red before you hit enter + +# --- modern CLI -------------------------------------------------------------- +brew "fzf" # fuzzy pickers + Ctrl-T/Alt-C (Ctrl-R stays with atuin) +brew "zoxide" # frecency-ranked cd (z ) +brew "eza" # ls with git status column (ll alias) +brew "bat" # syntax-highlighted cat with git gutter +brew "fd" # gitignore-aware find +brew "git-delta" # readable side-by-side git diffs (wired in gitconfig) +brew "yq" # jq for YAML/TOML/XML +brew "jless" # interactive JSON pager +brew "glow" # markdown renderer/pager for the terminal +brew "dust" # visual du +brew "lazygit" # git TUI (same author as lazydocker) + +# --- languages & build ------------------------------------------------------- +brew "go" +brew "golangci-lint" +brew "gomplate" +brew "goreleaser" +brew "mage" +brew "rust" +brew "openjdk" +brew "maven" +brew "sbt" +brew "protobuf" +brew "pipx" +brew "uv" +brew "virtualenv" +brew "libpq" +brew "comby" + +# --- containers & kubernetes ------------------------------------------------- +brew "colima" +brew "docker" +brew "docker-buildx" +brew "cosign" +brew "helm" +brew "k9s" +brew "kind" +brew "ko" +brew "kustomize" +brew "skaffold" +brew "skopeo" +brew "stern" +brew "k6" +brew "mkcert" +brew "kubectx" # kubectx + kubens; fuzzy pickers with fzf installed +brew "kubecolor" # colorized kubectl output (aliased to kubectl) +brew "dive" # layer-by-layer container image explorer +brew "trivy" # CVE/misconfig scanner for images, IaC, clusters +brew "dyff" # YAML-aware structural diff (helm/k8s manifests) +brew "viddy" # modern watch with diff highlighting + time travel +brew "kubeconform" # fast k8s manifest schema validation +brew "jesseduffield/lazydocker/lazydocker" +brew "loft-sh/tap/vcluster" +brew "minio/stable/mc" + +# --- cloud & misc ------------------------------------------------------------ +brew "cloudflared" +brew "hugo" +brew "agent-browser" +brew "herdr" +brew "openclaw/tap/gitcrawl" + +# --- casks ------------------------------------------------------------------- +# Previously direct-download apps, adopted into brew management (make cask-adopt). +cask "1password" +cask "claude" +cask "devin-desktop" +cask "google-chrome" +cask "openvpn-connect" +cask "tailscale-app" +cask "wispr-flow" + +cask "1password-cli" +cask "claude-code@latest" +cask "copilot-cli" +cask "devin-cli" +cask "ghostty" +cask "helium-browser" # Chromium-based; to replace google-chrome eventually +cask "itsycal" +cask "obsidian" +cask "raycast" # launcher + clipboard history + window snapping + extensions +cask "slack" +cask "visual-studio-code" +cask "zoom" +cask "sanketsudake/tap/cc-proxy", trusted: true +cask "sanketsudake/tap/chrome-cdp", trusted: true +cask "sanketsudake/tap/portless", trusted: true + +# --- App Store apps (need App Store sign-in on a new Mac) -------------------- +mas "1Password for Safari", id: 1569813296 +mas "Numbers", id: 361304891 +mas "Okta Verify", id: 490179405 + +# --- vscode extensions ------------------------------------------------------- +vscode "adpyke.vscode-sql-formatter" +vscode "anthropic.claude-code" +vscode "davidanson.vscode-markdownlint" +vscode "docker.docker" +vscode "drblury.protobuf-vsc" +vscode "eamodio.gitlens" +vscode "foxundermoon.shell-format" +vscode "github.github-vscode-theme" +vscode "github.vscode-github-actions" +vscode "golang.go" +vscode "mechatroner.rainbow-csv" +vscode "ms-azuretools.vscode-containers" +vscode "ms-python.debugpy" +vscode "ms-python.python" +vscode "ms-python.vscode-pylance" +vscode "ms-python.vscode-python-envs" +vscode "ms-vscode.makefile-tools" +vscode "ms-vscode.vscode-speech" +vscode "oracle.oracle-java" +vscode "redhat.vscode-yaml" +vscode "scala-lang.scala" +vscode "shd101wyy.markdown-preview-enhanced" +vscode "vscjava.vscode-java-debug" +vscode "vscjava.vscode-java-dependency" +vscode "vscjava.vscode-maven" diff --git a/CLAUDE.md b/CLAUDE.md index b837e64..3233b25 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,28 +4,29 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## What this repo is -A dotfiles-style config repo that provisions two tools across two Claude profiles (personal/work): +One repo for the whole machine: -- **pi** (the `pi-mono` coding agent) — config lives under `pi/` and is stowed into `~/.pi` via GNU stow. +- **macOS dotfiles** — stow packages under `packages/` (targeted at `$HOME`), a curated `Brewfile`, tool manifests under `manifests/`, `bootstrap.sh` for new Macs, and `macos/defaults.sh`. +- **pi** (the `pi-mono` coding agent) — config lives under `packages/pi/` and is stowed into `~/.pi` via GNU stow. - **Claude Code** — a shared global `CLAUDE.md`, `skills/`, `commands/`, `rules/`, `scripts/`, and `agents/` are symlinked into `~/.claude-personal/` and `~/.claude-work/`. There is no application to build/test/lint. -The `Makefile` is the primary interface. +The `Makefile` is the primary interface; targets follow `-` naming (`brew-install`, `stow-link`, `skills-fetch`). ## Makefile targets All targets follow a `-` naming convention (e.g. `skills-link`, `skills-sync`), except the `install`/`uninstall` aggregates. -- `make install` — runs `skills-materialize` (reconstructs the gitignored vendored skill dirs from `skills/vendored.json`), then `skills-link`, `claude-md-link`, `commands-link`, `rules-link`, `scripts-link`, `agents-link`, then `stow --adopt pi` into `~/.pi`. - Safe to re-run; it replaces existing symlinks and backs up real files it would overwrite. +- `make install` — runs `brew-install`, `stow-link` (the `$HOME` packages), `skills-materialize` (reconstructs the gitignored vendored skill dirs from `sources.toml`), `harness-link` (stows the `claude` package into both profiles and the `pi` package into `~/.pi`), then `tools-install`. + Safe to re-run; `harness-link` removes stale symlinks at the managed names, and a real file at a target must be moved aside by hand (never `--adopt` on the profile dirs). Materialization needs network on a fresh clone; offline, already-present vendored skills are left as-is and missing ones are reported as skipped. - `make uninstall` — reverses the above. - `make skills-sync` — clones/pulls `github.com/badlogic/pi-skills` into `/tmp/pi-skills` and copies each skill dir into `./skills/`. Bulk vendoring of the badlogic set; local edits to files under those `skills//` dirs are overwritten on next sync. For single skills from arbitrary repos, use `skills-fetch` (see "Skill & agent source management" below). -- `make extensions-sync` — clones/pulls `github.com/badlogic/pi-mono` into `/tmp/pi-mono` and copies the whitelisted set (see `PI_EXTENSIONS` in the Makefile) from `packages/coding-agent/examples/extensions` into `./pi/extensions/`. +- `make extensions-sync` — clones/pulls `github.com/badlogic/pi-mono` into `/tmp/pi-mono` and copies the whitelisted set (see `PI_EXTENSIONS` in the Makefile) from `packages/coding-agent/examples/extensions` into `./packages/pi/extensions/`. Same vendoring caveat applies. -- `make plugins-check` — diffs `claude/plugins.txt` (desired, user-scoped) against `/plugins/installed_plugins.json` for each profile, reporting missing/extra. +- `make plugins-check` — diffs `manifests/claude-plugins.txt` (desired, user-scoped) against `/plugins/installed_plugins.json` for each profile, reporting missing/extra. Requires `jq`. - `make plugins-sync` — same diff as `plugins-check` but emits the exact `/plugin install ` lines per profile, prefixed with the wrapper to enter (`pclaude` / `wclaude`). Copy-paste into a session in the right profile to close the drift. @@ -34,7 +35,7 @@ All targets follow a `-` naming convention (e.g. `skills-link` ## Skill & agent source management `scripts/resource-manager.sh` (wrapped by the `skills-*` and `agents-*` make targets) fetches individual **skills** or **agents** from any git repo at any subpath and tracks where each came from, so they can be updated later. -It is repo tooling and lives in top-level `scripts/`, not `claude/scripts/` — it is not symlinked into the profiles. +It is repo tooling and lives in top-level `scripts/`, not `packages/claude/scripts/` — it is not symlinked into the profiles. `scripts/test-*.sh` and `scripts/test-*.py` are the repo's own regression tests (run by CI); `scripts/test-resource-manager.sh` round-trips a fetch → materialize → doctor → delete in a throwaway clone, `scripts/test-safety-guard-hook.py` is the table-driven test of the safety gate. Hook scripts and repo tooling that parse JSON or do regex work are Python (stdlib only, 3.9+); shell stays for the thin wrappers around `make`/`git`. Requires `git` and `jq`. @@ -43,7 +44,7 @@ The tool takes a leading `--kind skill|agent`; the make targets supply it. The two kinds differ in structure: - A **skill** is a directory under `skills/` validated by a `SKILL.md`. -- An **agent** is a single `.md` file under `claude/agents/`; its sidecar is a **sibling** at `claude/agents/.source.json`. +- An **agent** is a single `.md` file under `packages/claude/agents/`; its committed record is its `[[agent]]` entry in `sources.toml`. They also differ in what gets committed, which turns on a resource's **provenance**: @@ -52,21 +53,24 @@ They also differ in what gets committed, which turns on a resource's **provenanc - **Authored** — born in this repo (`repo: null`, e.g. `harvest-automation`, `debug-ci`); this repo is its only home. `update` and `delete` treat it as having no upstream. +**Every resource is recorded in the single committed manifest, `sources.toml`** — name-sorted `[[skill]]` and `[[agent]]` arrays of tables, read and written through `scripts/toml-manifest.py` (stdlib `tomllib`, python >= 3.11; the writer is deterministic, so diffs stay per-entry). +A vendored entry carries `{name, repo, subpath, ref, commit, category, description}`, where `commit` is the resolved SHA of `ref` and `category`/`description` are cached so the catalog and doctor render without materializing; an authored entry carries just `{name, category[, note]}`. + **Vendored skills are not committed.** -They are recorded in a single committed manifest, `skills/vendored.json` — a name-sorted array of `{name, repo, subpath, ref, commit, category, description}`, where `commit` is the resolved SHA of `ref` and `category`/`description` are cached so the catalog and doctor render without materializing. -Each vendored skill's `skills//` dir — including its regenerated `.source.json` sidecar — is **gitignored** (a managed block in `.gitignore`, rewritten by the tool) and **materialized** from the pinned `commit` by `make install`. -So for skills the manifest is the source of truth; the on-disk `.source.json` is a materialization artifact, not the committed record. +Each vendored skill's `skills//` dir — including its regenerated `.source.json` materialize marker — is **gitignored** (a managed block in `.gitignore`, rewritten by the tool) and **materialized** from the pinned `commit` by `make install`. +The manifest is the source of truth; the on-disk `.source.json` is a materialization artifact, not a committed record. -**Authored skills stay committed** as normal `skills//` dirs with a `{"repo": null}` sidecar, exactly as before — they survive materialize and `skills-update-all` untouched. +**Authored skills stay committed** as normal `skills//` dirs — they survive materialize and `skills-update-all` untouched. -**Agents are always committed** (the manifest is skills-only): each carries a `.source.json` sidecar — `{"repo","subpath","ref","commit","fetched_at"}` when remote, `{"repo": null, ...}` when local; an agent with no sidecar is reported `unmanaged`. +**Agent `.md` files are always committed**, vendored or authored; only their source record lives in the manifest. +A resource with no manifest entry is reported `unmanaged`. -An optional `category` field groups a resource in `list`/catalog output (for vendored skills it lives in the manifest entry; for authored skills and agents, in the sidecar). +An optional `category` field on the manifest entry groups a resource in `list`/catalog output. Skills are categorized this way rather than by folder because Claude Code and pi scan `skills/` only one level deep, so nesting skills into category subfolders would hide them. Targets (each `skills-*` has an `agents-*` twin taking the same variables): -- `make skills-fetch REPO=owner/name SUBPATH=path/to/skill [REF=main] [NAME=…] [FORCE=1]` — shallow sparse-clone, validate the subpath, copy it into `skills//`; for a vendored skill it also upserts the `skills/vendored.json` entry (pinned `commit` + cached `category`/`description`) and adds the `.gitignore` line, so the fetched dir lands untracked. +- `make skills-fetch REPO=owner/name SUBPATH=path/to/skill [REF=main] [NAME=…] [FORCE=1]` — shallow sparse-clone, validate the subpath, copy it into `skills//`; for a vendored skill it also upserts the `sources.toml` entry (pinned `commit` + cached `category`/`description`) and adds the `.gitignore` line, so the fetched dir lands untracked. Refuses to overwrite unless `FORCE=1`. A full GitHub URL also works: `URL='https://github.com/owner/name/tree//'`. - `make skills-materialize [NAME=…] [FORCE=1]` — reconstruct the gitignored vendored skill dirs from the manifest pins (git-init + shallow fetch of the exact `commit` + sparse-checkout). @@ -75,23 +79,23 @@ Targets (each `skills-*` has an `agents-*` twin taking the same variables): Pruning is guarded to untracked dirs whose `.source.json` still names a `repo`, so a committed authored skill is never removed. `NAME=` materializes just that one and prunes nothing. `make install` runs this first; it never fails the build offline — unreachable skills are warned and skipped. -- `make agents-fetch REPO=owner/name SUBPATH=path/to/agent.md [REF=main] [NAME=…] [FORCE=1]` — same, but the subpath is a `.md` file (NAME defaults to its basename minus `.md`), copied into `claude/agents/.md`. +- `make agents-fetch REPO=owner/name SUBPATH=path/to/agent.md [REF=main] [NAME=…] [FORCE=1]` — same, but the subpath is a `.md` file (NAME defaults to its basename minus `.md`), copied into `packages/claude/agents/.md`. Accepts a `/blob/` URL too. - `fetch` also takes an optional `CATEGORY=…` to tag the sidecar on the way in. + `fetch` also takes an optional `CATEGORY=…` to tag the manifest entry on the way in. - `make skills-list` / `make agents-list` — every resource with its status (`remote`/`local`/`unmanaged`) and source, grouped under a ` ()` header (uncategorized last-ish, sorted). -- `make skills-category NAME=… CATEGORY=…` / `make agents-category NAME=… CATEGORY=…` — set/replace a resource's category in place: for a vendored skill it updates the `skills/vendored.json` entry; for an authored skill or agent it updates the `.source.json` sidecar (creating a minimal local one if none exists). +- `make skills-category NAME=… CATEGORY=…` / `make agents-category NAME=… CATEGORY=…` — set/replace a resource's category on its `sources.toml` entry (creating a minimal authored entry if none exists). Use kebab-case slugs that match the README's domain groups. - `make skills-update NAME=…` / `make agents-update NAME=…` — re-resolve the recorded `ref`; if the upstream commit moved, pin the new commit and re-copy, else report up to date (prints `old→new`, preserving `category`). - For a vendored skill this re-materializes the dir and rewrites its `skills/vendored.json` entry (refreshing the cached `description`); for an agent it rewrites the sidecar. + For a vendored skill this re-materializes the dir and rewrites its `sources.toml` entry (refreshing the cached `description`); for an agent it re-copies the `.md` and rewrites the entry. Skips `local`/authored and `unmanaged`. - `make skills-update-all` / `make agents-update-all` — update every remote resource of that kind. -- `make skills-delete NAME=… [YES=1]` / `make agents-delete NAME=… [YES=1]` — remove the resource; for a vendored skill this also drops its `skills/vendored.json` entry and `.gitignore` line, for an agent it removes both the `.md` and its sidecar. +- `make skills-delete NAME=… [YES=1]` / `make agents-delete NAME=… [YES=1]` — remove the resource and its `sources.toml` entry; for a vendored skill this also drops its `.gitignore` line. Prompts unless `YES=1`. - `make skills-catalog [CHECK=1]` — regenerate the standalone catalog at `skills/README.md` (category-grouped tables). - Vendored skills' `category`/`description` come from `skills/vendored.json`; authored skills' come from their `.source.json` `category` + `SKILL.md` frontmatter `description` (first sentence, truncated) — so the catalog regenerates correctly even on a bare checkout where the vendored dirs aren't materialized. + Every skill's `category` comes from `sources.toml`; vendored descriptions are cached there too, while authored ones come from `SKILL.md` frontmatter (first sentence, truncated) — so the catalog regenerates correctly even on a bare checkout where the vendored dirs aren't materialized. The main `README.md` just links to it. Run it after adding, removing, or recategorizing a skill; `CHECK=1` only verifies (exit 1 if stale). -- `make context-budget [CHECK=1] [TOP=N]` — estimate the always-loaded context this repo injects into every Claude Code session (shared `claude/CLAUDE.md` + `rules/*.md`, every skill's name+description, every agent's and command's name+description; tokens ≈ chars/4, no API call), per segment and in total, plus the N heaviest skill descriptions. +- `make context-budget [CHECK=1] [TOP=N]` — estimate the always-loaded context this repo injects into every Claude Code session (shared `packages/claude/CLAUDE.md` + `rules/*.md`, every skill's name+description, every agent's and command's name+description; tokens ≈ chars/4, no API call), per segment and in total, plus the N heaviest skill descriptions. `CHECK=1` exits 1 when the total exceeds `CONTEXT_BUDGET_TOKENS` (Makefile default 12000; `skills-doctor` enforces the same cap, so CI and the commit gate catch growth). Raise the cap deliberately in the Makefile — the diff is the alert. Plugin/marketplace skills live outside this repo and are not counted. @@ -102,7 +106,7 @@ Targets (each `skills-*` has an `agents-*` twin taking the same variables): `skills-fetch` and `skills-update` run the same scan on the staged skill **before** installing it and refuse on failure (`SKILLS_SCAN=0` installs unscanned, logged); when `skillspector` is not installed the gate warns and lets the install through. Install: `make skillspector-install` — pinned to `SKILLSPECTOR_REF` in the Makefile (the release the baselines were reviewed against); bump it deliberately and re-review `security/skillspector/`. `skills-delete` also removes the skill's baseline, and `skills-doctor` flags a baseline that names no skill. -- `make skills-doctor` / `make agents-doctor` — validate every resource: for skills, the manifest is well-formed (required fields, no duplicate names, every vendored dir gitignored), each authored dir has a `SKILL.md` + sidecar with `category`, an authored skill's optional `evals/evals.json` (golden tasks in the skill-creator shape `{skill_name, evals: [{id, name, prompt, expected_output, files}]}`, grown by `harvest-automation`'s `apply evals`) is well-formed, and no dir is a stale vendored orphan (a `.source.json` naming a `repo` with no manifest entry — `skills-materialize` prunes these), plus `skills/README.md` is current and the always-loaded context is under `CONTEXT_BUDGET_TOKENS` (see `context-budget`); for agents, markdown present with non-empty frontmatter `name`/`description` and a sidecar carrying a `category`. +- `make skills-doctor` / `make agents-doctor` — validate every resource: for skills, the manifest is valid TOML (required fields on vendored entries, no duplicate names, every vendored dir gitignored), each authored dir has a `SKILL.md` + a `sources.toml` entry with `category`, an authored skill's optional `evals/evals.json` (golden tasks in the skill-creator shape `{skill_name, evals: [{id, name, prompt, expected_output, files}]}`, grown by `harvest-automation`'s `apply evals`) is well-formed, and no dir is a stale vendored orphan (a `.source.json` naming a `repo` with no manifest entry — `skills-materialize` prunes these), plus `skills/README.md` is current and the always-loaded context is under `CONTEXT_BUDGET_TOKENS` (see `context-budget`); for agents, markdown present with non-empty frontmatter `name`/`description` and a `sources.toml` entry carrying a `category`. Exit 1 on any issue. - `make suites-catalog [CHECK=1]` — regenerate (or verify) the generated blocks in `suites/*/README.md` and the Suites index in `README.md` (see "Skill suites"). - `make preflight` / `make lint` / `make test` — the pre-flight gate (both doctors + lint), the syntax pass alone (`bash -n`, `py_compile`), and the repo's own regression tests (`scripts/test-*.{sh,py}`); the commit-gate hook and CI call these same targets. @@ -113,7 +117,7 @@ Note: the make variable is `SUBPATH`, not `PATH` — `PATH=` on a make command l A **suite** is a curated, ordered set of skills with a shareable landing page — pure metadata + docs, the flat `skills/` tree is untouched. -- `suites//suite.json` — `{"title", "tagline"?, "skills": [ordered skill names]}`; membership lives here, never in sidecars. +- `suites//suite.json` — `{"title", "tagline"?, "skills": [ordered skill names]}`; membership lives here, never in `sources.toml`. - `suites//README.md` — hand-written narrative; the region between `` markers (skill table + skills.sh install command) is generated. - The main `README.md`'s `` region (the Suites index) is generated too; `skills/README.md` remains owned by `skills-catalog` alone. - `make suites-catalog [CHECK=1]` regenerates (or verifies) all generated regions; `make skills-doctor` includes the check. @@ -127,43 +131,43 @@ Requires `npx` (Node.js) and `jq`; it also relies on `resource-manager.sh`. - `make skills-find [Q=query] [OWNER=org]` — `npx skills find`; prints ranked skills.sh hits as `owner/repo@skill`. - `make skills-add SOURCE=owner/repo [SKILL='a b'] [ALL=1] [REF=…] [CATEGORY=…] [FORCE=1]` — fetch + vendor. `SOURCE` accepts the `owner/repo@skill` form `skills-find` prints (paste it verbatim); the `@skill` suffix is peeled into a selected skill. - `CATEGORY=` tags every skill fetched in the call (it flows through to `resource-manager.sh`'s sidecar). + `CATEGORY=` tags every skill fetched in the call (it flows through to the `sources.toml` entry). The integration is deliberately **hybrid**, not a replacement for `resource-manager.sh`. `skills-vendor.sh` uses the CLI only as a *resolver/fetcher*: it runs `skills add … --copy` into a throwaway staging dir, reads the CLI's project `skills-lock.json` (per skill: `source`, `sourceType`, `skillPath`), then re-vendors each through `resource-manager.sh fetch`. -So a CLI-fetched skill is recorded in `skills/vendored.json` and gitignored/materialized **exactly like any other vendored skill**, and `skills-list` / `skills-update` / `skills-delete` plus the Makefile symlinks keep working unchanged — no second update mechanism, no CLI lockfile committed to the repo. +So a CLI-fetched skill is recorded in `sources.toml` and gitignored/materialized **exactly like any other vendored skill**, and `skills-list` / `skills-update` / `skills-delete` plus the Makefile symlinks keep working unchanged — no second update mechanism, no CLI lockfile committed to the repo. Why not let the `skills` CLI own installation directly (its `add`/`update`/`experimental_install`): - Its agent→path map is fixed (`claude-code` → `~/.claude/skills`); it ignores `CLAUDE_CONFIG_DIR`, so it can't target the two profiles (`~/.claude-personal`, `~/.claude-work`) — which our single symlinked `skills/` tree already serves. -- It manages skills only, not the `claude/agents/` subagents (`resource-manager.sh --kind agent` still owns those). -- It installs into per-agent dirs from its own canonical copy; this repo's reproducibility comes from the pinned `skills/vendored.json` manifest (materialized deterministically from each recorded `commit`), so our vendoring pipeline stays the backbone. +- It manages skills only, not the `packages/claude/agents/` subagents (`resource-manager.sh --kind agent` still owns those). +- It installs into per-agent dirs from its own canonical copy; this repo's reproducibility comes from the pinned `sources.toml` manifest (materialized deterministically from each recorded `commit`), so our vendoring pipeline stays the backbone. ## Architecture notes that are easy to miss - **Two Claude profiles via `CLAUDE_CONFIG_DIR`.** `scripts/claude-multi-account.sh` is documentation (shell-function snippets to copy into `~/.zprofile`), not something that runs. The `pclaude`/`wclaude` wrappers set `CLAUDE_CONFIG_DIR` to `~/.claude-personal` or `~/.claude-work`. - Both dirs share the same `CLAUDE.md` and `skills/` via symlinks maintained by the Makefile — changes to `claude/CLAUDE.md` or `skills/` immediately apply to both profiles. -- **`claude/CLAUDE.md` is the shared global user CLAUDE.md**, not this file. + Both dirs share the same `CLAUDE.md` and `skills/` via symlinks maintained by the Makefile — changes to `packages/claude/CLAUDE.md` or `skills/` immediately apply to both profiles. +- **`packages/claude/CLAUDE.md` is the shared global user CLAUDE.md**, not this file. It gets symlinked to `~/.claude-personal/CLAUDE.md` and `~/.claude-work/CLAUDE.md` by `claude-md-link`. Keep it minimal and profile-agnostic. -- **`pi/` is stowed with `--adopt`.** +- **`packages/pi/` is stowed with `--adopt`.** On first `make install`, stow moves any pre-existing files in `~/.pi` into this repo, replacing them with symlinks. - That means `pi/agent/settings.json`, `pi/extensions/*.ts`, and `pi/prompts/` are the live files the agent reads — edits here take effect immediately in `~/.pi/...`. - The `.pi/` directory in the repo root is unrelated scaffolding (empty). -- **`pi/extensions/subagent/` is a directory extension** (listed without `.ts` suffix in `PI_EXTENSIONS`); the rest are single-file TS extensions. + That means `packages/pi/agent/settings.json`, `packages/pi/extensions/*.ts`, and `packages/pi/prompts/` are the live files the agent reads — edits here take effect immediately in `~/.pi/...`. + +- **`packages/pi/extensions/subagent/` is a directory extension** (listed without `.ts` suffix in `PI_EXTENSIONS`); the rest are single-file TS extensions. Adding a new upstream extension requires editing `PI_EXTENSIONS` in the Makefile. - **`skills/` is the single source of truth** for skills across pi and both Claude profiles. - `skills-link` symlinks `$(CURDIR)/skills` into `~/.pi/skills`, `~/.claude-personal/skills`, `~/.claude-work/skills`. - What's *committed* under it, though, is only the authored skill dirs plus the `skills/vendored.json` manifest; vendored skill dirs are gitignored and materialized into place (see the manifest model above), so the symlinked tree the tools read is authored-committed + vendored-materialized. -- **`claude/commands/`, `claude/rules/`, `claude/scripts/`, and `claude/agents/`** are the single source of truth for user-scoped slash commands, rules, helper scripts, and subagents across both Claude profiles. -`commands-link` / `rules-link` / `scripts-link` / `agents-link` symlink them into `~/.claude-personal/` and `~/.claude-work/` (not into `~/.pi/` — pi doesn't consume these; pi has its own vendored `pi/extensions/subagent/agents/`). + The `claude` and `pi` stow packages each carry a committed `skills -> ../../skills` symlink, so `harness-link` exposes the tree at `~/.pi/skills`, `~/.claude-personal/skills`, `~/.claude-work/skills`. + What's *committed* under it, though, is only the authored skill dirs (their source records live in `sources.toml`); vendored skill dirs are gitignored and materialized into place (see the manifest model above), so the symlinked tree the tools read is authored-committed + vendored-materialized. +- **`packages/claude/commands/`, `packages/claude/rules/`, `packages/claude/scripts/`, and `packages/claude/agents/`** are the single source of truth for user-scoped slash commands, rules, helper scripts, and subagents across both Claude profiles. +`commands-link` / `rules-link` / `scripts-link` / `agents-link` symlink them into `~/.claude-personal/` and `~/.claude-work/` (not into `~/.pi/` — pi doesn't consume these; pi has its own vendored `packages/pi/extensions/subagent/agents/`). Rules and docs reference scripts via `$CLAUDE_CONFIG_DIR/scripts/...` so the path resolves correctly under either profile. -`claude/scripts/` currently holds `agent-routing-hook.sh` (the `PreToolUse` routing hook referenced by `rules/model-routing.md`), +`packages/claude/scripts/` currently holds `agent-routing-hook.sh` (the `PreToolUse` routing hook referenced by `rules/model-routing.md`), `safety-guard-hook.py` (a `PreToolUse` deny/ask gate on `Bash` and `Edit|Write` — the Claude-side twin of pi's `permission-gate.ts` + `protected-paths.ts`, referenced by `rules/git-hygiene.md`), `usage-log-hook.py` + `usage-report.py` (`SubagentStop`/`Stop`/`SessionEnd` telemetry into `$CLAUDE_CONFIG_DIR/usage.jsonl` and its aggregator, also referenced by `rules/model-routing.md`; `make usage-report` runs the aggregator for every profile), -`browser-endpoint.sh` (prints the CDP endpoint of the user's browser, referenced by `claude/CLAUDE.md`), +`browser-endpoint.sh` (prints the CDP endpoint of the user's browser, referenced by `packages/claude/CLAUDE.md`), and `statusline-command.sh` (a `statusLine` hook script). No hook among them is wired by default; a profile must opt in via its own `settings.json`, which is not tracked in this repo — each hook script's header carries its wiring snippet. `browser-endpoint.sh` is a plain helper instead: it is invoked by path and needs no wiring. @@ -172,7 +176,7 @@ Agents are single `.md` files fetched and tracked by `resource-manager.sh` (see Installation is manual per-profile; the Makefile only reports drift. Lines are `@`; blanks and `#` comments are ignored. - **Several `.gitignore`'d paths live in the tree but are not checked in.** - The vendored skill dirs (a managed block in `.gitignore`, one `/skills//` line each — rewritten by `resource-manager.sh`) are materialized from `skills/vendored.json`, so after a fresh clone they're absent until `make install` (or `make skills-materialize`) reconstructs them. + The vendored skill dirs (a managed block in `.gitignore`, one `/skills//` line each — rewritten by `resource-manager.sh`) are materialized from `sources.toml`, so after a fresh clone they're absent until `make install` (or `make skills-materialize`) reconstructs them. `docs/superpowers/` holds local-only design artifacts (brainstorming specs, implementation plans). `skills/bin/` holds the `parakeet-cpp-transcribe` binary the `transcribe` skill downloads at runtime — under the symlinked profiles its `../bin` resolves back into the repo, so it's ignored to keep the blob out of git. Don't expect any of these to be present after a fresh clone. @@ -183,13 +187,13 @@ Agents are single `.md` files fetched and tracked by `resource-manager.sh` (see Each skill should do one well-scoped thing so it can be invoked on its own or chained with others, rather than bundling several unrelated workflows. Prefer extracting shared logic into a script the skill calls over duplicating it across skills, and keep `SKILL.md` focused enough that another skill (or the model) can lean on it without inheriting unrelated behavior. When a skill needs another skill, reference it by name as a soft dependency instead of copying its contents. -- Treat vendored skill dirs and `pi/extensions/` as read-only upstream copies — for vendored skills the dir is gitignored and `make skills-materialize`/`skills-update` overwrites it wholesale from the pinned commit, so local edits there are lost with no trace. - If you genuinely need to diverge from a vendored skill, **reclassify it as authored**: restore the committed dir, rewrite its `.source.json` to `{"repo": null, ...}` with a `note` recording the fork point, drop it from `skills/vendored.json` and the `.gitignore` block — then it's committed and safe to edit. +- Treat vendored skill dirs and `packages/pi/extensions/` as read-only upstream copies — for vendored skills the dir is gitignored and `make skills-materialize`/`skills-update` overwrites it wholesale from the pinned commit, so local edits there are lost with no trace. + If you genuinely need to diverge from a vendored skill, **reclassify it as authored**: restore the committed dir, strip the `repo`/`subpath`/`ref`/`commit` fields from its `sources.toml` entry (keep `category`, add a `note` recording the fork point), and drop its `.gitignore` line — then it's committed and safe to edit. This is a temporary state, not a destination: `itr-india` was forked this way, the divergence was contributed back upstream, and once it merged the skill was re-vendored (`make skills-fetch … FORCE=1` + `git rm -r --cached`) so it tracks a pin again. Prefer that round trip over holding a permanent fork. -- The committed record for a vendored skill is its `skills/vendored.json` entry, not the on-disk `.source.json` (which is gitignored and regenerated on materialize); change a vendored skill's source by re-fetching/updating, which rewrites the manifest entry. - For agents and authored skills, the `.source.json` sidecar is still the committed record but is likewise regenerated on fetch/update — don't hand-edit it expecting persistence. - Authored resources (no upstream) keep a `{"repo": null}` sidecar so they survive `skills-update-all` / `agents-update-all` untouched. +- The committed record for every resource is its `sources.toml` entry, not the on-disk `.source.json` (a gitignored marker regenerated on materialize); change a resource's source by re-fetching/updating, which rewrites the entry. + Fetch/update rewrite an entry wholesale, so a hand-added field other than the known ones does not persist. + Authored resources (no `repo` in their entry) survive `skills-update-all` / `agents-update-all` untouched. - When adding a new profile, update `CLAUDE_CONFIG_DIRS` (Makefile line 9) — it drives both the CLAUDE.md and skills symlink loops. ## Skill authoring addenda @@ -198,3 +202,13 @@ Agents are single `.md` files fetched and tracked by `resource-manager.sh` (see - Before committing a new or changed skill, run `make skills-scan NAME=`; fix real findings, and accept a false positive only with a reason in `security/skillspector/.json`. - Before committing any change, run the pre-flight gate: `make preflight` (both doctors — which cover the catalog, suites, and the context budget — plus `bash -n`/`py_compile` over every script) and `make test` (every `scripts/test-*.{sh,py}`). The gate is defined once in the Makefile and enforced twice: `.claude/settings.json` wires `scripts/precommit-gate-hook.sh` as a project-scoped `PreToolUse` hook that blocks any `git commit` while `make preflight` fails, and `.github/workflows/checks.yml` runs `make preflight`, `make test`, and the SkillSpector scan on push and PR. + +## Dotfiles conventions + +- Home stow flags are fixed at `--dotfiles --no-folding` and must not be changed; both are security invariants, explained in README.md § "The stow model". + They apply only to the `$HOME` packages (`HOME_PACKAGES` in the Makefile); the harness links (`~/.claude-*`, `~/.pi`) are whole-directory symlinks on purpose. +- File names in `packages/` use the `dot-` prefix (`dot-zshrc` → `~/.zshrc`); requires stow ≥ 2.4.0. +- Never add packages for credential-bearing dirs: `gh/hosts.yml`, `gcloud`, `1Password`, `op`, `github-copilot`. +- `Brewfile` is the curated package list; `Brewfile.dump` (gitignored) is regenerated via `make brew-dump` for re-curation diffs only. +- `bootstrap.sh` is the new-Mac entry point; keep it idempotent, check-then-act. +- To add a new tool config, follow the numbered recipe in README.md § "Adding a new tool config"; it is the canonical version. diff --git a/Makefile b/Makefile index 78fbdec..4a5ff13 100644 --- a/Makefile +++ b/Makefile @@ -1,21 +1,34 @@ STOW := stow -STOW_DIR := $(CURDIR) PI_TARGET := $(HOME)/.pi +# sources.toml is read/written via tomllib, so python >= 3.11 is required; +# prefer the brew python the Brewfile installs over a stale system python3. +PYTHON ?= python3 + +# Home stow packages (dotfiles). Flags are a security invariant, not a style +# choice — see "The stow model" in README.md. +HOME_STOW_FLAGS := --dir=$(CURDIR)/packages --target=$(HOME) --dotfiles --no-folding --verbose=1 +HOME_PACKAGES := zsh git atuin btop gh bin + +BREWFILE := $(CURDIR)/Brewfile +MANIFESTS := $(CURDIR)/manifests + PI_SKILLS_REPO := https://github.com/badlogic/pi-skills PI_SKILLS_CACHE := /tmp/pi-skills PI_SKILLS_DIR := $(CURDIR)/skills CLAUDE_CONFIG_DIRS := $(HOME)/.claude-personal $(HOME)/.claude-work -SKILL_LINK_TARGETS := $(PI_TARGET) $(CLAUDE_CONFIG_DIRS) -CLAUDE_DIR := $(CURDIR)/claude -PLUGINS_FILE := $(CLAUDE_DIR)/plugins.txt -CLAUDE_MD_FILE := $(CLAUDE_DIR)/CLAUDE.md -COMMANDS_DIR := $(CLAUDE_DIR)/commands -RULES_DIR := $(CLAUDE_DIR)/rules +CLAUDE_DIR := $(CURDIR)/packages/claude +PLUGINS_FILE := $(MANIFESTS)/claude-plugins.txt SCRIPTS_DIR := $(CLAUDE_DIR)/scripts -AGENTS_DIR := $(CLAUDE_DIR)/agents + +# Harness stow packages (claude → each profile, pi → ~/.pi). These stow with +# directory folding ON — whole-dir symlinks are the point, unlike the +# --no-folding invariant that protects the $HOME packages. +HARNESS_STOW := $(STOW) --dir=$(CURDIR)/packages +CLAUDE_PKG_ENTRIES := CLAUDE.md commands rules scripts agents skills +PI_PKG_ENTRIES := agent extensions prompts skills README.md RESOURCE_MANAGER := $(CURDIR)/scripts/resource-manager.sh # NVIDIA SkillSpector release the security scan and its baselines were reviewed @@ -48,10 +61,11 @@ PI_EXTENSIONS := \ SHELL := /bin/bash -.PHONY: install uninstall \ - skills-link skills-unlink claude-md-link claude-md-unlink \ - commands-link commands-unlink rules-link rules-unlink scripts-link scripts-unlink \ - agents-link agents-unlink \ +.PHONY: install uninstall doctor drift python-check \ + brew-install brew-check brew-dump cask-adopt \ + go-install npm-install pipx-install tools-install \ + stow-link stow-unlink stow-adopt macos-apply raycast-export \ + harness-link harness-unlink \ skills-sync extensions-sync plugins-check plugins-sync \ skills-find skills-add \ skills-fetch skills-materialize skills-list skills-update skills-update-all skills-category skills-delete \ @@ -59,152 +73,110 @@ SHELL := /bin/bash agents-fetch agents-list agents-update agents-update-all agents-category agents-delete \ agents-doctor preflight lint test -install: skills-materialize skills-link claude-md-link commands-link rules-link scripts-link agents-link - mkdir -p $(PI_TARGET) - $(STOW) --dir=$(STOW_DIR) --target=$(PI_TARGET) --adopt pi +install: brew-install stow-link skills-materialize harness-link tools-install -uninstall: skills-unlink claude-md-unlink commands-unlink rules-unlink scripts-unlink agents-unlink - $(STOW) --dir=$(STOW_DIR) --target=$(PI_TARGET) --delete pi +uninstall: stow-unlink harness-unlink -skills-link: - mkdir -p $(PI_SKILLS_DIR) - for target in $(SKILL_LINK_TARGETS); do \ - mkdir -p $$target; \ - link=$$target/skills; \ - if [ -L $$link ]; then \ - rm $$link; \ - elif [ -d $$link ]; then \ - rmdir $$link 2>/dev/null || { echo "skip: $$link is a non-empty directory"; continue; }; \ - fi; \ - ln -s $(PI_SKILLS_DIR) $$link; \ - echo "linked: $$link -> $(PI_SKILLS_DIR)"; \ - done +brew-install: + brew bundle --file=$(BREWFILE) -skills-unlink: - for target in $(SKILL_LINK_TARGETS); do \ - link=$$target/skills; \ - if [ -L $$link ] && [ "$$(readlink $$link)" = "$(PI_SKILLS_DIR)" ]; then \ - rm $$link; \ - echo "unlinked: $$link"; \ - fi; \ - done +brew-check: + brew bundle check --file=$(BREWFILE) -claude-md-link: - @test -f $(CLAUDE_MD_FILE) || { echo "missing: $(CLAUDE_MD_FILE)"; exit 1; } - for target in $(CLAUDE_CONFIG_DIRS); do \ - mkdir -p $$target; \ - link=$$target/CLAUDE.md; \ - if [ -L $$link ]; then \ - rm $$link; \ - elif [ -f $$link ]; then \ - backup=$$link.bak.$$(date +%Y%m%d%H%M%S); \ - mv $$link $$backup; \ - echo "backed up: $$link -> $$backup"; \ - fi; \ - ln -s $(CLAUDE_MD_FILE) $$link; \ - echo "linked: $$link -> $(CLAUDE_MD_FILE)"; \ - done +# Regenerate Brewfile.dump (gitignored) to diff against the curated Brewfile; +# never overwrites Brewfile itself. +brew-dump: + brew bundle dump --file=$(CURDIR)/Brewfile.dump --describe --force + @echo "wrote Brewfile.dump — diff against Brewfile to re-curate" -claude-md-unlink: - for target in $(CLAUDE_CONFIG_DIRS); do \ - link=$$target/CLAUDE.md; \ - if [ -L $$link ] && [ "$$(readlink $$link)" = "$(CLAUDE_MD_FILE)" ]; then \ - rm $$link; \ - echo "unlinked: $$link"; \ - fi; \ - done +# Take over apps that were installed outside brew (pkg-based casks prompt for sudo). +cask-adopt: + brew install --cask --adopt 1password claude devin-desktop google-chrome \ + openvpn-connect tailscale-app wispr-flow -commands-link: - mkdir -p $(COMMANDS_DIR) - for target in $(CLAUDE_CONFIG_DIRS); do \ - mkdir -p $$target; \ - link=$$target/commands; \ - if [ -L $$link ]; then \ - rm $$link; \ - elif [ -d $$link ]; then \ - rmdir $$link 2>/dev/null || { echo "skip: $$link is a non-empty directory"; continue; }; \ - fi; \ - ln -s $(COMMANDS_DIR) $$link; \ - echo "linked: $$link -> $(COMMANDS_DIR)"; \ - done +tools-install: go-install npm-install pipx-install -commands-unlink: - for target in $(CLAUDE_CONFIG_DIRS); do \ - link=$$target/commands; \ - if [ -L $$link ] && [ "$$(readlink $$link)" = "$(COMMANDS_DIR)" ]; then \ - rm $$link; \ - echo "unlinked: $$link"; \ - fi; \ +# Installs each module from manifests/go-tools.txt; lines without @version get @latest. +go-install: + @command -v go >/dev/null || { echo "go not found — run: make brew-install"; exit 1; } + @grep -vE '^[[:space:]]*#|^[[:space:]]*$$' $(MANIFESTS)/go-tools.txt | while read -r mod; do \ + case "$$mod" in *@*) ;; *) mod="$$mod@latest" ;; esac; \ + echo "go install $$mod"; \ + go install "$$mod"; \ done -rules-link: - mkdir -p $(RULES_DIR) - for target in $(CLAUDE_CONFIG_DIRS); do \ - mkdir -p $$target; \ - link=$$target/rules; \ - if [ -L $$link ]; then \ - rm $$link; \ - elif [ -d $$link ]; then \ - rmdir $$link 2>/dev/null || { echo "skip: $$link is a non-empty directory"; continue; }; \ - fi; \ - ln -s $(RULES_DIR) $$link; \ - echo "linked: $$link -> $(RULES_DIR)"; \ - done +npm-install: + @command -v npm >/dev/null || { echo "npm not found — run: nvm install --lts"; exit 1; } + @grep -vE '^[[:space:]]*#|^[[:space:]]*$$' $(MANIFESTS)/npm-globals.txt | xargs npm install -g -rules-unlink: - for target in $(CLAUDE_CONFIG_DIRS); do \ - link=$$target/rules; \ - if [ -L $$link ] && [ "$$(readlink $$link)" = "$(RULES_DIR)" ]; then \ - rm $$link; \ - echo "unlinked: $$link"; \ - fi; \ - done - -scripts-link: - mkdir -p $(SCRIPTS_DIR) - for target in $(CLAUDE_CONFIG_DIRS); do \ - mkdir -p $$target; \ - link=$$target/scripts; \ - if [ -L $$link ]; then \ - rm $$link; \ - elif [ -d $$link ]; then \ - rmdir $$link 2>/dev/null || { echo "skip: $$link is a non-empty directory"; continue; }; \ - fi; \ - ln -s $(SCRIPTS_DIR) $$link; \ - echo "linked: $$link -> $(SCRIPTS_DIR)"; \ +pipx-install: + @command -v pipx >/dev/null || { echo "pipx not found — run: make brew-install"; exit 1; } + @grep -vE '^[[:space:]]*#|^[[:space:]]*$$' $(MANIFESTS)/pipx-tools.txt | while read -r pkg; do \ + pipx install "$$pkg"; \ done -scripts-unlink: - for target in $(CLAUDE_CONFIG_DIRS); do \ - link=$$target/scripts; \ - if [ -L $$link ] && [ "$$(readlink $$link)" = "$(SCRIPTS_DIR)" ]; then \ - rm $$link; \ - echo "unlinked: $$link"; \ - fi; \ +stow-link: + $(STOW) $(HOME_STOW_FLAGS) --restow $(HOME_PACKAGES) + +stow-unlink: + $(STOW) $(HOME_STOW_FLAGS) --delete $(HOME_PACKAGES) + +# Absorb pre-existing real files at target paths into the repo working tree. +stow-adopt: + $(STOW) $(HOME_STOW_FLAGS) --adopt $(HOME_PACKAGES) + @echo "" + @echo "== adopted; repo state now ==" + @git status --short + @echo "" + @echo "!! REVIEW 'git diff' BEFORE COMMITTING — adopt replaces repo files with the live ones !!" + +macos-apply: + bash $(CURDIR)/macos/defaults.sh + +doctor: + bash $(CURDIR)/scripts/doctor.sh + +drift: + bash $(CURDIR)/scripts/drift.sh + +# Raycast keeps settings in an encrypted local DB (extension configs can hold +# API tokens), so its own encrypted export is the backup mechanism — never this +# repo (*.rayconfig is gitignored). Opens the export dialog; save the file to a +# private location (iCloud Drive / 1Password). Restore on a new Mac via +# Raycast Settings -> Advanced -> Import. +raycast-export: + open "raycast://extensions/raycast/raycast/export-settings-data" + +# Stow the claude package into each profile and the pi package into ~/.pi. +# Pre-clean removes only SYMLINKS at the managed names (stale links from the +# pre-stow era, or dangling ones after a repo move — including per-file links +# inside real target subdirs); a real file or dir is left +# for stow to conflict on — move it aside yourself (timestamped backup), and do +# not reach for --adopt on the profile dirs. +harness-link: + @for d in $(CLAUDE_CONFIG_DIRS); do \ + mkdir -p $$d; \ + for entry in $(CLAUDE_PKG_ENTRIES); do \ + if [ -L $$d/$$entry ]; then rm $$d/$$entry; fi; \ + done; \ + find $$d -maxdepth 2 -type l ! -exec test -e {} \; -delete; \ + $(HARNESS_STOW) --target=$$d --restow claude; \ + echo "stowed: claude -> $$d"; \ done - -agents-link: - mkdir -p $(AGENTS_DIR) - for target in $(CLAUDE_CONFIG_DIRS); do \ - mkdir -p $$target; \ - link=$$target/agents; \ - if [ -L $$link ]; then \ - rm $$link; \ - elif [ -d $$link ]; then \ - rmdir $$link 2>/dev/null || { echo "skip: $$link is a non-empty directory"; continue; }; \ - fi; \ - ln -s $(AGENTS_DIR) $$link; \ - echo "linked: $$link -> $(AGENTS_DIR)"; \ + @mkdir -p $(PI_TARGET) + @for entry in $(PI_PKG_ENTRIES); do \ + if [ -L $(PI_TARGET)/$$entry ]; then rm $(PI_TARGET)/$$entry; fi; \ done + @find $(PI_TARGET) -maxdepth 2 -type l ! -exec test -e {} \; -delete + @$(HARNESS_STOW) --target=$(PI_TARGET) --adopt pi + @echo "stowed: pi -> $(PI_TARGET)" -agents-unlink: - for target in $(CLAUDE_CONFIG_DIRS); do \ - link=$$target/agents; \ - if [ -L $$link ] && [ "$$(readlink $$link)" = "$(AGENTS_DIR)" ]; then \ - rm $$link; \ - echo "unlinked: $$link"; \ - fi; \ +harness-unlink: + @for d in $(CLAUDE_CONFIG_DIRS); do \ + $(HARNESS_STOW) --target=$$d --delete claude && echo "unstowed: claude -> $$d"; \ done + @$(HARNESS_STOW) --target=$(PI_TARGET) --delete pi + @echo "unstowed: pi -> $(PI_TARGET)" plugins-check: @test -f $(PLUGINS_FILE) || { echo "missing: $(PLUGINS_FILE)"; exit 1; } @@ -285,9 +257,9 @@ extensions-sync: # Front-end onto the vercel-labs `skills` CLI (npx skills, the skills.sh # ecosystem). skills-find discovers; skills-add fetches via the CLI and vendors # the result into skills/ through resource-manager.sh, so each lands with a -# .source.json and stays manageable by skills-list / skills-update / skills-delete. +# sources.toml entry and stays manageable by skills-list / skills-update / skills-delete. # The CLI is used only as a resolver/fetcher — it never installs per-agent, so -# the two Claude profiles and claude/agents/ subagents are unaffected. +# the two Claude profiles and packages/claude/agents/ subagents are unaffected. # Set GITHUB_TOKEN (or have `gh` logged in) to avoid anonymous rate limits. skills-find: @@ -304,8 +276,8 @@ skills-add: $(if $(FORCE),--force) # --- Source management (scripts/resource-manager.sh) ----------------------- -# Fetch one skill (dir under skills/) or agent (.md under claude/agents/) from -# any repo/subpath, tracking its source in a .source.json sidecar so it can be +# Fetch one skill (dir under skills/) or agent (.md under packages/claude/agents/) from +# any repo/subpath, tracking its source in the sources.toml manifest so it can be # listed, updated, and deleted. skills-fetch: @@ -319,7 +291,7 @@ skills-fetch: $(if $(FORCE),--force) # Reconstruct vendored skills' working files from the pinned commits in -# skills/vendored.json (their files are gitignored, not committed). Idempotent — +# sources.toml (their files are gitignored, not committed). Idempotent — # skips any skill already present at the right commit. Run by `make install`; # NAME= materializes one, FORCE=1 re-fetches even if present. skills-materialize: @@ -360,11 +332,11 @@ suites-catalog: context-budget: @$(RESOURCE_MANAGER) --kind skill budget $(if $(CHECK),--check) $(if $(TOP),--top $(TOP)) -# Aggregate the usage telemetry (claude/scripts/usage-log-hook.py) of every +# Aggregate the usage telemetry (packages/claude/scripts/usage-log-hook.py) of every # profile: subagent spend by agent type × model, per-day cache-hit ratio. # SINCE=N limits to the last N days (default 30). usage-report: - @python3 $(SCRIPTS_DIR)/usage-report.py $(if $(SINCE),--since $(SINCE)) $(foreach d,$(CLAUDE_CONFIG_DIRS),--log "$(d)/usage.jsonl") + @$(PYTHON) $(SCRIPTS_DIR)/usage-report.py $(if $(SINCE),--since $(SINCE)) $(foreach d,$(CLAUDE_CONFIG_DIRS),--log "$(d)/usage.jsonl") # Security-scan skills with NVIDIA SkillSpector (scripts/skills-scan.py): # every skill by default, NAME=x for one, LLM=1 adds the semantic pass via the @@ -374,7 +346,7 @@ usage-report: # skills-fetch / skills-update run the same scan on the staged skill before # installing it (SKILLS_SCAN=0 skips). skills-scan: - @python3 $(CURDIR)/scripts/skills-scan.py $(if $(NAME),--name "$(NAME)") $(if $(LLM),--llm) $(if $(SHOW),--show-suppressed) $(if $(REPORT),--report "$(REPORT)") $(if $(FAIL_AT),--fail-at $(FAIL_AT)) $(if $(QUIET),--quiet) + @$(PYTHON) $(CURDIR)/scripts/skills-scan.py $(if $(NAME),--name "$(NAME)") $(if $(LLM),--llm) $(if $(SHOW),--show-suppressed) $(if $(REPORT),--report "$(REPORT)") $(if $(FAIL_AT),--fail-at $(FAIL_AT)) $(if $(QUIET),--quiet) # Install (or move to) the pinned SkillSpector release with uv. skillspector-install: @@ -419,13 +391,17 @@ agents-doctor: # catalog, suites, and the context budget) plus a syntax pass over every script. # The project commit-gate hook (scripts/precommit-gate-hook.sh) and CI run this # same target; a new check goes here and nowhere else. -preflight: skills-doctor agents-doctor lint +preflight: python-check skills-doctor agents-doctor lint + +python-check: + @$(PYTHON) -c 'import tomllib' 2>/dev/null \ + || { echo "python3 >= 3.11 with tomllib is required (brew install python)"; exit 1; } lint: - @for f in scripts/*.sh claude/scripts/*.sh; do bash -n "$$f" || exit 1; done - @python3 -m py_compile scripts/*.py claude/scripts/*.py + @for f in scripts/*.sh packages/claude/scripts/*.sh; do bash -n "$$f" || exit 1; done + @$(PYTHON) -m py_compile scripts/*.py packages/claude/scripts/*.py # The repo's own regression tests: every scripts/test-*.sh and scripts/test-*.py. test: @set -e; for t in scripts/test-*.sh; do echo "== $$t"; bash "$$t"; done; \ - for t in scripts/test-*.py; do echo "== $$t"; python3 "$$t"; done + for t in scripts/test-*.py; do echo "== $$t"; $(PYTHON) "$$t"; done diff --git a/README.md b/README.md index 801fd31..b441bdc 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,21 @@ -# harness-configs +# dotfiles -Portable AI coding-harness configs — shared skills, commands, rules, agents, and settings — provisioned from **one source of truth** across multiple [Claude Code](https://claude.com/claude-code) and [pi](https://github.com/badlogic/pi-mono) profiles. +One repo for the whole machine: macOS dotfiles (GNU stow packages, a curated Brewfile, a one-shot bootstrap) **and** portable AI coding-harness configs — shared skills, commands, rules, agents, and settings — provisioned from **one source of truth** across multiple [Claude Code](https://claude.com/claude-code) and [pi](https://github.com/badlogic/pi-mono) profiles. -A personal dotfiles-style repo, published so others can borrow the architecture. +A personal repo, published so others can borrow the architecture. Paths are hardcoded to one machine — adapt before adopting (see [Adopt it](#adopt-it)). +## New Mac quick start + +```sh +curl -fsSL https://raw.githubusercontent.com/sanketsudake/dotfiles/main/bootstrap.sh -o /tmp/bootstrap.sh +bash /tmp/bootstrap.sh +``` + +The script is idempotent: Xcode CLT and Homebrew if missing, clone to `~/personal/dotfiles`, `brew bundle`, stow links (pre-existing files are moved to a timestamped `~/.dotfiles-backup-*` dir), harness links, and `make doctor`. +Afterwards run the printed manual steps (`gh auth login`, `atuin login`, `git lfs install`) and open a new terminal. +On an existing machine, clone the repo and run `make install`. + ## Ideas worth stealing - **One source of truth, many harnesses.** @@ -12,7 +23,7 @@ Paths are hardcoded to one machine — adapt before adopting (see [Adopt it](#ad - **Two Claude profiles via `CLAUDE_CONFIG_DIR`.** `pclaude` / `wclaude` wrappers keep personal and work accounts isolated while sharing the same skills and rules. - **Per-resource source tracking.** - Each vendored skill is pinned in `skills/vendored.json` (repo, subpath, commit) and materialized on install — so an upstream update is one command and a bare clone stays reproducible. + Every skill and agent source lives in one `sources.toml` manifest; vendored skills are pinned there (repo, subpath, commit) and materialized on install — so an upstream update is one command and a bare clone stays reproducible. - **Generated catalog, enforced by a doctor.** `skills/README.md` is generated from that metadata; `make skills-doctor` fails when anything drifts. - **Guardrails as code, not hope.** @@ -28,13 +39,18 @@ Paths are hardcoded to one machine — adapt before adopting (see [Adopt it](#ad ## Layout ``` -harness-configs/ +dotfiles/ ├── Makefile # primary interface — every - target ├── CLAUDE.md # guide for agents working IN this repo (full Makefile reference) +├── bootstrap.sh # new-Mac entry point +├── Brewfile # curated brew formulae, casks, mas apps, VS Code extensions +├── manifests/ # non-brew tools: go-tools.txt, npm-globals.txt, pipx-tools.txt +├── macos/ # deliberately-changed macOS defaults (make macos-apply) +├── packages/ # stow packages for $HOME (zsh, git, atuin, btop, gh, bin) ├── claude/ # Claude Code config, symlinked into both profiles │ ├── CLAUDE.md # shared global user instructions │ ├── commands/ # slash commands -│ ├── agents/ # subagents (+ .source.json sidecars) +│ ├── agents/ # subagents │ ├── rules/ # model routing, git hygiene, delegation │ ├── scripts/ # hooks (routing, safety guard, usage telemetry) + statusline │ └── plugins.txt # desired-state plugin list @@ -43,7 +59,7 @@ harness-configs/ ├── suites/ # curated skill-suite landing pages ├── security/ # SkillSpector baselines (accepted findings, with reasons) ├── pi/ # pi agent config, stowed into ~/.pi -└── scripts/ # repo tooling (not symlinked into profiles) +└── scripts/ # repo tooling (doctor, drift, resource manager — not symlinked) ``` `make install` symlinks `skills/` and `claude/*` into both Claude profiles and `~/.pi`, and stows `pi/` — safe to re-run. @@ -88,13 +104,14 @@ Plus the shared `CLAUDE.md` (secrets hygiene, semantic-line-break markdown per [ Prerequisites: `git`, [GNU `stow`](https://www.gnu.org/software/stow/), `jq` (plus `gh` / `python3` / `npx` for the skills that use them). ```sh -git clone https://github.com/sanketsudake/harness-configs.git -cd harness-configs +git clone https://github.com/sanketsudake/dotfiles.git +cd dotfiles # Before installing: # 1. Edit CLAUDE_CONFIG_DIRS in the Makefile (your profiles) # 2. Copy the pclaude/wclaude snippets from scripts/claude-multi-account.sh into your shell profile # 3. Make claude/CLAUDE.md yours — it's opinionated -make install # symlink claude/ + skills/ into profiles, stow pi/ into ~/.pi +# 4. Review Brewfile, manifests/, and packages/ — they describe one person's machine +make install # brew bundle, stow $HOME packages, link claude/ + skills/ into profiles, stow pi/ make skills-list # see each skill's source and status ``` @@ -115,6 +132,44 @@ Everyday targets — `CLAUDE.md` carries the full `-` referenc Two gotchas: vendored `skills/` and `pi/extensions/` are overwritten on re-sync — diverge intentionally and note it durably; and use `SUBPATH=`, never `PATH=`, on fetch targets (the latter clobbers the shell `PATH`). Plugin installation stays manual per profile — Claude Code has no headless `/plugin install`. +## Machine setup + +| Target | Does | +|--------|------| +| `install` | `brew-install` + `stow-link` + harness links + `tools-install` | +| `tools-install` | `go-install` + `npm-install` + `pipx-install` from `manifests/` | +| `brew-install` / `brew-check` | Apply / verify the Brewfile | +| `brew-dump` | Regenerate gitignored `Brewfile.dump` to diff against the curated Brewfile | +| `cask-adopt` | Take over apps installed outside brew (pkg casks prompt for sudo) | +| `stow-link` / `stow-unlink` | Create / remove the `$HOME` symlinks (idempotent) | +| `stow-adopt` | Absorb pre-existing real files into the working tree; always review `git diff` after | +| `macos-apply` | Run `macos/defaults.sh` | +| `doctor` | Run all health checks | +| `drift` | Report divergence between recorded config and the live system, both directions | +| `raycast-export` | Open Raycast's encrypted settings export; save the file privately (never committed) | + +### The stow model + +Home packages are stowed from `packages/` into `$HOME` with `--dotfiles --no-folding` (stow ≥ 2.4.0, enforced by `doctor`). +`--dotfiles` maps `dot-zshrc` → `~/.zshrc`, so no hidden files exist in the repo. +`--no-folding` is a security invariant: it links individual files instead of whole directories, so `~/.config/` stays a real directory and credential files written beside managed configs (e.g. `gh`'s `hosts.yml`) can never land in the repo. +Never add packages for credential-bearing dirs (`gcloud`, `1Password`, `op`, `github-copilot`). +These flags apply to the `$HOME` packages only; the harness targets (`~/.claude-*`, `~/.pi`) link whole directories on purpose. + +zsh follows the same drop-in idea: `~/.zshrc` is a thin loader sourcing `~/.config/zsh/*.zsh` in `NN-` prefix order, and machine-local uncommitted overrides go in `~/.config/zsh/90-local.zsh` (gitignored; see `90-local.zsh.example`). + +### Adding a new tool config + +1. `mkdir -p packages//dot-config/` and copy the non-secret config file(s) in, using `dot-` names for anything dotted. +2. Add `` to `HOME_PACKAGES` in the Makefile. +3. `make stow-adopt`, review `git diff`, then commit. + +### Secrets policy + +Nothing outside `packages/` is ever stowed into `$HOME`, and no package references a credential-bearing file. +`.gitignore` blocks secret-like filenames as a second layer, and `make doctor` fails on secret-pattern filenames, credential-looking content in tracked files, or a `~/.config` dir that has become a symlink. +`stow-adopt` imports live machine files into tracked paths, so always review `git diff` before committing after it. + ## License [Apache-2.0](LICENSE). diff --git a/bootstrap.sh b/bootstrap.sh new file mode 100755 index 0000000..eb5be29 --- /dev/null +++ b/bootstrap.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +# New-Mac bootstrap: Xcode CLT -> Homebrew -> clone dotfiles -> brew bundle -> +# stow links -> harness links -> doctor. Idempotent; every step checks first. +# +# Preferred invocation (casks may prompt for sudo, which clashes with curl|bash +# sharing stdin with the script): +# curl -fsSL https://raw.githubusercontent.com/sanketsudake/dotfiles/main/bootstrap.sh -o /tmp/bootstrap.sh +# bash /tmp/bootstrap.sh +set -euo pipefail + +DOTFILES_DIR="${DOTFILES_DIR:-$HOME/personal/dotfiles}" +DOTFILES_SSH="git@github.com:sanketsudake/dotfiles.git" +DOTFILES_HTTPS="https://github.com/sanketsudake/dotfiles.git" + +step() { printf '\n==> %s\n' "$*"; } + +# Load brew into this shell from whichever prefix exists (Apple Silicon/Intel). +brew_env() { + local p + for p in /opt/homebrew /usr/local; do + if [ -x "$p/bin/brew" ]; then + eval "$("$p/bin/brew" shellenv)" + return 0 + fi + done + return 1 +} + +step "Xcode Command Line Tools" +if ! xcode-select -p >/dev/null 2>&1; then + xcode-select --install + echo "CLT install dialog opened — finish it, then re-run this script." + exit 0 +fi +echo "ok: $(xcode-select -p)" + +step "Homebrew" +if ! brew_env; then + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + brew_env +fi +echo "ok: $(brew --prefix)" + +step "Dotfiles repo" +# When run via curl there is no local clone yet; clone it. SSH first (keys +# usually not set up yet on a fresh machine, so fall back to https, bypassing +# any pre-existing gitconfig https->ssh rewrite). +if [ ! -d "$DOTFILES_DIR/.git" ]; then + mkdir -p "$(dirname "$DOTFILES_DIR")" + git clone "$DOTFILES_SSH" "$DOTFILES_DIR" 2>/dev/null \ + || GIT_CONFIG_GLOBAL=/dev/null git clone "$DOTFILES_HTTPS" "$DOTFILES_DIR" +fi +cd "$DOTFILES_DIR" +echo "ok: $DOTFILES_DIR" + +step "Brew packages (brew bundle)" +make brew-install + +step "Stow links" +# Deterministic: move any pre-existing real file at a managed path aside, then +# link. End state is always the repo's configs live, originals preserved. +backup_dir="$HOME/.dotfiles-backup-$(date +%s)" +while IFS= read -r rel; do + target="$HOME/$rel" + if [ -e "$target" ] && [ ! -L "$target" ]; then + mkdir -p "$backup_dir/$(dirname "$rel")" + mv "$target" "$backup_dir/$rel" + echo "moved aside: ~/$rel -> $backup_dir/$rel" + fi +done < <(scripts/managed-targets.sh) +make stow-link +if [ -d "$backup_dir" ]; then + echo "!! Pre-existing files were moved to $backup_dir — port anything you need" + echo "!! into ~/.config/zsh/90-local.zsh (gitignored), then delete the backup." +fi + +step "AI harness (claude profiles + pi)" +make skills-materialize harness-link || { + echo "!! harness link failed (skills materialize needs network)." + echo "!! Re-run: make skills-materialize harness-link" +} + +step "Doctor" +make doctor || true + +cat <<'EOF' + +Bootstrap complete. Manual steps that need your credentials: + 1. gh auth login + 2. atuin login (history sync) + 3. git lfs install + 4. Restore any machine-private ~/.ssh/config entries (host aliases, keys). + 5. Open a new terminal so zsh picks up the managed config. +EOF diff --git a/claude/agents/bulk-mechanic.source.json b/claude/agents/bulk-mechanic.source.json deleted file mode 100644 index 4dabdaf..0000000 --- a/claude/agents/bulk-mechanic.source.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "repo": null, - "category": "execution" -} diff --git a/claude/agents/plan-reviewer.source.json b/claude/agents/plan-reviewer.source.json deleted file mode 100644 index f4c5a5d..0000000 --- a/claude/agents/plan-reviewer.source.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "repo": null, - "category": "planning" -} diff --git a/claude/agents/pr-shepherd.source.json b/claude/agents/pr-shepherd.source.json deleted file mode 100644 index c23ee95..0000000 --- a/claude/agents/pr-shepherd.source.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "repo": null, - "category": "pr-review" -} diff --git a/claude/agents/skill-auditor.source.json b/claude/agents/skill-auditor.source.json deleted file mode 100644 index 16609c9..0000000 --- a/claude/agents/skill-auditor.source.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "repo": null, - "category": "meta" -} diff --git a/claude/agents/thermo-nuclear-code-quality-review-subagent.source.json b/claude/agents/thermo-nuclear-code-quality-review-subagent.source.json deleted file mode 100644 index 60c3011..0000000 --- a/claude/agents/thermo-nuclear-code-quality-review-subagent.source.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "repo": "https://github.com/cursor/plugins", - "subpath": "thermos/agents/thermo-nuclear-code-quality-review-subagent.md", - "ref": "main", - "commit": "60c641e4fad674784b30abcf9f8915dea39df38d", - "fetched_at": "2026-08-19T16:02:26Z", - "category": "pr-review" -} diff --git a/claude/agents/thermo-nuclear-review-subagent.source.json b/claude/agents/thermo-nuclear-review-subagent.source.json deleted file mode 100644 index 2ba5903..0000000 --- a/claude/agents/thermo-nuclear-review-subagent.source.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "repo": "https://github.com/cursor/plugins", - "subpath": "thermos/agents/thermo-nuclear-review-subagent.md", - "ref": "main", - "commit": "60c641e4fad674784b30abcf9f8915dea39df38d", - "fetched_at": "2026-08-19T16:02:17Z", - "category": "pr-review" -} diff --git a/macos/defaults.sh b/macos/defaults.sh new file mode 100755 index 0000000..92f5d45 --- /dev/null +++ b/macos/defaults.sh @@ -0,0 +1,128 @@ +#!/usr/bin/env bash +# macOS system preferences as code, captured from the live machine (expanded 2026-08-03). +# Only deliberately-changed settings are recorded — stock defaults are not +# restated, so Apple's defaults can evolve without this file fighting them. +# Apply with `make macos-apply`; idempotent. Grouped by domain; append new +# settings to the matching group and re-run. +set -euo pipefail + +echo "Applying macOS defaults..." + +# --- appearance -------------------------------------------------------------- +# Dark mode (fully applies to running apps after re-login). +defaults write NSGlobalDomain AppleInterfaceStyle -string "Dark" + +# --- keyboard ---------------------------------------------------------------- +# Fast key repeat (values below the Settings UI minimum; re-login to fully apply). +defaults write NSGlobalDomain KeyRepeat -int 2 +defaults write NSGlobalDomain InitialKeyRepeat -int 15 +# Holding a key repeats it instead of opening the accent picker. +defaults write NSGlobalDomain ApplePressAndHoldEnabled -bool false + +# --- finder ------------------------------------------------------------------ +# List view in all Finder windows by default. +defaults write com.apple.finder FXPreferredViewStyle -string "Nlsv" +# Show all filename extensions, the path bar, and the status bar. +defaults write NSGlobalDomain AppleShowAllExtensions -bool true +defaults write com.apple.finder ShowPathbar -bool true +defaults write com.apple.finder ShowStatusBar -bool true +# No warning when changing a file extension. +defaults write com.apple.finder FXEnableExtensionChangeWarning -bool false +# Don't litter network shares and USB volumes with .DS_Store files. +defaults write com.apple.desktopservices DSDontWriteNetworkStores -bool true +defaults write com.apple.desktopservices DSDontWriteUSBStores -bool true + +# --- screenshots ------------------------------------------------------------- +# Save to ~/Screenshots (created here) without the window drop shadow. +mkdir -p "$HOME/Screenshots" +defaults write com.apple.screencapture location -string "$HOME/Screenshots" +defaults write com.apple.screencapture disable-shadow -bool true + +# --- dock & spaces ----------------------------------------------------------- +# Auto-hide the Dock, no recent apps section, and Spaces keep their order. +defaults write com.apple.dock autohide -bool true +defaults write com.apple.dock show-recents -bool false +defaults write com.apple.dock mru-spaces -bool false + +# --- dialogs ----------------------------------------------------------------- +# Save and print dialogs open expanded; new documents save locally, not iCloud. +defaults write NSGlobalDomain NSNavPanelExpandedStateForSaveMode -bool true +defaults write NSGlobalDomain NSNavPanelExpandedStateForSaveMode2 -bool true +defaults write NSGlobalDomain PMPrintingExpandedStateForPrint -bool true +defaults write NSGlobalDomain PMPrintingExpandedStateForPrint2 -bool true +defaults write NSGlobalDomain NSDocumentSaveNewDocumentsToCloud -bool false + +# --- trackpad ---------------------------------------------------------------- +# Tap to click (built-in + bluetooth trackpads, and the per-host mouse behavior). +defaults write com.apple.AppleMultitouchTrackpad Clicking -bool true +defaults write com.apple.driver.AppleBluetoothMultitouch.trackpad Clicking -bool true +defaults -currentHost write NSGlobalDomain com.apple.mouse.tapBehavior -int 1 + +# --- helium (browser) -------------------------------------------------------- +# Helium reads Chromium enterprise policy from its own preferences domain, so +# `defaults write` reaches it — no sudo, no /Library/Managed Preferences. An +# unforced write lands as level "recommended": it sets the default but Settings +# can still override it. Confirm with helium://policy after a relaunch. +# Not set here: PasswordManagerEnabled and PasswordManagerPasskeysEnabled. +# Helium's built-in HOP provider already forces both off, at a higher priority +# than any policy this file can write. +# No autofill of cards or addresses, and sites cannot probe for saved cards. +defaults write net.imput.helium AutofillCreditCardEnabled -bool false +defaults write net.imput.helium AutofillAddressEnabled -bool false +defaults write net.imput.helium PaymentMethodQueryEnabled -bool false +# No omnibox keystrokes to the search engine, and no link prefetch (2 = off). +defaults write net.imput.helium SearchSuggestEnabled -bool false +defaults write net.imput.helium NetworkPredictionOptions -int 2 +# Ask where to save each download. +defaults write net.imput.helium PromptForDownloadLocation -bool true + +# The new tab page's "frequently visited" tiles have no policy, so they live in +# the profile's Preferences JSON. The `shortcust` typo is Chromium's own key +# name. Helium rewrites that file when it exits, so the patch only applies while +# it is closed. Add further JSON-only keys to WANTED below. +HELIUM_PREFS="$HOME/Library/Application Support/net.imput.helium/Default/Preferences" +if [[ ! -f "$HELIUM_PREFS" ]]; then + echo " helium: no profile at $HELIUM_PREFS — prefs patch skipped" +elif pgrep -x Helium >/dev/null 2>&1; then + echo " helium: running — quit Helium and re-run to apply the prefs patch" +else + python3 - "$HELIUM_PREFS" <<'PY' +import json, os, sys, tempfile + +WANTED = { + ("ntp", "shortcust_visible"): False, # typo is Chromium's own key +} + +path = sys.argv[1] +with open(path, encoding="utf-8") as fh: + prefs = json.load(fh) + +changed = [] +for (section, key), value in WANTED.items(): + block = prefs.setdefault(section, {}) + if block.get(key) != value: + block[key] = value + changed.append(key) + +if not changed: + print(" helium: prefs already set") +else: + directory = os.path.dirname(path) + fd, tmp = tempfile.mkstemp(dir=directory, prefix="Preferences.") + try: + with os.fdopen(fd, "w", encoding="utf-8") as fh: + json.dump(prefs, fh, separators=(",", ":")) + os.chmod(tmp, os.stat(path).st_mode & 0o777) + os.replace(tmp, path) + except BaseException: + os.path.exists(tmp) and os.unlink(tmp) + raise + print(" helium: set " + ", ".join(sorted(changed))) +PY +fi + +# --- apply ------------------------------------------------------------------- +killall Finder 2>/dev/null || true +killall Dock 2>/dev/null || true +killall SystemUIServer 2>/dev/null || true +echo "Done. Keyboard repeat and appearance fully apply after re-login." diff --git a/claude/plugins.txt b/manifests/claude-plugins.txt similarity index 100% rename from claude/plugins.txt rename to manifests/claude-plugins.txt diff --git a/manifests/go-tools.txt b/manifests/go-tools.txt new file mode 100644 index 0000000..0409c69 --- /dev/null +++ b/manifests/go-tools.txt @@ -0,0 +1,41 @@ +# Go tools installed to ~/go/bin via `make go-install` (go install @latest). +# Lines may pin a version with module@vX.Y.Z; default is @latest. +# Curated deliberately — accidental `go install ./...` artifacts are not tracked. + +# debugging & language server +github.com/go-delve/delve/cmd/dlv +golang.org/x/tools/gopls + +# code generation & scaffolding +golang.org/x/tools/cmd/goimports +golang.org/x/tools/cmd/stringer +github.com/josharian/impl +github.com/cweill/gotests/gotests +github.com/vektra/mockery/v3 + +# testing +gotest.tools/gotestsum +chipaca.com/goctest +github.com/t-yuki/gocover-cobertura + +# analysis & linting +honnef.co/go/tools/cmd/staticcheck +golang.org/x/tools/cmd/deadcode +golang.org/x/vuln/cmd/govulncheck +github.com/ashanbrown/makezero/v2 +chipaca.com/gofomash + +# supply chain & licensing +github.com/google/go-licenses +github.com/slsa-framework/slsa-verifier/v2/cli/slsa-verifier + +# protobuf +google.golang.org/protobuf/cmd/protoc-gen-go +google.golang.org/grpc/cmd/protoc-gen-go-grpc +github.com/ckaznocha/protoc-gen-lint +github.com/favadi/protoc-go-inject-tag + +# kubernetes & misc +sigs.k8s.io/controller-runtime/tools/setup-envtest +github.com/grafana/dashboard-linter +github.com/darccio/diffty/cmd/diffty diff --git a/manifests/npm-globals.txt b/manifests/npm-globals.txt new file mode 100644 index 0000000..501890b --- /dev/null +++ b/manifests/npm-globals.txt @@ -0,0 +1,4 @@ +# npm packages installed globally (into the active nvm node) via `make npm-install`. +@readwise/cli +@tobilu/qmd +corepack diff --git a/manifests/pipx-tools.txt b/manifests/pipx-tools.txt new file mode 100644 index 0000000..228e2f8 --- /dev/null +++ b/manifests/pipx-tools.txt @@ -0,0 +1,2 @@ +# Python CLI tools installed in isolated envs via `make pipx-install`. +evernote-backup diff --git a/packages/atuin/dot-config/atuin/config.toml b/packages/atuin/dot-config/atuin/config.toml new file mode 100644 index 0000000..986199c --- /dev/null +++ b/packages/atuin/dot-config/atuin/config.toml @@ -0,0 +1,9 @@ +# Only the settings that differ from atuin's defaults. +# Full reference: https://docs.atuin.sh/configuration/config/ + +# Hitting enter runs the selected command instead of only pasting it at the prompt. +enter_accept = true + +[sync] +# Sync v2 (record store) instead of the legacy history sync. +records = true diff --git a/packages/bin/dot-local/bin/coffee b/packages/bin/dot-local/bin/coffee new file mode 100755 index 0000000..e6949d7 --- /dev/null +++ b/packages/bin/dot-local/bin/coffee @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +# coffee — keep the Mac awake (a friendly caffeinate wrapper). +# +# coffee stay awake until 'coffee stop' (display may sleep) +# coffee 45m | 2h | 90s stay awake for a duration +# coffee watch stay awake while a process runs, e.g. coffee watch claude +# coffee status is a coffee session active? +# coffee stop end the session +# +# Add -d before any form to also keep the display awake: coffee -d 2h +# Note: closing the lid still sleeps the Mac unless on power with an external display. +set -euo pipefail + +PIDFILE="${TMPDIR:-/tmp}/coffee-$USER.pid" +FLAGS="-is" # -i idle sleep, -s system sleep (AC power) + +usage() { grep '^# ' "$0" | sed 's/^# //'; } + +alive() { [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; } + +start_bg() { + if alive; then + echo "coffee: already brewing (pid $(cat "$PIDFILE")) — 'coffee stop' first" >&2 + exit 1 + fi + caffeinate $FLAGS "$@" & + echo $! > "$PIDFILE" +} + +if [ "${1:-}" = "-d" ]; then + FLAGS="-dis" + shift +fi + +case "${1:-}" in + -h|--help) + usage + ;; + status) + if alive; then + echo "coffee: awake (pid $(cat "$PIDFILE"), since $(ps -o lstart= -p "$(cat "$PIDFILE")" | sed 's/^ *//'))" + else + echo "coffee: not running — Mac sleeps normally" + fi + ;; + stop) + if alive; then + kill "$(cat "$PIDFILE")" && rm -f "$PIDFILE" + echo "coffee: stopped — Mac sleeps normally" + else + rm -f "$PIDFILE" + echo "coffee: nothing to stop" + fi + ;; + watch) + target="${2:-}" + [ -n "$target" ] || { echo "usage: coffee watch " >&2; exit 2; } + if [[ "$target" =~ ^[0-9]+$ ]]; then + pid="$target" + else + pid="$(pgrep -n "$target" || true)" + [ -n "$pid" ] || { echo "coffee: no process matching '$target'" >&2; exit 1; } + fi + start_bg -w "$pid" + echo "coffee: awake while pid $pid ($(ps -o comm= -p "$pid" | xargs basename)) runs" + ;; + "") + start_bg + echo "coffee: awake until 'coffee stop'" + ;; + *[0-9][smh]|*[0-9]) + n="${1%[smh]}" + case "$1" in + *m) secs=$((n * 60)) ;; + *h) secs=$((n * 3600)) ;; + *) secs=$n ;; + esac + start_bg -t "$secs" + echo "coffee: awake for $1" + ;; + *) + usage >&2 + exit 2 + ;; +esac diff --git a/packages/btop/dot-config/btop/btop.conf b/packages/btop/dot-config/btop/btop.conf new file mode 100644 index 0000000..67846f3 --- /dev/null +++ b/packages/btop/dot-config/btop/btop.conf @@ -0,0 +1,283 @@ +#? Config file for btop v.1.4.7 + +#* Name of a btop++/bpytop/bashtop formatted ".theme" file, "Default" and "TTY" for builtin themes. +#* Themes should be placed in "../share/btop/themes" relative to binary or "$HOME/.config/btop/themes" +color_theme = "Default" + +#* If the theme set background should be shown, set to False if you want terminal background transparency. +theme_background = true + +#* Sets if 24-bit truecolor should be used, will convert 24-bit colors to 256 color (6x6x6 color cube) if false. +truecolor = true + +#* Set to true to force tty mode regardless if a real tty has been detected or not. +#* Will force 16-color mode and TTY theme, set all graph symbols to "tty" and swap out other non tty friendly symbols. +force_tty = false + +#* Option to disable presets. Either the default preset, custom presets, or all presets. +#* "Off" All presets are enabled. +#* "Default" preset is disabled.#* "Custom" presets are disabled.#* "All" presets are disabled. +disable_presets = "Off" + +#* Define presets for the layout of the boxes. Preset 0 is always all boxes shown with default settings. Max 9 presets. +#* Format: "box_name:P:G,box_name:P:G" P=(0 or 1) for alternate positions, G=graph symbol to use for box. +#* Use whitespace " " as separator between different presets. +#* Example: "cpu:0:default,mem:0:tty,proc:1:default cpu:0:braille,proc:0:tty" +presets = "cpu:1:default,proc:0:default cpu:0:default,mem:0:default,net:0:default cpu:0:block,net:0:tty" + +#* Set to True to enable "h,j,k,l,g,G" keys for directional control in lists. +#* Conflicting keys for h:"help" and k:"kill" is accessible while holding shift. +vim_keys = false + +#* Disable all mouse events. +disable_mouse = false + +#* Rounded corners on boxes, is ignored if TTY mode is ON. +rounded_corners = true + +#* Use terminal synchronized output sequences to reduce flickering on supported terminals. +terminal_sync = true + +#* Default symbols to use for graph creation, "braille", "block" or "tty". +#* "braille" offers the highest resolution but might not be included in all fonts. +#* "block" has half the resolution of braille but uses more common characters. +#* "tty" uses only 3 different symbols but will work with most fonts and should work in a real TTY. +#* Note that "tty" only has half the horizontal resolution of the other two, so will show a shorter historical view. +graph_symbol = "braille" + +# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty". +graph_symbol_cpu = "default" + +# Graph symbol to use for graphs in gpu box, "default", "braille", "block" or "tty". +graph_symbol_gpu = "default" + +# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty". +graph_symbol_mem = "default" + +# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty". +graph_symbol_net = "default" + +# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty". +graph_symbol_proc = "default" + +#* Manually set which boxes to show. Available values are "cpu mem net proc" and "gpu0" through "gpu5", separate values with whitespace. +shown_boxes = "cpu mem net" + +#* Update time in milliseconds, recommended 2000 ms or above for better sample times for graphs. +update_ms = 2000 + +#* Processes sorting, "pid" "program" "arguments" "threads" "user" "memory" "cpu lazy" "cpu direct", +#* "cpu lazy" sorts top process over time (easier to follow), "cpu direct" updates top process directly. +proc_sorting = "cpu direct" + +#* Reverse sorting order, True or False. +proc_reversed = false + +#* Show processes as a tree. +proc_tree = false + +#* Use the cpu graph colors in the process list. +proc_colors = true + +#* Use a darkening gradient in the process list. +proc_gradient = true + +#* If process cpu usage should be of the core it's running on or usage of the total available cpu power. +proc_per_core = false + +#* Show process memory as bytes instead of percent. +proc_mem_bytes = true + +#* Show cpu graph for each process. +proc_cpu_graphs = true + +#* Use /proc/[pid]/smaps for memory information in the process info box (very slow but more accurate) +proc_info_smaps = false + +#* Show proc box on left side of screen instead of right. +proc_left = false + +#* (Linux) Filter processes tied to the Linux kernel(similar behavior to htop). +proc_filter_kernel = false + +#* Should the process list follow the selected process when detailed view is open. +proc_follow_detailed = true + +#* In tree-view, always accumulate child process resources in the parent process. +proc_aggregate = false + +#* Should cpu and memory usage display be preserved for dead processes when paused. +keep_dead_proc_usage = false + +#* Sets the CPU stat shown in upper half of the CPU graph, "total" is always available. +#* Select from a list of detected attributes from the options menu. +cpu_graph_upper = "Auto" + +#* Sets the CPU stat shown in lower half of the CPU graph, "total" is always available. +#* Select from a list of detected attributes from the options menu. +cpu_graph_lower = "Auto" + +#* If gpu info should be shown in the cpu box. Available values = "Auto", "On" and "Off". +show_gpu_info = "Auto" + +#* Toggles if the lower CPU graph should be inverted. +cpu_invert_lower = true + +#* Set to True to completely disable the lower CPU graph. +cpu_single_graph = false + +#* Show cpu box at bottom of screen instead of top. +cpu_bottom = false + +#* Shows the system uptime in the CPU box. +show_uptime = true + +#* Shows the CPU package current power consumption in watts. Requires running `make setcap` or `make setuid` or running with sudo. +show_cpu_watts = true + +#* Show cpu temperature. +check_temp = true + +#* Which sensor to use for cpu temperature, use options menu to select from list of available sensors. +cpu_sensor = "Auto" + +#* Show temperatures for cpu cores also if check_temp is True and sensors has been found. +show_coretemp = true + +#* Set a custom mapping between core and coretemp, can be needed on certain cpus to get correct temperature for correct core. +#* Use lm-sensors or similar to see which cores are reporting temperatures on your machine. +#* Format "x:y" x=core with wrong temp, y=core with correct temp, use space as separator between multiple entries. +#* Example: "4:0 5:1 6:3" +cpu_core_map = "" + +#* Which temperature scale to use, available values: "celsius", "fahrenheit", "kelvin" and "rankine". +temp_scale = "celsius" + +#* Use base 10 for bits/bytes sizes, KB = 1000 instead of KiB = 1024. +base_10_sizes = false + +#* Show CPU frequency. +show_cpu_freq = true + +#* Draw a clock at top of screen, formatting according to strftime, empty string to disable. +#* Special formatting: /host = hostname | /user = username | /uptime = system uptime +clock_format = "%X" + +#* Update main ui in background when menus are showing, set this to false if the menus is flickering too much for comfort. +background_update = true + +#* Custom cpu model name, empty string to disable. +custom_cpu_name = "" + +#* Optional filter for shown disks, should be full path of a mountpoint, separate multiple values with whitespace " ". +#* Only disks matching the filter will be shown. Prepend exclude= to only show disks not matching the filter. Examples: disk_filter="/boot /home/user", disks_filter="exclude=/boot /home/user" +disks_filter = "" + +#* Show graphs instead of meters for memory values. +mem_graphs = true + +#* Show mem box below net box instead of above. +mem_below_net = false + +#* Count ZFS ARC in cached and available memory. +zfs_arc_cached = true + +#* If swap memory should be shown in memory box. +show_swap = true + +#* Show swap as a disk, ignores show_swap value above, inserts itself after first disk. +swap_disk = true + +#* If mem box should be split to also show disks info. +show_disks = true + +#* Filter out non physical disks. Set this to False to include network disks, RAM disks and similar. +only_physical = true + +#* Read disks list from /etc/fstab. This also disables only_physical. +use_fstab = true + +#* Setting this to True will hide all datasets, and only show ZFS pools. (IO stats will be calculated per-pool) +zfs_hide_datasets = false + +#* Set to true to show available disk space for privileged users. +disk_free_priv = false + +#* Toggles if io activity % (disk busy time) should be shown in regular disk usage view. +show_io_stat = true + +#* Toggles io mode for disks, showing big graphs for disk read/write speeds. +io_mode = false + +#* Set to True to show combined read/write io graphs in io mode. +io_graph_combined = false + +#* Set the top speed for the io graphs in MiB/s (100 by default), use format "mountpoint:speed" separate disks with whitespace " ". +#* Example: "/mnt/media:100 /:20 /boot:1". +io_graph_speeds = "" + +#* Swap the positions of the upload and download speed graphs. When true, upload will be on top. +swap_upload_download = false + +#* Set fixed values for network graphs in Mebibits. Is only used if net_auto is also set to False. +net_download = 100 + +net_upload = 100 + +#* Use network graphs auto rescaling mode, ignores any values set above and rescales down to 10 Kibibytes at the lowest. +net_auto = true + +#* Sync the auto scaling for download and upload to whichever currently has the highest scale. +net_sync = true + +#* Starts with the Network Interface specified here. +net_iface = "" + +#* "True" shows bitrates in base 10 (Kbps, Mbps). "False" shows bitrates in binary sizes (Kibps, Mibps, etc.). "Auto" uses base_10_sizes. +base_10_bitrate = "Auto" + +#* Show battery stats in top right if battery is present. +show_battery = true + +#* Which battery to use if multiple are present. "Auto" for auto detection. +selected_battery = "Auto" + +#* Show power stats of battery next to charge indicator. +show_battery_watts = true + +#* Set loglevel for "~/.local/state/btop.log" levels are: "ERROR" "WARNING" "INFO" "DEBUG". +#* The level set includes all lower levels, i.e. "DEBUG" will show all logging info. +log_level = "WARNING" + +#* Automatically save current settings to config file on exit. +save_config_on_exit = true + +#* Measure PCIe throughput on NVIDIA cards, may impact performance on certain cards. +nvml_measure_pcie_speeds = true + +#* Measure PCIe throughput on AMD cards, may impact performance on certain cards. +rsmi_measure_pcie_speeds = true + +#* Horizontally mirror the GPU graph. +gpu_mirror_graph = true + +#* Set which GPU vendors to show. Available values are "nvidia amd intel apple" +shown_gpus = "nvidia amd intel apple" + +#* Custom gpu0 model name, empty string to disable. +custom_gpu_name0 = "" + +#* Custom gpu1 model name, empty string to disable. +custom_gpu_name1 = "" + +#* Custom gpu2 model name, empty string to disable. +custom_gpu_name2 = "" + +#* Custom gpu3 model name, empty string to disable. +custom_gpu_name3 = "" + +#* Custom gpu4 model name, empty string to disable. +custom_gpu_name4 = "" + +#* Custom gpu5 model name, empty string to disable. +custom_gpu_name5 = "" diff --git a/claude/CLAUDE.md b/packages/claude/CLAUDE.md similarity index 100% rename from claude/CLAUDE.md rename to packages/claude/CLAUDE.md diff --git a/claude/agents/bulk-mechanic.md b/packages/claude/agents/bulk-mechanic.md similarity index 100% rename from claude/agents/bulk-mechanic.md rename to packages/claude/agents/bulk-mechanic.md diff --git a/claude/agents/plan-reviewer.md b/packages/claude/agents/plan-reviewer.md similarity index 100% rename from claude/agents/plan-reviewer.md rename to packages/claude/agents/plan-reviewer.md diff --git a/claude/agents/pr-shepherd.md b/packages/claude/agents/pr-shepherd.md similarity index 100% rename from claude/agents/pr-shepherd.md rename to packages/claude/agents/pr-shepherd.md diff --git a/claude/agents/skill-auditor.md b/packages/claude/agents/skill-auditor.md similarity index 96% rename from claude/agents/skill-auditor.md rename to packages/claude/agents/skill-auditor.md index efb87bb..281c568 100644 --- a/claude/agents/skill-auditor.md +++ b/packages/claude/agents/skill-auditor.md @@ -25,7 +25,7 @@ It is the local copy of those guidelines — audit against it even when offline. Don't let the description script the workflow step-by-step — agents may follow it instead of reading the body. 4. **License** — `license:` present (this repo stamps `Apache-2.0`, matching the repo LICENSE on authored skills). 5. **Metadata** — `metadata:` map present with at least `author` and `version`. - (`category` deliberately lives in the `.source.json` sidecar / `skills/vendored.json`, not frontmatter — flag duplication as drift risk.) + (`category` deliberately lives in the skill's `sources.toml` entry, not frontmatter — flag duplication as drift risk.) 6. **Optional fields sane** — `compatibility` ≤500 chars if present; `allowed-tools` space-delimited; `disable-model-invocation` boolean. ### B. Body content (best practices) @@ -47,7 +47,7 @@ It is the local copy of those guidelines — audit against it even when offline. Flag bundled unrelated workflows; each should be its own skill. 15. **Soft dependencies** — when the skill needs another skill, it references it by name instead of copying its content. Flag duplicated logic that should be a shared script. -16. **Sidecar** — `.source.json` present with a `category`; locally authored skills carry `{"repo": null}`. +16. **Manifest entry** — a `sources.toml` entry present with a `category`; locally authored skills have no `repo` field there. (`make skills-doctor` checks this mechanically — still report it so one audit covers everything.) 17. **No PII** — examples use fake placeholders; no real names, meetings, client/project identifiers, emails, or tokens anywhere in the skill. 18. **Self-containment** — referenced helper scripts exist inside the skill dir (or are declared external tools); paths use `{baseDir}`-style or relative references that survive the symlinked profiles. diff --git a/claude/agents/thermo-nuclear-code-quality-review-subagent.md b/packages/claude/agents/thermo-nuclear-code-quality-review-subagent.md similarity index 100% rename from claude/agents/thermo-nuclear-code-quality-review-subagent.md rename to packages/claude/agents/thermo-nuclear-code-quality-review-subagent.md diff --git a/claude/agents/thermo-nuclear-review-subagent.md b/packages/claude/agents/thermo-nuclear-review-subagent.md similarity index 100% rename from claude/agents/thermo-nuclear-review-subagent.md rename to packages/claude/agents/thermo-nuclear-review-subagent.md diff --git a/claude/commands/.gitkeep b/packages/claude/commands/.gitkeep similarity index 100% rename from claude/commands/.gitkeep rename to packages/claude/commands/.gitkeep diff --git a/claude/commands/history.md b/packages/claude/commands/history.md similarity index 100% rename from claude/commands/history.md rename to packages/claude/commands/history.md diff --git a/claude/rules/delegation.md b/packages/claude/rules/delegation.md similarity index 100% rename from claude/rules/delegation.md rename to packages/claude/rules/delegation.md diff --git a/claude/rules/git-hygiene.md b/packages/claude/rules/git-hygiene.md similarity index 100% rename from claude/rules/git-hygiene.md rename to packages/claude/rules/git-hygiene.md diff --git a/claude/rules/model-routing.md b/packages/claude/rules/model-routing.md similarity index 100% rename from claude/rules/model-routing.md rename to packages/claude/rules/model-routing.md diff --git a/claude/scripts/agent-routing-hook.sh b/packages/claude/scripts/agent-routing-hook.sh similarity index 100% rename from claude/scripts/agent-routing-hook.sh rename to packages/claude/scripts/agent-routing-hook.sh diff --git a/claude/scripts/browser-endpoint.sh b/packages/claude/scripts/browser-endpoint.sh similarity index 100% rename from claude/scripts/browser-endpoint.sh rename to packages/claude/scripts/browser-endpoint.sh diff --git a/claude/scripts/safety-guard-hook.py b/packages/claude/scripts/safety-guard-hook.py similarity index 100% rename from claude/scripts/safety-guard-hook.py rename to packages/claude/scripts/safety-guard-hook.py diff --git a/claude/scripts/statusline-command.sh b/packages/claude/scripts/statusline-command.sh similarity index 100% rename from claude/scripts/statusline-command.sh rename to packages/claude/scripts/statusline-command.sh diff --git a/claude/scripts/usage-log-hook.py b/packages/claude/scripts/usage-log-hook.py similarity index 100% rename from claude/scripts/usage-log-hook.py rename to packages/claude/scripts/usage-log-hook.py diff --git a/claude/scripts/usage-report.py b/packages/claude/scripts/usage-report.py similarity index 100% rename from claude/scripts/usage-report.py rename to packages/claude/scripts/usage-report.py diff --git a/packages/claude/skills b/packages/claude/skills new file mode 120000 index 0000000..5dcab58 --- /dev/null +++ b/packages/claude/skills @@ -0,0 +1 @@ +../../skills \ No newline at end of file diff --git a/packages/gh/dot-config/gh/config.yml b/packages/gh/dot-config/gh/config.yml new file mode 100644 index 0000000..1044065 --- /dev/null +++ b/packages/gh/dot-config/gh/config.yml @@ -0,0 +1,19 @@ +# The current version of the config schema +version: 1 +# What protocol to use when performing git operations. Supported values: ssh, https +git_protocol: https +# What editor gh should run when creating issues, pull requests, etc. If blank, will refer to environment. +editor: +# When to interactively prompt. This is a global config that cannot be overridden by hostname. Supported values: enabled, disabled +prompt: enabled +# Preference for editor-based interactive prompting. This is a global config that cannot be overridden by hostname. Supported values: enabled, disabled +prefer_editor_prompt: disabled +# A pager program to send command output to, e.g. "less". If blank, will refer to environment. Set the value to "cat" to disable the pager. +pager: +# Aliases allow you to create nicknames for gh commands +aliases: + co: pr checkout +# The path to a unix socket through which send HTTP connections. If blank, HTTP traffic will be handled by net/http.DefaultTransport. +http_unix_socket: +# What web browser gh should use when opening URLs. If blank, will refer to environment. +browser: diff --git a/packages/git/dot-config/git/config-personal b/packages/git/dot-config/git/config-personal new file mode 100644 index 0000000..19e4e9e --- /dev/null +++ b/packages/git/dot-config/git/config-personal @@ -0,0 +1,3 @@ +[user] + name = Sanket Sudake + email = sanketsudake@gmail.com diff --git a/packages/git/dot-config/git/config-qwiet b/packages/git/dot-config/git/config-qwiet new file mode 100644 index 0000000..56ac5d7 --- /dev/null +++ b/packages/git/dot-config/git/config-qwiet @@ -0,0 +1,3 @@ +[user] + name = Sanket Sudake + email = sanket.infracloud@qwiet.ai diff --git a/packages/git/dot-config/git/ignore b/packages/git/dot-config/git/ignore new file mode 100644 index 0000000..66d62f8 --- /dev/null +++ b/packages/git/dot-config/git/ignore @@ -0,0 +1 @@ +**/.claude/settings.local.json diff --git a/packages/git/dot-gitconfig b/packages/git/dot-gitconfig new file mode 100644 index 0000000..9954938 --- /dev/null +++ b/packages/git/dot-gitconfig @@ -0,0 +1,18 @@ +[filter "lfs"] + clean = git-lfs clean -- %f + smudge = git-lfs smudge -- %f + process = git-lfs filter-process + required = true +[core] + pager = delta +[interactive] + diffFilter = delta --color-only +[delta] + navigate = true +[merge] + conflictStyle = zdiff3 +[url "ssh://git@github.com/"] + insteadOf = https://github.com/ +# Identity: personal everywhere. +[include] + path = ~/.config/git/config-personal diff --git a/pi/README.md b/packages/pi/README.md similarity index 100% rename from pi/README.md rename to packages/pi/README.md diff --git a/pi/agent/settings.json b/packages/pi/agent/settings.json similarity index 100% rename from pi/agent/settings.json rename to packages/pi/agent/settings.json diff --git a/pi/extensions/confirm-destructive.ts b/packages/pi/extensions/confirm-destructive.ts similarity index 100% rename from pi/extensions/confirm-destructive.ts rename to packages/pi/extensions/confirm-destructive.ts diff --git a/pi/extensions/dirty-repo-guard.ts b/packages/pi/extensions/dirty-repo-guard.ts similarity index 100% rename from pi/extensions/dirty-repo-guard.ts rename to packages/pi/extensions/dirty-repo-guard.ts diff --git a/pi/extensions/handoff.ts b/packages/pi/extensions/handoff.ts similarity index 100% rename from pi/extensions/handoff.ts rename to packages/pi/extensions/handoff.ts diff --git a/pi/extensions/mac-system-theme.ts b/packages/pi/extensions/mac-system-theme.ts similarity index 100% rename from pi/extensions/mac-system-theme.ts rename to packages/pi/extensions/mac-system-theme.ts diff --git a/pi/extensions/notify.ts b/packages/pi/extensions/notify.ts similarity index 100% rename from pi/extensions/notify.ts rename to packages/pi/extensions/notify.ts diff --git a/pi/extensions/permission-gate.ts b/packages/pi/extensions/permission-gate.ts similarity index 100% rename from pi/extensions/permission-gate.ts rename to packages/pi/extensions/permission-gate.ts diff --git a/pi/extensions/protected-paths.ts b/packages/pi/extensions/protected-paths.ts similarity index 100% rename from pi/extensions/protected-paths.ts rename to packages/pi/extensions/protected-paths.ts diff --git a/pi/extensions/status-line.ts b/packages/pi/extensions/status-line.ts similarity index 100% rename from pi/extensions/status-line.ts rename to packages/pi/extensions/status-line.ts diff --git a/pi/extensions/subagent/README.md b/packages/pi/extensions/subagent/README.md similarity index 100% rename from pi/extensions/subagent/README.md rename to packages/pi/extensions/subagent/README.md diff --git a/pi/extensions/subagent/agents.ts b/packages/pi/extensions/subagent/agents.ts similarity index 100% rename from pi/extensions/subagent/agents.ts rename to packages/pi/extensions/subagent/agents.ts diff --git a/pi/extensions/subagent/agents/planner.md b/packages/pi/extensions/subagent/agents/planner.md similarity index 100% rename from pi/extensions/subagent/agents/planner.md rename to packages/pi/extensions/subagent/agents/planner.md diff --git a/pi/extensions/subagent/agents/reviewer.md b/packages/pi/extensions/subagent/agents/reviewer.md similarity index 100% rename from pi/extensions/subagent/agents/reviewer.md rename to packages/pi/extensions/subagent/agents/reviewer.md diff --git a/pi/extensions/subagent/agents/scout.md b/packages/pi/extensions/subagent/agents/scout.md similarity index 100% rename from pi/extensions/subagent/agents/scout.md rename to packages/pi/extensions/subagent/agents/scout.md diff --git a/pi/extensions/subagent/agents/worker.md b/packages/pi/extensions/subagent/agents/worker.md similarity index 100% rename from pi/extensions/subagent/agents/worker.md rename to packages/pi/extensions/subagent/agents/worker.md diff --git a/pi/extensions/subagent/index.ts b/packages/pi/extensions/subagent/index.ts similarity index 100% rename from pi/extensions/subagent/index.ts rename to packages/pi/extensions/subagent/index.ts diff --git a/pi/extensions/subagent/prompts/implement-and-review.md b/packages/pi/extensions/subagent/prompts/implement-and-review.md similarity index 100% rename from pi/extensions/subagent/prompts/implement-and-review.md rename to packages/pi/extensions/subagent/prompts/implement-and-review.md diff --git a/pi/extensions/subagent/prompts/implement.md b/packages/pi/extensions/subagent/prompts/implement.md similarity index 100% rename from pi/extensions/subagent/prompts/implement.md rename to packages/pi/extensions/subagent/prompts/implement.md diff --git a/pi/extensions/subagent/prompts/scout-and-plan.md b/packages/pi/extensions/subagent/prompts/scout-and-plan.md similarity index 100% rename from pi/extensions/subagent/prompts/scout-and-plan.md rename to packages/pi/extensions/subagent/prompts/scout-and-plan.md diff --git a/pi/extensions/todo.ts b/packages/pi/extensions/todo.ts similarity index 100% rename from pi/extensions/todo.ts rename to packages/pi/extensions/todo.ts diff --git a/pi/prompts/.gitkeep b/packages/pi/prompts/.gitkeep similarity index 100% rename from pi/prompts/.gitkeep rename to packages/pi/prompts/.gitkeep diff --git a/packages/pi/skills b/packages/pi/skills new file mode 120000 index 0000000..5dcab58 --- /dev/null +++ b/packages/pi/skills @@ -0,0 +1 @@ +../../skills \ No newline at end of file diff --git a/packages/zsh/dot-config/zsh/00-env.zsh b/packages/zsh/dot-config/zsh/00-env.zsh new file mode 100644 index 0000000..50faed4 --- /dev/null +++ b/packages/zsh/dot-config/zsh/00-env.zsh @@ -0,0 +1,3 @@ +export DOTFILES="$HOME/personal/dotfiles" +export DOTFILES_DIR="$HOME/personal/dotfiles" +export CPPFLAGS="-I${HOMEBREW_PREFIX:-/opt/homebrew}/opt/openjdk/include" diff --git a/packages/zsh/dot-config/zsh/10-path.zsh b/packages/zsh/dot-config/zsh/10-path.zsh new file mode 100644 index 0000000..fa50837 --- /dev/null +++ b/packages/zsh/dot-config/zsh/10-path.zsh @@ -0,0 +1,10 @@ +# HOMEBREW_PREFIX is set by brew shellenv in ~/.zprofile; fall back for odd shells. +_brew_prefix="${HOMEBREW_PREFIX:-/opt/homebrew}" +export PATH="$_brew_prefix/opt/libpq/bin:$PATH" +export PATH="$_brew_prefix/opt/openjdk/bin:$PATH" +export PATH="$_brew_prefix/opt/grep/libexec/gnubin:$PATH" +export PATH="$_brew_prefix/opt/make/libexec/gnubin:$PATH" +export PATH="$HOME/go/bin:$PATH" +export PATH="$HOME/.codeium/windsurf/bin:$PATH" +export PATH="$HOME/.local/bin:$PATH" +unset _brew_prefix diff --git a/packages/zsh/dot-config/zsh/20-aliases.zsh b/packages/zsh/dot-config/zsh/20-aliases.zsh new file mode 100644 index 0000000..b339b4c --- /dev/null +++ b/packages/zsh/dot-config/zsh/20-aliases.zsh @@ -0,0 +1,12 @@ +# Guarded: only define aliases whose targets exist on this machine. +[ -x /Applications/Tailscale.app/Contents/MacOS/Tailscale ] \ + && alias tailscale="/Applications/Tailscale.app/Contents/MacOS/Tailscale" +[ -x "$HOME/chrome-doctor.sh" ] \ + && alias chrome-doctor="$HOME/chrome-doctor.sh" +if command -v kubecolor >/dev/null; then + alias kubectl=kubecolor + compdef kubecolor=kubectl 2>/dev/null +fi +command -v eza >/dev/null && alias ll='eza -l --git' +command -v lazygit >/dev/null && alias lg=lazygit +command -v glow >/dev/null && alias md='glow -p' diff --git a/packages/zsh/dot-config/zsh/30-functions.zsh b/packages/zsh/dot-config/zsh/30-functions.zsh new file mode 100644 index 0000000..5254497 --- /dev/null +++ b/packages/zsh/dot-config/zsh/30-functions.zsh @@ -0,0 +1,48 @@ +# Decode a JWT's header and payload (needs jq); the signature is printed, not verified. +jwtd() { + if ! command -v jq >/dev/null; then + echo "jwtd: needs jq (brew install jq)" >&2 + return 127 + fi + jq -R 'split(".") | .[0],.[1] | @base64d | fromjson' <<< "${1}" + echo "Signature: $(echo "${1}" | awk -F'.' '{print $3}')" +} + +# Strip the password from a PDF -> passwordless copy (needs qpdf). +# Usage: pdfunlock input.pdf [output.pdf] (default output: -unlocked.pdf) +# Prompts for the password hidden, so it never lands in shell history. +pdfunlock() { + if [[ -z "$1" ]]; then + echo "usage: pdfunlock input.pdf [output.pdf]" >&2 + return 2 + fi + if ! command -v qpdf >/dev/null; then + echo "pdfunlock: needs qpdf (brew install qpdf)" >&2 + return 127 + fi + local in="$1" + local out="${2:-${1%.pdf}-unlocked.pdf}" + local pw + read -rs "pw?Password for $in: " + echo + if printf '%s' "$pw" | qpdf --password-file=- --decrypt "$in" "$out"; then + echo "Unlocked -> $out" + else + echo "Failed (wrong password, or file not encrypted?)" >&2 + return 1 + fi +} + +# Copy an image file to the macOS clipboard as PNG data (pasteable into Docs, Slack, etc.) +imgcopy() { + if ! command -v osascript >/dev/null; then + echo "imgcopy: needs osascript (macOS only)" >&2 + return 127 + fi + if [[ ! -f "$1" ]]; then + echo "imgcopy: no such file: $1" >&2 + return 1 + fi + osascript -e "set the clipboard to (read (POSIX file \"$(realpath "$1")\") as «class PNGf»)" \ + && echo "copied: $1" +} diff --git a/packages/zsh/dot-config/zsh/35-fzf.zsh b/packages/zsh/dot-config/zsh/35-fzf.zsh new file mode 100644 index 0000000..b8e6171 --- /dev/null +++ b/packages/zsh/dot-config/zsh/35-fzf.zsh @@ -0,0 +1,6 @@ +# fzf keybindings + completion (Ctrl-T files, Alt-C cd, ** tab-completion). +# Numbered BEFORE 40-tools.zsh on purpose: fzf binds Ctrl-R here, then atuin +# (loaded later) rebinds it, so history search stays with atuin. +command -v fzf >/dev/null && eval "$(fzf --zsh)" +export FZF_DEFAULT_COMMAND='fd --type f --hidden --exclude .git 2>/dev/null || find . -type f' +export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND" diff --git a/packages/zsh/dot-config/zsh/40-tools.zsh b/packages/zsh/dot-config/zsh/40-tools.zsh new file mode 100644 index 0000000..de18da5 --- /dev/null +++ b/packages/zsh/dot-config/zsh/40-tools.zsh @@ -0,0 +1,17 @@ +# atuin — shell history sync/search (binds Ctrl-R after fzf in 35-fzf.zsh). +command -v atuin >/dev/null && eval "$(atuin init zsh)" + +# zoxide — frecency-ranked cd (z , zi interactive). +command -v zoxide >/dev/null && eval "$(zoxide init zsh)" + +# zsh-autosuggestions — ghost-text next-command suggestion, accept with right arrow. +_zsh_autosuggest="${HOMEBREW_PREFIX:-/opt/homebrew}/share/zsh-autosuggestions/zsh-autosuggestions.zsh" +[ -r "$_zsh_autosuggest" ] && source "$_zsh_autosuggest" +unset _zsh_autosuggest + +# nvm — node version manager (also provides npx for the skill-vendoring tooling). +_nvm_dir="${HOMEBREW_PREFIX:-/opt/homebrew}/opt/nvm" +export NVM_DIR="$HOME/.nvm" +[ -s "$_nvm_dir/nvm.sh" ] && \. "$_nvm_dir/nvm.sh" +[ -s "$_nvm_dir/etc/bash_completion.d/nvm" ] && \. "$_nvm_dir/etc/bash_completion.d/nvm" +unset _nvm_dir diff --git a/packages/zsh/dot-config/zsh/50-harness.zsh b/packages/zsh/dot-config/zsh/50-harness.zsh new file mode 100644 index 0000000..7411d9c --- /dev/null +++ b/packages/zsh/dot-config/zsh/50-harness.zsh @@ -0,0 +1,5 @@ +# AI-harness shell functions (pclaude/wclaude/claude wrappers) from this +# repo's scripts/; skipped silently until the repo is cloned. +_harness_sh="${DOTFILES_DIR:-$HOME/personal/dotfiles}/scripts/claude-multi-account.sh" +[ -r "$_harness_sh" ] && source "$_harness_sh" +unset _harness_sh diff --git a/packages/zsh/dot-config/zsh/90-local.zsh.example b/packages/zsh/dot-config/zsh/90-local.zsh.example new file mode 100644 index 0000000..72a109a --- /dev/null +++ b/packages/zsh/dot-config/zsh/90-local.zsh.example @@ -0,0 +1,7 @@ +# Machine-local overrides — copy to 90-local.zsh (gitignored) on this machine only. +# Put anything here that must not be committed: secrets, work-specific exports, +# host-specific paths. +# +# cp ~/.config/zsh/90-local.zsh.example ~/.config/zsh/90-local.zsh +# +# export MY_TOKEN=... diff --git a/packages/zsh/dot-config/zsh/95-syntax-highlighting.zsh b/packages/zsh/dot-config/zsh/95-syntax-highlighting.zsh new file mode 100644 index 0000000..59d2a8a --- /dev/null +++ b/packages/zsh/dot-config/zsh/95-syntax-highlighting.zsh @@ -0,0 +1,5 @@ +# zsh-syntax-highlighting must be sourced after all other plugins and widgets — +# hence the highest module number (after the gitignored 90-local.zsh too). +_zsh_hl="${HOMEBREW_PREFIX:-/opt/homebrew}/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh" +[ -r "$_zsh_hl" ] && source "$_zsh_hl" +unset _zsh_hl diff --git a/packages/zsh/dot-zprofile b/packages/zsh/dot-zprofile new file mode 100644 index 0000000..26e43ae --- /dev/null +++ b/packages/zsh/dot-zprofile @@ -0,0 +1,6 @@ +# Login-shell setup only; everything interactive lives in ~/.config/zsh/ (see ~/.zshrc). +if [ -x /opt/homebrew/bin/brew ]; then + eval "$(/opt/homebrew/bin/brew shellenv)" +elif [ -x /usr/local/bin/brew ]; then + eval "$(/usr/local/bin/brew shellenv)" +fi diff --git a/packages/zsh/dot-zshrc b/packages/zsh/dot-zshrc new file mode 100644 index 0000000..906da1d --- /dev/null +++ b/packages/zsh/dot-zshrc @@ -0,0 +1,5 @@ +# Thin loader — all interactive config lives in ~/.config/zsh/*.zsh (managed in dotfiles). +# Modules load in NN- prefix order; drop a new file in to extend. +for f in "$HOME"/.config/zsh/*.zsh(N); do + source "$f" +done diff --git a/scripts/doctor.sh b/scripts/doctor.sh new file mode 100755 index 0000000..6d62b7e --- /dev/null +++ b/scripts/doctor.sh @@ -0,0 +1,127 @@ +#!/usr/bin/env bash +# Health checks for the dotfiles setup. Exit non-zero if anything is wrong. +set -uo pipefail + +REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +FAIL=0 + +ok() { printf ' ok: %s\n' "$*"; } +warn() { printf ' warn: %s\n' "$*"; } +bad() { printf 'FAIL: %s\n' "$*"; FAIL=1; } + +resolve() { + realpath "$1" 2>/dev/null \ + || python3 -c 'import os,sys; print(os.path.realpath(sys.argv[1]))' "$1" 2>/dev/null \ + || true +} + +echo "== tools ==" +if command -v brew >/dev/null; then ok "brew $(brew --version | head -1 | awk '{print $2}')"; else bad "brew not found"; fi +if command -v stow >/dev/null; then + stow_ver="$(stow --version | head -1 | awk '{print $NF}')" + IFS=. read -r maj min _ <<< "$stow_ver" + if [ "${maj:-0}" -gt 2 ] 2>/dev/null || { [ "${maj:-0}" -eq 2 ] && [ "${min:-0}" -ge 4 ]; } 2>/dev/null; then + ok "stow $stow_ver" + else + bad "stow $stow_ver too old or unparseable — need >= 2.4.0 for --dotfiles dir handling" + fi +else + bad "stow not found" +fi +# npx is only needed for the optional skills-find/vendor targets. +if command -v npx >/dev/null; then ok "npx ($(command -v npx))"; else warn "npx not found — run: nvm install --lts (needed only for skill vendoring)"; fi + +if python3 -c 'import tomllib' 2>/dev/null; then + ok "python3 with tomllib ($(python3 --version | awk '{print $2}'))" +else + bad "python3 >= 3.11 with tomllib not found — sources.toml tooling needs it (brew install python)" +fi + +echo "== brew bundle ==" +if brew bundle check --file="$REPO_DIR/Brewfile" >/dev/null 2>&1; then + ok "Brewfile satisfied" +else + # mas relies on the Spotlight index, which can lag or go stale; if the only + # unmet entries are App Store apps that exist on disk, that's a warning. + unmet="$(brew bundle check --verbose --file="$REPO_DIR/Brewfile" 2>&1 | grep '^→' || true)" + non_mas="$(printf '%s\n' "$unmet" | grep -v '^→ App ' || true)" + missing_apps="" + while IFS= read -r line; do + app="${line#→ App }"; app="${app% needs to be installed or updated.}" + [ -d "/Applications/$app.app" ] || missing_apps="$missing_apps $app" + done < <(printf '%s\n' "$unmet" | grep '^→ App ' || true) + if [ -z "$non_mas" ] && [ -z "$missing_apps" ]; then + warn "Brewfile mas entries unmet only per Spotlight index; all apps present on disk" + else + bad "Brewfile unsatisfied — run: make brew-install"$'\n'"$unmet" + fi +fi + +echo "== symlinks ==" +# Target list is derived from packages/ (managed-targets.sh), so new packages +# are health-checked automatically. +while IFS= read -r target; do + path="$HOME/$target" + resolved="$(resolve "$path")" + if [ -L "$path" ] && [[ "$resolved" == "$REPO_DIR/packages/"* ]]; then + ok "$target" + else + bad "$target is not a symlink into packages/ — run: make stow-link" + fi +done < <("$REPO_DIR/scripts/managed-targets.sh") + +echo "== secret safety ==" +for dir in .config/gh .config/git .config/zsh; do + if [ -d "$HOME/$dir" ] && [ ! -L "$HOME/$dir" ]; then + ok "~/$dir is a real directory" + else + bad "~/$dir is missing or a symlink — credentials could land in the repo (--no-folding violated)" + fi +done +if [ -e "$HOME/.config/gh/hosts.yml" ] && [ ! -L "$HOME/.config/gh/hosts.yml" ]; then + ok "gh hosts.yml is a plain local file" +elif [ -L "$HOME/.config/gh/hosts.yml" ]; then + bad "gh hosts.yml is a symlink — tokens may be inside the repo" +else + ok "gh hosts.yml absent (run gh auth login)" +fi +leaks="$(cd "$REPO_DIR" && git ls-files | grep -Ei 'hosts\.yml|\.env($|\.)|\.pem$|\.key$|token|credential' || true)" +if [ -z "$leaks" ]; then + ok "no secret-pattern filenames tracked by git" +else + bad "secret-pattern filenames tracked by git:"$'\n'"$leaks" +fi +# Content scan: catch credential-looking values inside tracked files (e.g. a +# stow-adopted shell profile that carried exported tokens). +content_leaks="$(cd "$REPO_DIR" && git grep -nIiE "(api[_-]?key|secret|token|password)[[:space:]]*[=:][[:space:]]*['\"]?[A-Za-z0-9_/+=-]{12,}|BEGIN [A-Z ]*PRIVATE KEY" -- packages/ 2>/dev/null || true)" +if [ -z "$content_leaks" ]; then + ok "no credential-looking content in tracked packages/" +else + bad "credential-looking content in tracked files:"$'\n'"$content_leaks" +fi + +echo "== broken symlinks ==" +broken="$(find "$HOME" -maxdepth 3 -type l ! -exec test -e {} \; -print 2>/dev/null || true)" +if [ -z "$broken" ]; then + ok "no broken symlinks in ~ (depth 3)" +else + warn "broken symlinks (stale stow links or removed targets):"$'\n'"$(printf '%s\n' "$broken" | sed 's/^/ /')" +fi + +echo "== AI harness ==" +if [ -r "$REPO_DIR/scripts/claude-multi-account.sh" ]; then + ok "claude-multi-account.sh readable (sourced by ~/.config/zsh/50-harness.zsh)" +else + bad "claude-multi-account.sh missing" +fi +for t in "$HOME/.claude-personal/CLAUDE.md" "$HOME/.claude-work/CLAUDE.md" "$HOME/.pi/skills"; do + r="$(resolve "$t")" + case "$r" in + "$REPO_DIR"/*) ok "$t -> repo" ;; + *) bad "$t does not resolve into $REPO_DIR — run: make harness-link" ;; + esac +done + +echo "" +if [ "$FAIL" -eq 0 ]; then echo "doctor: all checks passed"; else echo "doctor: FAILURES above"; fi +exit "$FAIL" diff --git a/scripts/drift.sh b/scripts/drift.sh new file mode 100644 index 0000000..7f302fe --- /dev/null +++ b/scripts/drift.sh @@ -0,0 +1,143 @@ +#!/usr/bin/env bash +# Drift report: recorded configuration (Brewfile, manifests/, macos/defaults.sh) +# vs the live system, in both directions. Prints the reconcile command for every +# finding. Exit 1 if any drift; Spotlight-lagged mas entries are warnings only. +set -uo pipefail + +REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +DRIFT=0 + +ok() { printf ' ok: %s\n' "$*"; } +warn() { printf ' warn: %s\n' "$*"; } +drift() { printf 'DRIFT: %s\n' "$*"; DRIFT=1; } + +manifest_entries() { grep -vE '^[[:space:]]*#|^[[:space:]]*$' "$1"; } + +# Normalize a Brewfile-ish file to sorted "kind name" pairs. Only kinds we +# curate; brew-6 dump also emits go/npm lines, which our manifests own. +normalize_bundle() { + awk ' + /^(tap|brew|cask|mas|vscode) / { + kind=$1; name=$2 + gsub(/[",]/, "", name) + print kind, name + } + ' "$1" | sort -u +} + +echo "== brew (formulae, casks, taps, mas, vscode) ==" +dump="$(mktemp)" +trap 'rm -f "$dump"' EXIT +if brew bundle dump --file="$dump" --force >/dev/null 2>&1; then + declared="$(normalize_bundle "$REPO_DIR/Brewfile")" + installed="$(normalize_bundle "$dump")" + missing="$(comm -23 <(printf '%s\n' "$declared") <(printf '%s\n' "$installed"))" + extra="$(comm -13 <(printf '%s\n' "$declared") <(printf '%s\n' "$installed"))" + # mas can't see App Store apps when the Spotlight index lags; if the app + # bundle exists on disk, downgrade declared-but-missing mas rows to warnings. + if [ -n "$missing" ]; then + real_missing="" + while IFS= read -r row; do + [ -z "$row" ] && continue + kind="${row%% *}"; name="${row#* }" + if [ "$kind" = "mas" ]; then + app_line="$(grep -E "^mas \"[^\"]+\", id: .*$" "$REPO_DIR/Brewfile" | grep -F "$name" || true)" + app_name="$(printf '%s' "$app_line" | sed -E 's/^mas "([^"]+)".*/\1/')" + if [ -n "$app_name" ] && [ -d "/Applications/$app_name.app" ]; then + warn "mas $app_name unmet only per Spotlight index; app present on disk" + continue + fi + fi + real_missing="$real_missing$row"$'\n' + done <<< "$missing" + missing="$(printf '%s' "$real_missing")" + fi + if [ -n "$missing" ]; then + drift "declared but not installed — run: make brew-install"$'\n'"$(printf '%s\n' "$missing" | sed 's/^/ /')" + fi + if [ -n "$extra" ]; then + drift "installed but not in Brewfile — add there, or brew uninstall / brew untap:"$'\n'"$(printf '%s\n' "$extra" | sed 's/^/ /')" + fi + [ -z "$missing" ] && [ -z "$extra" ] && ok "Brewfile matches installed state" +else + drift "brew bundle dump failed — is brew healthy?" +fi + +echo "== go tools (manifests/go-tools.txt vs ~/go/bin) ==" +declared_bins="$(manifest_entries "$REPO_DIR/manifests/go-tools.txt" | while read -r mod; do + mod="${mod%@*}" + bin="${mod##*/}" + case "$bin" in v[0-9]*) mod="${mod%/*}"; bin="${mod##*/}" ;; esac + printf '%s\n' "$bin" +done | sort -u)" +installed_bins="$(ls "$HOME/go/bin" 2>/dev/null | sort -u || true)" +go_missing="$(comm -23 <(printf '%s\n' "$declared_bins") <(printf '%s\n' "$installed_bins"))" +go_extra="$(comm -13 <(printf '%s\n' "$declared_bins") <(printf '%s\n' "$installed_bins"))" +[ -n "$go_missing" ] && drift "manifest tools missing from ~/go/bin — run: make go-install"$'\n'"$(printf '%s\n' "$go_missing" | sed 's/^/ /')" +[ -n "$go_extra" ] && drift "~/go/bin binaries not in the manifest — add there, or rm ~/go/bin/:"$'\n'"$(printf '%s\n' "$go_extra" | sed 's/^/ /')" +[ -z "$go_missing" ] && [ -z "$go_extra" ] && ok "go tools match manifest" + +echo "== npm globals (manifests/npm-globals.txt) ==" +if command -v npm >/dev/null; then + declared_npm="$(manifest_entries "$REPO_DIR/manifests/npm-globals.txt" | sort -u)" + installed_npm="$(npm ls -g --depth=0 --json 2>/dev/null | jq -r '.dependencies | keys[]' 2>/dev/null | grep -vx npm | sort -u || true)" + npm_missing="$(comm -23 <(printf '%s\n' "$declared_npm") <(printf '%s\n' "$installed_npm"))" + npm_extra="$(comm -13 <(printf '%s\n' "$declared_npm") <(printf '%s\n' "$installed_npm"))" + [ -n "$npm_missing" ] && drift "manifest npm globals missing — run: make npm-install"$'\n'"$(printf '%s\n' "$npm_missing" | sed 's/^/ /')" + [ -n "$npm_extra" ] && drift "npm globals not in manifest — add there, or npm uninstall -g :"$'\n'"$(printf '%s\n' "$npm_extra" | sed 's/^/ /')" + [ -z "$npm_missing" ] && [ -z "$npm_extra" ] && ok "npm globals match manifest" +else + warn "npm not on PATH — skipping (nvm not loaded in this shell?)" +fi + +echo "== pipx (manifests/pipx-tools.txt) ==" +if command -v pipx >/dev/null; then + declared_pipx="$(manifest_entries "$REPO_DIR/manifests/pipx-tools.txt" | sort -u)" + installed_pipx="$(pipx list --short 2>/dev/null | awk '{print $1}' | sort -u || true)" + pipx_missing="$(comm -23 <(printf '%s\n' "$declared_pipx") <(printf '%s\n' "$installed_pipx"))" + pipx_extra="$(comm -13 <(printf '%s\n' "$declared_pipx") <(printf '%s\n' "$installed_pipx"))" + [ -n "$pipx_missing" ] && drift "manifest pipx tools missing — run: make pipx-install"$'\n'"$(printf '%s\n' "$pipx_missing" | sed 's/^/ /')" + [ -n "$pipx_extra" ] && drift "pipx tools not in manifest — add there, or pipx uninstall :"$'\n'"$(printf '%s\n' "$pipx_extra" | sed 's/^/ /')" + [ -z "$pipx_missing" ] && [ -z "$pipx_extra" ] && ok "pipx tools match manifest" +else + warn "pipx not on PATH — skipping" +fi + +echo "== macOS defaults (macos/defaults.sh vs live) ==" +defaults_drift=0 +# Parse each `defaults [-currentHost] write - ` line +# and compare with the live value. Booleans normalize to 1/0. +while IFS= read -r line; do + line="${line#"${line%%[![:space:]]*}"}" + host_flag="" + rest="${line#defaults }" + case "$rest" in + -currentHost\ write\ *) host_flag="-currentHost"; rest="${rest#-currentHost write }" ;; + write\ *) rest="${rest#write }" ;; + *) continue ;; + esac + domain="${rest%% *}"; rest="${rest#* }" + key="${rest%% *}"; rest="${rest#* }" + rest="${rest# }" + value="${rest#-* }" + value="${value%\"}"; value="${value#\"}" + value="${value//\$HOME/$HOME}" + case "$value" in + true) value=1 ;; + false) value=0 ;; + esac + live="$(defaults $host_flag read "$domain" "$key" 2>/dev/null || echo '')" + if [ "$live" != "$value" ]; then + drift "$domain $key: recorded '$value', live '$live' — update macos/defaults.sh, or run: make macos-apply" + defaults_drift=1 + fi +done < <(grep -E '^[[:space:]]*defaults (-currentHost )?write ' "$REPO_DIR/macos/defaults.sh") +[ "$defaults_drift" -eq 0 ] && ok "recorded defaults match live values" + +echo "" +if [ "$DRIFT" -eq 0 ]; then + echo "drift: system matches recorded configuration" +else + echo "drift: DIVERGENCES above — reconcile in whichever direction is right" +fi +exit "$DRIFT" diff --git a/scripts/managed-targets.sh b/scripts/managed-targets.sh new file mode 100755 index 0000000..183c07b --- /dev/null +++ b/scripts/managed-targets.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# Print every $HOME-relative path managed by the stow packages, derived from +# packages/ itself so the list can never drift from reality. +# Mapping mirrors stow --dotfiles: a leading "dot-" on any path component -> ".". +set -euo pipefail +cd "$(dirname "${BASH_SOURCE[0]}")/../packages" +find . -mindepth 2 \( -type f -o -type l \) \ + | sed -E 's#^\./[^/]+/##' \ + | sed -E 's#(^|/)dot-#\1.#g' \ + | sort -u diff --git a/scripts/resource-manager.sh b/scripts/resource-manager.sh index 90f0c4d..531ef33 100755 --- a/scripts/resource-manager.sh +++ b/scripts/resource-manager.sh @@ -4,22 +4,19 @@ # (skills or agents) from arbitrary git repos, tracking each resource's source. # # A "skill" is a directory containing SKILL.md, vendored under skills/. -# An "agent" is a single .md file, vendored under claude/agents/. +# An "agent" is a single .md file, vendored under packages/claude/agents/. # -# VENDORED SKILLS are recorded in a single committed manifest and their files -# are NOT committed — they are materialized from their pinned commit on install: -# manifest: skills/vendored.json — an array of -# {"name","repo","subpath","ref","commit","category","description"} -# working files: skills// — gitignored, rebuilt by `materialize`. -# (materialize also writes a gitignored skills//.source.json marker.) +# EVERY resource is recorded in the single committed manifest, sources.toml +# ([[skill]] and [[agent]] arrays of tables; read/written via +# scripts/toml-manifest.py). An entry with a non-empty "repo" is VENDORED: +# {"name","repo","subpath","ref","commit","category","description"[,"fetched_at"]} +# a VENDORED SKILL's files are NOT committed — skills// is gitignored and +# materialized from the pinned commit on install (materialize also writes a +# gitignored skills//.source.json marker recording what is on disk). +# Vendored AGENT .md files stay committed. # -# AUTHORED skills (no upstream) and ALL AGENTS keep an in-tree sidecar and stay -# committed as before: -# local sidecar: {"repo": null[, "category"]} -# remote sidecar (agents): {"repo","subpath","ref","commit","fetched_at"[,"category"]} -# skill sidecar: skills//.source.json (inside the dir) -# agent sidecar: claude/agents/.source.json (sibling of the .md) -# A resource with neither a manifest entry nor a sidecar is "unmanaged". +# An entry without "repo" is AUTHORED (born here): {"name","category"[,"note"]}; +# its files are committed. A resource with no manifest entry is "unmanaged". # # Usage: # resource-manager.sh --kind {skill|agent} fetch (--url URL | --repo REPO --subpath SUBPATH) [--ref REF] [--name NAME] [--category CAT] [--force] @@ -44,6 +41,7 @@ rel() { printf '%s' "${1#"$REPO_ROOT"/}"; } command -v git >/dev/null || die "git is required" command -v jq >/dev/null || die "jq is required" +command -v python3 >/dev/null || die "python3 (>= 3.11, for tomllib) is required" # Temp dirs are tracked globally and cleaned once on exit. A per-function # RETURN trap would be wrong here: bash traps are global, so it would re-fire @@ -61,16 +59,18 @@ mktmp() { MKTMP_DIR="$(mktemp -d)"; TMPDIRS+=("$MKTMP_DIR"); } KIND="" RESOURCE_ROOT="" -MANIFEST="" # skills/vendored.json (skill kind only) +MANIFEST="" # sources.toml (both kinds; KIND selects the array) +TOML_MANIFEST="$REPO_ROOT/scripts/toml-manifest.py" GITIGNORE="$REPO_ROOT/.gitignore" SCAN_BASELINE_DIR="$REPO_ROOT/security/skillspector" # per-skill SkillSpector baselines configure_kind() { case "$KIND" in - skill) RESOURCE_ROOT="$REPO_ROOT/skills"; MANIFEST="$RESOURCE_ROOT/vendored.json" ;; - agent) RESOURCE_ROOT="$REPO_ROOT/claude/agents" ;; + skill) RESOURCE_ROOT="$REPO_ROOT/skills" ;; + agent) RESOURCE_ROOT="$REPO_ROOT/packages/claude/agents" ;; *) die "missing or unknown --kind '$KIND' (expected skill|agent)" ;; esac + MANIFEST="$REPO_ROOT/sources.toml" } # Primary artifact path for a resource (a dir for skills, a .md file for agents). @@ -81,12 +81,10 @@ artifact_path() { esac } -# Sidecar path: inside the dir for skills, sibling of the .md for agents. +# Gitignored materialize-marker path (skill kind only): records what commit is +# on disk so materialize can skip up-to-date dirs. Not a committed record. sidecar_path() { - case "$KIND" in - skill) printf '%s/%s/.source.json' "$RESOURCE_ROOT" "$1" ;; - agent) printf '%s/%s.source.json' "$RESOURCE_ROOT" "$1" ;; - esac + printf '%s/%s/.source.json' "$RESOURCE_ROOT" "$1" } # Default resource name from a subpath. @@ -125,23 +123,19 @@ copy_artifact() { esac } -# Emit "namesidecar_path" for each managed resource of this kind. +# Emit the name of each on-disk resource of this kind. iter_resources() { case "$KIND" in skill) - local dir name + local dir for dir in "$RESOURCE_ROOT"/*/; do - [[ -d "$dir" ]] || continue - name="$(basename "$dir")" - printf '%s\t%s\n' "$name" "$dir.source.json" + [[ -d "$dir" ]] && basename "$dir" done ;; agent) - local f name + local f for f in "$RESOURCE_ROOT"/*.md; do - [[ -f "$f" ]] || continue - name="$(basename "$f" .md)" - printf '%s\t%s\n' "$name" "$RESOURCE_ROOT/$name.source.json" + [[ -f "$f" ]] && basename "$f" .md done ;; esac @@ -214,33 +208,48 @@ fetch_commit() { git -C "$dest" checkout -q FETCH_HEAD 2>/dev/null || return 1 } -# --- vendored-skill manifest (skills/vendored.json) ------------------------ -# The manifest is the committed source of truth for vendored skills. Their -# files are gitignored and materialized from the pinned commit. +# --- sources manifest (sources.toml, both kinds) --------------------------- +# The manifest is the committed source of truth for every skill and agent. +# Reads and writes go through toml-manifest.py; the jq logic in between works +# on the {"skill": [...], "agent": [...]} JSON form, and mutations always +# round-trip the FULL document so the other kind's entries are preserved. -manifest_read() { [[ -f "$MANIFEST" ]] && cat "$MANIFEST" || printf '[]'; } +sources_read_all() { + if [[ -f "$MANIFEST" ]]; then python3 "$TOML_MANIFEST" to-json "$MANIFEST" + else printf '{"skill":[],"agent":[]}'; fi +} +# Apply a jq filter (with its args) to the full doc and persist the result. +sources_write() { + local tmp; tmp="$(mktemp)" + sources_read_all | jq "$@" > "$tmp" || { rm -f "$tmp"; return 1; } + python3 "$TOML_MANIFEST" from-json "$MANIFEST" < "$tmp" + rm -f "$tmp" +} +manifest_read() { sources_read_all | jq -c --arg k "$KIND" '.[$k] // []'; } manifest_names() { manifest_read | jq -r '.[].name'; } +vendored_names() { manifest_read | jq -r '.[] | select((.repo // "") != "") | .name'; } manifest_entry() { manifest_read | jq -c --arg n "$1" 'map(select(.name==$n))[0] // empty'; } manifest_field() { local e; e="$(manifest_entry "$1")"; [[ -n "$e" ]] && jq -r --arg f "$2" '.[$f] // ""' <<<"$e" || printf ''; } -is_vendored() { [[ -n "$(manifest_entry "$1")" ]]; } +is_vendored() { [[ -n "$(manifest_field "$1" repo)" ]]; } -manifest_upsert() { # name repo subpath ref commit category description - local tmp; tmp="$(mktemp)" - manifest_read | jq \ +manifest_upsert() { # name repo subpath ref commit category description [fetched_at] + sources_write --arg kind "$KIND" \ --arg name "$1" --arg repo "$2" --arg subpath "$3" --arg ref "$4" \ - --arg commit "$5" --arg category "$6" --arg description "$7" \ - 'map(select(.name != $name)) - + [{name:$name, repo:$repo, subpath:$subpath, ref:$ref, commit:$commit, category:$category, description:$description}] - | sort_by(.name)' \ - > "$tmp" && mv "$tmp" "$MANIFEST" + --arg commit "$5" --arg category "$6" --arg description "$7" --arg fetched_at "${8:-}" \ + '.[$kind] |= (map(select(.name != $name)) + + [{name:$name, repo:$repo, subpath:$subpath, ref:$ref, commit:$commit, + category:$category, description:$description, fetched_at:$fetched_at}] + | sort_by(.name))' } manifest_remove() { - local tmp; tmp="$(mktemp)" - manifest_read | jq --arg n "$1" 'map(select(.name != $n))' > "$tmp" && mv "$tmp" "$MANIFEST" + sources_write --arg kind "$KIND" --arg n "$1" '.[$kind] |= map(select(.name != $n))' } +# Set/replace a category, creating a minimal authored entry if none exists. manifest_set_category() { - local tmp; tmp="$(mktemp)" - manifest_read | jq --arg n "$1" --arg c "$2" 'map(if .name==$n then .category=$c else . end)' > "$tmp" && mv "$tmp" "$MANIFEST" + sources_write --arg kind "$KIND" --arg n "$1" --arg c "$2" \ + '.[$kind] |= (if any(.[]; .name==$n) + then map(if .name==$n then .category=$c else . end) + else . + [{name:$n, category:$c}] | sort_by(.name) end)' } # --- skill accessors (dual-source: manifest for vendored, sidecar/SKILL.md @@ -258,12 +267,8 @@ skill_description() { else frontmatter_field "$RESOURCE_ROOT/$1/SKILL.md" description; fi } skill_category() { - local c sc - if is_vendored "$1"; then c="$(manifest_field "$1" category)"; printf '%s' "${c:-uncategorized}" - else - sc="$RESOURCE_ROOT/$1/.source.json" - if [[ -f "$sc" ]]; then jq -r '.category // "uncategorized"' "$sc"; else printf 'uncategorized'; fi - fi + local c; c="$(manifest_field "$1" category)" + printf '%s' "${c:-uncategorized}" } # Catalog link target for a skill. Vendored skill dirs are gitignored (absent on # GitHub), so link to their upstream source at the pinned commit; authored dirs @@ -291,7 +296,7 @@ sync_gitignore() { local blk tmp blk="$(mktemp)"; tmp="$(mktemp)" { printf '%s\n' "$GI_BEGIN" - manifest_names | sort | while IFS= read -r n; do [[ -n "$n" ]] && printf '/skills/%s/\n' "$n"; done + vendored_names | sort | while IFS= read -r n; do [[ -n "$n" ]] && printf '/skills/%s/\n' "$n"; done printf '%s\n' "$GI_END" } > "$blk" if [[ -f "$GITIGNORE" ]] && grep -qxF "$GI_BEGIN" "$GITIGNORE"; then @@ -375,7 +380,7 @@ cmd_materialize() { while IFS= read -r n; do [[ -n "$n" ]] || continue if materialize_one "$n" "$force"; then ok=$((ok + 1)); else fail=$((fail + 1)); fi - done < <(manifest_names) + done < <(vendored_names) # Reconcile: a vendored dir dropped from the manifest is stale — remove it so the # on-disk vendored set matches the manifest, rather than leaving a skill active in @@ -478,35 +483,37 @@ cmd_fetch() { write_sidecar "$name" "$repo" "$subpath" "$ref" "$commit" "$category" # gitignored materialize marker sync_gitignore else - write_sidecar "$name" "$repo" "$subpath" "$ref" "$commit" "$category" + manifest_upsert "$name" "$repo" "$subpath" "$ref" "$commit" "$category" "" \ + "$(date -u +%Y-%m-%dT%H:%M:%SZ)" fi info "fetched $(rel "$dest") @ ${commit:0:7}${category:+ [$category]}" } -# Build list rows from in-tree sidecars (agents, and authored/unmanaged skill -# dirs). Emits: category\tname\tstatus\trepo\tsubpath\tref\tcommit\tfetched. +# Build list rows for agents from the manifest plus the on-disk .md files. +# Emits: category\tname\tstatus\trepo\tsubpath\tref\tcommit\tfetched. list_data_generic() { - local name sidecar repo subpath ref commit fetched category status - while IFS=$'\t' read -r name sidecar; do + local name entry repo subpath ref commit fetched category status + while IFS= read -r name; do [[ -n "$name" ]] || continue + entry="$(manifest_entry "$name")" repo=-; subpath=-; ref=-; commit=-; fetched=- - if [[ ! -f "$sidecar" ]]; then + if [[ -z "$entry" ]]; then status=unmanaged; category=uncategorized else - category="$(jq -r '.category // "uncategorized"' "$sidecar")" - repo="$(jq -r '.repo // empty' "$sidecar")" + category="$(jq -r '.category // "uncategorized"' <<<"$entry")" + repo="$(jq -r '.repo // empty' <<<"$entry")" if [[ -z "$repo" ]]; then status=local; repo=- else status=remote - subpath="$(jq -r '.subpath // "-"' "$sidecar")" - ref="$(jq -r '.ref // "-"' "$sidecar")" - commit="$(jq -r '.commit // "-"' "$sidecar")"; commit="${commit:0:7}" - fetched="$(jq -r '.fetched_at // "-"' "$sidecar")" + subpath="$(jq -r '.subpath // "-"' <<<"$entry")" + ref="$(jq -r '.ref // "-"' <<<"$entry")" + commit="$(jq -r '.commit // "-"' <<<"$entry")"; commit="${commit:0:7}" + fetched="$(jq -r '.fetched_at // "-"' <<<"$entry")" fi fi printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' "$category" "$name" "$status" "$repo" "$subpath" "$ref" "$commit" "$fetched" - done < <(iter_resources) + done < <({ iter_resources; manifest_names; } | sort -u) } # Build skill list rows: vendored from the manifest (status materialized/pinned @@ -523,16 +530,15 @@ list_data_skill() { category="$(jq -r '.category // "uncategorized"' <<<"$entry")"; [[ -n "$category" ]] || category=uncategorized if [[ -f "$RESOURCE_ROOT/$name/SKILL.md" ]]; then status=materialized; else status=pinned; fi printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' "$category" "$name" "$status" "$repo" "$subpath" "$ref" "$commit" "-" - done < <(manifest_names) + done < <(vendored_names) for dir in "$RESOURCE_ROOT"/*/; do [[ -d "$dir" ]] || continue name="$(basename "$dir")" is_vendored "$name" && continue - sidecar="$dir.source.json" - if [[ ! -f "$sidecar" ]]; then + if [[ -z "$(manifest_entry "$name")" ]]; then status=unmanaged; category=uncategorized else - category="$(jq -r '.category // "uncategorized"' "$sidecar")" + category="$(skill_category "$name")" status=local fi printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' "$category" "$name" "$status" "-" "-" "-" "-" "-" @@ -603,24 +609,24 @@ update_one_skill() { update_one() { local name="$1" [[ "$KIND" == "skill" ]] && { update_one_skill "$name"; return; } - local artifact sidecar + local artifact entry artifact="$(artifact_path "$name")" - sidecar="$(sidecar_path "$name")" [[ -e "$artifact" ]] || { err "$name: no such $KIND"; return 1; } - if [[ ! -f "$sidecar" ]]; then - info "$name: unmanaged (no .source.json), skipping" + entry="$(manifest_entry "$name")" + if [[ -z "$entry" ]]; then + info "$name: unmanaged (no manifest entry), skipping" return 0 fi - local repo; repo="$(jq -r '.repo // empty' "$sidecar")" + local repo; repo="$(jq -r '.repo // empty' <<<"$entry")" if [[ -z "$repo" ]]; then info "$name: local $KIND, nothing to update" return 0 fi local subpath ref old_commit category - subpath="$(jq -r '.subpath' "$sidecar")" - ref="$(jq -r '.ref' "$sidecar")" - old_commit="$(jq -r '.commit' "$sidecar")" - category="$(jq -r '.category // ""' "$sidecar")" # preserve across re-fetch + subpath="$(jq -r '.subpath' <<<"$entry")" + ref="$(jq -r '.ref' <<<"$entry")" + old_commit="$(jq -r '.commit' <<<"$entry")" + category="$(jq -r '.category // ""' <<<"$entry")" # preserve across re-fetch local tmp; mktmp; tmp="$MKTMP_DIR" sparse_clone "$repo" "$ref" "$(sparse_set_path "$subpath")" "$tmp/repo" @@ -634,7 +640,8 @@ update_one() { || { err "$name: subpath $subpath is no longer a valid $KIND upstream, skipping"; return 1; } copy_artifact "$tmp/repo/$subpath" "$artifact" - write_sidecar "$name" "$repo" "$subpath" "$ref" "$new_commit" "$category" + manifest_upsert "$name" "$repo" "$subpath" "$ref" "$new_commit" "$category" "" \ + "$(date -u +%Y-%m-%dT%H:%M:%SZ)" info "$name: updated ${old_commit:0:7} -> ${new_commit:0:7}" } @@ -649,19 +656,10 @@ cmd_update() { done if [[ "$all" -eq 1 ]]; then - if [[ "$KIND" == "skill" ]]; then - local n - while IFS= read -r n; do - [[ -n "$n" ]] && { update_one_skill "$n" || true; } - done < <(manifest_names) - else - local n s - while IFS=$'\t' read -r n s; do - [[ -f "$s" ]] || continue - [[ "$(jq -r '.repo // empty' "$s")" ]] || continue - update_one "$n" || true - done < <(iter_resources) - fi + local n + while IFS= read -r n; do + [[ -n "$n" ]] && { update_one "$n" || true; } + done < <(vendored_names) return 0 fi @@ -703,8 +701,9 @@ cmd_delete() { fi case "$KIND" in skill) rm -rf "$artifact"; drop_scan_baseline "$name" ;; - agent) rm -f "$artifact" "$(sidecar_path "$name")" ;; + agent) rm -f "$artifact" ;; esac + manifest_remove "$name" info "deleted $(rel "$artifact")" } @@ -767,7 +766,8 @@ render_catalog() { local data="" name category desc purpose link total # all_skill_names is sorted, so rows are alphabetical within each category. # Vendored skills draw category/description from the manifest (so the catalog - # renders on a bare checkout); authored skills from their sidecar + SKILL.md. + # renders on a bare checkout); authored skills their category from the + # manifest and their description from the committed SKILL.md. while IFS= read -r name; do [[ -n "$name" ]] || continue skill_exists "$name" || continue @@ -790,7 +790,7 @@ render_catalog() { fi printf '# Skills catalog\n\n' - printf '%s skills, grouped by `category` (from `skills/vendored.json` for vendored skills, from each `.source.json` sidecar for authored ones).\n' "$total" + printf '%s skills, grouped by `category` (from each `sources.toml` entry).\n' "$total" printf 'Each name links to its source: authored skills to the in-repo `SKILL.md`, vendored skills to their upstream repo at the pinned commit (their dirs are gitignored, so they are not present in this repo).\n' printf 'Generated by `make skills-catalog` — do not edit by hand (`make skills-doctor` flags a stale file).\n\n' @@ -853,7 +853,7 @@ cmd_catalog() { # command) and the Suites index in the top-level README.md. SUITES_ROOT="$REPO_ROOT/suites" -SUITE_INSTALL_REPO="sanketsudake/harness-configs" +SUITE_INSTALL_REPO="sanketsudake/dotfiles" SUITE_BEGIN='' SUITE_END='' SUITES_INDEX_BEGIN='' @@ -1015,7 +1015,7 @@ cmd_budget() { esac done [[ "$limit" =~ ^[1-9][0-9]*$ ]] || die "budget: limit must be a positive integer (got '$limit'; set CONTEXT_BUDGET_TOKENS or --limit)" - local claude_dir="$REPO_ROOT/claude" + local claude_dir="$REPO_ROOT/packages/claude" # Each segment is "N items, T estimated tokens"; sum_segment reads whole # files, sum_descs reads "name: description" strings from stdin, one per line. sum_segment() { local t=0 n=0 f; for f in "$@"; do [[ -f "$f" ]] || continue; t=$((t + $(est_tokens "$(cat "$f")"))); n=$((n + 1)); done; echo "$t $n"; } @@ -1056,11 +1056,12 @@ cmd_doctor() { flag() { printf '%s\n' "$*"; issues=$((issues + 1)); } if [[ "$KIND" == "skill" ]]; then - # Vendored skills: validate the manifest. Their files may be un-materialized. + # The manifest itself, then vendored entries (their files may be + # un-materialized), then authored entries. if [[ ! -f "$MANIFEST" ]]; then flag "$(rel "$MANIFEST"): missing" - elif ! jq -e 'type == "array"' "$MANIFEST" >/dev/null 2>&1; then - flag "$(rel "$MANIFEST"): not a JSON array" + elif ! python3 "$TOML_MANIFEST" to-json "$MANIFEST" >/dev/null 2>&1; then + flag "$(rel "$MANIFEST"): not valid TOML" else local dup f dup="$(manifest_names | sort | uniq -d)" @@ -1074,9 +1075,18 @@ cmd_doctor() { done grep -qxF "/skills/$name/" "$GITIGNORE" 2>/dev/null \ || flag "$name: vendored dir not listed in .gitignore managed block" + done < <(vendored_names) + # Authored entries must have a category and a committed dir. + while IFS= read -r name; do + [[ -n "$name" ]] || continue + is_vendored "$name" && continue + [[ -n "$(manifest_field "$name" category)" ]] \ + || flag "$name: manifest entry has no 'category'" + [[ -f "$RESOURCE_ROOT/$name/SKILL.md" ]] \ + || flag "$name: manifest entry for an authored skill with no skills/$name/SKILL.md" done < <(manifest_names) fi - # Authored / unmanaged skills: dirs not in the manifest. + # Authored / unmanaged skills: dirs not vendored. local dir for dir in "$RESOURCE_ROOT"/*/; do [[ -d "$dir" ]] || continue @@ -1095,13 +1105,13 @@ cmd_doctor() { ' "$dir/evals/evals.json" >/dev/null 2>&1; then flag "$name: evals/evals.json malformed (want {skill_name: \"$name\", evals: [{name, prompt, expected_output, files?}, ...]} with unique names)" fi - sidecar="$dir.source.json" - if [[ ! -f "$sidecar" ]]; then - flag "$name: unmanaged (no .source.json sidecar)" - elif [[ "$(jq -r '.repo // "null"' "$sidecar")" != "null" ]]; then - flag "$name: stale vendored dir (sidecar names a repo but not in $(rel "$MANIFEST")); prune with 'make skills-materialize' or re-add with 'make skills-fetch'" - elif [[ "$(jq -r '.category // ""' "$sidecar")" == "" ]]; then - flag "$name: sidecar has no 'category'" + if [[ -z "$(manifest_entry "$name")" ]]; then + sidecar="$dir.source.json" + if [[ -f "$sidecar" && "$(jq -r '.repo // "null"' "$sidecar" 2>/dev/null)" != "null" ]]; then + flag "$name: stale vendored dir (on-disk marker names a repo but no $(rel "$MANIFEST") entry); prune with 'make skills-materialize' or re-add with 'make skills-fetch'" + else + flag "$name: unmanaged (no $(rel "$MANIFEST") entry)" + fi fi done # Scan baselines must name a live skill (skills-delete removes them; catch drift). @@ -1116,22 +1126,22 @@ cmd_doctor() { cmd_suites --check || issues=$((issues + 1)) cmd_budget --check --top 0 >/dev/null || issues=$((issues + 1)) else - while IFS=$'\t' read -r name sidecar; do + while IFS= read -r name; do [[ -n "$name" ]] || continue md="$RESOURCE_ROOT/$name.md" if [[ ! -f "$md" ]]; then - flag "$name: missing $(rel "$md")" + flag "$name: manifest entry for a missing $(rel "$md")" continue fi [[ -n "$(frontmatter_field "$md" name)" ]] || flag "$name: agent frontmatter has no 'name'" desc="$(frontmatter_field "$md" description)" [[ -n "$desc" ]] || flag "$name: frontmatter has no 'description'" - if [[ ! -f "$sidecar" ]]; then - flag "$name: unmanaged (no .source.json sidecar)" - elif [[ "$(jq -r '.category // ""' "$sidecar")" == "" ]]; then - flag "$name: sidecar has no 'category'" + if [[ -z "$(manifest_entry "$name")" ]]; then + flag "$name: unmanaged (no $(rel "$MANIFEST") entry)" + elif [[ -z "$(manifest_field "$name" category)" ]]; then + flag "$name: manifest entry has no 'category'" fi - done < <(iter_resources) + done < <({ iter_resources; manifest_names; } | sort -u) fi if [[ "$issues" -gt 0 ]]; then @@ -1141,8 +1151,8 @@ cmd_doctor() { info "doctor: all ${KIND}s healthy" } -# Set/replace the category on an existing resource's sidecar (in place, so it -# survives update). Creates a minimal local sidecar if none exists yet. +# Set/replace the category on a resource's manifest entry (in place, so it +# survives update). Creates a minimal authored entry if none exists yet. cmd_category() { local name="" category="" while [[ $# -gt 0 ]]; do @@ -1154,21 +1164,10 @@ cmd_category() { done [[ -n "$name" ]] || die "category: --name NAME is required" [[ -n "$category" ]] || die "category: --category CAT is required" - if [[ "$KIND" == "skill" ]] && is_vendored "$name"; then - manifest_set_category "$name" "$category" - info "$name: category set to '$category'" - return 0 - fi - local artifact sidecar - artifact="$(artifact_path "$name")" - sidecar="$(sidecar_path "$name")" - [[ -e "$artifact" ]] || die "$name: no such $KIND" - if [[ -f "$sidecar" ]]; then - local tmp; tmp="$(mktemp)" - jq --arg c "$category" '.category = $c' "$sidecar" > "$tmp" && mv "$tmp" "$sidecar" - else - jq -n --arg c "$category" '{repo:null, category:$c}' > "$sidecar" + if ! is_vendored "$name" && [[ ! -e "$(artifact_path "$name")" ]]; then + die "$name: no such $KIND" fi + manifest_set_category "$name" "$category" info "$name: category set to '$category'" } diff --git a/scripts/skills-vendor.sh b/scripts/skills-vendor.sh index 79112ea..53761ac 100755 --- a/scripts/skills-vendor.sh +++ b/scripts/skills-vendor.sh @@ -2,7 +2,7 @@ # # skills-vendor.sh — discover/fetch skills with the vercel-labs `skills` CLI # (the skills.sh ecosystem), then vendor them into this repo's skills/ tree -# via resource-manager.sh so they keep a .source.json sidecar and stay +# via resource-manager.sh so they land in sources.toml and stay # manageable with the existing skills-list / skills-update / skills-delete # targets and the Makefile symlinks. # @@ -77,7 +77,7 @@ lock="$staging/skills-lock.json" [[ -f "$lock" ]] || die "skills CLI wrote no skills-lock.json (nothing fetched?)" # Translate each lock entry into a resource-manager fetch so the vendored skill -# gets our standard .source.json. lock schema (v1): +# gets our standard sources.toml entry. lock schema (v1): # skills. = { source, sourceType, skillPath: "/SKILL.md", ... } # Read into an array with a while-loop (mapfile is bash 4+; macOS ships 3.2). entries=() diff --git a/scripts/test-resource-manager.sh b/scripts/test-resource-manager.sh index 4af0922..b1637b9 100755 --- a/scripts/test-resource-manager.sh +++ b/scripts/test-resource-manager.sh @@ -10,8 +10,14 @@ git clone --quiet "$REPO_ROOT" "$tmp/repo" # Test the tooling as it is on disk (uncommitted edits included), not the last commit. cp "$REPO_ROOT"/scripts/*.sh "$REPO_ROOT"/scripts/*.py "$tmp/repo/scripts/" cp "$REPO_ROOT/Makefile" "$tmp/repo/Makefile" +cp "$REPO_ROOT/sources.toml" "$tmp/repo/sources.toml" cd "$tmp/repo" -git -c user.email=ci@example.invalid -c user.name=ci commit -qam "working-tree tooling" || true +git add scripts Makefile sources.toml +git -c user.email=ci@example.invalid -c user.name=ci commit -qm "working-tree tooling" || true +# Absorb catalog-wording drift the working-tree tooling may carry, so the +# final clean-tree assertion checks only the round-trip's own effects. +make -s skills-catalog >/dev/null 2>&1 || true +git -c user.email=ci@example.invalid -c user.name=ci commit -qam "working-tree catalog" || true # Hermetic upstream: a local git repo holding one small skill, so the test # never depends on a third party's default branch (a rename there must not red CI). @@ -39,13 +45,14 @@ fail() { echo "FAIL: $*" >&2; exit 1; } echo "== fetch" make -s skills-fetch REPO=$REPO SUBPATH=$SUBPATH NAME=$NAME CATEGORY=ci-smoke -commit=$(jq -re --arg n "$NAME" '.[]|select(.name==$n)|.commit' skills/vendored.json) || fail "no manifest entry" +manifest_json() { python3 scripts/toml-manifest.py to-json sources.toml; } +commit=$(manifest_json | jq -re --arg n "$NAME" '.skill[]|select(.name==$n)|.commit') || fail "no manifest entry" [[ "$commit" =~ ^[0-9a-f]{40}$ ]] || fail "manifest commit is not a sha: $commit" grep -qx "/skills/$NAME/" .gitignore || fail ".gitignore line missing" [[ -f skills/$NAME/SKILL.md ]] || fail "SKILL.md not fetched" [[ "$(jq -r .commit skills/$NAME/.source.json)" == "$commit" ]] || fail "sidecar commit != manifest commit" [[ -z "$(git status --porcelain -- skills/$NAME)" ]] || fail "fetched dir is not gitignored" -jq -e --arg n "$NAME" '[.[]|select(.name==$n)]|length==1' skills/vendored.json >/dev/null || fail "duplicate manifest entries" +manifest_json | jq -e --arg n "$NAME" '[.skill[]|select(.name==$n)]|length==1' >/dev/null || fail "duplicate manifest entries" echo "== materialize (fresh-clone path)" rm -rf "skills/$NAME" @@ -62,7 +69,7 @@ mkdir -p security/skillspector && echo '{"version":2,"rules":[]}' > "security/sk make -s skills-delete NAME=$NAME YES=1 [[ ! -e "security/skillspector/$NAME.json" ]] || fail "scan baseline survived delete" make -s skills-catalog -jq -e --arg n "$NAME" '[.[]|select(.name==$n)]|length==0' skills/vendored.json >/dev/null || fail "manifest entry survived delete" +manifest_json | jq -e --arg n "$NAME" '[.skill[]|select(.name==$n)]|length==0' >/dev/null || fail "manifest entry survived delete" grep -qx "/skills/$NAME/" .gitignore && fail ".gitignore line survived delete" [[ ! -e skills/$NAME ]] || fail "dir survived delete" [[ -z "$(git status --porcelain)" ]] || { git status --porcelain; fail "tree not clean after round-trip"; } diff --git a/scripts/test-safety-guard-hook.py b/scripts/test-safety-guard-hook.py index b0ff778..af905fc 100755 --- a/scripts/test-safety-guard-hook.py +++ b/scripts/test-safety-guard-hook.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Table-driven test for claude/scripts/safety-guard-hook.py. +"""Table-driven test for packages/claude/scripts/safety-guard-hook.py. Each case: (tool, tool_input, expected decision). Runs the hook as a subprocess with a stock PATH and a temp CLAUDE_CONFIG_DIR, exactly as Claude Code would. @@ -15,7 +15,7 @@ import unittest from pathlib import Path -HOOK = Path(__file__).resolve().parent.parent / "claude" / "scripts" / "safety-guard-hook.py" +HOOK = Path(__file__).resolve().parent.parent / "packages" / "claude" / "scripts" / "safety-guard-hook.py" CASES = [ # --- Bash: deny diff --git a/scripts/toml-manifest.py b/scripts/toml-manifest.py new file mode 100755 index 0000000..3cde18e --- /dev/null +++ b/scripts/toml-manifest.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +"""toml-manifest.py — read/write the repo's sources.toml manifest. + +The manifest is the single committed record of every skill and agent +source: two arrays of tables, [[skill]] and [[agent]], each entry at +least {name}, plus {repo, subpath, ref, commit} when vendored and +optional {category, description, fetched_at, note}. + +Commands (stdin/stdout are JSON; the TOML file is the argument): + to-json print {"skill": [...], "agent": [...]} + from-json read the same JSON on stdin, write the file + +The writer is deterministic — fixed key order, entries sorted by name, +one key per line — so a round-trip of an untouched manifest is +byte-identical and diffs stay per-entry. Stdlib only (tomllib, py>=3.11). +""" +import json +import sys +import tomllib + +KINDS = ("skill", "agent") +KEY_ORDER = ("name", "repo", "subpath", "ref", "commit", "fetched_at", "category", "description", "note") + + +def die(msg: str) -> None: + print(f"toml-manifest: {msg}", file=sys.stderr) + raise SystemExit(1) + + +def toml_str(s: str) -> str: + if any(c in s for c in "\\\"\n\t"): + return json.dumps(s) # JSON string escapes are valid TOML basic-string escapes + return f'"{s}"' + + +def to_json(path: str) -> None: + try: + with open(path, "rb") as fh: + doc = tomllib.load(fh) + except FileNotFoundError: + doc = {} + out = {k: doc.get(k, []) for k in KINDS} + json.dump(out, sys.stdout) + print() + + +def from_json(path: str) -> None: + doc = json.load(sys.stdin) + lines = [ + "# sources.toml — the single manifest of every skill and agent source.", + "# Managed by scripts/resource-manager.sh (make skills-* / agents-*);", + "# an entry with repo+commit is vendored (skill files gitignored,", + "# materialized from the pin), one without repo is authored here.", + ] + for kind in KINDS: + entries = doc.get(kind) or [] + for entry in sorted(entries, key=lambda e: e.get("name", "")): + lines.append("") + lines.append(f"[[{kind}]]") + for key in KEY_ORDER: + val = entry.get(key) + if val is None or val == "": + continue + if not isinstance(val, str): + die(f"{kind} '{entry.get('name')}': field '{key}' must be a string") + lines.append(f"{key} = {toml_str(val)}") + extra = set(entry) - set(KEY_ORDER) - {"repo"} + extra = {k for k in extra if entry[k] not in (None, "")} + if extra: + die(f"{kind} '{entry.get('name')}': unknown fields {sorted(extra)}") + with open(path, "w", encoding="utf-8") as fh: + fh.write("\n".join(lines) + "\n") + + +def main() -> None: + if len(sys.argv) != 3 or sys.argv[1] not in ("to-json", "from-json"): + die("usage: toml-manifest.py {to-json|from-json} ") + (to_json if sys.argv[1] == "to-json" else from_json)(sys.argv[2]) + + +if __name__ == "__main__": + main() diff --git a/skills/README.md b/skills/README.md index 0547933..7117d76 100644 --- a/skills/README.md +++ b/skills/README.md @@ -1,6 +1,6 @@ # Skills catalog -72 skills, grouped by `category` (from `skills/vendored.json` for vendored skills, from each `.source.json` sidecar for authored ones). +72 skills, grouped by `category` (from each `sources.toml` entry). Each name links to its source: authored skills to the in-repo `SKILL.md`, vendored skills to their upstream repo at the pinned commit (their dirs are gitignored, so they are not present in this repo). Generated by `make skills-catalog` — do not edit by hand (`make skills-doctor` flags a stale file). @@ -26,12 +26,12 @@ Generated by `make skills-catalog` — do not edit by hand (`make skills-doctor` | Skill | Purpose | |-------|---------| -| [`docx`](https://github.com/anthropics/skills/tree/b29e7cf65e5cb78a5ac33d582270551bc74a14eb/skills/docx) | Use this skill whenever the user wants to create, read, edit, or manipulate Word documents (.docx files) or Word templates (.dotx files). | -| [`pdf`](https://github.com/anthropics/skills/tree/b29e7cf65e5cb78a5ac33d582270551bc74a14eb/skills/pdf) | Use this skill whenever the user wants to do anything with PDF files. | -| [`pptx`](https://github.com/anthropics/skills/tree/b29e7cf65e5cb78a5ac33d582270551bc74a14eb/skills/pptx) | Use this skill any time a .pptx or .potx file is involved in any way — as input, output, or both. | +| [`docx`](https://github.com/anthropics/skills/tree/3b3fad96af16a10759d930941b4520ba0c40edae/skills/docx) | Use this skill whenever the user wants to create, read, edit, or manipulate Word documents (.docx files) or Word templates (.dotx files). | +| [`pdf`](https://github.com/anthropics/skills/tree/3b3fad96af16a10759d930941b4520ba0c40edae/skills/pdf) | Use this skill whenever the user wants to do anything with PDF files. | +| [`pptx`](https://github.com/anthropics/skills/tree/3b3fad96af16a10759d930941b4520ba0c40edae/skills/pptx) | Use this skill any time a .pptx or .potx file is involved in any way — as input, output, or both. | | [`sembr-reformat`](https://github.com/sembr/skills/tree/5f973aaa75b1165b03dd45dca4cd1dc0437deba3/skills/sembr-reformat) | Reformat prose using Semantic Line Breaks (SemBr) from https://sembr.org while preserving rendered output and meaning. | | [`tufte-information-design`](tufte-information-design/SKILL.md) | Applies Edward Tufte's information-design principles to any information display — slides/decks, documents, blog posts, dashboards, HTML artifacts, tables, di... | -| [`xlsx`](https://github.com/anthropics/skills/tree/b29e7cf65e5cb78a5ac33d582270551bc74a14eb/skills/xlsx) | Use this skill any time a spreadsheet file is the primary input or output. | +| [`xlsx`](https://github.com/anthropics/skills/tree/3b3fad96af16a10759d930941b4520ba0c40edae/skills/xlsx) | Use this skill any time a spreadsheet file is the primary input or output. | ## engineering @@ -39,18 +39,18 @@ Generated by `make skills-catalog` — do not edit by hand (`make skills-doctor` | Skill | Purpose | |-------|---------| -| [`codebase-design`](https://github.com/mattpocock/skills/tree/2ffb184ffbb752faa664c0b204f3c9241b1428e9/skills/engineering/codebase-design) | Shared vocabulary for designing deep modules. | -| [`domain-modeling`](https://github.com/mattpocock/skills/tree/2ffb184ffbb752faa664c0b204f3c9241b1428e9/skills/engineering/domain-modeling) | Build and sharpen a project's domain model. | -| [`grill-with-docs`](https://github.com/mattpocock/skills/tree/2ffb184ffbb752faa664c0b204f3c9241b1428e9/skills/engineering/grill-with-docs) | A relentless interview to sharpen a plan or design, which also creates docs (ADR's and glossary) as we go. | -| [`grilling`](https://github.com/mattpocock/skills/tree/2ffb184ffbb752faa664c0b204f3c9241b1428e9/skills/productivity/grilling) | Grill the user relentlessly about a plan, decision, or idea. | -| [`implement`](https://github.com/mattpocock/skills/tree/2ffb184ffbb752faa664c0b204f3c9241b1428e9/skills/engineering/implement) | Implement a piece of work based on a spec or set of tickets. | -| [`improve-codebase-architecture`](https://github.com/mattpocock/skills/tree/2ffb184ffbb752faa664c0b204f3c9241b1428e9/skills/engineering/improve-codebase-architecture) | Scan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick. | -| [`matt-code-review`](https://github.com/mattpocock/skills/tree/2ffb184ffbb752faa664c0b204f3c9241b1428e9/skills/engineering/code-review) | Review the changes since a fixed point (commit, branch, tag, or merge-base) along two axes — Standards (does the code follow this repo's documented coding st... | -| [`prototype`](https://github.com/mattpocock/skills/tree/2ffb184ffbb752faa664c0b204f3c9241b1428e9/skills/engineering/prototype) | Build a throwaway prototype to answer a design question. | -| [`setup-matt-pocock-skills`](https://github.com/mattpocock/skills/tree/2ffb184ffbb752faa664c0b204f3c9241b1428e9/skills/engineering/setup-matt-pocock-skills) | Configure this repo for the engineering skills — set up its issue tracker, triage label vocabulary, and domain doc layout. | -| [`to-spec`](https://github.com/mattpocock/skills/tree/2ffb184ffbb752faa664c0b204f3c9241b1428e9/skills/engineering/to-spec) | Turn the current conversation into a spec and publish it to the project issue tracker — no interview, just synthesis of what you've already discussed. | -| [`to-tickets`](https://github.com/mattpocock/skills/tree/2ffb184ffbb752faa664c0b204f3c9241b1428e9/skills/engineering/to-tickets) | Break a plan, spec, or the current conversation into a set of tracer-bullet tickets, each declaring its blocking edges, published to the configured tracker —... | -| [`wayfinder`](https://github.com/mattpocock/skills/tree/2ffb184ffbb752faa664c0b204f3c9241b1428e9/skills/engineering/wayfinder) | Plan a huge chunk of work — more than one agent session can hold — as a shared map of decision tickets on your issue tracker, and resolve them one at a time ... | +| [`codebase-design`](https://github.com/mattpocock/skills/tree/6654f6b60cd9d5be8b54c6fafe44346dabeb3b76/skills/engineering/codebase-design) | Shared vocabulary for designing deep modules. | +| [`domain-modeling`](https://github.com/mattpocock/skills/tree/6654f6b60cd9d5be8b54c6fafe44346dabeb3b76/skills/engineering/domain-modeling) | Build and sharpen a project's domain model. | +| [`grill-with-docs`](https://github.com/mattpocock/skills/tree/6654f6b60cd9d5be8b54c6fafe44346dabeb3b76/skills/engineering/grill-with-docs) | A relentless interview to sharpen a plan or design, which also creates docs (ADR's and glossary) as we go. | +| [`grilling`](https://github.com/mattpocock/skills/tree/6654f6b60cd9d5be8b54c6fafe44346dabeb3b76/skills/productivity/grilling) | Grill the user relentlessly about a plan, decision, or idea. | +| [`implement`](https://github.com/mattpocock/skills/tree/6654f6b60cd9d5be8b54c6fafe44346dabeb3b76/skills/engineering/implement) | Implement a piece of work based on a spec or set of tickets. | +| [`improve-codebase-architecture`](https://github.com/mattpocock/skills/tree/6654f6b60cd9d5be8b54c6fafe44346dabeb3b76/skills/engineering/improve-codebase-architecture) | Scan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick. | +| [`matt-code-review`](https://github.com/mattpocock/skills/tree/6654f6b60cd9d5be8b54c6fafe44346dabeb3b76/skills/engineering/code-review) | Review the changes since a fixed point (commit, branch, tag, or merge-base) along two axes: Standards (does the code follow this repo's documented coding sta... | +| [`prototype`](https://github.com/mattpocock/skills/tree/6654f6b60cd9d5be8b54c6fafe44346dabeb3b76/skills/engineering/prototype) | Build a throwaway prototype to answer a design question. | +| [`setup-matt-pocock-skills`](https://github.com/mattpocock/skills/tree/6654f6b60cd9d5be8b54c6fafe44346dabeb3b76/skills/engineering/setup-matt-pocock-skills) | Configure this repo for the engineering skills: set up its issue tracker, triage label vocabulary, and domain doc layout. | +| [`to-spec`](https://github.com/mattpocock/skills/tree/6654f6b60cd9d5be8b54c6fafe44346dabeb3b76/skills/engineering/to-spec) | Turn the current conversation into a spec and publish it to the project issue tracker: no interview, just synthesis of what you've already discussed. | +| [`to-tickets`](https://github.com/mattpocock/skills/tree/6654f6b60cd9d5be8b54c6fafe44346dabeb3b76/skills/engineering/to-tickets) | Break a plan, spec, or the current conversation into a set of tracer-bullet tickets, each declaring its blocking edges, published to the configured tracker (... | +| [`wayfinder`](https://github.com/mattpocock/skills/tree/6654f6b60cd9d5be8b54c6fafe44346dabeb3b76/skills/engineering/wayfinder) | Plan a huge chunk of work (more than one agent session can hold) as a shared map of decision tickets on your issue tracker, and resolve them one at a time un... | ## gh-maintenance @@ -115,8 +115,8 @@ Generated by `make skills-catalog` — do not edit by hand (`make skills-doctor` | Skill | Purpose | |-------|---------| -| [`caveman`](https://github.com/juliusbrussee/caveman/tree/ec83e5bace4c20484d704dea21e12fc4eb94e9aa/skills/caveman) | Ultra-compressed communication mode. | -| [`find-skills`](https://github.com/vercel-labs/skills/tree/ab4fc49265c443279a5deae20297e631470da68c/skills/find-skills) | Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express ... | +| [`caveman`](https://github.com/juliusbrussee/caveman/tree/81536f57b3303b7de7f5bc5b564cc344f9112d68/skills/caveman) | Ultra-compressed communication mode. | +| [`find-skills`](https://github.com/vercel-labs/skills/tree/435076e78988e1e6ec40d00b0b1d76bdbbc5419a/skills/find-skills) | Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express ... | | [`harvest-automation`](harvest-automation/SKILL.md) | Mines past Claude Code sessions for recurring patterns and turns them into durable automation: skills, CLAUDE.md entries, memory, and permission allowlists. | ## personal-finance @@ -133,13 +133,13 @@ Generated by `make skills-catalog` — do not edit by hand (`make skills-doctor` | Skill | Purpose | |-------|---------| -| [`deslop`](https://github.com/cursor/plugins/tree/8185ad9fbb903efc7d1cf152a9be9777e516cfbc/cursor-team-kit/skills/deslop) | Remove AI-generated code slop and clean up code style | -| [`make-pr-easy-to-review`](https://github.com/cursor/plugins/tree/8185ad9fbb903efc7d1cf152a9be9777e516cfbc/cursor-team-kit/skills/make-pr-easy-to-review) | Prepare PRs for review by cleaning noisy history, improving PR descriptions, and adding reviewer guidance without changing code behavior. | -| [`pr-review-canvas`](https://github.com/cursor/plugins/tree/8185ad9fbb903efc7d1cf152a9be9777e516cfbc/cursor-team-kit/skills/pr-review-canvas) | Generate an interactive PR review walkthrough as an HTML page. | +| [`deslop`](https://github.com/cursor/plugins/tree/fd878692de15a3069c21c8f429eb0b9f2fe178fa/cursor-team-kit/skills/deslop) | Remove AI-generated code slop and clean up code style | +| [`make-pr-easy-to-review`](https://github.com/cursor/plugins/tree/bdf7aa355337897f167153e05069aca505dae17c/cursor-team-kit/skills/make-pr-easy-to-review) | Prepare PRs for review by cleaning noisy history, improving PR descriptions, and adding reviewer guidance without changing code behavior. | +| [`pr-review-canvas`](https://github.com/cursor/plugins/tree/bdf7aa355337897f167153e05069aca505dae17c/cursor-team-kit/skills/pr-review-canvas) | Generate an interactive PR review walkthrough as an HTML page. | | [`resolve-bot-review-threads`](resolve-bot-review-threads/SKILL.md) | Clears bot/Copilot review threads on a PR — fixes the issues, marks threads resolved via GraphQL, and re-requests the bot until the PR is clean. | | [`review-battery`](review-battery/SKILL.md) | Full review of a change set — the working diff, a branch vs a base ref, or a GitHub PR number reviewed in an isolated worktree — using the thermos subagents ... | -| [`thermo-nuclear-code-quality-review`](https://github.com/cursor/plugins/tree/60c641e4fad674784b30abcf9f8915dea39df38d/thermos/skills/thermo-nuclear-code-quality-review) | Run an extremely strict maintainability review for abstraction quality, giant files, and spaghetti-condition growth. | -| [`thermo-nuclear-review`](https://github.com/cursor/plugins/tree/60c641e4fad674784b30abcf9f8915dea39df38d/thermos/skills/thermo-nuclear-review) | Comprehensive security and correctness audit of a branch's changes. | +| [`thermo-nuclear-code-quality-review`](https://github.com/cursor/plugins/tree/bdf7aa355337897f167153e05069aca505dae17c/thermos/skills/thermo-nuclear-code-quality-review) | Run an extremely strict maintainability review for abstraction quality, giant files, and spaghetti-condition growth. | +| [`thermo-nuclear-review`](https://github.com/cursor/plugins/tree/bdf7aa355337897f167153e05069aca505dae17c/thermos/skills/thermo-nuclear-review) | Comprehensive security and correctness audit of a branch's changes. | ## search-media @@ -147,7 +147,7 @@ Generated by `make skills-catalog` — do not edit by hand (`make skills-doctor` | Skill | Purpose | |-------|---------| -| [`agent-browser`](https://github.com/vercel-labs/agent-browser/tree/acbc22bdc5d4f6c5a88d97d4a4745d3c5eb0591f/skills/agent-browser) | Browser automation CLI for AI agents. | +| [`agent-browser`](https://github.com/vercel-labs/agent-browser/tree/9d9a3bbd5e4f8c0a17a1c4dfd2f8e8d74b5ee998/skills/agent-browser) | Browser automation CLI for AI agents. | | [`brave-search`](https://github.com/badlogic/pi-skills/tree/90bb51cae36515a648515b633a81c0c6efc8c74d/brave-search) | Web search and content extraction via Brave Search API. | | [`transcribe`](https://github.com/badlogic/pi-skills/tree/90bb51cae36515a648515b633a81c0c6efc8c74d/transcribe) | Local speech-to-text transcription on Apple Silicon macOS. | | [`vscode`](https://github.com/badlogic/pi-skills/tree/90bb51cae36515a648515b633a81c0c6efc8c74d/vscode) | VS Code integration for viewing diffs and comparing files. | diff --git a/skills/add-llms-txt/.source.json b/skills/add-llms-txt/.source.json deleted file mode 100644 index 89f5d82..0000000 --- a/skills/add-llms-txt/.source.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "repo": null, - "note": "Local skill, maintained in this repo", - "category": "static-site" -} diff --git a/skills/analyze-go-pprof/.source.json b/skills/analyze-go-pprof/.source.json deleted file mode 100644 index 13b5f5e..0000000 --- a/skills/analyze-go-pprof/.source.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "repo": null, - "note": "Local skill, maintained in this repo", - "category": "ci-go" -} diff --git a/skills/analyze-prometheus-tsdb/.source.json b/skills/analyze-prometheus-tsdb/.source.json deleted file mode 100644 index 13b5f5e..0000000 --- a/skills/analyze-prometheus-tsdb/.source.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "repo": null, - "note": "Local skill, maintained in this repo", - "category": "ci-go" -} diff --git a/skills/apply-workday-leave/.source.json b/skills/apply-workday-leave/.source.json deleted file mode 100644 index bea005a..0000000 --- a/skills/apply-workday-leave/.source.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "repo": null, - "category": "internal-automation" -} diff --git a/skills/approve-workday-tasks/.source.json b/skills/approve-workday-tasks/.source.json deleted file mode 100644 index bea005a..0000000 --- a/skills/approve-workday-tasks/.source.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "repo": null, - "category": "internal-automation" -} diff --git a/skills/audit-static-site/.source.json b/skills/audit-static-site/.source.json deleted file mode 100644 index 89f5d82..0000000 --- a/skills/audit-static-site/.source.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "repo": null, - "note": "Local skill, maintained in this repo", - "category": "static-site" -} diff --git a/skills/author-mermaid-diagram/.source.json b/skills/author-mermaid-diagram/.source.json deleted file mode 100644 index 89f5d82..0000000 --- a/skills/author-mermaid-diagram/.source.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "repo": null, - "note": "Local skill, maintained in this repo", - "category": "static-site" -} diff --git a/skills/author-security-advisory/.source.json b/skills/author-security-advisory/.source.json deleted file mode 100644 index c33f095..0000000 --- a/skills/author-security-advisory/.source.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "repo": null, - "note": "Local skill, maintained in this repo", - "category": "gh-security" -} diff --git a/skills/bump-ci-tool-versions/.source.json b/skills/bump-ci-tool-versions/.source.json deleted file mode 100644 index 13b5f5e..0000000 --- a/skills/bump-ci-tool-versions/.source.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "repo": null, - "note": "Local skill, maintained in this repo", - "category": "ci-go" -} diff --git a/skills/bump-hugo-versions/.source.json b/skills/bump-hugo-versions/.source.json deleted file mode 100644 index 89f5d82..0000000 --- a/skills/bump-hugo-versions/.source.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "repo": null, - "note": "Local skill, maintained in this repo", - "category": "static-site" -} diff --git a/skills/debug-ci/.source.json b/skills/debug-ci/.source.json deleted file mode 100644 index 13b5f5e..0000000 --- a/skills/debug-ci/.source.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "repo": null, - "note": "Local skill, maintained in this repo", - "category": "ci-go" -} diff --git a/skills/fill-workday-timesheet/.source.json b/skills/fill-workday-timesheet/.source.json deleted file mode 100644 index bea005a..0000000 --- a/skills/fill-workday-timesheet/.source.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "repo": null, - "category": "internal-automation" -} diff --git a/skills/generate-og-images/.source.json b/skills/generate-og-images/.source.json deleted file mode 100644 index 89f5d82..0000000 --- a/skills/generate-og-images/.source.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "repo": null, - "note": "Local skill, maintained in this repo", - "category": "static-site" -} diff --git a/skills/go-deps-security-sweep/.source.json b/skills/go-deps-security-sweep/.source.json deleted file mode 100644 index 13b5f5e..0000000 --- a/skills/go-deps-security-sweep/.source.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "repo": null, - "note": "Local skill, maintained in this repo", - "category": "ci-go" -} diff --git a/skills/harvest-automation/.source.json b/skills/harvest-automation/.source.json deleted file mode 100644 index 336204f..0000000 --- a/skills/harvest-automation/.source.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "repo": null, - "note": "Local skill, maintained in this repo", - "category": "meta" -} diff --git a/skills/harvest-automation/SKILL.md b/skills/harvest-automation/SKILL.md index c86ed43..64987c2 100644 --- a/skills/harvest-automation/SKILL.md +++ b/skills/harvest-automation/SKILL.md @@ -19,7 +19,7 @@ metadata: # Harvest Automation -> Local skill (`.source.json` has `"repo": null`); replaces the old `retrospect` and `workflow-from-chats` skills. +> Local skill (its `sources.toml` entry has no `repo`); replaces the old `retrospect` and `workflow-from-chats` skills. ## Core principle @@ -77,7 +77,7 @@ Delegate to existing skills; do not reimplement their logic. - Skill — a recurring multi-step workflow with clear triggers. Draft a proposal (name, "Use when…" description, trigger, steps, scripts) and hand it to `superpowers:writing-skills` to author and validate under `skills//`. - Mark it local with a `{"repo": null}` `.source.json`. + Mark it local with a repo-less `sources.toml` entry (`make skills-category NAME=… CATEGORY=…` creates one). Never scaffold skill files by hand. - CLAUDE.md — a collaborator-visible project fact (build/test commands, invariants, code locations, "always use X helper"). - Memory — a user-private preference (terse vs verbose, tool choices, workflow habits). diff --git a/skills/improve-codecov-coverage/.source.json b/skills/improve-codecov-coverage/.source.json deleted file mode 100644 index 13b5f5e..0000000 --- a/skills/improve-codecov-coverage/.source.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "repo": null, - "note": "Local skill, maintained in this repo", - "category": "ci-go" -} diff --git a/skills/list-week-meetings/.source.json b/skills/list-week-meetings/.source.json deleted file mode 100644 index bea005a..0000000 --- a/skills/list-week-meetings/.source.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "repo": null, - "category": "internal-automation" -} diff --git a/skills/login-microsoft-sso/.source.json b/skills/login-microsoft-sso/.source.json deleted file mode 100644 index bea005a..0000000 --- a/skills/login-microsoft-sso/.source.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "repo": null, - "category": "internal-automation" -} diff --git a/skills/login-microsoft-sso/SKILL.md b/skills/login-microsoft-sso/SKILL.md index 244fcd5..ebceb3a 100644 --- a/skills/login-microsoft-sso/SKILL.md +++ b/skills/login-microsoft-sso/SKILL.md @@ -23,7 +23,7 @@ Ensure a browser tab is signed in to an app behind your organization's **Microso Use the **`chrome-cdp`** CLI on the user's real, already signed-in browser — this skill types **no** credentials. > See **`drive-chrome-cdp`** for CLI setup, output contract, and the passkey rule. -> Local skill, maintained in this repo (`.source.json` has `"repo": null`). +> Local skill, maintained in this repo (its `sources.toml` entry has no `repo`). ## Supported apps & config diff --git a/skills/optimize-svg/.source.json b/skills/optimize-svg/.source.json deleted file mode 100644 index 89f5d82..0000000 --- a/skills/optimize-svg/.source.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "repo": null, - "note": "Local skill, maintained in this repo", - "category": "static-site" -} diff --git a/skills/record-engage-activity/.source.json b/skills/record-engage-activity/.source.json deleted file mode 100644 index bea005a..0000000 --- a/skills/record-engage-activity/.source.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "repo": null, - "category": "internal-automation" -} diff --git a/skills/remediate-codeql-alerts/.source.json b/skills/remediate-codeql-alerts/.source.json deleted file mode 100644 index c33f095..0000000 --- a/skills/remediate-codeql-alerts/.source.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "repo": null, - "note": "Local skill, maintained in this repo", - "category": "gh-security" -} diff --git a/skills/report-site-analytics/.source.json b/skills/report-site-analytics/.source.json deleted file mode 100644 index 89f5d82..0000000 --- a/skills/report-site-analytics/.source.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "repo": null, - "note": "Local skill, maintained in this repo", - "category": "static-site" -} diff --git a/skills/resolve-bot-review-threads/.source.json b/skills/resolve-bot-review-threads/.source.json deleted file mode 100644 index ee0c0d8..0000000 --- a/skills/resolve-bot-review-threads/.source.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "repo": null, - "note": "Local skill, maintained in this repo", - "category": "pr-review" -} diff --git a/skills/review-battery/.source.json b/skills/review-battery/.source.json deleted file mode 100644 index d9b98d6..0000000 --- a/skills/review-battery/.source.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "repo": null, - "note": "Authored orchestrator; replaces the former review-battery and review-pr-worktree commands and drives the vendored thermos subagents", - "category": "pr-review" -} diff --git a/skills/source-code-for-gh-advisory/.source.json b/skills/source-code-for-gh-advisory/.source.json deleted file mode 100644 index c33f095..0000000 --- a/skills/source-code-for-gh-advisory/.source.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "repo": null, - "note": "Local skill, maintained in this repo", - "category": "gh-security" -} diff --git a/skills/triage-gh-backlog/.source.json b/skills/triage-gh-backlog/.source.json deleted file mode 100644 index 301bc9f..0000000 --- a/skills/triage-gh-backlog/.source.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "repo": null, - "note": "Local skill, maintained in this repo", - "category": "gh-maintenance" -} diff --git a/skills/triage-gh-backlog/references/pipeline.md b/skills/triage-gh-backlog/references/pipeline.md index 01b00ad..e5c9ffe 100644 --- a/skills/triage-gh-backlog/references/pipeline.md +++ b/skills/triage-gh-backlog/references/pipeline.md @@ -43,7 +43,8 @@ No network. Safe to re-run any time after a sync. ## Stage 3 — triage (`triage.py`) -Deterministic rule engine over `threads.jsonl`, config-driven. Triages **open, not-locally-closed** threads only. Builds duplicate groups locally (same-kind title-token Jaccard ≥ `dup_title_jaccard`, default 0.7; cross-references upgrade confidence) — no embeddings. Emits `triage.jsonl`: `disposition, tier, add_labels[], comment_template, context, rationale, type, areas`. +Deterministic rule engine over `threads.jsonl`, config-driven. Triages **open, not-locally-closed** threads only. Builds duplicate groups locally (same-kind title-token Jaccard ≥ `dup_title_jaccard`, default 0.7; cross-references upgrade confidence) — no embeddings. +`dup_exclude_authors` / `dup_exclude_title_patterns` drop machine-generated threads out of clustering entirely (bot logins match bare, `app/`-prefixed and `[bot]`-suffixed forms): they share a title template by construction, so title overlap is pure false-positive signal for them. Emits `triage.jsonl`: `disposition, tier, add_labels[], comment_template, context, rationale, type, areas`. See `triage-rules.md` for the full rule list. Pure; re-run after tuning config without re-syncing. diff --git a/skills/triage-gh-backlog/scripts/config.example.toml b/skills/triage-gh-backlog/scripts/config.example.toml index 8e3fc23..2777cfb 100644 --- a/skills/triage-gh-backlog/scripts/config.example.toml +++ b/skills/triage-gh-backlog/scripts/config.example.toml @@ -31,6 +31,13 @@ gitcrawl_db = "~/.config/gitcrawl/gitcrawl.db" # stricter. Cross-references only UPGRADE confidence (title->ref => auto tier); # they never create a duplicate on their own (a "fixes #N" link is not a dup). dup_title_jaccard = 0.7 +# Threads excluded from duplicate clustering entirely. Machine-generated threads +# share a title template by construction, so title-token overlap is meaningless +# for them: every "chore(deps): bump the X group with N updates" scores near 1.0 +# against every other one and the engine proposes closing live PRs as duplicates. +# Author logins match bare, "app/" -prefixed and "[bot]"-suffixed forms. +dup_exclude_authors = ["dependabot", "renovate", "github-actions"] +dup_exclude_title_patterns = ["^chore\\(deps\\)", "^build\\(deps"] # --- staleness --- stale_days = 540 # ~18mo no activity -> stale candidate stale_comment_max = 8 # skip stale-close if more engaged than this diff --git a/skills/triage-gh-backlog/scripts/triage.py b/skills/triage-gh-backlog/scripts/triage.py index 9ff2553..517726f 100644 --- a/skills/triage-gh-backlog/scripts/triage.py +++ b/skills/triage-gh-backlog/scripts/triage.py @@ -66,14 +66,48 @@ def below_floor(found: str, floor: str) -> bool: # --------------------------------------------------------------------------- # # duplicate grouping # # --------------------------------------------------------------------------- # -def build_dup_groups(threads: list[dict], jaccard_floor: float = 0.7) -> dict[int, dict]: +def _dup_excluded_numbers( + threads: list[dict], + exclude_authors: list[str] | None, + exclude_title_patterns: list[str] | None, +) -> set[int]: + """Numbers that must never participate in duplicate clustering.""" + authors = {a.lower() for a in (exclude_authors or [])} + pats = [re.compile(p, re.I) for p in (exclude_title_patterns or [])] + out: set[int] = set() + for t in threads: + author = (t.get("author") or "").lower() + # match bare and app-suffixed bot logins: dependabot / app/dependabot / dependabot[bot] + stripped = author.removeprefix("app/").removesuffix("[bot]") + if author in authors or stripped in authors: + out.add(t["number"]) + continue + title = t.get("title") or "" + if any(p.search(title) for p in pats): + out.add(t["number"]) + return out + + +def build_dup_groups( + threads: list[dict], + jaccard_floor: float = 0.7, + exclude_authors: list[str] | None = None, + exclude_title_patterns: list[str] | None = None, +) -> dict[int, dict]: """Return {number: {"canonical": n, "evidence": "ref"|"title", "members":[...]}}. A group's canonical is its oldest still-relevant thread (lowest number). Only OPEN, non-protected members get a dup disposition later; the canonical is whichever member is open with the lowest number (fallback: lowest number). + + `exclude_authors` / `exclude_title_patterns` remove threads from duplicate + clustering entirely. Machine-generated threads share a title template by + construction — every `chore(deps): bump the X group with N updates` from a + bot scores near-1.0 Jaccard against every other one — so title overlap + carries no duplicate signal for them, only false positives. """ by_num = {t["number"]: t for t in threads} + dup_excluded = _dup_excluded_numbers(threads, exclude_authors, exclude_title_patterns) parent: dict[int, int] = {} def find(x: int) -> int: @@ -100,10 +134,16 @@ def union(a: int, b: int) -> None: for ref in t["references"]: other = by_num.get(ref) if other and other["kind"] == t["kind"]: + if t["number"] in dup_excluded or ref in dup_excluded: + continue ref_pairs.add(frozenset((t["number"], ref))) toks = {t["number"]: norm_tokens(t["title"]) for t in threads} - nums = [t["number"] for t in threads if len(toks[t["number"]]) >= 4] + nums = [ + t["number"] + for t in threads + if len(toks[t["number"]]) >= 4 and t["number"] not in dup_excluded + ] bucket: dict[str, list[int]] = defaultdict(list) for n in nums: for tok in toks[n]: @@ -384,8 +424,14 @@ def main() -> None: # Only triage things that are actionable: open and not already locally closed. open_threads = [t for t in threads if t["state"] == "open" and not t["closed_local"]] - jac = ctx["cfg"].get("triage", {}).get("dup_title_jaccard", 0.7) - dup_map = build_dup_groups(threads, jaccard_floor=jac) # over all to find canonicals + tri_cfg = ctx["cfg"].get("triage", {}) + jac = tri_cfg.get("dup_title_jaccard", 0.7) + dup_map = build_dup_groups( # over all threads, to find canonicals + threads, + jaccard_floor=jac, + exclude_authors=tri_cfg.get("dup_exclude_authors", []), + exclude_title_patterns=tri_cfg.get("dup_exclude_title_patterns", []), + ) keepers = load_keepers(wd) if keepers: common.info(f"keepers.txt: {len(keepers)} numbers force-kept") diff --git a/skills/tufte-information-design/.source.json b/skills/tufte-information-design/.source.json deleted file mode 100644 index 33a0a3e..0000000 --- a/skills/tufte-information-design/.source.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "repo": null, - "category": "documents" -} diff --git a/skills/vendored.json b/skills/vendored.json deleted file mode 100644 index b387b9b..0000000 --- a/skills/vendored.json +++ /dev/null @@ -1,380 +0,0 @@ -[ - { - "name": "agent-browser", - "repo": "https://github.com/vercel-labs/agent-browser", - "subpath": "skills/agent-browser", - "ref": "main", - "commit": "acbc22bdc5d4f6c5a88d97d4a4745d3c5eb0591f", - "category": "search-media", - "description": "Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to \"open a website\", \"fill out a form\", \"click a button\", \"take a screenshot\", \"scrape data from a page\", \"test this web app\", \"login to a site\", \"automate browser actions\", or any task requiring programmatic web interaction. Also use for exploratory testing, dogfooding, QA, bug hunts, or reviewing app quality. Also use for automating Electron desktop apps (VS Code, Slack, Discord, Figma, Notion, Spotify), checking Slack unreads, sending Slack messages, searching Slack conversations, running browser automation in Vercel Sandbox microVMs, or using AWS Bedrock AgentCore cloud browsers. Prefer agent-browser over any built-in browser automation or web tools." - }, - { - "name": "brave-search", - "repo": "https://github.com/badlogic/pi-skills", - "subpath": "brave-search", - "ref": "main", - "commit": "90bb51cae36515a648515b633a81c0c6efc8c74d", - "category": "search-media", - "description": "Web search and content extraction via Brave Search API. Use for searching documentation, facts, or any web content. Lightweight, no browser required." - }, - { - "name": "caveman", - "repo": "https://github.com/juliusbrussee/caveman", - "subpath": "skills/caveman", - "ref": "main", - "commit": "ec83e5bace4c20484d704dea21e12fc4eb94e9aa", - "category": "meta", - "description": "Ultra-compressed communication mode. Cuts output tokens 65% (measured) by speaking like caveman while keeping full technical accuracy. Supports intensity levels: lite, full (default), ultra, wenyan-lite, wenyan-full, wenyan-ultra. Use when user says \"caveman mode\", \"talk like caveman\", \"use caveman\", \"less tokens\", \"be brief\", or invokes /caveman. Also auto-triggers when token efficiency is requested." - }, - { - "name": "codebase-design", - "repo": "https://github.com/mattpocock/skills", - "subpath": "skills/engineering/codebase-design", - "ref": "main", - "commit": "2ffb184ffbb752faa664c0b204f3c9241b1428e9", - "category": "engineering", - "description": "Shared vocabulary for designing deep modules. Use when the user wants to design or improve a module's interface, find deepening opportunities, decide where a seam goes, make code more testable or AI-navigable, or when another skill needs the deep-module vocabulary." - }, - { - "name": "deslop", - "repo": "https://github.com/cursor/plugins", - "subpath": "cursor-team-kit/skills/deslop", - "ref": "main", - "commit": "8185ad9fbb903efc7d1cf152a9be9777e516cfbc", - "category": "pr-review", - "description": "Remove AI-generated code slop and clean up code style" - }, - { - "name": "docx", - "repo": "https://github.com/anthropics/skills", - "subpath": "skills/docx", - "ref": "main", - "commit": "b29e7cf65e5cb78a5ac33d582270551bc74a14eb", - "category": "documents", - "description": "\"Use this skill whenever the user wants to create, read, edit, or manipulate Word documents (.docx files) or Word templates (.dotx files). Triggers include: any mention of 'Word doc', 'word document', '.docx', '.dotx', or requests to produce professional documents with formatting like tables of contents, headings, page numbers, or letterheads. Also use when extracting or reorganizing content from .docx or .dotx files, inserting or replacing images in documents, performing find-and-replace in Word files, working with tracked changes or comments, or converting content into a polished Word document. If the user asks for a 'report', 'memo', 'letter', 'template', or similar deliverable as a Word or .docx file, use this skill. Do NOT use for PDFs, spreadsheets, Google Docs, or general coding tasks unrelated to document generation.\"" - }, - { - "name": "domain-modeling", - "repo": "https://github.com/mattpocock/skills", - "subpath": "skills/engineering/domain-modeling", - "ref": "main", - "commit": "2ffb184ffbb752faa664c0b204f3c9241b1428e9", - "category": "engineering", - "description": "Build and sharpen a project's domain model. Use when the user wants to pin down domain terminology or a ubiquitous language, record an architectural decision, or when another skill needs to maintain the domain model." - }, - { - "name": "drive-chrome-cdp", - "repo": "https://github.com/sanketsudake/chrome-cdp-cli", - "subpath": "skills/drive-chrome-cdp", - "ref": "main", - "commit": "deee3056260e6dea5f182760e3559d79a7222291", - "category": "internal-automation", - "description": "Drive the user's real, already-running local Chrome — its live tabs, logins, and cookies, so it types no credentials — from the shell via the `chrome-cdp` CLI, which answers every command with one JSON envelope and a stable exit code. Use when a skill names `chrome-cdp`, or when a task needs addressing CSS cannot express (`--by name|ref|cell|label`, `--in-row`), cascade `select`, `grid` table reads, `wait --request`, or `console`/`net` to explain why an action did nothing. Triggers include \"click X in my browser\", \"read what's on my screen\", \"fill in this form in Chrome\", \"check console/network errors on this page\", \"automate this web app in my logged-in session\". The building block other logged-in-app skills follow to get a driven, signed-in tab." - }, - { - "name": "find-skills", - "repo": "https://github.com/vercel-labs/skills", - "subpath": "skills/find-skills", - "ref": "main", - "commit": "ab4fc49265c443279a5deae20297e631470da68c", - "category": "meta", - "description": "Helps users discover and install agent skills when they ask questions like \"how do I do X\", \"find a skill for X\", \"is there a skill that can...\", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill." - }, - { - "name": "gccli", - "repo": "https://github.com/badlogic/pi-skills", - "subpath": "gccli", - "ref": "main", - "commit": "90bb51cae36515a648515b633a81c0c6efc8c74d", - "category": "google-cli", - "description": "Google Calendar CLI for listing calendars, viewing/creating/updating events, and checking availability." - }, - { - "name": "gdcli", - "repo": "https://github.com/badlogic/pi-skills", - "subpath": "gdcli", - "ref": "main", - "commit": "90bb51cae36515a648515b633a81c0c6efc8c74d", - "category": "google-cli", - "description": "Google Drive CLI for listing, searching, uploading, downloading, and sharing files and folders." - }, - { - "name": "gmcli", - "repo": "https://github.com/badlogic/pi-skills", - "subpath": "gmcli", - "ref": "main", - "commit": "90bb51cae36515a648515b633a81c0c6efc8c74d", - "category": "google-cli", - "description": "Gmail CLI for searching emails, reading threads, sending messages, managing drafts, and handling labels/attachments." - }, - { - "name": "grill-with-docs", - "repo": "https://github.com/mattpocock/skills", - "subpath": "skills/engineering/grill-with-docs", - "ref": "main", - "commit": "2ffb184ffbb752faa664c0b204f3c9241b1428e9", - "category": "engineering", - "description": "A relentless interview to sharpen a plan or design, which also creates docs (ADR's and glossary) as we go." - }, - { - "name": "grilling", - "repo": "https://github.com/mattpocock/skills", - "subpath": "skills/productivity/grilling", - "ref": "main", - "commit": "2ffb184ffbb752faa664c0b204f3c9241b1428e9", - "category": "engineering", - "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." - }, - { - "name": "implement", - "repo": "https://github.com/mattpocock/skills", - "subpath": "skills/engineering/implement", - "ref": "main", - "commit": "2ffb184ffbb752faa664c0b204f3c9241b1428e9", - "category": "engineering", - "description": "\"Implement a piece of work based on a spec or set of tickets.\"" - }, - { - "name": "improve-codebase-architecture", - "repo": "https://github.com/mattpocock/skills", - "subpath": "skills/engineering/improve-codebase-architecture", - "ref": "main", - "commit": "2ffb184ffbb752faa664c0b204f3c9241b1428e9", - "category": "engineering", - "description": "Scan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick." - }, - { - "name": "itr-india", - "repo": "https://github.com/shivprime94/file-itr", - "subpath": "skills/itr-india", - "ref": "main", - "commit": "59185d78128bdef6b6d079f885cb6f4778454929", - "category": "personal-finance", - "description": "Assist a resident Indian individual with preparing and e-filing an Income Tax Return (ITR-1/2/3/4) on the e-filing portal (eportal.incometax.gov.in), under EITHER the old or new tax regime (non-resident / RNOR returns are out of scope). Use WHENEVER the user mentions filing taxes in India, ITR, income tax return, 26AS, AIS, Form 16, old vs new regime, 115BAC, Form 10-IEA, 80C/80D/HRA/home-loan/NPS/80G deductions, 44ADA/44AD presumptive, self-assessment/advance tax/234B/234C, TDS reconciliation, capital gains on Indian shares/mutual funds/property, or crypto/VDA (115BBH/194S) — even if they don't name the form or regime. Covers gathering and reconciling income documents, comparing both regimes to pick the cheaper one, choosing the form, computing tax, filling the portal schedule-by-schedule, fixing validation defects, and guiding payment and e-verification. India personal income tax only — not US/UK/other-country tax, GST, TDS-return (24Q/26Q), or company returns." - }, - { - "name": "make-pr-easy-to-review", - "repo": "https://github.com/cursor/plugins", - "subpath": "cursor-team-kit/skills/make-pr-easy-to-review", - "ref": "main", - "commit": "8185ad9fbb903efc7d1cf152a9be9777e516cfbc", - "category": "pr-review", - "description": "Prepare PRs for review by cleaning noisy history, improving PR descriptions, and adding reviewer guidance without changing code behavior. Use for \"make this easy to review\", \"tidy this PR\", \"clean up commits\", or \"annotate the diff\"." - }, - { - "name": "matt-code-review", - "repo": "https://github.com/mattpocock/skills", - "subpath": "skills/engineering/code-review", - "ref": "main", - "commit": "2ffb184ffbb752faa664c0b204f3c9241b1428e9", - "category": "engineering", - "description": "Review the changes since a fixed point (commit, branch, tag, or merge-base) along two axes — Standards (does the code follow this repo's documented coding standards?) and Spec (does the code match what the originating issue/spec asked for?). Runs both reviews in parallel sub-agents and reports them side by side. Use when the user wants to review a branch, a PR, work-in-progress changes, or asks to \"review since X\"." - }, - { - "name": "pdf", - "repo": "https://github.com/anthropics/skills", - "subpath": "skills/pdf", - "ref": "main", - "commit": "b29e7cf65e5cb78a5ac33d582270551bc74a14eb", - "category": "documents", - "description": "Use this skill whenever the user wants to do anything with PDF files. This includes reading or extracting text/tables from PDFs, combining or merging multiple PDFs into one, splitting PDFs apart, rotating pages, adding watermarks, creating new PDFs, filling PDF forms, encrypting/decrypting PDFs, extracting images, and OCR on scanned PDFs to make them searchable. If the user mentions a .pdf file or asks to produce one, use this skill." - }, - { - "name": "pptx", - "repo": "https://github.com/anthropics/skills", - "subpath": "skills/pptx", - "ref": "main", - "commit": "b29e7cf65e5cb78a5ac33d582270551bc74a14eb", - "category": "documents", - "description": "\"Use this skill any time a .pptx or .potx file is involved in any way — as input, output, or both. This includes: creating slide decks, pitch decks, or presentations; reading, parsing, or extracting text from any .pptx or .potx file (even if the extracted content will be used elsewhere, like in an email or summary); editing, modifying, or updating existing presentations; combining or splitting slide files; working with templates (.potx), layouts, speaker notes, or comments. Trigger whenever the user mentions \\\"deck,\\\" \\\"slides,\\\" \\\"presentation,\\\" or references a .pptx or .potx filename, regardless of what they plan to do with the content afterward. If a .pptx or .potx file needs to be opened, created, or touched, use this skill.\"" - }, - { - "name": "pr-review-canvas", - "repo": "https://github.com/cursor/plugins", - "subpath": "cursor-team-kit/skills/pr-review-canvas", - "ref": "main", - "commit": "8185ad9fbb903efc7d1cf152a9be9777e516cfbc", - "category": "pr-review", - "description": "Generate an interactive PR review walkthrough as an HTML page. Fetches PR data via gh API, categorizes files into core vs mechanical changes, adds reviewer annotations, and renders diffs with moved-code detection. Use when the user pastes a GitHub PR URL and asks for a review, walkthrough, or summary, or says \"review this PR\"." - }, - { - "name": "prototype", - "repo": "https://github.com/mattpocock/skills", - "subpath": "skills/engineering/prototype", - "ref": "main", - "commit": "2ffb184ffbb752faa664c0b204f3c9241b1428e9", - "category": "engineering", - "description": "Build a throwaway prototype to answer a design question. Use when the user wants to sanity-check whether a state model or logic feels right, or explore what a UI should look like." - }, - { - "name": "readwise-cli", - "repo": "https://github.com/readwiseio/readwise-skills", - "subpath": "skills/readwise-cli", - "ref": "master", - "commit": "2d1ce9627c611d24f510dfc2e05a123fa509d2f6", - "category": "knowledge-base", - "description": "How to use the Readwise CLI — access highlights, documents, and your entire reading library from the command line" - }, - { - "name": "readwise-second-brain-sync", - "repo": "https://github.com/sanketsudake/second-brain", - "subpath": "skills/readwise-second-brain-sync", - "ref": "main", - "commit": "7c503f4367c7ef08a0a74a70541672ccab0de86d", - "category": "knowledge-base", - "description": "Syncs Readwise highlights and Reader documents into the second-brain vault's raw/ folder in Obsidian-Web-Clipper format. Use when the user says \"sync readwise\", \"pull my readwise highlights\", \"update raw/ from reader\", or wants their reading library reflected in the wiki (triggers: readwise sync, reader sync, import highlights). Sync only — follow with /second-brain-ingest." - }, - { - "name": "second-brain", - "repo": "https://github.com/sanketsudake/second-brain", - "subpath": "skills/second-brain", - "ref": "main", - "commit": "7c503f4367c7ef08a0a74a70541672ccab0de86d", - "category": "knowledge-base", - "description": "Sets up a new Obsidian knowledge base using the LLM Wiki pattern, where the LLM acts as librarian over raw sources. Use when the user wants to create a second brain, initialize a vault, set up a personal knowledge base, or says \"onboard\". Interactive setup wizard." - }, - { - "name": "second-brain-ideate", - "repo": "https://github.com/sanketsudake/second-brain", - "subpath": "skills/second-brain-ideate", - "ref": "main", - "commit": "7c503f4367c7ef08a0a74a70541672ccab0de86d", - "category": "knowledge-base", - "description": "Mines the knowledge-base wiki for strong, defensible content ideas — blog posts, conference talks, internal sessions, threads. Use when the user says \"ideate\", \"what should I write about\", \"find content ideas in my wiki\", \"blog/talk ideas\", or wants to turn collected knowledge into publishable content. Produces a scored shortlist with outlines in output/ and maintains an ideas backlog in wiki/synthesis/." - }, - { - "name": "second-brain-ingest", - "repo": "https://github.com/sanketsudake/second-brain", - "subpath": "skills/second-brain-ingest", - "ref": "main", - "commit": "7c503f4367c7ef08a0a74a70541672ccab0de86d", - "category": "knowledge-base", - "description": "Process raw source documents into wiki pages. Use when the user adds files to raw/ and wants them ingested, or says \"process this source\", \"ingest this article\", \"batch ingest\", \"deepen\", \"I added something to raw/\", \"I clipped some articles\", or wants to incorporate new material into their knowledge base. Sweeps the vault's Clippings/ folder (Obsidian Web Clipper) into raw/ first, enriching metadata on the way." - }, - { - "name": "second-brain-lint", - "repo": "https://github.com/sanketsudake/second-brain", - "subpath": "skills/second-brain-lint", - "ref": "main", - "commit": "7c503f4367c7ef08a0a74a70541672ccab0de86d", - "category": "knowledge-base", - "description": "Health-checks the wiki for contradictions, orphan pages, stale claims, and missing cross-references. Use when the user says \"audit\", \"health check\", \"lint\", \"find problems\", or wants to improve wiki quality." - }, - { - "name": "second-brain-query", - "repo": "https://github.com/sanketsudake/second-brain", - "subpath": "skills/second-brain-query", - "ref": "main", - "commit": "7c503f4367c7ef08a0a74a70541672ccab0de86d", - "category": "knowledge-base", - "description": "Answers questions against the knowledge base wiki. Use when the user asks a question about their collected knowledge, wants to explore connections between topics, says \"what do I know about X\", or wants to search their wiki." - }, - { - "name": "second-brain-review", - "repo": "https://github.com/sanketsudake/second-brain", - "subpath": "skills/second-brain-review", - "ref": "main", - "commit": "7c503f4367c7ef08a0a74a70541672ccab0de86d", - "category": "knowledge-base", - "description": "Resurfaces knowledge from the second-brain wiki: a daily or periodic review of highlights, concepts, and stale pages, replacing Readwise's daily review. Use when the user says \"daily review\", \"review my highlights\", \"resurface something\", \"what should I revisit\", or wants spaced-repetition-style engagement with their wiki." - }, - { - "name": "sembr-reformat", - "repo": "https://github.com/sembr/skills", - "subpath": "skills/sembr-reformat", - "ref": "main", - "commit": "5f973aaa75b1165b03dd45dca4cd1dc0437deba3", - "category": "documents", - "description": "Reformat prose using Semantic Line Breaks (SemBr) from https://sembr.org while preserving rendered output and meaning. Use when asked to reflow plain text or compatible markup (Markdown, AsciiDoc, reStructuredText, LaTeX, Org, MediaWiki) into semantic one-thought-per-line formatting, or when improving prose diffs and editorial readability." - }, - { - "name": "setup-matt-pocock-skills", - "repo": "https://github.com/mattpocock/skills", - "subpath": "skills/engineering/setup-matt-pocock-skills", - "ref": "main", - "commit": "2ffb184ffbb752faa664c0b204f3c9241b1428e9", - "category": "engineering", - "description": "Configure this repo for the engineering skills — set up its issue tracker, triage label vocabulary, and domain doc layout. Run once before first use of the other engineering skills." - }, - { - "name": "thermo-nuclear-code-quality-review", - "repo": "https://github.com/cursor/plugins", - "subpath": "thermos/skills/thermo-nuclear-code-quality-review", - "ref": "main", - "commit": "60c641e4fad674784b30abcf9f8915dea39df38d", - "category": "pr-review", - "description": "Run an extremely strict maintainability review for abstraction quality, giant files, and spaghetti-condition growth. Use for a thermo-nuclear code quality review, thermonuclear review, deep code quality audit, or especially harsh maintainability review." - }, - { - "name": "thermo-nuclear-review", - "repo": "https://github.com/cursor/plugins", - "subpath": "thermos/skills/thermo-nuclear-review", - "ref": "main", - "commit": "60c641e4fad674784b30abcf9f8915dea39df38d", - "category": "pr-review", - "description": "Comprehensive security and correctness audit of a branch's changes. Use for thermo nuclear, thermonuclear, or deep review requests, or branch/PR diff audits focused on bugs, breaking changes, security issues, devex regressions, and feature-gate leaks." - }, - { - "name": "to-spec", - "repo": "https://github.com/mattpocock/skills", - "subpath": "skills/engineering/to-spec", - "ref": "main", - "commit": "2ffb184ffbb752faa664c0b204f3c9241b1428e9", - "category": "engineering", - "description": "Turn the current conversation into a spec and publish it to the project issue tracker — no interview, just synthesis of what you've already discussed." - }, - { - "name": "to-tickets", - "repo": "https://github.com/mattpocock/skills", - "subpath": "skills/engineering/to-tickets", - "ref": "main", - "commit": "2ffb184ffbb752faa664c0b204f3c9241b1428e9", - "category": "engineering", - "description": "Break a plan, spec, or the current conversation into a set of tracer-bullet tickets, each declaring its blocking edges, published to the configured tracker — edges as text in one file per ticket locally, or native blocking links on a real tracker." - }, - { - "name": "transcribe", - "repo": "https://github.com/badlogic/pi-skills", - "subpath": "transcribe", - "ref": "main", - "commit": "90bb51cae36515a648515b633a81c0c6efc8c74d", - "category": "search-media", - "description": "Local speech-to-text transcription on Apple Silicon macOS. Supports wav directly and other audio formats via ffmpeg." - }, - { - "name": "vscode", - "repo": "https://github.com/badlogic/pi-skills", - "subpath": "vscode", - "ref": "main", - "commit": "90bb51cae36515a648515b633a81c0c6efc8c74d", - "category": "search-media", - "description": "VS Code integration for viewing diffs and comparing files. Use when showing file differences to the user." - }, - { - "name": "wayfinder", - "repo": "https://github.com/mattpocock/skills", - "subpath": "skills/engineering/wayfinder", - "ref": "main", - "commit": "2ffb184ffbb752faa664c0b204f3c9241b1428e9", - "category": "engineering", - "description": "Plan a huge chunk of work — more than one agent session can hold — as a shared map of decision tickets on your issue tracker, and resolve them one at a time until the way to the destination is clear." - }, - { - "name": "xlsx", - "repo": "https://github.com/anthropics/skills", - "subpath": "skills/xlsx", - "ref": "main", - "commit": "b29e7cf65e5cb78a5ac33d582270551bc74a14eb", - "category": "documents", - "description": "\"Use this skill any time a spreadsheet file is the primary input or output. This means any task where the user wants to: open, read, edit, or fix an existing .xlsx, .xlsm, .xltx, .csv, or .tsv file (e.g., adding columns, computing formulas, formatting, charting, cleaning messy data); create a new spreadsheet from scratch or from other data sources; or convert between tabular file formats. Trigger especially when the user references a spreadsheet file by name or path — even casually (like \\\"the xlsx in my downloads\\\") — and wants something done to it or produced from it. Also trigger for cleaning or restructuring messy tabular data files (malformed rows, misplaced headers, junk data) into proper spreadsheets. The deliverable must be a spreadsheet file. Do NOT trigger when the primary deliverable is a Word document, HTML report, standalone Python script, database pipeline, or Google Sheets API integration, even if tabular data is involved.\"" - }, - { - "name": "youtube-transcript", - "repo": "https://github.com/badlogic/pi-skills", - "subpath": "youtube-transcript", - "ref": "main", - "commit": "90bb51cae36515a648515b633a81c0c6efc8c74d", - "category": "search-media", - "description": "Fetch transcripts from YouTube videos for summarization and analysis." - } -] diff --git a/skills/verify-hugo-build/.source.json b/skills/verify-hugo-build/.source.json deleted file mode 100644 index 89f5d82..0000000 --- a/skills/verify-hugo-build/.source.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "repo": null, - "note": "Local skill, maintained in this repo", - "category": "static-site" -} diff --git a/skills/watch-ci/.source.json b/skills/watch-ci/.source.json deleted file mode 100644 index 13b5f5e..0000000 --- a/skills/watch-ci/.source.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "repo": null, - "note": "Local skill, maintained in this repo", - "category": "ci-go" -} diff --git a/skills/write-hugo-blog-post/.source.json b/skills/write-hugo-blog-post/.source.json deleted file mode 100644 index 89f5d82..0000000 --- a/skills/write-hugo-blog-post/.source.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "repo": null, - "note": "Local skill, maintained in this repo", - "category": "static-site" -} diff --git a/sources.toml b/sources.toml new file mode 100644 index 0000000..e7c4058 --- /dev/null +++ b/sources.toml @@ -0,0 +1,559 @@ +# sources.toml — the single manifest of every skill and agent source. +# Managed by scripts/resource-manager.sh (make skills-* / agents-*); +# an entry with repo+commit is vendored (skill files gitignored, +# materialized from the pin), one without repo is authored here. + +[[skill]] +name = "add-llms-txt" +category = "static-site" +note = "Local skill, maintained in this repo" + +[[skill]] +name = "agent-browser" +repo = "https://github.com/vercel-labs/agent-browser" +subpath = "skills/agent-browser" +ref = "main" +commit = "9d9a3bbd5e4f8c0a17a1c4dfd2f8e8d74b5ee998" +category = "search-media" +description = "Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to \"open a website\", \"fill out a form\", \"click a button\", \"take a screenshot\", \"scrape data from a page\", \"test this web app\", \"login to a site\", \"automate browser actions\", or any task requiring programmatic web interaction. Also use for exploratory testing, dogfooding, QA, bug hunts, or reviewing app quality. Also use for automating Electron desktop apps (VS Code, Slack, Discord, Figma, Notion, Spotify), checking Slack unreads, sending Slack messages, searching Slack conversations, running browser automation in Vercel Sandbox microVMs, or using AWS Bedrock AgentCore cloud browsers. Prefer agent-browser over any built-in browser automation or web tools." + +[[skill]] +name = "analyze-go-pprof" +category = "ci-go" +note = "Local skill, maintained in this repo" + +[[skill]] +name = "analyze-prometheus-tsdb" +category = "ci-go" +note = "Local skill, maintained in this repo" + +[[skill]] +name = "apply-workday-leave" +category = "internal-automation" + +[[skill]] +name = "approve-workday-tasks" +category = "internal-automation" + +[[skill]] +name = "audit-static-site" +category = "static-site" +note = "Local skill, maintained in this repo" + +[[skill]] +name = "author-mermaid-diagram" +category = "static-site" +note = "Local skill, maintained in this repo" + +[[skill]] +name = "author-security-advisory" +category = "gh-security" +note = "Local skill, maintained in this repo" + +[[skill]] +name = "brave-search" +repo = "https://github.com/badlogic/pi-skills" +subpath = "brave-search" +ref = "main" +commit = "90bb51cae36515a648515b633a81c0c6efc8c74d" +category = "search-media" +description = "Web search and content extraction via Brave Search API. Use for searching documentation, facts, or any web content. Lightweight, no browser required." + +[[skill]] +name = "bump-ci-tool-versions" +category = "ci-go" +note = "Local skill, maintained in this repo" + +[[skill]] +name = "bump-hugo-versions" +category = "static-site" +note = "Local skill, maintained in this repo" + +[[skill]] +name = "caveman" +repo = "https://github.com/juliusbrussee/caveman" +subpath = "skills/caveman" +ref = "main" +commit = "81536f57b3303b7de7f5bc5b564cc344f9112d68" +category = "meta" +description = "Ultra-compressed communication mode. Cuts output tokens 65% (measured) by speaking like caveman while keeping full technical accuracy. Supports intensity levels: lite, full (default), ultra, wenyan-lite, wenyan-full, wenyan-ultra. Use when user says \"caveman mode\", \"talk like caveman\", \"use caveman\", \"less tokens\", \"be brief\", or invokes /caveman. Also auto-triggers when token efficiency is requested." + +[[skill]] +name = "codebase-design" +repo = "https://github.com/mattpocock/skills" +subpath = "skills/engineering/codebase-design" +ref = "main" +commit = "6654f6b60cd9d5be8b54c6fafe44346dabeb3b76" +category = "engineering" +description = "Shared vocabulary for designing deep modules. Use when the user wants to design or improve a module's interface, find deepening opportunities, decide where a seam goes, make code more testable or AI-navigable, or when another skill needs the deep-module vocabulary." + +[[skill]] +name = "debug-ci" +category = "ci-go" +note = "Local skill, maintained in this repo" + +[[skill]] +name = "deslop" +repo = "https://github.com/cursor/plugins" +subpath = "cursor-team-kit/skills/deslop" +ref = "main" +commit = "fd878692de15a3069c21c8f429eb0b9f2fe178fa" +category = "pr-review" +description = "Remove AI-generated code slop and clean up code style" + +[[skill]] +name = "docx" +repo = "https://github.com/anthropics/skills" +subpath = "skills/docx" +ref = "main" +commit = "3b3fad96af16a10759d930941b4520ba0c40edae" +category = "documents" +description = "\"Use this skill whenever the user wants to create, read, edit, or manipulate Word documents (.docx files) or Word templates (.dotx files). Triggers include: any mention of 'Word doc', 'word document', '.docx', '.dotx', or requests to produce professional documents with formatting like tables of contents, headings, page numbers, or letterheads. Also use when extracting or reorganizing content from .docx or .dotx files, inserting or replacing images in documents, performing find-and-replace in Word files, working with tracked changes or comments, or converting content into a polished Word document. If the user asks for a 'report', 'memo', 'letter', 'template', or similar deliverable as a Word or .docx file, use this skill. Do NOT use for PDFs, spreadsheets, Google Docs, or general coding tasks unrelated to document generation.\"" + +[[skill]] +name = "domain-modeling" +repo = "https://github.com/mattpocock/skills" +subpath = "skills/engineering/domain-modeling" +ref = "main" +commit = "6654f6b60cd9d5be8b54c6fafe44346dabeb3b76" +category = "engineering" +description = "Build and sharpen a project's domain model. Use when discussing codebase terminology, writing or editing a CONTEXT.md, or recording or editing an ADR." + +[[skill]] +name = "drive-chrome-cdp" +repo = "https://github.com/sanketsudake/chrome-cdp-cli" +subpath = "skills/drive-chrome-cdp" +ref = "main" +commit = "deee3056260e6dea5f182760e3559d79a7222291" +category = "internal-automation" +description = "Drive the user's real, already-running local Chrome \u2014 its live tabs, logins, and cookies, so it types no credentials \u2014 from the shell via the `chrome-cdp` CLI, which answers every command with one JSON envelope and a stable exit code. Use when a skill names `chrome-cdp`, or when a task needs addressing CSS cannot express (`--by name|ref|cell|label`, `--in-row`), cascade `select`, `grid` table reads, `wait --request`, or `console`/`net` to explain why an action did nothing. Triggers include \"click X in my browser\", \"read what's on my screen\", \"fill in this form in Chrome\", \"check console/network errors on this page\", \"automate this web app in my logged-in session\". The building block other logged-in-app skills follow to get a driven, signed-in tab." + +[[skill]] +name = "fill-workday-timesheet" +category = "internal-automation" + +[[skill]] +name = "find-skills" +repo = "https://github.com/vercel-labs/skills" +subpath = "skills/find-skills" +ref = "main" +commit = "435076e78988e1e6ec40d00b0b1d76bdbbc5419a" +category = "meta" +description = "Helps users discover and install agent skills when they ask questions like \"how do I do X\", \"find a skill for X\", \"is there a skill that can...\", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill." + +[[skill]] +name = "gccli" +repo = "https://github.com/badlogic/pi-skills" +subpath = "gccli" +ref = "main" +commit = "90bb51cae36515a648515b633a81c0c6efc8c74d" +category = "google-cli" +description = "Google Calendar CLI for listing calendars, viewing/creating/updating events, and checking availability." + +[[skill]] +name = "gdcli" +repo = "https://github.com/badlogic/pi-skills" +subpath = "gdcli" +ref = "main" +commit = "90bb51cae36515a648515b633a81c0c6efc8c74d" +category = "google-cli" +description = "Google Drive CLI for listing, searching, uploading, downloading, and sharing files and folders." + +[[skill]] +name = "generate-og-images" +category = "static-site" +note = "Local skill, maintained in this repo" + +[[skill]] +name = "gmcli" +repo = "https://github.com/badlogic/pi-skills" +subpath = "gmcli" +ref = "main" +commit = "90bb51cae36515a648515b633a81c0c6efc8c74d" +category = "google-cli" +description = "Gmail CLI for searching emails, reading threads, sending messages, managing drafts, and handling labels/attachments." + +[[skill]] +name = "go-deps-security-sweep" +category = "ci-go" +note = "Local skill, maintained in this repo" + +[[skill]] +name = "grill-with-docs" +repo = "https://github.com/mattpocock/skills" +subpath = "skills/engineering/grill-with-docs" +ref = "main" +commit = "6654f6b60cd9d5be8b54c6fafe44346dabeb3b76" +category = "engineering" +description = "A relentless interview to sharpen a plan or design, which also creates docs (ADR's and glossary) as we go." + +[[skill]] +name = "grilling" +repo = "https://github.com/mattpocock/skills" +subpath = "skills/productivity/grilling" +ref = "main" +commit = "6654f6b60cd9d5be8b54c6fafe44346dabeb3b76" +category = "engineering" +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." + +[[skill]] +name = "harvest-automation" +category = "meta" +note = "Local skill, maintained in this repo" + +[[skill]] +name = "implement" +repo = "https://github.com/mattpocock/skills" +subpath = "skills/engineering/implement" +ref = "main" +commit = "6654f6b60cd9d5be8b54c6fafe44346dabeb3b76" +category = "engineering" +description = "\"Implement a piece of work based on a spec or set of tickets.\"" + +[[skill]] +name = "improve-codebase-architecture" +repo = "https://github.com/mattpocock/skills" +subpath = "skills/engineering/improve-codebase-architecture" +ref = "main" +commit = "6654f6b60cd9d5be8b54c6fafe44346dabeb3b76" +category = "engineering" +description = "Scan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick." + +[[skill]] +name = "improve-codecov-coverage" +category = "ci-go" +note = "Local skill, maintained in this repo" + +[[skill]] +name = "itr-india" +repo = "https://github.com/shivprime94/file-itr" +subpath = "skills/itr-india" +ref = "main" +commit = "59185d78128bdef6b6d079f885cb6f4778454929" +category = "personal-finance" +description = "Assist a resident Indian individual with preparing and e-filing an Income Tax Return (ITR-1/2/3/4) on the e-filing portal (eportal.incometax.gov.in), under EITHER the old or new tax regime (non-resident / RNOR returns are out of scope). Use WHENEVER the user mentions filing taxes in India, ITR, income tax return, 26AS, AIS, Form 16, old vs new regime, 115BAC, Form 10-IEA, 80C/80D/HRA/home-loan/NPS/80G deductions, 44ADA/44AD presumptive, self-assessment/advance tax/234B/234C, TDS reconciliation, capital gains on Indian shares/mutual funds/property, or crypto/VDA (115BBH/194S) — even if they don't name the form or regime. Covers gathering and reconciling income documents, comparing both regimes to pick the cheaper one, choosing the form, computing tax, filling the portal schedule-by-schedule, fixing validation defects, and guiding payment and e-verification. India personal income tax only — not US/UK/other-country tax, GST, TDS-return (24Q/26Q), or company returns." + +[[skill]] +name = "list-week-meetings" +category = "internal-automation" + +[[skill]] +name = "login-microsoft-sso" +category = "internal-automation" + +[[skill]] +name = "make-pr-easy-to-review" +repo = "https://github.com/cursor/plugins" +subpath = "cursor-team-kit/skills/make-pr-easy-to-review" +ref = "main" +commit = "bdf7aa355337897f167153e05069aca505dae17c" +category = "pr-review" +description = "Prepare PRs for review by cleaning noisy history, improving PR descriptions, and adding reviewer guidance without changing code behavior. Use for \"make this easy to review\", \"tidy this PR\", \"clean up commits\", or \"annotate the diff\"." + +[[skill]] +name = "matt-code-review" +repo = "https://github.com/mattpocock/skills" +subpath = "skills/engineering/code-review" +ref = "main" +commit = "6654f6b60cd9d5be8b54c6fafe44346dabeb3b76" +category = "engineering" +description = "\"Review the changes since a fixed point (commit, branch, tag, or merge-base) along two axes: Standards (does the code follow this repo's documented coding standards?) and Spec (does the code match what the originating issue/spec asked for?). Runs both reviews in parallel sub-agents and reports them side by side. Use when the user wants to review a branch, a PR, work-in-progress changes, or asks to \\\"review since X\\\".\"" + +[[skill]] +name = "optimize-svg" +category = "static-site" +note = "Local skill, maintained in this repo" + +[[skill]] +name = "pdf" +repo = "https://github.com/anthropics/skills" +subpath = "skills/pdf" +ref = "main" +commit = "3b3fad96af16a10759d930941b4520ba0c40edae" +category = "documents" +description = "Use this skill whenever the user wants to do anything with PDF files. This includes reading or extracting text/tables from PDFs, combining or merging multiple PDFs into one, splitting PDFs apart, rotating pages, adding watermarks, creating new PDFs, filling PDF forms, encrypting/decrypting PDFs, extracting images, and OCR on scanned PDFs to make them searchable. If the user mentions a .pdf file or asks to produce one, use this skill." + +[[skill]] +name = "pptx" +repo = "https://github.com/anthropics/skills" +subpath = "skills/pptx" +ref = "main" +commit = "3b3fad96af16a10759d930941b4520ba0c40edae" +category = "documents" +description = "\"Use this skill any time a .pptx or .potx file is involved in any way \u2014 as input, output, or both. This includes: creating slide decks, pitch decks, or presentations; reading, parsing, or extracting text from any .pptx or .potx file (even if the extracted content will be used elsewhere, like in an email or summary); editing, modifying, or updating existing presentations; combining or splitting slide files; working with templates (.potx), layouts, speaker notes, or comments. Trigger whenever the user mentions \\\"deck,\\\" \\\"slides,\\\" \\\"presentation,\\\" or references a .pptx or .potx filename, regardless of what they plan to do with the content afterward. If a .pptx or .potx file needs to be opened, created, or touched, use this skill.\"" + +[[skill]] +name = "pr-review-canvas" +repo = "https://github.com/cursor/plugins" +subpath = "cursor-team-kit/skills/pr-review-canvas" +ref = "main" +commit = "bdf7aa355337897f167153e05069aca505dae17c" +category = "pr-review" +description = "Generate an interactive PR review walkthrough as an HTML page. Fetches PR data via gh API, categorizes files into core vs mechanical changes, adds reviewer annotations, and renders diffs with moved-code detection. Use when the user pastes a GitHub PR URL and asks for a review, walkthrough, or summary, or says \"review this PR\"." + +[[skill]] +name = "prototype" +repo = "https://github.com/mattpocock/skills" +subpath = "skills/engineering/prototype" +ref = "main" +commit = "6654f6b60cd9d5be8b54c6fafe44346dabeb3b76" +category = "engineering" +description = "Build a throwaway prototype to answer a design question. Use when the user wants to sanity-check whether a state model or logic feels right, or explore what a UI should look like." + +[[skill]] +name = "readwise-cli" +repo = "https://github.com/readwiseio/readwise-skills" +subpath = "skills/readwise-cli" +ref = "master" +commit = "2d1ce9627c611d24f510dfc2e05a123fa509d2f6" +category = "knowledge-base" +description = "How to use the Readwise CLI — access highlights, documents, and your entire reading library from the command line" + +[[skill]] +name = "readwise-second-brain-sync" +repo = "https://github.com/sanketsudake/second-brain" +subpath = "skills/readwise-second-brain-sync" +ref = "main" +commit = "7c503f4367c7ef08a0a74a70541672ccab0de86d" +category = "knowledge-base" +description = "Syncs Readwise highlights and Reader documents into the second-brain vault's raw/ folder in Obsidian-Web-Clipper format. Use when the user says \"sync readwise\", \"pull my readwise highlights\", \"update raw/ from reader\", or wants their reading library reflected in the wiki (triggers: readwise sync, reader sync, import highlights). Sync only \u2014 follow with /second-brain-ingest." + +[[skill]] +name = "record-engage-activity" +category = "internal-automation" + +[[skill]] +name = "remediate-codeql-alerts" +category = "gh-security" +note = "Local skill, maintained in this repo" + +[[skill]] +name = "report-site-analytics" +category = "static-site" +note = "Local skill, maintained in this repo" + +[[skill]] +name = "resolve-bot-review-threads" +category = "pr-review" +note = "Local skill, maintained in this repo" + +[[skill]] +name = "review-battery" +category = "pr-review" +note = "Authored orchestrator; replaces the former review-battery and review-pr-worktree commands and drives the vendored thermos subagents" + +[[skill]] +name = "second-brain" +repo = "https://github.com/sanketsudake/second-brain" +subpath = "skills/second-brain" +ref = "main" +commit = "7c503f4367c7ef08a0a74a70541672ccab0de86d" +category = "knowledge-base" +description = "Sets up a new Obsidian knowledge base using the LLM Wiki pattern, where the LLM acts as librarian over raw sources. Use when the user wants to create a second brain, initialize a vault, set up a personal knowledge base, or says \"onboard\". Interactive setup wizard." + +[[skill]] +name = "second-brain-ideate" +repo = "https://github.com/sanketsudake/second-brain" +subpath = "skills/second-brain-ideate" +ref = "main" +commit = "7c503f4367c7ef08a0a74a70541672ccab0de86d" +category = "knowledge-base" +description = "Mines the knowledge-base wiki for strong, defensible content ideas \u2014 blog posts, conference talks, internal sessions, threads. Use when the user says \"ideate\", \"what should I write about\", \"find content ideas in my wiki\", \"blog/talk ideas\", or wants to turn collected knowledge into publishable content. Produces a scored shortlist with outlines in output/ and maintains an ideas backlog in wiki/synthesis/." + +[[skill]] +name = "second-brain-ingest" +repo = "https://github.com/sanketsudake/second-brain" +subpath = "skills/second-brain-ingest" +ref = "main" +commit = "7c503f4367c7ef08a0a74a70541672ccab0de86d" +category = "knowledge-base" +description = "Process raw source documents into wiki pages. Use when the user adds files to raw/ and wants them ingested, or says \"process this source\", \"ingest this article\", \"batch ingest\", \"deepen\", \"I added something to raw/\", \"I clipped some articles\", or wants to incorporate new material into their knowledge base. Sweeps the vault's Clippings/ folder (Obsidian Web Clipper) into raw/ first, enriching metadata on the way." + +[[skill]] +name = "second-brain-lint" +repo = "https://github.com/sanketsudake/second-brain" +subpath = "skills/second-brain-lint" +ref = "main" +commit = "7c503f4367c7ef08a0a74a70541672ccab0de86d" +category = "knowledge-base" +description = "Health-checks the wiki for contradictions, orphan pages, stale claims, and missing cross-references. Use when the user says \"audit\", \"health check\", \"lint\", \"find problems\", or wants to improve wiki quality." + +[[skill]] +name = "second-brain-query" +repo = "https://github.com/sanketsudake/second-brain" +subpath = "skills/second-brain-query" +ref = "main" +commit = "7c503f4367c7ef08a0a74a70541672ccab0de86d" +category = "knowledge-base" +description = "Answers questions against the knowledge base wiki. Use when the user asks a question about their collected knowledge, wants to explore connections between topics, says \"what do I know about X\", or wants to search their wiki." + +[[skill]] +name = "second-brain-review" +repo = "https://github.com/sanketsudake/second-brain" +subpath = "skills/second-brain-review" +ref = "main" +commit = "7c503f4367c7ef08a0a74a70541672ccab0de86d" +category = "knowledge-base" +description = "Resurfaces knowledge from the second-brain wiki: a daily or periodic review of highlights, concepts, and stale pages, replacing Readwise's daily review. Use when the user says \"daily review\", \"review my highlights\", \"resurface something\", \"what should I revisit\", or wants spaced-repetition-style engagement with their wiki." + +[[skill]] +name = "sembr-reformat" +repo = "https://github.com/sembr/skills" +subpath = "skills/sembr-reformat" +ref = "main" +commit = "5f973aaa75b1165b03dd45dca4cd1dc0437deba3" +category = "documents" +description = "Reformat prose using Semantic Line Breaks (SemBr) from https://sembr.org while preserving rendered output and meaning. Use when asked to reflow plain text or compatible markup (Markdown, AsciiDoc, reStructuredText, LaTeX, Org, MediaWiki) into semantic one-thought-per-line formatting, or when improving prose diffs and editorial readability." + +[[skill]] +name = "setup-matt-pocock-skills" +repo = "https://github.com/mattpocock/skills" +subpath = "skills/engineering/setup-matt-pocock-skills" +ref = "main" +commit = "6654f6b60cd9d5be8b54c6fafe44346dabeb3b76" +category = "engineering" +description = "\"Configure this repo for the engineering skills: set up its issue tracker, triage label vocabulary, and domain doc layout. Run once before first use of the other engineering skills.\"" + +[[skill]] +name = "source-code-for-gh-advisory" +category = "gh-security" +note = "Local skill, maintained in this repo" + +[[skill]] +name = "thermo-nuclear-code-quality-review" +repo = "https://github.com/cursor/plugins" +subpath = "thermos/skills/thermo-nuclear-code-quality-review" +ref = "main" +commit = "bdf7aa355337897f167153e05069aca505dae17c" +category = "pr-review" +description = "Run an extremely strict maintainability review for abstraction quality, giant files, and spaghetti-condition growth. Use for a thermo-nuclear code quality review, thermonuclear review, deep code quality audit, or especially harsh maintainability review." + +[[skill]] +name = "thermo-nuclear-review" +repo = "https://github.com/cursor/plugins" +subpath = "thermos/skills/thermo-nuclear-review" +ref = "main" +commit = "bdf7aa355337897f167153e05069aca505dae17c" +category = "pr-review" +description = "Comprehensive security and correctness audit of a branch's changes. Use for thermo nuclear, thermonuclear, or deep review requests, or branch/PR diff audits focused on bugs, breaking changes, security issues, devex regressions, and feature-gate leaks." + +[[skill]] +name = "to-spec" +repo = "https://github.com/mattpocock/skills" +subpath = "skills/engineering/to-spec" +ref = "main" +commit = "6654f6b60cd9d5be8b54c6fafe44346dabeb3b76" +category = "engineering" +description = "\"Turn the current conversation into a spec and publish it to the project issue tracker: no interview, just synthesis of what you've already discussed.\"" + +[[skill]] +name = "to-tickets" +repo = "https://github.com/mattpocock/skills" +subpath = "skills/engineering/to-tickets" +ref = "main" +commit = "6654f6b60cd9d5be8b54c6fafe44346dabeb3b76" +category = "engineering" +description = "Break a plan, spec, or the current conversation into a set of tracer-bullet tickets, each declaring its blocking edges, published to the configured tracker (edges as text in one file per ticket locally, or native blocking links on a real tracker)." + +[[skill]] +name = "transcribe" +repo = "https://github.com/badlogic/pi-skills" +subpath = "transcribe" +ref = "main" +commit = "90bb51cae36515a648515b633a81c0c6efc8c74d" +category = "search-media" +description = "Local speech-to-text transcription on Apple Silicon macOS. Supports wav directly and other audio formats via ffmpeg." + +[[skill]] +name = "triage-gh-backlog" +category = "gh-maintenance" +note = "Local skill, maintained in this repo" + +[[skill]] +name = "tufte-information-design" +category = "documents" + +[[skill]] +name = "verify-hugo-build" +category = "static-site" +note = "Local skill, maintained in this repo" + +[[skill]] +name = "vscode" +repo = "https://github.com/badlogic/pi-skills" +subpath = "vscode" +ref = "main" +commit = "90bb51cae36515a648515b633a81c0c6efc8c74d" +category = "search-media" +description = "VS Code integration for viewing diffs and comparing files. Use when showing file differences to the user." + +[[skill]] +name = "watch-ci" +category = "ci-go" +note = "Local skill, maintained in this repo" + +[[skill]] +name = "wayfinder" +repo = "https://github.com/mattpocock/skills" +subpath = "skills/engineering/wayfinder" +ref = "main" +commit = "6654f6b60cd9d5be8b54c6fafe44346dabeb3b76" +category = "engineering" +description = "Plan a huge chunk of work (more than one agent session can hold) as a shared map of decision tickets on your issue tracker, and resolve them one at a time until the way to the destination is clear." + +[[skill]] +name = "write-hugo-blog-post" +category = "static-site" +note = "Local skill, maintained in this repo" + +[[skill]] +name = "xlsx" +repo = "https://github.com/anthropics/skills" +subpath = "skills/xlsx" +ref = "main" +commit = "3b3fad96af16a10759d930941b4520ba0c40edae" +category = "documents" +description = "\"Use this skill any time a spreadsheet file is the primary input or output. This means any task where the user wants to: open, read, edit, or fix an existing .xlsx, .xlsm, .xltx, .csv, or .tsv file (e.g., adding columns, computing formulas, formatting, charting, cleaning messy data); create a new spreadsheet from scratch or from other data sources; or convert between tabular file formats. Trigger especially when the user references a spreadsheet file by name or path \u2014 even casually (like \\\"the xlsx in my downloads\\\") \u2014 and wants something done to it or produced from it. Also trigger for cleaning or restructuring messy tabular data files (malformed rows, misplaced headers, junk data) into proper spreadsheets. The deliverable must be a spreadsheet file. Do NOT trigger when the primary deliverable is a Word document, HTML report, standalone Python script, database pipeline, or Google Sheets API integration, even if tabular data is involved.\"" + +[[skill]] +name = "youtube-transcript" +repo = "https://github.com/badlogic/pi-skills" +subpath = "youtube-transcript" +ref = "main" +commit = "90bb51cae36515a648515b633a81c0c6efc8c74d" +category = "search-media" +description = "Fetch transcripts from YouTube videos for summarization and analysis." + +[[agent]] +name = "bulk-mechanic" +category = "execution" + +[[agent]] +name = "plan-reviewer" +category = "planning" + +[[agent]] +name = "pr-shepherd" +category = "pr-review" + +[[agent]] +name = "skill-auditor" +category = "meta" + +[[agent]] +name = "thermo-nuclear-code-quality-review-subagent" +repo = "https://github.com/cursor/plugins" +subpath = "thermos/agents/thermo-nuclear-code-quality-review-subagent.md" +ref = "main" +commit = "fd878692de15a3069c21c8f429eb0b9f2fe178fa" +fetched_at = "2026-08-31T16:38:45Z" +category = "pr-review" + +[[agent]] +name = "thermo-nuclear-review-subagent" +repo = "https://github.com/cursor/plugins" +subpath = "thermos/agents/thermo-nuclear-review-subagent.md" +ref = "main" +commit = "fd878692de15a3069c21c8f429eb0b9f2fe178fa" +fetched_at = "2026-08-31T16:38:52Z" +category = "pr-review" diff --git a/suites/go-ci-health/README.md b/suites/go-ci-health/README.md index afa48eb..7b8354f 100644 --- a/suites/go-ci-health/README.md +++ b/suites/go-ci-health/README.md @@ -67,7 +67,7 @@ Each step is the literal phrase you say to your agent (Claude Code, pi, or any h With the [skills.sh](https://www.skills.sh/) CLI (needs Node.js): ```bash -npx skills add sanketsudake/harness-configs \ +npx skills add sanketsudake/dotfiles \ --skill debug-ci \ --skill watch-ci \ --skill analyze-go-pprof \ @@ -89,4 +89,4 @@ npx skills add sanketsudake/harness-configs \ --- -Part of [harness-configs](../../README.md); browse all skills in the [catalog](../../skills/README.md). +Part of [dotfiles](../../README.md); browse all skills in the [catalog](../../skills/README.md). diff --git a/suites/hugo-site/README.md b/suites/hugo-site/README.md index 9a09a3e..81eed9a 100644 --- a/suites/hugo-site/README.md +++ b/suites/hugo-site/README.md @@ -72,7 +72,7 @@ Each step is the literal phrase you say to your agent (Claude Code, pi, or any h With the [skills.sh](https://www.skills.sh/) CLI (needs Node.js): ```bash -npx skills add sanketsudake/harness-configs \ +npx skills add sanketsudake/dotfiles \ --skill write-hugo-blog-post \ --skill author-mermaid-diagram \ --skill generate-og-images \ @@ -96,4 +96,4 @@ npx skills add sanketsudake/harness-configs \ --- -Part of [harness-configs](../../README.md); browse all skills in the [catalog](../../skills/README.md). +Part of [dotfiles](../../README.md); browse all skills in the [catalog](../../skills/README.md). diff --git a/suites/oss-maintainer/README.md b/suites/oss-maintainer/README.md index 6e31f0a..6d94254 100644 --- a/suites/oss-maintainer/README.md +++ b/suites/oss-maintainer/README.md @@ -56,7 +56,7 @@ Each step is the literal phrase you say to your agent (Claude Code, pi, or any h With the [skills.sh](https://www.skills.sh/) CLI (needs Node.js): ```bash -npx skills add sanketsudake/harness-configs \ +npx skills add sanketsudake/dotfiles \ --skill triage-gh-backlog \ --skill remediate-codeql-alerts \ --skill go-deps-security-sweep \ @@ -76,4 +76,4 @@ npx skills add sanketsudake/harness-configs \ --- -Part of [harness-configs](../../README.md); browse all skills in the [catalog](../../skills/README.md). +Part of [dotfiles](../../README.md); browse all skills in the [catalog](../../skills/README.md). diff --git a/suites/pr-shepherding/README.md b/suites/pr-shepherding/README.md index 798ddad..f4f0e8f 100644 --- a/suites/pr-shepherding/README.md +++ b/suites/pr-shepherding/README.md @@ -48,8 +48,8 @@ Each step is the literal phrase you say to your agent (Claude Code, pi, or any h | Skill | Purpose | |-------|---------| -| [`make-pr-easy-to-review`](https://github.com/cursor/plugins/tree/8185ad9fbb903efc7d1cf152a9be9777e516cfbc/cursor-team-kit/skills/make-pr-easy-to-review) | Prepare PRs for review by cleaning noisy history, improving PR descriptions, and adding reviewer guidance without changing code behavior. | -| [`deslop`](https://github.com/cursor/plugins/tree/8185ad9fbb903efc7d1cf152a9be9777e516cfbc/cursor-team-kit/skills/deslop) | Remove AI-generated code slop and clean up code style | +| [`make-pr-easy-to-review`](https://github.com/cursor/plugins/tree/bdf7aa355337897f167153e05069aca505dae17c/cursor-team-kit/skills/make-pr-easy-to-review) | Prepare PRs for review by cleaning noisy history, improving PR descriptions, and adding reviewer guidance without changing code behavior. | +| [`deslop`](https://github.com/cursor/plugins/tree/fd878692de15a3069c21c8f429eb0b9f2fe178fa/cursor-team-kit/skills/deslop) | Remove AI-generated code slop and clean up code style | | [`resolve-bot-review-threads`](../../skills/resolve-bot-review-threads/SKILL.md) | Clears bot/Copilot review threads on a PR — fixes the issues, marks threads resolved via GraphQL, and re-requests the bot until the PR is clean. | | [`watch-ci`](../../skills/watch-ci/SKILL.md) | Watches a PR's CI checks to terminal state in the background and turns each transition into a notification, instead of a foreground polling loop. | | [`debug-ci`](../../skills/debug-ci/SKILL.md) | Triages and root-causes a failing GitHub Actions CI run on a PR by separating real regressions from pre-existing noise and escalating log fetches cheapest-fi... | @@ -59,7 +59,7 @@ Each step is the literal phrase you say to your agent (Claude Code, pi, or any h With the [skills.sh](https://www.skills.sh/) CLI (needs Node.js): ```bash -npx skills add sanketsudake/harness-configs \ +npx skills add sanketsudake/dotfiles \ --skill make-pr-easy-to-review \ --skill deslop \ --skill resolve-bot-review-threads \ @@ -77,4 +77,4 @@ npx skills add sanketsudake/harness-configs \ --- -Part of [harness-configs](../../README.md); browse all skills in the [catalog](../../skills/README.md). +Part of [dotfiles](../../README.md); browse all skills in the [catalog](../../skills/README.md). diff --git a/suites/second-brain/README.md b/suites/second-brain/README.md index a067b2f..a8e7c54 100644 --- a/suites/second-brain/README.md +++ b/suites/second-brain/README.md @@ -61,7 +61,7 @@ Each step is the literal phrase you say to your agent (Claude Code, pi, or any h With the [skills.sh](https://www.skills.sh/) CLI (needs Node.js): ```bash -npx skills add sanketsudake/harness-configs \ +npx skills add sanketsudake/dotfiles \ --skill second-brain \ --skill readwise-second-brain-sync \ --skill second-brain-ingest \ @@ -83,4 +83,4 @@ Optional companion: if you use Readwise, also install [`readwise-cli`](../../ski --- -Part of [harness-configs](../../README.md); browse all skills in the [catalog](../../skills/README.md). +Part of [dotfiles](../../README.md); browse all skills in the [catalog](../../skills/README.md).