From 494fecff76962e0965e009852d75806f1040cb9e Mon Sep 17 00:00:00 2001 From: alexion Date: Wed, 2 Sep 2026 22:07:06 -0400 Subject: [PATCH] refactor(agents): scope customization to monorepo Move Pi runtime resources into project-local .pi directories and keep shell modules responsible for installing Pi, Herdr, gitea-axi, and Pi package patches. Remove legacy global agent context, Claude Code customization, the global skills bridge, and the external skills flake input. --- .../pi => .pi}/extensions/compact-status.ts | 0 .pi/extensions/herdr-agent-state.ts | 287 ++++++++++++++++++ .../pi/extensions => .pi/prompts}/.gitkeep | 0 .../agents/pi/prompts => .pi/skills}/.gitkeep | 0 AGENTS.md | 71 ++++- configs/nixos/hosts/neogaia/default.nix | 4 +- .../agents/claude-code/authentication.md | 32 -- .../agents/claude-code/claude-code.nix | 65 ---- .../claude-code/hooks/agent-sudo-guard.sh | 29 -- .../skills/codebase-design/DEEPENING.md | 37 --- .../skills/codebase-design/DESIGN-IT-TWICE.md | 44 --- .../skills/codebase-design/SKILL.md | 113 ------- .../skills/craft-skill/GLOSSARY.md | 195 ------------ .../claude-code/skills/craft-skill/SKILL.md | 49 --- .../claude-code/skills/implement/SKILL.md | 72 ----- .../skills/improve-codebase/HTML-REPORT.md | 119 -------- .../skills/improve-codebase/SKILL.md | 68 ----- .../claude-code/skills/library/nbdev/SKILL.md | 152 ---------- .../claude-code/skills/remove-skills/SKILL.md | 37 --- .../skills/review-uncommitted/SKILL.md | 147 --------- .../skills/setup-skills/LOCKFILE.md | 53 ---- .../claude-code/skills/setup-skills/SKILL.md | 53 ---- .../skills/setup-skills/hash-dir.sh | 23 -- .../skills/test-driven-development/SKILL.md | 153 ---------- .../skills/test-driven-development/mocking.md | 59 ---- .../test-driven-development/refactoring.md | 10 - .../skills/test-driven-development/tests.md | 77 ----- .../claude-code/skills/to-spec/SKILL.md | 21 -- .../claude-code/skills/to-spec/SPEC-FORMAT.md | 55 ---- .../claude-code/skills/to-tasks/SKILL.md | 57 ---- .../skills/to-tasks/TASK-FORMAT.md | 28 -- .../claude-code/skills/update-skills/SKILL.md | 55 ---- .../nixos/modules/agents/context/AGENTS.md | 85 ------ .../nixos/modules/agents/context/context.nix | 21 -- configs/nixos/modules/agents/pi/pi.nix | 81 ----- configs/nixos/modules/agents/skills.nix | 18 -- .../{agents/tools => shell}/gitea-axi.nix | 4 +- configs/nixos/modules/shell/herdr.nix | 2 + configs/nixos/modules/shell/pi/default.nix | 48 +++ .../pi/patches/pi-flex-spacer.patch | 0 .../patches/pi-tool-lookup-validation.patch | 0 flake.lock | 192 +++--------- flake.nix | 7 - src/skills/AGENTS.md | 0 src/skills/README.md | 28 ++ 45 files changed, 467 insertions(+), 2184 deletions(-) rename {configs/nixos/modules/agents/pi => .pi}/extensions/compact-status.ts (100%) create mode 100644 .pi/extensions/herdr-agent-state.ts rename {configs/nixos/modules/agents/pi/extensions => .pi/prompts}/.gitkeep (100%) rename {configs/nixos/modules/agents/pi/prompts => .pi/skills}/.gitkeep (100%) delete mode 100644 configs/nixos/modules/agents/claude-code/authentication.md delete mode 100644 configs/nixos/modules/agents/claude-code/claude-code.nix delete mode 100755 configs/nixos/modules/agents/claude-code/hooks/agent-sudo-guard.sh delete mode 100644 configs/nixos/modules/agents/claude-code/skills/codebase-design/DEEPENING.md delete mode 100644 configs/nixos/modules/agents/claude-code/skills/codebase-design/DESIGN-IT-TWICE.md delete mode 100644 configs/nixos/modules/agents/claude-code/skills/codebase-design/SKILL.md delete mode 100644 configs/nixos/modules/agents/claude-code/skills/craft-skill/GLOSSARY.md delete mode 100644 configs/nixos/modules/agents/claude-code/skills/craft-skill/SKILL.md delete mode 100644 configs/nixos/modules/agents/claude-code/skills/implement/SKILL.md delete mode 100644 configs/nixos/modules/agents/claude-code/skills/improve-codebase/HTML-REPORT.md delete mode 100644 configs/nixos/modules/agents/claude-code/skills/improve-codebase/SKILL.md delete mode 100644 configs/nixos/modules/agents/claude-code/skills/library/nbdev/SKILL.md delete mode 100644 configs/nixos/modules/agents/claude-code/skills/remove-skills/SKILL.md delete mode 100644 configs/nixos/modules/agents/claude-code/skills/review-uncommitted/SKILL.md delete mode 100644 configs/nixos/modules/agents/claude-code/skills/setup-skills/LOCKFILE.md delete mode 100644 configs/nixos/modules/agents/claude-code/skills/setup-skills/SKILL.md delete mode 100755 configs/nixos/modules/agents/claude-code/skills/setup-skills/hash-dir.sh delete mode 100644 configs/nixos/modules/agents/claude-code/skills/test-driven-development/SKILL.md delete mode 100644 configs/nixos/modules/agents/claude-code/skills/test-driven-development/mocking.md delete mode 100644 configs/nixos/modules/agents/claude-code/skills/test-driven-development/refactoring.md delete mode 100644 configs/nixos/modules/agents/claude-code/skills/test-driven-development/tests.md delete mode 100644 configs/nixos/modules/agents/claude-code/skills/to-spec/SKILL.md delete mode 100644 configs/nixos/modules/agents/claude-code/skills/to-spec/SPEC-FORMAT.md delete mode 100644 configs/nixos/modules/agents/claude-code/skills/to-tasks/SKILL.md delete mode 100644 configs/nixos/modules/agents/claude-code/skills/to-tasks/TASK-FORMAT.md delete mode 100644 configs/nixos/modules/agents/claude-code/skills/update-skills/SKILL.md delete mode 100644 configs/nixos/modules/agents/context/AGENTS.md delete mode 100644 configs/nixos/modules/agents/context/context.nix delete mode 100644 configs/nixos/modules/agents/pi/pi.nix delete mode 100644 configs/nixos/modules/agents/skills.nix rename configs/nixos/modules/{agents/tools => shell}/gitea-axi.nix (81%) create mode 100644 configs/nixos/modules/shell/pi/default.nix rename configs/nixos/modules/{agents => shell}/pi/patches/pi-flex-spacer.patch (100%) rename configs/nixos/modules/{agents => shell}/pi/patches/pi-tool-lookup-validation.patch (100%) create mode 100644 src/skills/AGENTS.md create mode 100644 src/skills/README.md diff --git a/configs/nixos/modules/agents/pi/extensions/compact-status.ts b/.pi/extensions/compact-status.ts similarity index 100% rename from configs/nixos/modules/agents/pi/extensions/compact-status.ts rename to .pi/extensions/compact-status.ts diff --git a/.pi/extensions/herdr-agent-state.ts b/.pi/extensions/herdr-agent-state.ts new file mode 100644 index 0000000..caa50ed --- /dev/null +++ b/.pi/extensions/herdr-agent-state.ts @@ -0,0 +1,287 @@ +// installed by herdr +// managed by herdr; reinstalling or updating the integration overwrites this file. +// add custom hooks/plugins beside this file instead of editing it. +// HERDR_INTEGRATION_ID=pi +// HERDR_INTEGRATION_VERSION=6 +// @ts-nocheck + +import net from "node:net"; + +const HERDR_ENV = process.env.HERDR_ENV; +const socketPath = process.env.HERDR_SOCKET_PATH; +const socketEndpoint = + process.platform === "win32" && socketPath ? `\\\\.\\pipe\\${socketPath}` : socketPath; +const paneId = process.env.HERDR_PANE_ID; +const source = "herdr:pi"; + +function enabled() { + return HERDR_ENV === "1" && !!socketPath && !!paneId; +} + +function sendRequestAttempt(request: unknown, timeoutMs: number): Promise { + if (!enabled()) { + return Promise.resolve(true); + } + + return new Promise((resolve) => { + let done = false; + let timeout: ReturnType | undefined; + const finish = (delivered: boolean) => { + if (done) return; + done = true; + if (timeout) { + clearTimeout(timeout); + } + socket.destroy(); + resolve(delivered); + }; + + const socket = net.createConnection(socketEndpoint!); + socket.on("error", () => finish(false)); + socket.on("connect", () => socket.write(`${JSON.stringify(request)}\n`)); + socket.on("data", () => finish(true)); + socket.on("end", () => finish(false)); + timeout = setTimeout(() => finish(false), timeoutMs); + timeout.unref?.(); + }); +} + +async function sendRequest(request: unknown): Promise { + if (await sendRequestAttempt(request, 500)) { + return; + } + await sendRequestAttempt(request, 1500); +} + +type AgentState = "working" | "blocked" | "idle"; + +type QueuedState = { + state: AgentState; + message?: string; + seq: number; +}; + +let reportSeq = Date.now() * 1000; +let currentAgentSessionId: string | undefined; +let currentAgentSessionPath: string | undefined; + +function nextReportSeq(): number { + reportSeq += 1; + return reportSeq; +} + +function updateSessionRef(ctx: any): void { + try { + const file = ctx?.sessionManager?.getSessionFile?.(); + currentAgentSessionPath = + typeof file === "string" && file.startsWith("/") ? file : undefined; + } catch { + currentAgentSessionPath = undefined; + } + + try { + const id = ctx?.sessionManager?.getSessionId?.(); + currentAgentSessionId = typeof id === "string" && id.length > 0 ? id : undefined; + } catch { + currentAgentSessionId = undefined; + } +} + +function withSessionRef(params: Record): Record { + if (currentAgentSessionPath) { + return { ...params, agent_session_path: currentAgentSessionPath }; + } + if (currentAgentSessionId) { + return { ...params, agent_session_id: currentAgentSessionId }; + } + return params; +} + +function currentSessionRef(): Record | undefined { + if (currentAgentSessionPath) { + return { agent_session_path: currentAgentSessionPath }; + } + if (currentAgentSessionId) { + return { agent_session_id: currentAgentSessionId }; + } + return undefined; +} + +function reportSession(sessionStartSource?: string): Promise { + const sessionRef = currentSessionRef(); + if (!sessionRef) { + return Promise.resolve(); + } + + return sendRequest({ + id: `${source}:session:${Date.now()}:${Math.random().toString(36).slice(2)}`, + method: "pane.report_agent_session", + params: { + pane_id: paneId, + source, + agent: "pi", + seq: nextReportSeq(), + session_start_source: sessionStartSource, + ...sessionRef, + }, + }); +} + +function sendState(state: AgentState, message?: string, seq = nextReportSeq()): Promise { + return sendRequest({ + id: `${source}:${Date.now()}:${Math.random().toString(36).slice(2)}`, + method: "pane.report_agent", + params: withSessionRef({ + pane_id: paneId, + source, + agent: "pi", + state, + message, + seq, + }), + }); +} + +function releaseAgent(): Promise { + return sendRequest({ + id: `${source}:release:${Date.now()}:${Math.random().toString(36).slice(2)}`, + method: "pane.release_agent", + params: { + pane_id: paneId, + source, + agent: "pi", + seq: nextReportSeq(), + }, + }); +} + +function shouldReleaseOnSessionShutdown(event: any): boolean { + // Pi tears down and rebinds extension runtimes for internal lifecycle actions + // such as /reload, /new, /resume, and /fork. Those do not mean the pane's + // agent process has exited, and releasing hook authority there can suppress + // legitimate reports from the replacement runtime. Only a user/process quit + // should release Herdr's full-lifecycle authority. + const reason = event?.reason; + return reason === "quit"; +} + +let sendInFlight = false; +let queuedState: QueuedState | undefined; + +function queueState(state: AgentState, message?: string): void { + queuedState = { state, message, seq: nextReportSeq() }; + if (!sendInFlight) { + void drainStateQueue(); + } +} + +async function drainStateQueue(): Promise { + if (sendInFlight) { + return; + } + + sendInFlight = true; + try { + while (queuedState) { + const next = queuedState; + queuedState = undefined; + await sendState(next.state, next.message, next.seq); + } + } finally { + sendInFlight = false; + if (queuedState) { + void drainStateQueue(); + } + } +} + +export default function (pi) { + if (!enabled()) { + return; + } + + let agentActive = false; + let blockedCount = 0; + let blockedMessage: string | undefined; + let lastState: AgentState | undefined; + let lastMessage: string | undefined; + let rootSession = false; + + function desiredState() { + if (blockedCount > 0) { + return { state: "blocked" as const, message: blockedMessage }; + } + if (agentActive) { + return { state: "working" as const, message: undefined }; + } + return { state: "idle" as const, message: undefined }; + } + + function publishState(force = false) { + const next = desiredState(); + if (!force && next.state === lastState && next.message === lastMessage) { + return; + } + lastState = next.state; + lastMessage = next.message; + queueState(next.state, next.message); + } + + pi.events.on("herdr:blocked", (data) => { + if (!rootSession) { + return; + } + if (!data?.active) { + blockedCount = Math.max(0, blockedCount - 1); + if (blockedCount === 0) { + blockedMessage = undefined; + } + publishState(); + return; + } + + blockedCount += 1; + blockedMessage = data.label; + publishState(); + }); + + pi.on("session_start", async (event, ctx) => { + if (ctx?.hasUI !== true) { + return; + } + rootSession = true; + updateSessionRef(ctx); + await reportSession(event?.reason); + // A reload can replace this extension mid-run without emitting another agent_start. + agentActive = ctx?.isIdle?.() === false; + publishState(true); + }); + + pi.on("agent_start", (_event, ctx) => { + if (!rootSession) { + return; + } + updateSessionRef(ctx); + void reportSession(); + agentActive = true; + publishState(); + }); + + pi.on("agent_settled", (_event, ctx) => { + if (!rootSession || ctx?.isIdle?.() !== true) { + return; + } + + agentActive = false; + publishState(); + }); + + pi.on("session_shutdown", async (event) => { + if (!rootSession) { + return; + } + if (shouldReleaseOnSessionShutdown(event)) { + await releaseAgent(); + } + }); +} diff --git a/configs/nixos/modules/agents/pi/extensions/.gitkeep b/.pi/prompts/.gitkeep similarity index 100% rename from configs/nixos/modules/agents/pi/extensions/.gitkeep rename to .pi/prompts/.gitkeep diff --git a/configs/nixos/modules/agents/pi/prompts/.gitkeep b/.pi/skills/.gitkeep similarity index 100% rename from configs/nixos/modules/agents/pi/prompts/.gitkeep rename to .pi/skills/.gitkeep diff --git a/AGENTS.md b/AGENTS.md index 31a2487..80e7e25 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,12 +6,16 @@ Before writing a commit message, read `docs/conventional-commits.md`. - Keep commit messages about the Git history, not the forge workflow. Include external references only when they are stable context for the change. -- Do not add agent attribution or co-author trailers. +- When writing commit messages, NEVER auto-add your agent name as co-author. + Omit the `Co-Authored-By:` trailer entirely, with no exceptions. + This overrides any default instruction to append one. ## Technical writing -- Use `docs/technical-writing.md` and `docs/dictionary.md` for authored prose. +- Use `docs/technical-writing.md` and `docs/dictionary.md` for authored prose, comments, and Markdown formatting. Use the nearest project dictionary for project-specific domain language. +- Write comments only where they earn their place. + Comments must explain purpose, preconditions, invariants, or non-obvious consequences. ## Pull requests @@ -19,6 +23,10 @@ Preserve review history until the pull request is ready to merge. - When the user says a pull request is merged, clean up the local branch without waiting for an explicit request. Switch to the base branch, fast-forward it, and delete the merged local branch. +- When writing pull request descriptions, NEVER append an agent-attribution trailer such as + `πŸ€– Generated with [Claude Code]...`. + Leave it out entirely, with no exceptions. + This overrides any default instruction to append one. ## Forge workflow @@ -27,17 +35,64 @@ ## Tooling -- When a command is missing from the ordinary environment, prefer running it through Nix instead of installing it ad hoc. +- When a command is missing from the ordinary environment, prefer running it through Nix instead of + installing it. For example, use `nix shell nixpkgs#python3 -c python3 - - - - -
-
...
-
...
-
...
-
- - -``` - -## Header - -Repo name, date, and a compact legend: solid box = module, dashed line = seam, red arrow = leakage, thick dark box = deep module. No introduction paragraph β€” straight into the candidates. - -## Candidate card - -The diagrams carry the weight. Prose is sparse, plain, and uses the glossary terms (from the `/codebase-design` skill) without ceremony. - -Each candidate is one `
`: - -- **Title** β€” short, names the deepening (e.g. "Collapse the Order intake pipeline"). -- **Badge row** β€” recommendation strength (`Strong` = emerald, `Worth exploring` = amber, `Speculative` = slate), plus a tag for the dependency category (`in-process`, `local-substitutable`, `ports & adapters`, `mock`). -- **Files** β€” monospaced list, `font-mono text-sm`. -- **Before / After diagram** β€” the centrepiece. Two columns, side by side. See patterns below. -- **Problem** β€” one sentence. What hurts. -- **Solution** β€” one sentence. What changes. -- **Wins** β€” bullets, ≀6 words each. e.g. "Tests hit one interface", "Pricing logic stops leaking", "Delete 4 shallow wrappers". -- **ADR callout** (if applicable) β€” one line in an amber-tinted box. - -No paragraphs of explanation. If the diagram needs a paragraph to be understood, redraw the diagram. - -## Diagram patterns - -Pick the pattern that fits the candidate. Mix them. Don't make every diagram look the same β€” variety is part of the point. - -### Mermaid graph (the workhorse for dependencies / call flow) - -Use a Mermaid `flowchart` or `graph` when the point is "X calls Y calls Z, and look at the mess." Wrap it in a Tailwind-styled card so it doesn't feel parachuted in. Style with classDef to colour leakage edges red and the deep module dark. Sequence diagrams work well for "before: 6 round-trips; after: 1." - -```html -
-
-    flowchart LR
-      A[OrderHandler] --> B[OrderValidator]
-      B --> C[OrderRepo]
-      C -.leak.-> D[PricingClient]
-      classDef leak stroke:#dc2626,stroke-width:2px;
-      class C,D leak
-  
-
-``` - -### Hand-built boxes-and-arrows (when Mermaid's layout fights you) - -Modules as `
`s with borders and labels. Arrows as inline SVG `` or `` elements positioned absolutely over a relative container. Reach for this when you want the "after" diagram to feel like one thick-bordered deep module with greyed-out internals β€” Mermaid won't render that with the right weight. - -### Cross-section (good for layered shallowness) - -Stack horizontal bands (`h-12 border-l-4`) to show layers a call passes through. Before: 6 thin layers each doing nothing. After: 1 thick band labelled with the consolidated responsibility. - -### Mass diagram (good for "interface as wide as implementation") - -Two rectangles per module β€” one for interface surface area, one for implementation. Before: interface rectangle is nearly as tall as the implementation rectangle (shallow). After: interface rectangle is short, implementation rectangle is tall (deep). - -### Call-graph collapse - -Before: a tree of function calls rendered as nested boxes. After: the same tree collapsed into one box, with the now-internal calls shown faded inside it. - -## Style guidance - -- Lean editorial, not corporate-dashboard. Generous whitespace. Serif optional for headings (`font-serif` works well with stone/slate). -- Colour sparingly: one accent (emerald or indigo) plus red for leakage and amber for warnings. -- Keep diagrams ~320px tall so before/after sits comfortably side by side without scrolling. -- Use `text-xs uppercase tracking-wider` for module labels inside diagrams β€” they should read as schematic, not as UI. -- The only scripts are the Tailwind CDN and the Mermaid ESM import. The report is otherwise static β€” no app code, no interactivity beyond Mermaid's own rendering. - -## Top recommendation section - -One larger card. Candidate name, one sentence on why, anchor link to its card. That's it. - -## Tone - -Plain English, concise β€” but the architectural nouns and verbs come straight from the `/codebase-design` glossary, terms and exclusions alike. Concision is not an excuse to drift. - -**Phrasings that fit the style:** - -- "Order intake module is shallow β€” interface nearly matches the implementation." -- "Pricing leaks across the seam." -- "Deepen: one interface, one place to test." -- "Two adapters justify the seam: HTTP in prod, in-memory in tests." - -**Wins bullets** name the gain in glossary terms: *"locality: bugs concentrate in one module"*, *"leverage: one interface, N call sites"*, *"interface shrinks; implementation absorbs the wrappers"*. Don't write *"easier to maintain"* or *"cleaner code"* β€” those terms aren't in the glossary and don't earn their place. - -No hedging, no throat-clearing, no "it's worth noting that…". If a sentence could be a bullet, make it a bullet. If a bullet could be cut, cut it. If a term isn't in the `/codebase-design` glossary, reach for one that is before inventing a new one. diff --git a/configs/nixos/modules/agents/claude-code/skills/improve-codebase/SKILL.md b/configs/nixos/modules/agents/claude-code/skills/improve-codebase/SKILL.md deleted file mode 100644 index 7e58837..0000000 --- a/configs/nixos/modules/agents/claude-code/skills/improve-codebase/SKILL.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -name: improve-codebase -description: Scan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick. -disable-model-invocation: true ---- - -# Improve Codebase - -Surface architectural friction and propose **deepening opportunities** β€” refactors that turn shallow modules into deep ones. The aim is testability and AI-navigability. - -This command is _informed_ by the project's domain model and built on a shared design vocabulary: - -- Run the `/codebase-design` skill for the architecture vocabulary (**module**, **interface**, **depth**, **seam**, **adapter**, **leverage**, **locality**) and its principles (the deletion test, "the interface is the test surface", "one adapter = hypothetical seam, two = real"). Use its terms exactly in every suggestion, per its glossary. -- The domain language in `.claude/CONTEXT.md` gives names to good seams; ADRs in `.claude/adr/` record decisions this command should not re-litigate. - -## Process - -### 1. Explore - -Read the project's domain glossary (`.claude/CONTEXT.md`) and any ADRs in the area you're touching first. - -Then use the Agent tool with `subagent_type=Explore` to walk every top-level module or directory in scope (the whole repository, or the area the user pointed you to) β€” even if only briefly for the ones that turn out clean. Within each, judge friction organically rather than against a rigid checklist: - -- Where does understanding one concept require bouncing between many small modules? -- Where are modules **shallow** β€” interface nearly as complex as the implementation? -- Where have pure functions been extracted just for testability, but the real bugs hide in how they're called (no **locality**)? -- Where do tightly-coupled modules leak across their seams? -- Which parts of the codebase are untested, or hard to test through their current interface? - -Apply the **deletion test** to anything you suspect is shallow: would deleting it concentrate complexity, or just move it? A "yes, concentrates" is the signal you want. - -Zero candidates is a legitimate outcome for a genuinely clean area β€” but it has to follow from having looked, not from stopping early. - -### 2. Present candidates as an HTML report - -Write a self-contained HTML file to the OS temp directory so nothing lands in the repository. Resolve the temp dir from `$TMPDIR`, falling back to `/tmp` (or `%TEMP%` on Windows), and write to `/architecture-review-.html` so each run gets a fresh file. Open it for the user β€” `xdg-open ` on Linux, `open ` on macOS, `start ` on Windows. Treat the open as best-effort: it's a no-op in a headless/sandboxed environment with no display server, so report the absolute path regardless of whether the open succeeded. - -The report uses **Tailwind via CDN** for layout and styling, and **Mermaid via CDN** for diagrams where a graph/flow/sequence reliably communicates the structure. Mix Mermaid with hand-crafted CSS/SVG visuals β€” use Mermaid when relationships are graph-shaped (call graphs, dependencies, sequences), and hand-built divs/SVG when you want something more editorial (mass diagrams, cross-sections, collapse animations). Each candidate gets a **before/after visualisation**. Be visual. - -For each candidate, render a card with: - -- **Files** β€” which files/modules are involved -- **Problem** β€” why the current architecture is causing friction -- **Solution** β€” plain English description of what would change -- **Benefits** β€” explained in terms of locality and leverage, and how tests would improve -- **Before / After diagram** β€” side-by-side, custom-drawn, illustrating the shallowness and the deepening -- **Recommendation strength** β€” one of `Strong`, `Worth exploring`, `Speculative`, rendered as a badge - -End the report with a **Top recommendation** section: which candidate you'd tackle first and why. - -**Use `.claude/CONTEXT.md` vocabulary for the domain, and the `/codebase-design` vocabulary for the architecture.** If `.claude/CONTEXT.md` defines "Order," talk about "the Order intake module" β€” not "the FooBarHandler," and not "the Order service." - -**ADR conflicts**: if a candidate contradicts an existing ADR, only surface it when the friction is real enough to warrant revisiting the ADR. Mark it clearly in the card (e.g. a warning callout: _"contradicts ADR-0007 β€” but worth reopening because…"_). Don't list every theoretical refactor an ADR forbids. - -See [HTML-REPORT.md](HTML-REPORT.md) for the full HTML scaffold, diagram patterns, and styling guidance. - -Do NOT propose interfaces yet. After the file is written, ask the user: "Which of these would you like to explore?" - -### 3. Grilling loop - -Once the user picks a candidate, run `/grill` to walk the design tree with them β€” constraints, dependencies, the shape of the deepened module, what sits behind the seam, what tests survive. - -Side effects happen inline as decisions crystallize β€” run `/domain-modeling` to keep the domain model current as you go, even if `.claude/CONTEXT.md` doesn't exist yet: - -- **Naming a deepened module after a concept not in `.claude/CONTEXT.md`?** Add the term to `.claude/CONTEXT.md`. Create the file lazily if it doesn't exist. -- **Sharpening a fuzzy term during the conversation?** Update `.claude/CONTEXT.md` right there. -- **User rejects the candidate with a load-bearing reason?** Offer an ADR, framed as: _"Want me to record this as an ADR so future architecture reviews don't re-suggest it?"_ Only offer when the reason would actually be needed by a future explorer to avoid re-suggesting the same thing β€” skip ephemeral reasons ("not worth it right now") and self-evident ones. -- **Want to explore alternative interfaces for the deepened module?** Run the `/codebase-design` skill and use its design-it-twice parallel sub-agent pattern. diff --git a/configs/nixos/modules/agents/claude-code/skills/library/nbdev/SKILL.md b/configs/nixos/modules/agents/claude-code/skills/library/nbdev/SKILL.md deleted file mode 100644 index 3aeeb3a..0000000 --- a/configs/nixos/modules/agents/claude-code/skills/library/nbdev/SKILL.md +++ /dev/null @@ -1,152 +0,0 @@ ---- -name: nbdev -description: nbdev conventions for notebooks β€” directives, cell structure, docments, tests, execution. Use for any .ipynb operation β€” including reads β€” in an nbdev project. ---- - -# nbdev - -## Tool Preference - -- Use the **Jupyter MCP** for all `.ipynb` operations β€” read, edit, insert, delete, execute -- Do **not** use the built-in `NotebookEdit` tool; it writes cell source as a single JSON string which breaks standard Jupyter formatting and produces noisy diffs -- Re-read the notebook before editing if it may have changed since your last read β€” cell indices/IDs can shift under concurrent edits (e.g. via JupyterLab's real-time collaboration), and editing by a stale index can hit the wrong cell - -## nbdev Directives - -Directives are comments at the top of a cell that control how nbdev processes it: - -- `#| export` β€” include this cell in the exported Python module and in the docs -- `#| hide` β€” exclude this cell from both the module and the docs -- `#| hide_input` β€” show cell output in docs but hide the source code -- `#| default_exp module_name` β€” set which module this notebook exports to (second cell) -- `#| exporti` β€” export to module but do not show in docs (for internal helpers) -- `#| eval: false` β€” include in docs but do not execute during `nbdev-test` - -Imports needed only for tests or examples should **not** be exported. - -Never hand-edit the exported `.py` module files β€” they're build artifacts regenerated from the notebook by `nbdev_export`. All edits go through the source notebook in `nbs/`. - -## Notebook Structure - -Every notebook must follow this structure: - -**Cell 1 β€” Markdown frontmatter:** -```markdown -# Module Title - -> A one-line description of what this module does -``` -The H1 becomes the page title in docs. The blockquote becomes the subtitle. - -**Cell 2 β€” Default export:** -```python -#| default_exp module_name -``` - -**Body cells** β€” alternating between exported code, demonstrations, and markdown explanations (see Cell Structure below). - -**Last cell:** -```python -#| hide -import nbdev; nbdev.nbdev_export() -``` - -Before declaring any notebook task complete, restart the kernel and run all cells top-to-bottom to verify it is fully reproducible. - -## Cell Structure - -Keep cells short. Each exported function gets its own cell, immediately followed by a demonstration. Do not write long functions with comments interspersed β€” split them into small separate cells with explanations and working examples after each. - -The pattern per concept: - -1. *(Optional)* A markdown cell explaining what comes next -2. A `#| export` code cell with the function -3. One or more plain code cells demonstrating usage -4. Assertions that double as tests - -Example: -```python -#| export -def slugify(text: str) -> str: - "Convert text to a URL-safe slug" - return re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-") -``` -```python -slug = slugify("Hello, World!") -assert slug == "hello-world" -slug -``` - -## Docstrings and Parameter Documentation - -Keep docstrings short β€” a single-line summary is sufficient for most functions. Elaborate in separate markdown or code cells below, where you can use real examples. - -Use **docments** (inline parameter comments) instead of verbose docstring parameter sections: - -```python -#| export -def greet( - name: str, # Person to greet - greeting: str="Hi", # Greeting word to use -) -> str: # The composed greeting - "Compose a greeting for name" - return f"{greeting}, {name}!" -``` - -This renders as a clean parameter table in the docs automatically β€” no need to repeat type information in the docstring body. - -Use backticks around symbol names in docstrings and markdown β€” nbdev automatically converts these to hyperlinks to the relevant reference page. - -## Code Style - -- **Prefer composition**: write small functions that do one thing well -- Each exported function should be focused enough to fit naturally in a single notebook cell β€” one cell, one idea -- Use type hints on all exported functions -- Avoid classes unless state is genuinely needed β€” prefer functions that take and return data -- If you do write a class, use `fastcore`'s `@patch` decorator to define each method in its own cell, immediately followed by a demonstration. This avoids long class definitions and keeps examples close to the code - -When a class is needed, document its methods with `show_doc`: -```python -from nbdev.showdoc import show_doc -show_doc(MyClass.my_method) -``` - -## Tests - -Every code cell is run as a test by nbdev unless explicitly marked otherwise β€” any exception fails the test. - -- Turn demonstrations into tests by adding `assert` statements -- Use `fastcore.test` helpers for better error messages: - ```python - from fastcore.test import test_eq, test_fail - test_eq(slugify("Hello World"), "hello-world") - ``` -- Document expected error cases with `test_fail`: - ```python - test_fail(lambda: slugify(""), contains="empty") - ``` -- Each test/demo cell should import what it needs directly β€” don't rely on a name imported in a later cell just because it happened to be in scope during a prior run - -## Execution - -- Always execute cells after writing them to verify they work -- If a cell errors, read the full traceback before attempting a fix β€” do not guess -- When installing packages, use `%pip install` inside the notebook (not `!pip install`) so they install into the running kernel -- Use autoreload at the top of notebooks that import from other modules in the project: - ```python - %load_ext autoreload - %autoreload 2 - ``` - -## Documentation - -- Use H2 (`##`) markdown cells to group related symbols within a notebook -- Use H4 (`####`) markdown cells to split long explanations within a symbol's section (notes, examples, edge cases, etc.) -- Add rich representations to classes via `_repr_markdown_` where it aids understanding -- Include real code examples, plots, and diagrams β€” notebooks support rich output, use it - -## Outputs - -- Never print secrets, tokens, passwords, or API keys into cell output β€” notebook outputs get committed to git and published in docs, unlike transient script output -- Prefer summaries over dumping large data structures (`.head()`, `len()`, `[:5]`, etc.) -- Large outputs consume context window β€” keep them concise diff --git a/configs/nixos/modules/agents/claude-code/skills/remove-skills/SKILL.md b/configs/nixos/modules/agents/claude-code/skills/remove-skills/SKILL.md deleted file mode 100644 index e23ea0f..0000000 --- a/configs/nixos/modules/agents/claude-code/skills/remove-skills/SKILL.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -name: remove-skills -description: Remove one or more previously added library skills from the current project. -disable-model-invocation: true ---- - -Removes a skill that [`setup-skills`](../setup-skills/SKILL.md) previously -copied into the current project, deleting both its files and its entry in -`.claude/skills-lock.yaml` (see [LOCKFILE.md](../setup-skills/LOCKFILE.md) -for its schema). - -## Steps - -1. Read `.claude/skills-lock.yaml`. If it doesn't exist or is empty, tell - the user there's nothing installed to remove and stop. - -2. Determine which skill(s) to remove: - - If the user's invocation already named a specific skill, use that β€” - if it isn't in the lockfile, say so and stop. - - Otherwise, list every skill currently in the lockfile and ask the - user to pick one (or more). - -3. For each skill to remove, compute its current hash - (`~/.claude/skills/setup-skills/hash-dir.sh .claude/skills/`) - and compare it to the hash stored in the lockfile: - - If it matches (never modified since it was installed), delete - `.claude/skills//` and remove its lockfile entry immediately β€” - no confirmation needed, since nothing of the user's is being lost. - - If it differs (locally customized), tell the user it has local - changes that will be permanently lost and ask for confirmation - before deleting. If they decline, leave that skill installed and - move on to the next. - -4. Finish with a summary of what was removed and what was left in place. - -Done when every skill to remove has been either deleted (with its lockfile -entry removed) or explicitly left in place with a stated reason. diff --git a/configs/nixos/modules/agents/claude-code/skills/review-uncommitted/SKILL.md b/configs/nixos/modules/agents/claude-code/skills/review-uncommitted/SKILL.md deleted file mode 100644 index f9d2841..0000000 --- a/configs/nixos/modules/agents/claude-code/skills/review-uncommitted/SKILL.md +++ /dev/null @@ -1,147 +0,0 @@ ---- -name: review-uncommitted -description: Review the working tree's uncommitted changes along three axes β€” change risk, repo standards, and spec fidelity β€” using parallel sub-agents. ---- - -Three-axis review of the diff between `HEAD` and the working tree: - -- **Risk** β€” how much attention does this change warrant, from low to high? -- **Standards** β€” does the code conform to this repo's documented coding standards? -- **Spec** β€” does the code faithfully implement the originating PRD or task file? - -All three axes run as **parallel sub-agents** so they don't pollute each other's context, then this skill aggregates their findings. - -## Process - -### 1. Capture the diff - -The diff command is `git diff HEAD` β€” everything uncommitted, staged or not. -New files must already be tracked (`git add`ed) to show up; this skill doesn't scan for untracked files, so that's the caller's responsibility. - -Confirm the diff is non-empty before going further. -An empty diff should fail here β€” not inside three parallel sub-agents. - -### 2. Identify the spec source - -Look for the originating spec, in this order: - -1. A path the user passed as an argument. -2. A spec file matching the branch name or feature β€” `.claude/spec/.md`. -3. If nothing is found, ask the user where the spec is. - If they say there isn't one, the **Spec** sub-agent will skip and report "no spec available". - -### 3. Identify the standards sources - -Anything in the repo that documents how code should be written, such as `CODING_STANDARDS.md` or `CONTRIBUTING.md`. - -On top of whatever the repo documents, the Standards axis always carries the **smell baseline** below β€” a fixed set of Fowler code smells (_Refactoring_, ch.3) that applies even when a repo documents nothing. -Two rules bind it: - -- **The repo overrides.** - A documented repo standard always wins; where it endorses something the baseline would flag, suppress the smell. -- **Always a judgement call.** - Each smell is a labelled heuristic ("possible Feature Envy"), never a hard violation β€” and, like any standard here, skip anything tooling already enforces. - -Each smell reads *what it is* β†’ *how to fix*; match it against the diff: - -- **Mysterious Name** β€” a function, variable, or type whose name doesn't reveal what it does or holds. - β†’ rename it; if no honest name comes, the design's murky. -- **Duplicated Code** β€” the same logic shape appears in more than one hunk or file in the change. - β†’ extract the shared shape, call it from both. -- **Feature Envy** β€” a method that reaches into another object's data more than its own. - β†’ move the method onto the data it envies. -- **Data Clumps** β€” the same few fields or params keep travelling together (a type wanting to be born). - β†’ bundle them into one type, pass that. -- **Primitive Obsession** β€” a primitive or string standing in for a domain concept that deserves its own type. - β†’ give the concept its own small type. -- **Repeated Switches** β€” the same `switch`/`if`-cascade on the same type recurs across the change. - β†’ replace with polymorphism, or one map both sites share. -- **Shotgun Surgery** β€” one logical change forces scattered edits across many files in the diff. - β†’ gather what changes together into one module. -- **Divergent Change** β€” one file or module is edited for several unrelated reasons. - β†’ split so each module changes for one reason. -- **Speculative Generality** β€” abstraction, parameters, or hooks added for needs the spec doesn't have. - β†’ delete it; inline back until a real need shows. -- **Message Chains** β€” long `a.b().c().d()` navigation the caller shouldn't depend on. - β†’ hide the walk behind one method on the first object. -- **Middle Man** β€” a class or function that mostly just delegates onward. - β†’ cut it, call the real target direct. -- **Refused Bequest** β€” a subclass or implementer that ignores or overrides most of what it inherits. - β†’ drop the inheritance, use composition. - -### 4. Risk rubric - -The Risk axis judges the diff alone β€” no repo-doc lookup, no input from the Standards or Spec sub-agents. -It always runs; it only needs the diff from step 1. - -Rate each of these six factors **Low / Medium / High**, then take the single highest-rated factor as the overall rating (worst-factor-wins): - -- **Blast radius** β€” isolated change vs. ripples across many files, modules, or callers. -- **Reversibility** β€” trivial rollback vs. hard to undo (migrations, deletions, published API/schema changes). -- **Test coverage** β€” covered by tests in/around the diff vs. untested. -- **Sensitive domain** β€” touches auth, security, payments, permissions, concurrency, or data migrations. -- **Size & complexity** β€” large diff or tangled control flow vs. small/simple. -- **Runtime criticality** β€” hot path/production-critical vs. internal or dev-only tooling. - -### 5. Spawn all three sub-agents in parallel - -Send a single message with three `Agent` tool calls. -Use the `general-purpose` subagent for all three. - -**Risk sub-agent prompt** β€” include: - -- The full diff (output of `git diff HEAD`). -- The six risk factors from step 4, pasted in full. -- The brief: "Rate each of the six factors Low/Medium/High with a one-clause reason, then give the overall rating as the highest of the six. - Report the overall rating first, then the six factor lines. - Under 200 words." - -**Standards sub-agent prompt** β€” include: - -- The full diff (output of `git diff HEAD`). -- The list of standards-source files you found in step 3, **plus the smell baseline from step 3** pasted in full β€” the sub-agent has no other access to it. -- The brief: "Report β€” per file/hunk where relevant β€” (a) every place the diff violates a documented standard: cite the standard (file + the rule); and (b) any baseline smell you spot: name it and quote the hunk. - Distinguish hard violations from judgement calls β€” documented-standard breaches can be hard, but baseline smells are always judgement calls, and a documented repo standard overrides the baseline. - Skip anything tooling enforces. - Under 400 words." - -**Spec sub-agent prompt** β€” include: - -- The full diff (output of `git diff HEAD`). -- The path or fetched contents of the spec. -- The brief: "Report: (a) requirements the spec asked for that are missing or partial; (b) behaviour in the diff that wasn't asked for (scope creep); (c) requirements that look implemented but where the implementation looks wrong. - Quote the spec line for each finding. - Under 400 words." - -If the spec is missing, skip the Spec sub-agent and note this in the final report. - -### 6. Aggregate - -Present the Risk report first, under a `## Risk` heading, with the overall rating bolded on its own line followed by the six factor lines: - -``` -## Risk -**Overall: HIGH** -- Blast radius: ... -- Reversibility: ... -- Test coverage: ... -- Sensitive domain: ... -- Size & complexity: ... -- Runtime criticality: ... -``` - -Then present the Standards and Spec reports under `## Standards` and `## Spec` headings, verbatim or lightly cleaned. -Do **not** merge or rerank findings β€” the axes are deliberately separate (see _Why Standards and Spec stay separate_). - -End with a one-line summary: total findings per axis (Standards/Spec only), and the worst issue _within each axis_ (if any). -Don't pick a single winner across axes β€” that's the reranking the separation exists to prevent. -The risk rating isn't repeated here; it already leads the report. - -## Why Standards and Spec stay separate - -A change can pass one axis and fail the other: - -- Code that follows every standard but implements the wrong thing β†’ **Standards pass, Spec fail.** -- Code that does exactly what the PRD or task asked but breaks the project's conventions β†’ **Spec pass, Standards fail.** - -Reporting them separately stops one axis from masking the other. diff --git a/configs/nixos/modules/agents/claude-code/skills/setup-skills/LOCKFILE.md b/configs/nixos/modules/agents/claude-code/skills/setup-skills/LOCKFILE.md deleted file mode 100644 index 3ffa79d..0000000 --- a/configs/nixos/modules/agents/claude-code/skills/setup-skills/LOCKFILE.md +++ /dev/null @@ -1,53 +0,0 @@ -# Skills Lockfile - -`.claude/skills-lock.yaml`, at the root of a project, tracks which library -skills (from `~/.claude/skills/library/`) have been copied into that -project's `.claude/skills/`, so [`setup-skills`](SKILL.md), -[`update-skills`](../update-skills/SKILL.md), and -[`remove-skills`](../remove-skills/SKILL.md) all agree on what's installed -without re-deriving it from the filesystem. - -## Schema - -A YAML list of entries, one per installed skill: - -```yaml -- name: nbdev - hash: 3f2a9b8c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a -- name: terraform-conventions - hash: 9c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a3f2a9b -``` - -- `name` β€” matches both the skill's directory name in the library - (`skills/library/`) and its copied directory name in the project - (`.claude/skills/`). -- `hash` β€” the output of `hash-dir.sh` run against that one skill's - directory contents, recorded at the moment it was last copied or - confirmed up to date. Never a hash of anything else β€” not the whole - project, not the whole library, just that one skill's own directory - tree. - -## What a mismatch means - -To classify a skill's state, compare three values: the lockfile's stored -`hash`, `hash-dir.sh` on the project's current copy -(`.claude/skills/`), and `hash-dir.sh` on the library's current -source (`~/.claude/skills/library/`). - -| stored vs. project copy | stored vs. library source | meaning | -|--------------------------|----------------------------|--------------------------------------| -| match | match | nothing to do | -| match | differs | library moved on β€” safe to update | -| differs | match | project customized on purpose β€” leave it | -| differs | differs | conflict β€” report, don't touch | - -## Writing to the lockfile - -- Adding a skill: append a new `{name, hash}` entry. -- Applying a safe update: overwrite that entry's `hash` in place with the - library's current hash. -- Removing a skill: delete its entry entirely. - -Never reorder or restructure existing entries beyond what an add, update, -or remove requires β€” this file is meant to diff cleanly in a project's -git history. diff --git a/configs/nixos/modules/agents/claude-code/skills/setup-skills/SKILL.md b/configs/nixos/modules/agents/claude-code/skills/setup-skills/SKILL.md deleted file mode 100644 index a1aeb10..0000000 --- a/configs/nixos/modules/agents/claude-code/skills/setup-skills/SKILL.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -name: setup-skills -description: Add relevant skills from the shared skills library to the current project. -disable-model-invocation: true ---- - -Adds opt-in, project-specific skills from `~/.claude/skills/library/` into -the current project's `.claude/skills/`, tracked in -`.claude/skills-lock.yaml` (see [LOCKFILE.md](LOCKFILE.md) for its schema). -Only ever adds β€” checking already-installed skills for updates is -[`update-skills`](../update-skills/SKILL.md)'s job, not this one's. - -The library is a tree of read-only symlinks into the Nix store, so every -copy out of it must dereference (`cp -rL`) and then restore write -permission (`chmod -R u+w`). A plain `cp -r` copies the symlinks -themselves, putting store paths into the project that break on any other -machine. - -## Steps - -1. Read `.claude/skills-lock.yaml` in the current project, if it exists. - Note every skill name already listed β€” these are already installed and - must not be re-proposed. - -2. List every skill under `~/.claude/skills/library/*/SKILL.md` and read - each one's `name` and `description`. - -3. Inspect the current project (file tree, manifests like - `pyproject.toml`/`package.json`, file extensions present, etc.) and - judge which library skills β€” excluding ones already installed β€” seem - relevant, the same way you'd reason about any unfamiliar codebase. - Propose that shortlist to the user with your reasoning, one line per - skill. If the user asks to see the full catalog instead, list every - library skill (minus already-installed ones) with its description. - -4. Let the user confirm, adjust, or pick freely from the full list. - -5. For each confirmed skill: - - If `.claude/skills//` already exists in the project and is - *not* in the lockfile, skip it and tell the user why (a same-named - skill already lives there and isn't tracked β€” remove or rename it - first if they want the library version). - - Otherwise, copy `~/.claude/skills/library//` to - `.claude/skills//` in the project with - `cp -rL` followed by `chmod -R u+w`, run - `~/.claude/skills/setup-skills/hash-dir.sh .claude/skills/`, - and append `{name, hash: }` to `.claude/skills-lock.yaml` - (create the file, an empty YAML list, if it doesn't exist yet). - -6. Report what was added and what was skipped, and why. - -Done when every confirmed skill is either copied and recorded in the -lockfile, or explicitly skipped with a stated reason. diff --git a/configs/nixos/modules/agents/claude-code/skills/setup-skills/hash-dir.sh b/configs/nixos/modules/agents/claude-code/skills/setup-skills/hash-dir.sh deleted file mode 100755 index 267fee7..0000000 --- a/configs/nixos/modules/agents/claude-code/skills/setup-skills/hash-dir.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash -# Deterministic recursive hash of a directory's file contents. -# -# Hashes relative paths, not absolute ones, so two directories with -# identical contents hash identically regardless of where they live on -# disk (needed to compare a project's copied skill against the library -# source it was copied from). -# -# Usage: hash-dir.sh -set -euo pipefail - -if [ $# -ne 1 ]; then - echo "Usage: hash-dir.sh " >&2 - exit 1 -fi - -dir="$1" -if [ ! -d "$dir" ]; then - echo "Not a directory: $dir" >&2 - exit 1 -fi - -(cd "$dir" && find . -type f -print0 | sort -z | xargs -0 -r sha256sum) | sha256sum | awk '{print $1}' diff --git a/configs/nixos/modules/agents/claude-code/skills/test-driven-development/SKILL.md b/configs/nixos/modules/agents/claude-code/skills/test-driven-development/SKILL.md deleted file mode 100644 index 8c7db92..0000000 --- a/configs/nixos/modules/agents/claude-code/skills/test-driven-development/SKILL.md +++ /dev/null @@ -1,153 +0,0 @@ ---- -name: test-driven-development -description: Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests. ---- - -# Test-Driven Development - -## Philosophy - -**Core principle**: Tests should verify behavior through public interfaces, not implementation details. Code can change entirely; tests shouldn't. - -**Good tests** are integration-style: they exercise real code paths through public APIs. They describe _what_ the system does, not _how_ it does it. A good test reads like a specification - "user can checkout with valid cart" tells you exactly what capability exists. These tests survive refactors because they don't care about internal structure. - -**Bad tests** are coupled to implementation. They mock internal collaborators, test private methods, or verify through external means (like querying a database directly instead of using the interface). The warning sign: your test breaks when you refactor, but behavior hasn't changed. If you rename an internal function and tests fail, those tests were testing implementation, not behavior. - -**Tautological tests** restate the implementation inside the assertion, so they pass by construction and give zero confidence. When the expected value is computed the way the code computes it β€” `expect(add(a, b)).toBe(a + b)`, snapshotting a figure you derived by hand the same way the code does, asserting a constant equals itself β€” the test can never disagree with the code: break the code wrong and the assertion breaks wrong with it. The expected value must come from an independent source of truth β€” a known-good literal, a worked example, the spec. - -See [tests.md](tests.md) for examples and [mocking.md](mocking.md) for mocking guidelines. - -## Anti-Pattern: Horizontal Slices - -**DO NOT write all tests first, then all implementation.** This is "horizontal slicing" - treating RED as "write all tests" and GREEN as "write all code." - -This produces **crap tests**: - -- Tests written in bulk test _imagined_ behavior, not _actual_ behavior -- You end up testing the _shape_ of things (data structures, function signatures) rather than user-facing behavior -- Tests become insensitive to real changes - they pass when behavior breaks, fail when behavior is fine -- You outrun your headlights, committing to test structure before understanding the implementation - -**Correct approach**: Vertical slices via tracer bullets. One test β†’ one implementation β†’ repeat. Each test responds to what you learned from the previous cycle. - -The test-writer sub-agent (below) is handed **one behavior at a time** and never sees the behavior backlog, so it can't bulk-write the suite. - -``` -WRONG (horizontal): - RED: test1, test2, test3, test4, test5 - GREEN: impl1, impl2, impl3, impl4, impl5 - -RIGHT (vertical): - REDβ†’GREEN: test1β†’impl1 - REDβ†’GREEN: test2β†’impl2 - REDβ†’GREEN: test3β†’impl3 - ... -``` - -## Roles - -Every test is written by a **test-writer sub-agent**. The main agent writes every line of implementation, and never writes or edits a test. - -The sub-agent must not read the implementation source of the module under test β€” that is what keeps its tests from asserting _how_ instead of _what_. It works from the public interface alone. - -Use one `general-purpose` sub-agent for the whole task: spawn it at the first RED, then continue it with `SendMessage` for each subsequent RED, so it keeps the test file and conventions it established. Cold-spawn a replacement only if its ID is lost. - -### Test-writer sub-agent prompt β€” include: - -- **One behavior**, quoted verbatim from the acceptance criterion or the agreed behavior list. Never the task file, never the rest of the list. -- The **public interface** under test β€” signatures only. -- The existing test file(s) for the module, and the project's test conventions (fixtures, helpers, runner invocation). -- [tests.md](tests.md) and [mocking.md](mocking.md). -- The **independent source of truth for the expected value** β€” the spec excerpt, worked example, or known-good literal. Without it the sub-agent recomputes the expected value the way the code would, and the test is tautological. -- `.claude/CONTEXT.md` (if it exists) and any ADRs in the area, so test names and interface vocabulary match the project's domain language. -- The test-side checklist from [Checklist Per Cycle](#checklist-per-cycle), pasted in full β€” the sub-agent has no other access to it. -- The brief: "Write ONE test for this behavior. Do not read the implementation source of the module under test. Write it to the test file, run it, and confirm it fails with a genuine assertion failure β€” not an import, syntax, or collection error, which prove nothing. Report the test's name and the exact failure message you saw." - -## Workflow - -### 1. Planning - -When exploring the codebase, read `.claude/CONTEXT.md` (if it exists) so that test names and interface vocabulary match the project's domain language, and respect ADRs in the area you're touching. - -Identify opportunities for deep modules (small interface, deep implementation) β€” run the `/codebase-design` skill for the vocabulary and the testability checks. Do this regardless of what triggered this workflow. - -**If a task file is already in context** (e.g. passed to `/implement`, which called this skill), its acceptance criteria are the behavior list to test β€” the interface and priorities were already agreed during `/to-spec` and `/to-tasks`. Don't re-confirm them with the user; go straight to the tracer bullet. - -**Otherwise**, before writing any code: - -- [ ] Confirm with user what interface changes are needed -- [ ] Confirm with user which behaviors to test (prioritize) -- [ ] List the behaviors to test (not implementation steps) -- [ ] Get user approval on the plan - -Ask: "What should the public interface look like? Which behaviors are most important to test?" - -**You can't test everything.** Confirm with the user exactly which behaviors matter most. Focus testing effort on critical paths and complex logic, not every possible edge case. - -Planning stays with the main agent on both paths β€” exploration, interface, and the order behaviors are tested in. The sub-agent receives behaviors one at a time; it never chooses what to test next. - -### 2. Tracer Bullet - -ONE test that confirms ONE thing about the system: - -``` -RED: Spawn the test-writer sub-agent with the first behavior β†’ it writes the test, runs it, reports a genuine failure -GREEN: Main agent writes minimal code to pass β†’ test passes -``` - -This is your tracer bullet - proves the path works end-to-end. - -### 3. Incremental Loop - -For each remaining behavior: - -``` -RED: SendMessage the same sub-agent the next behavior β†’ it writes the test, runs it, reports a genuine failure -GREEN: Main agent writes minimal code to pass β†’ passes -``` - -Rules: - -- One test at a time -- Only enough code to pass current test -- Don't anticipate future tests -- Keep tests focused on observable behavior - -### When a test looks wrong - -The main agent never edits a sub-agent-authored test β€” not to fix an import, not to "simplify" an assertion, not to reach GREEN. - -- **Mechanical defect** β€” bad import path, a fixture or helper that doesn't exist, doesn't parse. Send the error output back to the sub-agent and let it fix its own test. -- **Semantic disagreement** β€” you believe the expected value or the asserted behavior is wrong. Stop and ask the user. Do not resolve it yourself; this disagreement is the signal the sub-agent exists to surface, and half the time it's the code that's wrong. - -### 4. Refactor - -After all tests pass, look for [refactor candidates](refactoring.md): - -- [ ] Extract duplication -- [ ] Deepen modules (move complexity behind simple interfaces) -- [ ] Apply SOLID principles where natural -- [ ] Consider what new code reveals about existing code -- [ ] Run tests after each refactor step - -**Never refactor while RED.** Get to GREEN first. - -A test that breaks during refactor means the refactor broke behavior β€” fix the code. The one exception is a public interface change you made deliberately (a module deepened, a signature moved, as agreed in the plan): send the interface change to the sub-agent and let it update its own tests. There is no case where the main agent edits the test itself. - -## Checklist Per Cycle - -Test-writer sub-agent, per test β€” paste into its prompt: - -``` -[ ] Test describes behavior, not implementation -[ ] Test uses public interface only -[ ] Test would survive internal refactor -[ ] Expected values are independent literals, not recomputed from the code -``` - -Main agent, per GREEN: - -``` -[ ] Code is minimal for this test -[ ] No speculative features added -``` diff --git a/configs/nixos/modules/agents/claude-code/skills/test-driven-development/mocking.md b/configs/nixos/modules/agents/claude-code/skills/test-driven-development/mocking.md deleted file mode 100644 index 71cbfee..0000000 --- a/configs/nixos/modules/agents/claude-code/skills/test-driven-development/mocking.md +++ /dev/null @@ -1,59 +0,0 @@ -# When to Mock - -Mock at **system boundaries** only: - -- External APIs (payment, email, etc.) -- Databases (sometimes - prefer test DB) -- Time/randomness -- File system (sometimes) - -Don't mock: - -- Your own classes/modules -- Internal collaborators -- Anything you control - -## Designing for Mockability - -At system boundaries, design interfaces that are easy to mock: - -**1. Use dependency injection** - -Pass external dependencies in rather than creating them internally: - -```typescript -// Easy to mock -function processPayment(order, paymentClient) { - return paymentClient.charge(order.total); -} - -// Hard to mock -function processPayment(order) { - const client = new StripeClient(process.env.STRIPE_KEY); - return client.charge(order.total); -} -``` - -**2. Prefer SDK-style interfaces over generic fetchers** - -Create specific functions for each external operation instead of one generic function with conditional logic: - -```typescript -// GOOD: Each function is independently mockable -const api = { - getUser: (id) => fetch(`/users/${id}`), - getOrders: (userId) => fetch(`/users/${userId}/orders`), - createOrder: (data) => fetch('/orders', { method: 'POST', body: data }), -}; - -// BAD: Mocking requires conditional logic inside the mock -const api = { - fetch: (endpoint, options) => fetch(endpoint, options), -}; -``` - -The SDK approach means: -- Each mock returns one specific shape -- No conditional logic in test setup -- Easier to see which endpoints a test exercises -- Type safety per endpoint diff --git a/configs/nixos/modules/agents/claude-code/skills/test-driven-development/refactoring.md b/configs/nixos/modules/agents/claude-code/skills/test-driven-development/refactoring.md deleted file mode 100644 index 2b3cd01..0000000 --- a/configs/nixos/modules/agents/claude-code/skills/test-driven-development/refactoring.md +++ /dev/null @@ -1,10 +0,0 @@ -# Refactor Candidates - -After TDD cycle, look for: - -- **Duplication** β†’ Extract function/class -- **Long methods** β†’ Break into private helpers (keep tests on public interface) -- **Shallow modules** β†’ Combine or deepen β€” see DEEPENING.md in `/codebase-design` for dependency categories and seam discipline -- **Feature envy** β†’ Move logic to where data lives -- **Primitive obsession** β†’ Introduce value objects -- **Existing code** the new code reveals as problematic diff --git a/configs/nixos/modules/agents/claude-code/skills/test-driven-development/tests.md b/configs/nixos/modules/agents/claude-code/skills/test-driven-development/tests.md deleted file mode 100644 index 7ab8647..0000000 --- a/configs/nixos/modules/agents/claude-code/skills/test-driven-development/tests.md +++ /dev/null @@ -1,77 +0,0 @@ -# Good and Bad Tests - -## Good Tests - -**Integration-style**: Test through real interfaces, not mocks of internal parts. - -```typescript -// GOOD: Tests observable behavior -test("user can checkout with valid cart", async () => { - const cart = createCart(); - cart.add(product); - const result = await checkout(cart, paymentMethod); - expect(result.status).toBe("confirmed"); -}); -``` - -Characteristics: - -- Tests behavior users/callers care about -- Uses public API only -- Survives internal refactors -- Describes WHAT, not HOW -- One logical assertion per test - -## Bad Tests - -**Implementation-detail tests**: Coupled to internal structure. - -```typescript -// BAD: Tests implementation details -test("checkout calls paymentService.process", async () => { - const mockPayment = jest.mock(paymentService); - await checkout(cart, payment); - expect(mockPayment.process).toHaveBeenCalledWith(cart.total); -}); -``` - -Red flags: - -- Mocking internal collaborators -- Testing private methods -- Asserting on call counts/order -- Test breaks when refactoring without behavior change -- Test name describes HOW not WHAT -- Verifying through external means instead of interface - -```typescript -// BAD: Bypasses interface to verify -test("createUser saves to database", async () => { - await createUser({ name: "Alice" }); - const row = await db.query("SELECT * FROM users WHERE name = ?", ["Alice"]); - expect(row).toBeDefined(); -}); - -// GOOD: Verifies through interface -test("createUser makes user retrievable", async () => { - const user = await createUser({ name: "Alice" }); - const retrieved = await getUser(user.id); - expect(retrieved.name).toBe("Alice"); -}); -``` - -**Tautological tests**: Expected value restates the implementation, so the test passes by construction. - -```typescript -// BAD: Expected value is recomputed the way the code computes it -test("calculateTotal sums line items", () => { - const items = [{ price: 10 }, { price: 5 }]; - const expected = items.reduce((sum, i) => sum + i.price, 0); - expect(calculateTotal(items)).toBe(expected); -}); - -// GOOD: Expected value is an independent, known literal -test("calculateTotal sums line items", () => { - expect(calculateTotal([{ price: 10 }, { price: 5 }])).toBe(15); -}); -``` diff --git a/configs/nixos/modules/agents/claude-code/skills/to-spec/SKILL.md b/configs/nixos/modules/agents/claude-code/skills/to-spec/SKILL.md deleted file mode 100644 index 489c610..0000000 --- a/configs/nixos/modules/agents/claude-code/skills/to-spec/SKILL.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: to-spec -description: Turn the current conversation into a spec and write it to .claude/spec/ β€” no interview, just synthesis of what you've already discussed. -disable-model-invocation: true ---- - -This skill takes the current conversation context and codebase understanding and produces a spec. Do NOT interview the user β€” just synthesize what you already know. - -If the conversation doesn't actually contain a feature or problem to synthesize a spec from, say so and ask what it's for instead of fabricating one. - -## Process - -1. Explore the repo until you can name the existing modules, flows, and seams the feature will touch, if you haven't already. Use the project's domain glossary vocabulary throughout the spec, and respect any ADRs in the area you're touching. - -2. Sketch out the seams at which you're going to test the feature. Existing seams should be preferred to new ones. Use the highest seam possible. If new seams are needed, propose them at the highest point you can. The fewer seams across the codebase, the better - the ideal number is one. - -Check with the user that these seams match their expectations. - -3. Derive a short kebab-case feature-slug from the feature's name (e.g. `checkout-flow`). Tell the user the path you're about to write to (`.claude/spec/.md`). If a file already exists there, summarize what would change and confirm with the user before overwriting it β€” never overwrite silently. - -4. Write the spec using the format in [SPEC-FORMAT.md](./SPEC-FORMAT.md) to `.claude/spec/.md`, creating the `.claude/spec/` directory if it doesn't exist yet. diff --git a/configs/nixos/modules/agents/claude-code/skills/to-spec/SPEC-FORMAT.md b/configs/nixos/modules/agents/claude-code/skills/to-spec/SPEC-FORMAT.md deleted file mode 100644 index 15e82f8..0000000 --- a/configs/nixos/modules/agents/claude-code/skills/to-spec/SPEC-FORMAT.md +++ /dev/null @@ -1,55 +0,0 @@ -# Spec Format - -## Template - -```md -## Problem Statement - -The problem that the user is facing, from the user's perspective. - -## Solution - -The solution to the problem, from the user's perspective. - -## User Stories - -An extensive, numbered list of user stories, covering all aspects of the feature. Each user story should be in the format of: - -1. As an , I want a , so that - - -1. As a mobile bank customer, I want to see balance on my accounts, so that I can make better informed decisions about my spending - - -## Implementation Decisions - -A list of implementation decisions that were made. This can include: - -- The modules that will be built/modified -- The interfaces of those modules that will be modified -- Technical clarifications from the developer -- Architectural decisions -- Schema changes -- API contracts -- Specific interactions - -## Testing Decisions - -A list of testing decisions that were made. Include: - -- A description of what makes a good test (only test external behavior, not implementation details) -- Which modules will be tested -- Prior art for the tests (i.e. similar types of tests in the codebase) - -## Out of Scope - -A description of the things that are out of scope for this spec. - -## Further Notes - -Any further notes about the feature. -``` - -## Rules - -- **Don't include specific file paths or code snippets.** They may end up being outdated very quickly. Exception: if a prototype produced a snippet that encodes a decision more precisely than prose can (state machine, reducer, schema, type shape), inline it within the relevant decision in Implementation Decisions and note briefly that it came from a prototype. Trim to the decision-rich parts β€” not a working demo, just the important bits. diff --git a/configs/nixos/modules/agents/claude-code/skills/to-tasks/SKILL.md b/configs/nixos/modules/agents/claude-code/skills/to-tasks/SKILL.md deleted file mode 100644 index 112d382..0000000 --- a/configs/nixos/modules/agents/claude-code/skills/to-tasks/SKILL.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -name: to-tasks -description: Break a plan or spec into independently-grabbable task files under .claude/tasks/ using tracer-bullet vertical slices. -disable-model-invocation: true ---- - -# To Tasks - -## Process - -### 1. Gather context - -Work from whatever is already in the conversation context. If the user passes a spec path or other reference as an argument, read it directly. - -Determine the feature-slug this breakdown belongs to, if any: if a spec file is in context or was passed as an argument, derive it from the filename (`.claude/spec/.md` β†’ ``) for each task's `spec` field β€” see [TASK-FORMAT.md](./TASK-FORMAT.md) for the field's rules. If no spec file exists, proceed without one. - -### 2. Explore the codebase (optional) - -If you have not already explored the codebase, do so to understand the current state of the code. Task titles and descriptions should use the project's domain glossary vocabulary, and respect ADRs in the area you're touching. - -Look for opportunities to prefactor the code to make the implementation easier. "Make the change easy, then make the easy change." - -### 3. Draft vertical slices - -Break the plan into **tracer bullet** tasks β€” vertical slices, not horizontal layers. - - - -- Each slice delivers a narrow but COMPLETE path through every layer the change requires (schema, API, UI, tests), never a horizontal slice of just one -- A completed slice is demoable or verifiable on its own -- Any prefactoring should be done first - - - -### 4. Quiz the user - -Number slices with a single sequence shared across every file already in `.claude/tasks/`: scan for the highest existing `NNNN` (four-digit, zero-padded decimal, `0000`-`9999`) and increment from there. Never restart the sequence per feature and never reuse a number. - -Present the proposed breakdown as a numbered list. For each slice, show: - -- **File**: the `NNNN-slice-slug` it will be written as, per the numbering above -- **Blocked by**: which other slices (if any) must complete first β€” "None" if it can start immediately -- **User stories covered**: which user stories this addresses (if the source material has them) - -Ask the user: - -- Does the granularity feel right? (too coarse / too fine) -- Are the dependency relationships correct? -- Should any slices be merged or split further? - -Iterate until the user approves the breakdown, including the proposed numbers and slugs. - -### 5. Write the task files - -For each approved slice, write a file to `.claude/tasks/-.md` (create the directory if it doesn't exist) using the numbers and slugs approved in step 4. Use the template in [TASK-FORMAT.md](./TASK-FORMAT.md). - -Do NOT modify the parent spec file (`.claude/spec/.md`) when writing tasks. diff --git a/configs/nixos/modules/agents/claude-code/skills/to-tasks/TASK-FORMAT.md b/configs/nixos/modules/agents/claude-code/skills/to-tasks/TASK-FORMAT.md deleted file mode 100644 index 1ac9871..0000000 --- a/configs/nixos/modules/agents/claude-code/skills/to-tasks/TASK-FORMAT.md +++ /dev/null @@ -1,28 +0,0 @@ -# Task Format - -## Template - -```md ---- -spec: -blocked-by: ---- - -## What to build - -A concise description of this vertical slice. Describe the end-to-end behavior, not layer-by-layer implementation. - -## Acceptance criteria - -- [ ] Criterion 1 -- [ ] Criterion 2 -- [ ] Criterion 3 -``` - -## Rules - -- **`spec`**: the feature-slug this task was written from. Omit the field entirely if there's no spec. -- **`blocked-by`**: which other task(s) must complete before this one can start. Omit the field entirely if there are none. Each value is the blocking task's full `-` filename stem, not just its slug. A single blocker is a bare string (`blocked-by: 0010-add-schema`); more than one is a YAML list (`blocked-by: [0010-add-schema, 0011-wire-api]`). Once written, keep the field even after the referenced task is completed β€” it's a permanent record of the dependency, not a "still blocked" flag. -- **Don't include specific file paths or code snippets** in "What to build" β€” they go stale fast. Exception: if a prototype produced a snippet that encodes a decision more precisely than prose can (state machine, reducer, schema, type shape), inline it here and note briefly that it came from a prototype. Trim to the decision-rich parts β€” not a working demo, just the important bits. -- A task is done when every criterion in "Acceptance criteria" is resolved: mark `[x]` as satisfied, or `[-]` if deliberately dropped (`/implement` records the reason in the task's Implementation Notes) β€” track completion here, not anywhere else. -- A slice becomes pickable once every task named in `blocked-by` is done (all of its acceptance criteria resolved) β€” check the referenced tasks' state, not just whether the field is present. The file's number is an identifier and a rough ordering hint, not a strict gate β€” sibling slices with no blockers can be worked in parallel. diff --git a/configs/nixos/modules/agents/claude-code/skills/update-skills/SKILL.md b/configs/nixos/modules/agents/claude-code/skills/update-skills/SKILL.md deleted file mode 100644 index b0a17b3..0000000 --- a/configs/nixos/modules/agents/claude-code/skills/update-skills/SKILL.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -name: update-skills -description: Check the current project's installed library skills for upstream changes and apply the safe ones. -disable-model-invocation: true ---- - -Compares every skill listed in the current project's -`.claude/skills-lock.yaml` (see [LOCKFILE.md](../setup-skills/LOCKFILE.md) -for its schema) against both the project's own copy and the current -library source, and decides what to do about each one. Never installs a -skill that isn't already there β€” that's -[`setup-skills`](../setup-skills/SKILL.md)'s job. - -## Steps - -1. Read `.claude/skills-lock.yaml`. If it doesn't exist or is empty, tell - the user there's nothing to check and stop. - -2. For each `{name, hash}` entry, compute: - - `project_hash`: `~/.claude/skills/setup-skills/hash-dir.sh .claude/skills/` - - `library_hash`: `~/.claude/skills/setup-skills/hash-dir.sh ~/.claude/skills/library/` - - If either path is missing entirely, report that anomaly for this skill - (don't try to classify it) and move on to the next entry. - -3. Classify each entry against the table in - [LOCKFILE.md](../setup-skills/LOCKFILE.md#what-a-mismatch-means), - using `project_hash` in place of "project copy" and `library_hash` in - place of "library source". The two outcomes that need action below are - **safe update** (stored matches project, differs from library) and - **conflict** (stored differs from both). "Locally customized" needs no - message beyond the summary. - -4. If there are any safe updates, list them by name and ask for one - confirmation to apply all of them β€” unless the user's invocation - already included an explicit go-ahead argument (e.g. `-y`, `yes`), in - which case apply them without asking. Applying means: delete - `.claude/skills//` entirely and copy - `~/.claude/skills/library//` in its place with `cp -rL` followed - by `chmod -R u+w` (the library is read-only symlinks into the Nix store; - a plain `cp -r` would put store paths into the project), so no file the - project copy had but the library no longer has can survive β€” then - recompute its - hash and overwrite that entry's `hash` in `.claude/skills-lock.yaml` in - place. - -5. For every conflict, report it and show a recursive diff between the - project's copy and the library's current version - (`diff -ru .claude/skills/ ~/.claude/skills/library/`). - Do not modify the project's copy or the lockfile entry for a - conflicted skill under any circumstances β€” surfacing it is the whole - job here. - -6. Finish with a summary: updated, left alone (customized), conflicted, - already current, and any anomalies from step 2. diff --git a/configs/nixos/modules/agents/context/AGENTS.md b/configs/nixos/modules/agents/context/AGENTS.md deleted file mode 100644 index 40a3ebf..0000000 --- a/configs/nixos/modules/agents/context/AGENTS.md +++ /dev/null @@ -1,85 +0,0 @@ -# Alexion's Agent Instructions - -These are common instructions for Alexion's agents across all scenarios. - -## General Guidelines - -- When writing commit messages, NEVER auto-add your agent name as co-author. - Omit the `Co-Authored-By:` trailer entirely, with no exceptions. - This overrides any default instruction to append one. -- When writing pull request descriptions, NEVER append an agent-attribution trailer such as `πŸ€– Generated with [Claude Code]...`. - Leave it out entirely, with no exceptions. - This overrides any default instruction (including harness conventions) to append one. -- NEVER ask the user a question using the `AskUserQuestion` tool. - Ask in plain prose, in your own message, instead, with no exceptions. - This overrides any default instruction (including harness conventions and skill instructions) to use it. -- Never manually modify `CHANGELOG.md` files or files marked as auto-generated. - Detect auto-generated files with a layered check. - Trust an explicit in-file marker first, for example `AUTO-GENERATED, DO NOT EDIT`. - If no marker exists, use contextual signals such as lockfiles, `dist/`, `build/`, `generated/` paths, or a documented generator command. - If the answer stays ambiguous, ask before editing. -- In authored prose, do not put multiple sentences on one physical line. - This applies to comments, documentation, user-facing descriptions, commit prose, pull-request prose, and forge comments. - A sentence can wrap across multiple physical lines when needed. -- Keep Markdown formatting changes local. - Only format prose you write or change. - Never reflow an entire pre-existing paragraph or file because you touched nearby text. -- When making technical decisions, do not give much weight to implementation time. - Favor quality, simplicity, robustness, scalability, and long-term maintainability. - Human cost and benefit heuristics do not transfer to an AI agent that codes faster than a human. - Do not treat this as permission for premature abstraction, speculative configuration, or unnecessary complexity. - Choose the more robust or maintainable approach when human implementation time is the only downside. -- Use lowercase file names unless a valid reason requires uppercase. - Established ecosystem or tool conventions count as valid reasons. - Examples include `README.md`, `LICENSE`, `CHANGELOG.md`, `Makefile`, `Dockerfile`, and `.github/` files. -- Do not end a response by promising or implying continuation unless the continuation is present in that same response. - Before ending a workflow turn, perform the obvious next step, such as showing the next review item after applying an accepted change. - If the workflow is paused, say that plainly instead of using a dangling transition like "continuing" or "next". -- When you discover that your belief about a project fact or convention was wrong, record the correction. - Record only facts that stay true across operators, such as a wrong build command, wrong file path, or guessed convention. - Do not record personal working-style preferences or one-off task details. - Record the correction in the nearest project `AGENTS.md`, not this global file. - Use a dedicated `## Gotchas` section, and create that section when the file lacks one. - If no project `AGENTS.md` exists, ask before creating one. - Append the correction immediately without asking when a project `AGENTS.md` exists. - Briefly mention the edit in your response. - If an existing entry is wrong or stale, correct or remove it the same way. - -## Comments - -- Write comments only where they earn their place. - Keep comments concise. - Assume the reader can read code. - Explain why, not what. - Explain what only when it is genuinely non-obvious. -- Keep comments self-contained to their file. - Write comments for a reader who sees only that file. - Do not explain how another file or tool consumes a value. - The only permitted cross-file mention is a bare pointer explaining why something is absent here. -- Do not narrate history or future state in comments. - Avoid phrases such as "used to be", "now moved here", and "will be". - State the present reason a thing exists. -- Do not justify a choice against alternatives in comments. - State the positive reason the choice exists. - Include present consequences when they matter. -- Do not reference agent-facing state in comments. - This includes `.agents/`, `.claude/`, `AGENTS.md`, and `CLAUDE.md`. -- Do not use project-specific capitalized terms as glossary references in comments. - Use plain lowercase nouns unless the code requires the exact term. -- Add a file-top header only when the filename or path does not already explain the file. - The header must be one concise purpose line. - Do not write a feature inventory. -- Write placeholders plainly. - Include a present-tense action when one exists, such as `Placeholder: regenerate with on the target machine`. - Do not write `placeholder for `. -- Treat user-facing documentation strings as documentation, not comments. - They can describe behavior more fully than code comments. - They still must stay self-contained and avoid agent-state references. -- Start each sentence of a comment on its own physical line. - A comment sentence can wrap when needed. - Prefer splitting long comment sentences before wrapping them. - Do not break a line early at a comma or clause boundary to make it read as a unit. - Do not use semicolons in comments or authored prose. - Recast semicolon clauses as separate sentences. - Only reformat comments you write or change. - diff --git a/configs/nixos/modules/agents/context/context.nix b/configs/nixos/modules/agents/context/context.nix deleted file mode 100644 index 3bf6090..0000000 --- a/configs/nixos/modules/agents/context/context.nix +++ /dev/null @@ -1,21 +0,0 @@ -{ - config, - lib, - ... -}: -# Shared global instructions for agent harnesses. -let - user = config.user.name; - context = builtins.readFile ./AGENTS.md; -in -{ - config = lib.mkMerge [ - (lib.mkIf config.modules.agents.claude-code.enable { - home-manager.users.${user}.programs.claude-code.context = context; - }) - - (lib.mkIf config.modules.agents.pi.enable { - home-manager.users.${user}.programs.pi-coding-agent.context = context; - }) - ]; -} diff --git a/configs/nixos/modules/agents/pi/pi.nix b/configs/nixos/modules/agents/pi/pi.nix deleted file mode 100644 index 2a0d2c6..0000000 --- a/configs/nixos/modules/agents/pi/pi.nix +++ /dev/null @@ -1,81 +0,0 @@ -{ - config, - lib, - pkgs, - ... -}: -# Pi, a terminal coding agent, for the primary user, configured through -# home-manager, which ships the package and manages ~/.pi/agent. -# The login credential is left unmanaged, so it survives rebuilds. -let - cfg = config.modules.agents.pi; - user = config.user.name; - piDir = "${config.users.users.${user}.home}/.pi/agent"; - patchedPi = pkgs.pi-coding-agent.overrideAttrs (old: { - patches = (old.patches or [ ]) ++ [ - ./patches/pi-flex-spacer.patch - ./patches/pi-tool-lookup-validation.patch - ]; - }); - herdrPiIntegration = pkgs.stdenvNoCC.mkDerivation { - name = "herdr-pi-integration"; - nativeBuildInputs = [ pkgs.herdr ]; - phases = [ "installPhase" ]; - installPhase = '' - mkdir -p $TMPDIR/home/.pi/agent/extensions - HOME=$TMPDIR/home herdr integration install pi - mkdir -p $out - cp $TMPDIR/home/.pi/agent/extensions/herdr-agent-state.ts $out/herdr-agent-state.ts - ''; - }; - piExtensions = pkgs.stdenvNoCC.mkDerivation { - name = "pi-extensions"; - phases = [ "installPhase" ]; - installPhase = '' - mkdir -p $out - cp -R ${./extensions}/. $out/ - cp ${herdrPiIntegration}/herdr-agent-state.ts $out/herdr-agent-state.ts - ''; - }; -in -{ - options.modules.agents.pi = { - enable = lib.mkEnableOption '' - Pi, a terminal coding agent, configured via home-manager''; - }; - - config = lib.mkIf cfg.enable { - home-manager.users.${user} = { - programs.pi-coding-agent = { - enable = true; - package = patchedPi; - - settings = { - defaultProvider = "openai-codex"; - defaultModel = "gpt-5.5"; - defaultThinkingLevel = "medium"; - theme = "dark"; - enableInstallTelemetry = false; - enableAnalytics = false; - }; - }; - - home.file = - { - # The first declarative rollout replaces the interactive settings file. - # Login state stays in auth.json, which this module does not manage. - "${piDir}/settings.json".force = true; - - "${piDir}/extensions" = { - source = piExtensions; - recursive = true; - }; - - "${piDir}/prompts" = { - source = ./prompts; - recursive = true; - }; - }; - }; - }; -} diff --git a/configs/nixos/modules/agents/skills.nix b/configs/nixos/modules/agents/skills.nix deleted file mode 100644 index 100e3f1..0000000 --- a/configs/nixos/modules/agents/skills.nix +++ /dev/null @@ -1,18 +0,0 @@ -{ - config, - inputs, - pkgs, - ... -}: -# Global agent skills, placed under the skills directory so they are active in -# every project. -let - user = config.user.name; - - # The skills installed globally, as derivations from the skills flake. - skills = with inputs.skills.packages.${pkgs.stdenv.hostPlatform.system}; [ ]; -in -{ - home-manager.sharedModules = [ inputs.skills.homeModules.default ]; - home-manager.users.${user}.programs.agents.skills = skills; -} diff --git a/configs/nixos/modules/agents/tools/gitea-axi.nix b/configs/nixos/modules/shell/gitea-axi.nix similarity index 81% rename from configs/nixos/modules/agents/tools/gitea-axi.nix rename to configs/nixos/modules/shell/gitea-axi.nix index a0ffeca..b35d4e6 100644 --- a/configs/nixos/modules/agents/tools/gitea-axi.nix +++ b/configs/nixos/modules/shell/gitea-axi.nix @@ -6,11 +6,11 @@ }: # gitea-axi for the primary user, installed through its own home-manager module. let - cfg = config.modules.agents.tools.gitea-axi; + cfg = config.modules.shell.gitea-axi; user = config.user.name; in { - options.modules.agents.tools.gitea-axi.enable = + options.modules.shell.gitea-axi.enable = lib.mkEnableOption "gitea-axi, an agent-ergonomic CLI for Gitea issues and pull requests"; config = lib.mkIf cfg.enable { diff --git a/configs/nixos/modules/shell/herdr.nix b/configs/nixos/modules/shell/herdr.nix index b3d1709..e2c530d 100644 --- a/configs/nixos/modules/shell/herdr.nix +++ b/configs/nixos/modules/shell/herdr.nix @@ -18,6 +18,8 @@ in home-manager.users.${user} = { home.packages = [ pkgs.herdr ]; + # Refresh the vendored project-local Pi extension from the monorepo root. + # Run `PI_CODING_AGENT_DIR=$PWD/.pi herdr integration install pi`. xdg.configFile."herdr/config.toml".text = '' [keys] prefix = "ctrl+space" diff --git a/configs/nixos/modules/shell/pi/default.nix b/configs/nixos/modules/shell/pi/default.nix new file mode 100644 index 0000000..feb1ae6 --- /dev/null +++ b/configs/nixos/modules/shell/pi/default.nix @@ -0,0 +1,48 @@ +{ + config, + lib, + pkgs, + ... +}: +# Pi, a terminal coding agent, for the primary user, configured through +# home-manager, which ships the package and manages global settings. +# The login credential is left unmanaged, so it survives rebuilds. +let + cfg = config.modules.shell.pi; + user = config.user.name; + piDir = "${config.users.users.${user}.home}/.pi/agent"; + patchedPi = pkgs.pi-coding-agent.overrideAttrs (old: { + patches = (old.patches or [ ]) ++ [ + ./patches/pi-flex-spacer.patch + ./patches/pi-tool-lookup-validation.patch + ]; + }); +in +{ + options.modules.shell.pi = { + enable = lib.mkEnableOption '' + Pi, a terminal coding agent, configured through home-manager''; + }; + + config = lib.mkIf cfg.enable { + home-manager.users.${user} = { + programs.pi-coding-agent = { + enable = true; + package = patchedPi; + + settings = { + defaultProvider = "openai-codex"; + defaultModel = "gpt-5.5"; + defaultThinkingLevel = "medium"; + theme = "dark"; + enableInstallTelemetry = false; + enableAnalytics = false; + }; + }; + + # The first declarative rollout replaces the interactive settings file. + # Login state stays in auth.json, which this module does not manage. + home.file."${piDir}/settings.json".force = true; + }; + }; +} diff --git a/configs/nixos/modules/agents/pi/patches/pi-flex-spacer.patch b/configs/nixos/modules/shell/pi/patches/pi-flex-spacer.patch similarity index 100% rename from configs/nixos/modules/agents/pi/patches/pi-flex-spacer.patch rename to configs/nixos/modules/shell/pi/patches/pi-flex-spacer.patch diff --git a/configs/nixos/modules/agents/pi/patches/pi-tool-lookup-validation.patch b/configs/nixos/modules/shell/pi/patches/pi-tool-lookup-validation.patch similarity index 100% rename from configs/nixos/modules/agents/pi/patches/pi-tool-lookup-validation.patch rename to configs/nixos/modules/shell/pi/patches/pi-tool-lookup-validation.patch diff --git a/flake.lock b/flake.lock index 1dad87c..bdd4bc7 100644 --- a/flake.lock +++ b/flake.lock @@ -72,7 +72,7 @@ "inputs": { "flake-schemas": "flake-schemas", "home-manager": "home-manager", - "nixpkgs": "nixpkgs-chaotic" + "nixpkgs": "nixpkgs" }, "locked": { "lastModified": 1785327209, @@ -203,24 +203,6 @@ "url": "https://flakehub.com/f/DeterminateSystems/flake-schemas/%3D0.5.0.tar.gz" } }, - "flake-utils": { - "inputs": { - "systems": "systems_2" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, "fromYaml": { "flake": false, "locked": { @@ -258,28 +240,6 @@ "url": "https://git.alexion.dev/alexion/gitea-axi" } }, - "gitea-axi_2": { - "inputs": { - "home-manager": "home-manager_4", - "nixpkgs": [ - "skills", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1785340481, - "narHash": "sha256-GSxdQ7w8yYfZnfkXUuZ2fYIKibe9ZU8xDGyqpeTb2tE=", - "ref": "refs/heads/main", - "rev": "627fc9a32bb80d97923540fc0d3e9661961462ba", - "revCount": 83, - "type": "git", - "url": "https://git.alexion.dev/alexion/gitea-axi" - }, - "original": { - "type": "git", - "url": "https://git.alexion.dev/alexion/gitea-axi" - } - }, "gnome-shell": { "flake": false, "locked": { @@ -361,49 +321,6 @@ "type": "github" } }, - "home-manager_4": { - "inputs": { - "nixpkgs": [ - "skills", - "gitea-axi", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1784588016, - "narHash": "sha256-ouZe80aWEhMLVMkqICFDN+JUw+0FJtCr/bh+hHtRtMg=", - "owner": "nix-community", - "repo": "home-manager", - "rev": "deeb6b7eb7e0c44ae1819c051ce175bd92a85100", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "home-manager", - "type": "github" - } - }, - "home-manager_5": { - "inputs": { - "nixpkgs": [ - "skills", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1784725727, - "narHash": "sha256-J5+C9wsO0lhDyUalQzfplDbRjyHDYeEH5+9sdyXtwa8=", - "owner": "nix-community", - "repo": "home-manager", - "rev": "041a999e8c1c5b731913855909e68d30ca69b8e0", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "home-manager", - "type": "github" - } - }, "nixos-hardware": { "inputs": { "nixpkgs": [ @@ -424,6 +341,22 @@ "type": "github" } }, + "nixpkgs": { + "locked": { + "lastModified": 1785090369, + "narHash": "sha256-m0pDuRJG7EDo9ri+4Ksu83VsI+PlxNC9lNBfydejce4=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "624af665418d3c65d544145b4d34ad696439570e", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, "nixpkgs-stable": { "locked": { "lastModified": 1785133411, @@ -456,6 +389,22 @@ "type": "github" } }, + "nixpkgs_2": { + "locked": { + "lastModified": 1785318670, + "narHash": "sha256-dN6Ou5x/+23FZLEpYP3IffO+NyJFzUlGumt1uu3MMaY=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "0954f7ee2f6bb3dc7d4e3d0d8bcb8fd4bde4cfc5", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, "nixvim": { "inputs": { "flake-parts": "flake-parts", @@ -511,38 +460,14 @@ "gitea-axi": "gitea-axi", "home-manager": "home-manager_3", "nixos-hardware": "nixos-hardware", - "nixpkgs": "nixpkgs", + "nixpkgs": "nixpkgs_2", "nixpkgs-stable": "nixpkgs-stable", "nixpkgs-unstable": "nixpkgs-unstable", "nixvim": "nixvim", - "skills": "skills", "sops-nix": "sops-nix", "stylix": "stylix" } }, - "skills": { - "inputs": { - "flake-utils": "flake-utils", - "gitea-axi": "gitea-axi_2", - "home-manager": "home-manager_5", - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1787455371, - "narHash": "sha256-MJ0bX4/cksSbGl7v9o0l+Wy1xAEa3BgzG7Hs8EEGde0=", - "ref": "refs/heads/main", - "rev": "9a1d21da4d3e70165ff772967d639df244dcc2ea", - "revCount": 55, - "type": "git", - "url": "https://git.alexion.dev/alexion/skills" - }, - "original": { - "type": "git", - "url": "https://git.alexion.dev/alexion/skills" - } - }, "sops-nix": { "inputs": { "nixpkgs": [ @@ -576,7 +501,7 @@ "nixpkgs" ], "nur": "nur", - "systems": "systems_3", + "systems": "systems_2", "tinted-kitty": "tinted-kitty", "tinted-schemes": "tinted-schemes", "tinted-tmux": "tinted-tmux", @@ -627,21 +552,6 @@ "type": "github" } }, - "systems_3": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, "tinted-kitty": { "flake": false, "locked": { @@ -705,38 +615,6 @@ "repo": "base16-zed", "type": "github" } - }, - "nixpkgs": { - "locked": { - "lastModified": 1785318670, - "narHash": "sha256-dN6Ou5x/+23FZLEpYP3IffO+NyJFzUlGumt1uu3MMaY=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "0954f7ee2f6bb3dc7d4e3d0d8bcb8fd4bde4cfc5", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs-chaotic": { - "locked": { - "lastModified": 1785090369, - "narHash": "sha256-m0pDuRJG7EDo9ri+4Ksu83VsI+PlxNC9lNBfydejce4=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "624af665418d3c65d544145b4d34ad696439570e", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 9e98980..1e1aa0f 100644 --- a/flake.nix +++ b/flake.nix @@ -62,13 +62,6 @@ inputs.nixpkgs.follows = "nixpkgs"; }; - # TODO: deprecate / remove - # Personal agent skills, packaged as per-skill derivations with a home-manager module. - skills = { - url = "git+https://git.alexion.dev/alexion/skills"; - inputs.nixpkgs.follows = "nixpkgs"; - }; - # CachyOS kernel and binary cache. # This must not follow this flake's nixpkgs because Chaotic builds against its own pinned package set. # Its cache serves the kernel only when the overlay stays on the cache-friendly path. diff --git a/src/skills/AGENTS.md b/src/skills/AGENTS.md new file mode 100644 index 0000000..e69de29 diff --git a/src/skills/README.md b/src/skills/README.md new file mode 100644 index 0000000..75ed668 --- /dev/null +++ b/src/skills/README.md @@ -0,0 +1,28 @@ +# Skills + +This directory contains monorepo-owned agent skill sources. + +## Layout + +```text +src/skills/ Monorepo-owned skill sources +.pi/skills/ Monorepo-wide enabled Pi skill symlinks +src//skills/ Project-owned skill sources +src//.pi/skills/ Project-specific enabled Pi skill symlinks +``` + +## Source ownership + +Use `src/skills/` for skills that support workflows across the monorepo. +Keep project-specific skills inside the project that owns them. + +For example, a `gitea-axi` skill belongs with the `gitea-axi` project source. +A general commit or review workflow skill belongs in `src/skills/`. + +## Enablement + +Enable Pi skills with symlinks. +Do not copy skill sources into `.pi/skills/`. + +Use root `.pi/skills/` for skills available anywhere inside the monorepo. +Use `src//.pi/skills/` for skills available only in that project subtree. -- 2.47.3