From e9a25d940f1979ab8dd67868b8bc8d32ba80dc13 Mon Sep 17 00:00:00 2001 From: Agent Norton Date: Fri, 21 Aug 2026 10:07:13 +0000 Subject: [PATCH] Deploy the shared agent rules to pi Pi is installed on this machine but the manifest never knew about it, so pi sessions ran with the skills and none of the rules. Pi already discovers ~/.agents/skills natively -- symlinked skill directories included -- but it loads exactly one global context file and supports no @-imports, so the rules can't be mirrored per-file the way home/.copilot/instructions does it. Generate home/.pi/agent/AGENTS.md from home/.agents/rules/*.md and link that one file into ~/.pi/agent, guarded by make check-pi-agents on the same self-heal-locally / fail-in-CI contract as the copilot mirror. Linking the whole directory instead would pull pi's sessions, trust.json and auth.json into a public repo and clobber the host's own ~/.pi provisioning, so .pi is the manifest's first partially-linked root and test_manifest_covers_link_dotfiles now requires an entry underneath it rather than one for it. Design, verification, and rejected alternatives: docs/superpowers/specs/2026-08-21-pi-harness-support-design.md Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/ci.yml | 1 + CLAUDE.md | 3 +- Makefile | 11 +- .../2026-08-21-pi-harness-support-design.md | 92 ++++++ home/.pi/.gitignore | 17 ++ home/.pi/agent/AGENTS.md | 262 ++++++++++++++++++ manifest | 1 + scripts/build-pi-agents.sh | 56 ++++ test/test_deploy.sh | 7 + 9 files changed, 448 insertions(+), 2 deletions(-) create mode 100644 docs/superpowers/specs/2026-08-21-pi-harness-support-design.md create mode 100644 home/.pi/.gitignore create mode 100644 home/.pi/agent/AGENTS.md create mode 100755 scripts/build-pi-agents.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 19cfb2d..9b693d8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,7 @@ jobs: ./deploy.sh audit - run: make check-copilot-instructions - run: make check-skills + - run: make check-pi-agents - uses: actions/setup-python@v6 with: python-version: "3.13" diff --git a/CLAUDE.md b/CLAUDE.md index 154b511..4331f36 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,13 +10,14 @@ Public dotfiles (`nonrational/dotfiles`), deployed by symlinking `home/` entries - `make test` — full suite: `test/test_deploy.sh` + `test/test_shell.sh`. Run either script directly for one suite; both sandbox a throwaway `$HOME` under mktemp and never touch the real one. - `./deploy.sh apply|audit [--dry-run]` — manifest-driven symlink deploy/verify. `make deploy` = apply plus re-asserting the skip-worktree flag (see Gotchas). -- `make check-symlinks` — fail on any dangling tracked symlink; `make check-skills` — same, scoped to `home/.agents/skills` (safe in CI); `make check-copilot-instructions` — self-heals the per-file mirror of rules into `home/.copilot/instructions/` (run after renaming anything in `home/.agents/rules/`); `make check-skill-frontmatter` — needs PyYAML. +- `make check-symlinks` — fail on any dangling tracked symlink; `make check-skills` — same, scoped to `home/.agents/skills` (safe in CI); `make check-copilot-instructions` — self-heals the per-file mirror of rules into `home/.copilot/instructions/` (run after renaming anything in `home/.agents/rules/`); `make check-pi-agents` — same idea for pi, regenerating `home/.pi/agent/AGENTS.md`; `make check-skill-frontmatter` — needs PyYAML. - CI (`.github/workflows/ci.yml`) runs `make test`, a deploy apply+audit against a temp `$HOME`, and all the check targets, on macOS and ubuntu. ## Architecture - **`manifest` + `deploy.sh`** — three whitespace columns: source, target, optional condition (`os=Darwin|Linux`, `host=`, `tool=`). deploy.sh is symlink-only by design: apply is `ln -s`, audit is a readlink comparison. Do not add copy/concat/generate behavior to it — that is a parked decision recorded in `docs/superpowers/specs/2026-07-06-manifest-deploy-spike-design.md`. - **`home/.agents` is the source of truth for agent config** (rules + skills), shared across harnesses through symlink shims: `~/.claude/rules` and `~/.claude/skills` point into it, as does `home/.gemini/antigravity-cli/skills`. `home/.copilot/instructions/*.instructions.md` are per-file symlinks mirroring `home/.agents/rules/*.md` — a rename in rules dangles them silently, which is exactly what `check-copilot-instructions` and `check-skills` guard. Design and parked decisions: `docs/superpowers/specs/2026-07-16-agents-source-of-truth-design.md`. +- **Pi needs no skill shim but can't share the rules by symlink.** Pi discovers `~/.agents/skills` natively and follows symlinked skill directories, so skills reach it for free. Context files are the gap: pi loads exactly one global file, `~/.pi/agent/AGENTS.md`, with no @-imports and no rules directory — so that file is a *generated* concatenation of `home/.agents/rules/*.md`, regenerated by `make check-pi-agents` and enforced in CI. Only that one file is symlinked into `~/.pi`; pi's `sessions/`, `trust.json`, and `auth.json` deliberately stay outside the repo. Machine-private context goes in the untracked `~/.pi/agent/APPEND_SYSTEM.md`. Design and parked decisions: `docs/superpowers/specs/2026-08-21-pi-harness-support-design.md`. - **Many skills are vendored, not local.** `home/.agents/ext/mattpocock-skills` is a git submodule; most entries in `home/.agents/skills/` are symlinks into it. Only the real directories there (e.g. `issue-sweep`, `ux-review`, `find-inspiration`, `prose-register`) are editable in this repo. - **OS/host branching is by filename**: `.Darwin`/`.Linux` suffixes, `.bashrc.`, `bin.Darwin` → `~/bin`. Shell is bash-first (Homebrew bash via `chsh`); zsh files exist but are secondary. Shell chain per window: `.bash_profile` → `.bashrc` → `.bashrc.` → `.bashrc.`. - **Design docs** live at `docs/superpowers/specs/YYYY-MM-DD--design.md` (plans in `docs/superpowers/plans/`). Specs record parked decisions — read the relevant one before restructuring the deploy or agents layout. diff --git a/Makefile b/Makefile index ce11f3d..6c3bc8d 100644 --- a/Makefile +++ b/Makefile @@ -116,6 +116,15 @@ check-copilot-instructions: echo "copilot instructions mirror home/.agents/rules"; \ fi +# Pi loads exactly one global context file (~/.pi/agent/AGENTS.md) and supports +# neither @-imports nor a rules directory, so home/.agents/rules/*.md can't be +# symlinked in the way home/.copilot/instructions mirrors them -- they're +# concatenated into home/.pi/agent/AGENTS.md instead. Same failure mode as the +# copilot mirror (a rule rename silently stops reaching the harness), so the +# same guard: self-heal locally, fail the build in CI. +check-pi-agents: + @./scripts/build-pi-agents.sh + link-karabiner: # don't link entire .config directory because it may contain secrets mkdir -p $$HOME/.config @@ -152,4 +161,4 @@ init-submodules: git submodule update --init --recursive # grep '^\w' Makefile | sed 's/:.*//g' | tr '\n' ' ' | pbcopy -.PHONY: default macos-setup init-post-reboot brew-install brew-bundle macos-reset-dock macos check-symlinks check-skills check-skill-frontmatter check-copilot-instructions test deploy link-karabiner link-sublime backup-preferences restore-preferences disable-restore-apps-on-login set-file-associations +.PHONY: default macos-setup init-post-reboot brew-install brew-bundle macos-reset-dock macos check-symlinks check-skills check-skill-frontmatter check-copilot-instructions check-pi-agents test deploy link-karabiner link-sublime backup-preferences restore-preferences disable-restore-apps-on-login set-file-associations diff --git a/docs/superpowers/specs/2026-08-21-pi-harness-support-design.md b/docs/superpowers/specs/2026-08-21-pi-harness-support-design.md new file mode 100644 index 0000000..1a8a0ca --- /dev/null +++ b/docs/superpowers/specs/2026-08-21-pi-harness-support-design.md @@ -0,0 +1,92 @@ +# Pi as a supported harness + +**Date:** 2026-08-21\ +**Status:** proposed — awaiting review\ +**Verified against:** pi 0.79.1 (the build installed at `~/.local/pi`, read directly rather than trusted from docs) and the current upstream README at `earendil-works/pi@main` (0.84.2) + +## Problem + +Pi (`pi.dev`, Earendil Inc., MIT) is a minimal terminal coding harness, and it is **already installed on the exe.dev VM** — `~/.local/bin/pi`, provisioned by the host along with an `exe-dev` extension. This repo doesn't know it exists. `manifest` deploys config for Claude Code, Copilot, and Gemini/Antigravity; pi gets nothing. + +That matters because pi is the fourth consumer of the same agent config, and `home/.agents` was made the source of truth precisely so a new harness costs a shim, not a fork. + +## What pi actually loads + +Read out of the installed binary (`collectSkillEntries`, `loadProjectContextFiles`, `getAgentDir`), not paraphrased from marketing: + +| Resource | Pi's discovery path | +| --- | --- | +| Global context file | `~/.pi/agent/AGENTS.md`, else `AGENTS.MD` / `CLAUDE.md` / `CLAUDE.MD` — **exactly one file**, first match wins | +| Ancestor context files | `AGENTS.md`/`CLAUDE.md` in every directory from `cwd` up to `/` | +| Skills (global) | `~/.pi/agent/skills/` **and `~/.agents/skills/`** | +| Skills (project, trust-gated) | `.pi/skills/`, and `.agents/skills/` in `cwd` and ancestors up to the git root | +| Prompt templates | `~/.pi/agent/prompts/*.md` (the `/name` slash-command analogue) | +| Settings | `~/.pi/agent/settings.json`, overridden by `.pi/settings.json` | +| System prompt | `~/.pi/agent/SYSTEM.md` replaces it; `APPEND_SYSTEM.md` appends to it | +| Runtime state | `~/.pi/agent/` — `sessions/`, `trust.json`, `auth.json`, `npm/`, `git/`, `bin/`, `extensions/` | + +The agent dir is `~/.pi/agent` (`$PI_CODING_AGENT_DIR` overrides), and it is *not* XDG-aware. + +## Skills already work, for free + +`~/.agents/skills/` is a first-class pi discovery path, scanned in "agents" mode: directories containing `SKILL.md` are found recursively, root-level `.md` files are ignored, and **symlinked directories are followed** (pi `statSync`s any entry that is a symlink rather than skipping it). So the ~29 entries in `home/.agents/skills/` — most of them symlinks into the `mattpocock-skills` submodule — are visible to pi today, with no shim, no settings entry, and no trust prompt. + +This is the `.agents`-as-source-of-truth bet paying off unprompted: pi's author chose `~/.agents/skills` as a cross-harness convention independently, and the 2026-07-16 rename landed us on it by accident. Worth recording as evidence the decision was right. + +Verified live, not just read: a `gpt-5-nano` run against a sandboxed agent dir listed 13 skills by name from `~/.agents/skills`. The other 14 entries there carry `disable-model-invocation: true` and are correctly withheld from the system prompt — same as in Claude Code — and stay reachable as `/skill:name`. + +Three caveats that are ours to remember, not to fix: + +- `--no-tools` suppresses the skills block entirely (skills are useless without `read`). Expect no skills in a `--no-tools` run; that is not a discovery failure. +- Pi applies `.gitignore`-style ignore rules found inside a skills tree. `home/.agents/skills/.gitignore` currently excludes `*/results/`, which is only eval transcripts — harmless. A broader pattern added there in future would silently hide skills from pi. +- Name collisions across skill locations warn and keep the first found. Nothing collides today. + +## The gap: rules + +`home/.agents/rules/*.md` reach Claude through `@rules/…` imports in `~/.claude/CLAUDE.md` and Copilot through eight per-file symlinks in `home/.copilot/instructions/`. Neither mechanism exists in pi: it reads **one** global context file and supports no imports and no rules directory. So today a pi session on this machine runs with the skills but none of the working style, communication register, git hygiene, or model-selection guidance. + +### Design + +Concatenate the rules into a generated `home/.pi/agent/AGENTS.md`, symlinked to `~/.pi/agent/AGENTS.md`, and guard the generation the same way the Copilot mirror is guarded. + +- `scripts/build-pi-agents.sh` renders a short header plus every `home/.agents/rules/*.md` verbatim, each preceded by an HTML comment naming its source file. Deterministic order (`LC_ALL=C sort`), idempotent output. +- `make check-pi-agents` rewrites the file locally and prints "please commit"; under `CI` it fails instead. Identical contract to `check-copilot-instructions`, chosen deliberately: the failure mode is identical too (rename a rule, and guidance stops reaching a harness with no visible symptom). +- One manifest entry, conditioned `tool=pi` so machines without pi skip it: + `home/.pi/agent/AGENTS.md ~/.pi/agent/AGENTS.md tool=pi` +- `home/.pi/.gitignore` is a default-deny allowlist, matching `home/.claude/.gitignore`. + +`test_manifest_covers_link_dotfiles` had to learn about `.pi`: it asserts every top-level entry under `home/` appears as a manifest source, and `home/.pi` deliberately isn't one. It now requires at least one entry *underneath* `home/.pi/` instead, so the guard still catches an un-deployed addition there. + +Verified end-to-end: a `gpt-5-nano` run with this file at `~/.pi/agent/AGENTS.md` quoted the first sentence under `## Continuous Improvement` verbatim and named `home/.agents/rules/improvement.md` as its source — so both the load path and the per-file provenance comments do what they claim. + +Cost: ~18KB of derived Markdown committed to the repo. Accepted because CI makes drift impossible and the alternative (below) breaks `deploy.sh` for anyone who doesn't run `make` first. + +## Rejected + +**Symlink `home/.pi` → `~/.pi` wholesale**, the way `.claude`/`.copilot`/`.gemini` are deployed. `deploy.sh` would move the host's live `~/.pi` to `~/.pi.bak` and replace it with a repo symlink, destroying the exe.dev-provisioned `extensions/exe-dev` and `bin/fd`. It would also pull pi's runtime state inside a public repo: `sessions/` holds full transcripts, `trust.json` accumulates private workspace paths (exactly the Antigravity `trustedWorkspaces` problem that needed skip-worktree), and `auth.json` holds provider credentials. Per-file linking is the only safe shape here. This is why `.pi` is the first partially-linked root in the manifest. + +**Generate `AGENTS.md` at deploy time and gitignore it.** Avoids committing a derived artifact, but `./deploy.sh apply` on a fresh clone would hard-fail on a missing source, and deploy.sh staying symlink-only is a parked decision from 2026-07-06. Not worth reopening for 18KB. + +**A pointer file** — an `AGENTS.md` that instructs the agent to go read `~/.agents/rules/*.md`. Cheapest and zero duplication, but it makes always-on rules conditional on the model choosing to read them. Rules that load only sometimes are worse than rules that cost 4k tokens. + +**`APPEND_SYSTEM.md` instead of `AGENTS.md`.** Higher precedence (system prompt rather than context), but `AGENTS.md` is the portable, inspectable convention and can be disabled per-run with `-nc`. `APPEND_SYSTEM.md` is reserved for the machine-private layer below. + +**Mirror `home/.claude/commands/` → `~/.pi/agent/prompts/`.** The formats look compatible (frontmatter `description` + `argument-hint`), but Claude commands use `$ARGUMENTS` substitution and `allowed-tools` naming Claude-only tools; pi appends arguments as a trailing `User: ` line and has no such tools. `then.md` would silently misbehave. Revisit only if a command is written portably. + +**Ship a `home/.pi/agent/settings.json`.** Every setting worth putting there today — theme, default model, thinking level, telemetry — is a preference, not a fix. Nothing speculative. + +**A `~/AGENTS.md` at the home directory root.** Pi walks ancestors from `cwd` to `/`, so this would reach pi *and* any other ancestor-walking harness. Too broad and too surprising: it applies to every repo on the machine, including ones with their own `AGENTS.md`. + +## Machine-private context + +`AGENTS.md` here is public, so `identity.md` and `exe.md` — which live untracked beside the Claude config — must not be folded into it. Pi's `~/.pi/agent/APPEND_SYSTEM.md` is the right home for them: appended to the system prompt, per-machine, never tracked by this repo. Setting it up is a manual per-machine step, deliberately not automated by the manifest. + +## Not ours to fix: the host's misplaced context file + +exe.dev provisions `~/.pi/AGENTS.md` — a root-owned symlink to the host's own VM/proxy briefing. Pi's agent dir is `~/.pi/agent`, so that path is never read — the host's own guidance has not been reaching pi sessions. Reported here for the record; the file belongs to the host image, and our `~/.pi/agent/AGENTS.md` does not collide with it. + +## Follow-ups + +- Installed pi is 0.79.1 (Jun 9); current is 0.84.2. `pi update` self-updates. Newer builds add `AGENTS.override.md`, which doesn't change anything above. +- Pi ships no subagent or todo tool. The Superpowers `references/pi-tools.md` mapping already covers this (`pi-subagents` package if wanted); no repo change needed. +- If prompt templates become worth sharing, the portable move is a neutral `home/.agents/prompts/` with harness-specific shims — not a symlink from one vendor's directory to another's. diff --git a/home/.pi/.gitignore b/home/.pi/.gitignore new file mode 100644 index 0000000..2c3747c --- /dev/null +++ b/home/.pi/.gitignore @@ -0,0 +1,17 @@ +# Allowlist approach: ignore everything, then explicitly allow tracked files. +# +# Only individual files under here are symlinked into ~/.pi (see the manifest), +# so pi's runtime state stays in the real ~/.pi/agent and never reaches this +# repo. The default-deny is insurance against that changing: sessions/ holds +# full transcripts, trust.json records private workspace paths, and auth.json +# holds provider credentials -- exactly the categories home/.claude/.gitignore +# exists to keep out of a public repo. + +* + +# Re-include the config directory entry itself. Per gitignore rules, a file +# cannot be re-included if its parent directory is excluded. +!agent/ + +!.gitignore +!agent/AGENTS.md diff --git a/home/.pi/agent/AGENTS.md b/home/.pi/agent/AGENTS.md new file mode 100644 index 0000000..e6165a3 --- /dev/null +++ b/home/.pi/agent/AGENTS.md @@ -0,0 +1,262 @@ + + +# AGENTS.md + +Pi's global instructions. Everything below is a verbatim copy of a rule file in `home/.agents/rules/`, which is the source of truth shared across harnesses -- edit the rule there, then run `make check-pi-agents`. + +Machine-specific and private instructions do not belong in this file: it is tracked in a public repo. Put those in `~/.pi/agent/APPEND_SYSTEM.md`, which pi appends to the system prompt and which this repo does not track. + + + +## Continuous Improvement + +Consider all work applied research. Constantly seek improvement and note effective patterns. + +When a correction occurs (e.g., user response starts with "No", "Remember", "Always"): + +1. Incorporate the corrective heuristic into the appropriate sub-config. +2. Check if the new direction conflicts with pre-existing guidance. If so, ask clarifying questions. + +I open source my dotfiles, including my agent config, and look for patterns worth publishing or writing about. When we hit upon a particularly effective instruction or workflow pattern, suggest whether we should fork a subagent to draft a re-usable skill to encode the pattern for future use. + + + +## Communication Guide + +Use this for all written communication, including email drafts. + +Scoped registers extend this guide and win where they apply: `code-review-register` for PR/code-review comments, `prose-register` for essays and posts. + +--- + +## Voice and Tone + +**Be direct.** State the point in the first sentence. + +> ❌ "I hope this email finds you well. I'm reaching out today because..." +> ✅ "Wanted to flag a change coming next week that may affect your team." + +**Use "we" when speaking for the team, "I" when speaking for yourself.** + +**Conversational, not casual.** Write how a thoughtful person talks, not how a press release reads. Contractions are fine. Stiff constructions are not. + +> ❌ "It is imperative that we address this matter." +> ✅ "We need to get ahead of this." + +**Light warmth is fine; gushing is not.** A "Great!" or "Thanks!" lands well. A paragraph about how excited you are does not. + +--- + +## Structure + +- **Short paragraphs.** Two or three sentences max. +- **Lead with what matters.** Most important thing first. +- **Name the WHO and WHEN explicitly** when your message involves a change, event, or request. +- **Use bullets or numbered lists** for three or more items of the same type. Don't bullet everything. +- **Bold key terms inline** so scanning surfaces them naturally. Don't bold whole sentences. +- **TL;DR at the top** when the message is long or the action is time-sensitive. + +--- + +## What to Avoid + +- No emojis unless for humor. +- No charged language; prefer "allowlist/blocklist", "leader/follower". +- No jargon without definition. Prefer simple technical phrases: "business logic" not "directed graph state machine", "framework" not "advanced subsystem". +- No "just" or "simply" as modifiers. +- No throat-clearing. Skip "I wanted to reach out...", "Per my last email...", "Hope you're doing well," and any preamble explaining what you're about to say. Jump straight to the message. +- No claimed feelings in status updates. State the prediction and the delta ("stronger than the plan's presumption" or "stronger than I presumed"), not the affect ("better than I hoped"). +- No passive voice when active is available. +- No editorializing. Keep formatting minimal. + +--- + +## Replies and Scope + +Match the *amount* of answer to what was asked. A reply is not a checklist to clear. + +- **Don't do the recipient's homework.** When they own a decision, acknowledge it and defer. +- **You don't have to answer every question in the incoming message.** State the recommendation, reassure where it helps, name the next step. Punting a whole thread to later is a legitimate move, not a gap. +- **Avoid the consulting-memo shape.** Bullets, hedges, and "here's the reasoning" at paragraph length read as mechanical corpospeak. Conversational bias for action. + +--- + +## Closings + +Match the register of the message. + +- For routine updates: no closing needed, or a plain "Thanks." +- For requests: "Let me know if you have questions." +- For time-sensitive items: "Happy to jump on a call if that'd be faster." +- For team communications: Leave it unsigned if the context is obvious. + +--- + +## Stylistic Patterns + +- **"Not X, but Y"** – soften qualifications without burying them ("Not a blocker, but worth a sync"). +- **Avoid em-dashes for asides** – Use full stops, semicolons, parenthesis. +- **Name things precisely** – "Thursday afternoon ET" not "sometime this week." +- **Footnotes for elaborations** – when a detail would break the flow but is worth having, push it down. + + + +## macOS Interactions (Darwin only) + +These instructions apply only on Darwin. On Linux, `pbcopy` does not exist — skip them. + +### Clipboard + +When you produce output the user is likely to paste elsewhere — GitHub (commit messages, PR/issue bodies, comments), email drafts, a browser, or any other external destination — also copy it to the clipboard with `pbcopy`. This covers markdown-formatted text (copied raw, per the markdown rules) and URLs the user will open or share. + +Pipe via a quoted heredoc to preserve formatting, e.g. `cat <<'EOF' | pbcopy … EOF`. + +The user runs a clipboard history tracker (Maccy), so clobbering the current clipboard is acceptable for the sake of speed — don't ask first. + +Always tell the user when you've copied something to the clipboard. + + + +## Markdown Output + +- When asked to produce markdown output (PR descriptions, docs, etc.), always reply with raw markdown source — `#`, `##`, `**`, backticks, etc. — so it can be copy-pasted directly into a markdown editor. Never render it as formatted output. +- **Never hard-wrap prose.** Write each paragraph, list item, and heading as one long source line and let the renderer handle wrapping. Manual line breaks at a fixed column render as ragged mid-sentence breaks (GitHub PR/issue/comment bodies treat single newlines as `
`). A newline in markdown source is a deliberate break, never a wrapping aid. +- When writing markdown documents with metadata headers (e.g., `**Prepared for:**`, `**Date:**`), add a backslash `\` at the end of each line to force line breaks. Without this, `pandoc` collapses consecutive bold lines into a single paragraph. +- When a markdown example itself contains a fenced code block (e.g. a ` ```markdown ` template that nests ` ```mermaid `, ` ```bash `, etc.), fence the OUTER example with tildes (`~~~`), not backticks. A `~~~` fence is only closed by `~~~`, so the inner ` ``` ` blocks nest cleanly — otherwise the first inner ` ``` ` prematurely closes the outer fence and the example renders broken. + + + +## Model and Effort Selection + +Use this when picking a model and reasoning effort for a task — your own or a delegated subagent's. These are defaults, not laws: the human overrides, and evidence of struggle beats a guess. + +### Match the model to the work + +- **Brainstorming, ideation, divergent drafting** — Opus or Fable, medium. Breadth matters more than depth here; save the heavy tiers for when a decision is on the line. +- **Planning, triage, architecture, hard root-cause debugging** — Opus, xhigh (max for the gnarliest calls). Choices that are expensive to reverse — or that many later steps lean on — earn the deepest reasoning. +- **Implementation, refactors, delegated research, code review** — Sonnet. The workhorse. Scale effort to risk: mechanical edits run low, integration work runs high. +- **Commit messages, PR summaries, short prose, mechanical transcription** — Haiku, low. Fast and cheap for low-stakes text where the answer is already known. + +### Principles + +- **Least powerful model that can do the job.** Start low, escalate on evidence — a subagent that stalls, a review that keeps missing things — not on a hunch that a task "feels hard". +- **Turn count beats token price.** A cheap model that flails for ten turns costs more than a capable one that lands in three. When the plan already spells out the steps, the cheapest tier fits; when it doesn't, floor at Sonnet. +- **Effort tracks stakes, not size.** A one-line change to auth logic is high effort; a hundred-line mechanical rename is low. Ask what breaks if you're wrong. +- **Give the last word to the top tier.** Whatever tier did the work, the final adversarial review — the one gating a merge — runs on the strongest model available (Opus, or Fable where enabled) at high effort or above. +- **Name the model when you delegate.** An omitted model inherits the session's, often the most expensive — set it explicitly on every subagent. + +### Before dispatching + +1. What is this — generate, decide, build or transcribe? +2. Pick the model from the list above; set effort by stakes, not line count. +3. Delegating? State model and effort explicitly in the dispatch. + + + +## Task Queue + +When the user prefixes a message with `/then` or `then:`, they are queuing a task for later — not asking you to start it now. + +Queue it on the native task list, exactly as the `/then` command does: ensure the current in-flight work is tracked as an in_progress task, add the queued text as a pending task after it, reply with a single line "Queued: [task]", and continue whatever you were already doing. In a harness without task tools, append it as a `- [ ]` line to `~/.claude/queue.md` instead. + +The queue is a program, not a parking lot: when the in-flight work completes and unblocked pending tasks remain, start the next one (lowest ID) instead of ending your turn. If nothing was in flight when the task was queued, start it right after replying. + + + +## Authoring Portable Tooling (Skills, Agents, Commands, Hooks) + +Applies whenever you create or edit a **skill, agent, command, hook, or any reusable artifact that will live outside a single project** — in `~/.claude/`, this (public) dotfiles repo, a plugin, or anywhere it will be shared or published. A skill checked into a project's own repo may reference that project; this rule is about artifacts that travel. + +### The rule + +**No project-specific identifiers in portable tooling.** When you synthesize a skill/agent/command from real work — a review you ran, a bug you fixed, a screen you built — keep the *structure and the lessons*, replace the *nouns*. If a reader could identify the source project, it is not generic enough. + +### What counts as an identifier — scrub all of these + +- Client, company or product names, and internal codenames. +- Domain nouns and jargon specific to one project — the words that only make sense inside that product. +- Named entities or instances from the data — records, environments, rooms, accounts, people by name. +- Third-party vendors or systems the project integrates with. +- Repo names, hostnames, internal URLs, ticket/issue IDs, seed-data values. + +### Instead + +Invent a neutral, universally-recognizable example — support tickets, invoices, pull requests, a settings table, a booking grid. Vary the domain across examples so none reads as a real product. + +### Verify before you finish + +Grep the artifact for the source project's identifiers before calling it done — a clean grep is the gate: + +```bash +grep -rniE '||||' +``` + +Fill the pattern with the actual terms from the project you drew the material from. + +**This rule obeys itself.** The placeholders above are deliberately fake — never drop a real client or product name into a portable artifact, not even as an "example". + +### Credit what you took + +When a portable artifact comes from someone else's work, record it in **frontmatter**, not prose. Frontmatter is greppable; a paragraph of credit is not. + +- `source: `. A verbatim copy, vendored as-is. +- `forked-from: `. A derivative. You kept the skeleton and changed the rules. + +```bash +grep -rnE '^(source|forked-from): https?://' # e.g. the skills AND commands trees +``` + +Anchor on the URL, not the key. A bare `source:` also matches template blocks *inside* a skill's body, where the same word means something else entirely. + +Name the relationship honestly. If the structure survived, it is `forked-from`, not "inspired by". Leave the original where it is; overwriting a vendored artifact in place erases the breadcrumb back to whoever wrote it. + + + +## Iterative Working Style + +- Ask good questions. Don't assume. Don't hide confusion. Surface tradeoffs. +- Simplicity is paramount. Minimum code that solves the problem. Nothing speculative. +- Touch only what you must. Leave the codebase cleaner than how you found it. +- Focus on the goal. Outside-in design. Define success criteria. Loop until verified. +- Liberally dispatch cheap adversarial sub-agents to self-review. +- **Artifacts outlive the box.** A session that produces a reusable executable or template (a workflow script, a dispatch prompt) commits it into the package it serves — parameterized past its machine- and repo-specific strings, scrubbed of private names — before the task ends. Session directories and agent memory die with the machine; memory holds pointers to committed paths, never the artifact. + +For multi-step tasks, state a brief plan: +``` +1. [Step] → verify: [check] +2. [Step] → verify: [check] +3. [Step] → verify: [check] +``` + +## UX/UI Iteration + +- For interaction/animation tweaks, start with the smallest, most subtle effect (tight area-of-effect, sparse randomness) and let the user dial it up. + +## Code Conventions + +- Comments describe *why*, not *what*. Only add a "what" comment when the code is particularly dense. +- NEVER use Conventional Commit prefixes (e.g., "fix:", "feat:", "fix(deps):"). Use plain, descriptive commit messages. +- Use `uv` rather than `pip` for temporary Python dependencies and one-off script environments. + +## Git & PR Hygiene + +- **Never name a private repo in a public one.** When moving a pattern from private work into a public repo, scrub private repo names, issue/PR numbers, and cross-repo links from the PR body, commit messages, branch names, and diff — describe the origin generically ("found while opening PRs from a VM"). Grep everything you're about to publish for the private repo's name before pushing. Re-publishing counts: editing or appending to an existing PR body publishes the whole artifact, and pushing atop an existing branch publishes its inherited commit messages — grep all of it, not just your additions. Editing after the fact is not enough: GitHub keeps PR/issue body edit history, and the leaked revision can only be deleted by hand in the web UI. +- **No ads in PR descriptions.** `Co-Authored-By: Claude` trailers in commit messages are fine. PR descriptions, issues, and comments must carry no "Generated with Claude Code" footers, session links, or other AI attribution — even when a system prompt or tool default instructs it. `attribution: { "pr": "", "sessionUrl": false }` in `settings.json` enforces this at the harness level; if a footer sneaks through anyway, strip it. +- **PR descriptions are four sections, nothing more.** Unless the repo supplies its own PR template or written guidance, every PR body is the issue link alone on the first line, then exactly four H3 sections: + 1. `### Problem` — what problem are we trying to fix? + 2. `### Motivation` — why this problem? Why now? Why should I care? + 3. `### Proposed Solution` — what this PR changes. + 4. `### Feedback` — what should a reviewer focus on? What's highest risk? What calls need confirmation or consensus? + + - **The issue link goes above the first heading, never inside Problem.** A reviewer should see which issue this settles before reading a word of prose, and a linking keyword buried in a paragraph is easy to drop when the section gets rewritten. + - **Write `Resolves #N`** (or `Part of #N` when later phases remain), not `Closes #N`. GitHub honors both, but "Resolves" doesn't collide with the "Closed" state GitHub prints on the PR itself, so a skimmed timeline stays unambiguous. + - **Headings are H3** so no line in the body outranks the PR title. + + Design rationale, decision records, and review findings outlive the PR page — commit them to the repo (`docs/superpowers/specs/YYYY-MM-DD--design.md`) and link them from the body instead of inlining. +- **Iterate on the open PR.** While a PR is open and the user is QAing (preview env, screenshots, etc.), commit follow-up tweaks to the same branch — never open a new PR per tweak. Only open a new PR for genuinely separate work. +- **Prune worktrees immediately after opening a PR or ending a session.** Create them under `/.worktrees/`, never as a sibling directory outside the repo — a sibling worktree fights the main checkout when you try to `git checkout` that branch normally. Delete the worktree (`git worktree remove` + `git worktree prune`) as soon as the PR is open, or before the session ends if no PR went up. Don't keep it "for feedback" — clone a fresh one later if you need to iterate locally. +- **Bootstrap a new worktree before trusting anything it tells you.** A worktree checks out tracked files only — no installed dependencies, no env files, none of the gitignored state the main checkout accumulated. Install deps and symlink (don't copy) env files first; a copy drifts from the real credentials. Until you do, a failure there is evidence about your worktree, not about your code. The dangerous ones don't look like setup errors: dependency resolution walks up to the parent repo, so a dev server or bundler refuses to serve files outside its project root, and a strict "no console errors" test assertion turns that into a failure raised *after* the test body has fully passed — indistinguishable from a regression in the change you just made. Before debugging any worktree-only failure, reproduce it in the main checkout. +- **Commit auto-formatter diffs.** If `prettier` / `eslint --fix` / equivalent reformats files outside the PR's scope, commit them on the same branch. CI lint will fail on them otherwise — there is no "separate formatting sweep PR". +- **Don't escape backticks in quoted heredocs.** When writing PR / issue / comment bodies via `gh … --body "$(cat <<'EOF' … EOF)"`, write backticks raw. The `'EOF'` quoting disables shell expansion, so `\`` survives literally and GitHub renders the backslash. +- **Don't pass `--delete-branch` to `gh pr merge`** when the repo has auto-delete enabled. The synchronous delete races with GitHub's auto-retarget of stacked dependent PRs and can auto-close the next PR in the stack instead of retargeting it. Let the repo's auto-delete handle cleanup. +- **Verify review follow-ups against the same interface.** Before saying a follow-up change addresses review feedback, confirm it changes the same public surface. A GraphQL path does not cover an equivalent REST path, and vice versa. diff --git a/manifest b/manifest index e815ffe..ce94105 100644 --- a/manifest +++ b/manifest @@ -19,6 +19,7 @@ home/.tool-versions ~/.tool-versions home/.inputrc ~/.inputrc home/.irbrc ~/.irbrc home/.nethackrc ~/.nethackrc +home/.pi/agent/AGENTS.md ~/.pi/agent/AGENTS.md tool=pi home/.profile ~/.profile home/.railsrc ~/.railsrc home/.screenrc ~/.screenrc diff --git a/scripts/build-pi-agents.sh b/scripts/build-pi-agents.sh new file mode 100755 index 0000000..5dbbac7 --- /dev/null +++ b/scripts/build-pi-agents.sh @@ -0,0 +1,56 @@ +#!/bin/bash +# Regenerate home/.pi/agent/AGENTS.md from home/.agents/rules/*.md. +# +# Pi loads exactly one global context file -- ~/.pi/agent/AGENTS.md -- with no +# @-import and no directory glob, so the rules have to be concatenated here +# rather than mirrored per-file the way home/.copilot/instructions does it. +# That makes AGENTS.md a generated artifact: this script rewrites it in place +# locally and fails instead under CI, so a rule rename shows up as a red build +# rather than as guidance that silently stopped reaching pi. +set -eu -o pipefail + +DOTS="$(cd "$(dirname "$0")/.." && pwd)" +RULES="$DOTS/home/.agents/rules" +OUT="$DOTS/home/.pi/agent/AGENTS.md" + +# A header-only AGENTS.md would look fine and silently ship pi zero rules, so +# refuse to write one. +rule_count="$(find "$RULES" -maxdepth 1 -name '*.md' | wc -l | tr -d ' ')" +if [ "$rule_count" -eq 0 ]; then + echo "error: no rule files found in $RULES" >&2 + exit 1 +fi + +generate() { + cat <<'EOF' + + +# AGENTS.md + +Pi's global instructions. Everything below is a verbatim copy of a rule file in `home/.agents/rules/`, which is the source of truth shared across harnesses -- edit the rule there, then run `make check-pi-agents`. + +Machine-specific and private instructions do not belong in this file: it is tracked in a public repo. Put those in `~/.pi/agent/APPEND_SYSTEM.md`, which pi appends to the system prompt and which this repo does not track. +EOF + while IFS= read -r rule; do + printf '\n\n\n' "$(basename "$rule")" + cat "$rule" + done < <(find "$RULES" -maxdepth 1 -name '*.md' | LC_ALL=C sort) +} + +tmp="$(mktemp)" +trap 'rm -f "$tmp"' EXIT +generate >"$tmp" + +if [ -f "$OUT" ] && cmp -s "$tmp" "$OUT"; then + echo "home/.pi/agent/AGENTS.md mirrors home/.agents/rules" + exit 0 +fi + +if [ -n "${CI:-}" ]; then + echo "[error] home/.pi/agent/AGENTS.md is out of sync with home/.agents/rules. failing CI." >&2 + exit 1 +fi + +mkdir -p "$(dirname "$OUT")" +cat "$tmp" >"$OUT" +echo "[fix] regenerated home/.pi/agent/AGENTS.md. please commit the changes." diff --git a/test/test_deploy.sh b/test/test_deploy.sh index 6fc56ff..1b8200f 100755 --- a/test/test_deploy.sh +++ b/test/test_deploy.sh @@ -353,6 +353,13 @@ test_manifest_covers_link_dotfiles() { if git -C "$ROOT" status --porcelain --ignored -- "home/$f" 2>/dev/null | grep -q '^!!'; then continue fi + # home/.pi is linked file-by-file rather than whole: ~/.pi also holds + # pi's sessions, auth.json, and trust.json, plus whatever the host + # provisioned there. Require an entry underneath it, not one for it. + if [ "$f" = ".pi" ]; then + grep -q '^home/\.pi/' <<<"$sources" || missing="$missing $f" + continue + fi grep -qxF "home/$f" <<<"$sources" || missing="$missing $f" done grep -qxF "home/bin.$(uname)" <<<"$sources" || missing="$missing bin.$(uname)"