Skip to content

Commit 29ee084

Browse files
committed
harness: devin + copilot CLIs share the repo's skills, agents, and rules
Devin CLI and GitHub Copilot CLI were the two harnesses still configured by hand. Both now link out-of-store into the repo, the same way ~/.claude-* and ~/.pi do. One skills link serves both: `~/.agents/skills` is the personal-skills path each CLI reads, so their per-tool skills dirs stay unlinked — a second link would register every skill twice. Every packages/claude/agents/*.md links in per-file under the name each tool expects: `<name>.md` for Devin (it takes the Claude agent format as-is) and `<name>.agent.md` for Copilot. Global always-on rules come from the new, harness-neutral packages/agents/AGENTS.md; packages/claude/CLAUDE.md stays Claude-only because of its $CLAUDE_CONFIG_DIR paths. Of each CLI's own config only the user-editable file is managed (packages/devin/config.json, packages/copilot/settings.json), with the herdr hook paths parametrized to $HOME. ~/.copilot/config.json holds login state and the two herdr-agent-state.sh scripts are herdr-installed, so all three stay local files; doctor now fails if the Copilot login file becomes a symlink and checks each new link. Verified live: `devin skills list` reads the repo tree, `devin doctor` loads all six subagent profiles, `devin rules list` shows AGENTS always-on, and Copilot runs `--agent bulk-mechanic` and answers from copilot-instructions.md. Copilot's own skills dir is gated behind the account feature flag SKILLS_INSTRUCTIONS, false today — the link is in place but idle on that side.
1 parent 3b56bc0 commit 29ee084

8 files changed

Lines changed: 251 additions & 23 deletions

File tree

CLAUDE.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ One repo for the whole machine:
88

99
- **macOS system** — a nix-darwin + home-manager flake (`flake.nix`, `nix/`): `nix/darwin/` declares macOS defaults and every Homebrew formula/cask/mas/vscode entry, `nix/home/` declares dotfile links and CLI packages. Dotfile sources live under `packages/` (stow-style `dot-` names, linked per-file by home-manager); tool manifests under `manifests/`; `bootstrap.sh` for new Macs; `macos/defaults.sh` is Helium-browser-only.
1010
- **pi** (the `pi-mono` coding agent) — config lives under `packages/pi/` and is linked into `~/.pi` by home-manager (`nix/home/harness.nix`).
11+
- **Devin CLI and GitHub Copilot CLI** — each CLI's user-editable config lives under `packages/devin/` and `packages/copilot/`, its global rules in the shared `packages/agents/AGENTS.md`, and both read the repo's `skills/` tree through one `~/.agents/skills` link (`nix/home/harness.nix`).
1112
- **Claude Code** — a shared global `CLAUDE.md`, `skills/`, `commands/`, `rules/`, `scripts/`, and `agents/` are symlinked into `~/.claude-personal/` and `~/.claude-work/`.
1213

1314
There is no application to build/test/lint.
@@ -158,10 +159,19 @@ Why not let the `skills` CLI own installation directly (its `add`/`update`/`expe
158159
Entry names come from the package dirs at eval time, so a vendored addition is picked up by the next `make nix-switch`.
159160
That means `packages/pi/agent/settings.json`, `packages/pi/extensions/*.ts`, and `packages/pi/prompts/` are the live files the agent reads — edits here take effect immediately in `~/.pi/...`.
160161

162+
- **Devin CLI and Copilot CLI share the repo's skills and agents** (`nix/home/harness.nix`).
163+
`~/.agents/skills` is the one personal-skills path both CLIs read, so it links to `skills/` and their per-tool skills dirs are deliberately left unlinked (a second link would make every skill discovered twice).
164+
Every `packages/claude/agents/*.md` links into `~/.config/devin/agents/<name>.md` (Devin takes the Claude agent format as-is) and `~/.copilot/agents/<name>.agent.md` (Copilot needs that suffix) — one source file, two names, derived from the dir at eval time like the pi links.
165+
Of each CLI's own config only the user-editable file is managed: `~/.config/devin/config.json` and `~/.copilot/settings.json`.
166+
Both are live out-of-store links, so a write by the tool (Devin records `shell.setup_complete`; herdr rewrites its hook block on reinstall) lands in the repo as a visible `git diff`, or replaces the link with a real file if the tool writes by temp+rename (`make doctor` flags that) — review either rather than reverting blindly.
167+
**Never manage** `~/.copilot/config.json` (login state) or either `herdr-agent-state.sh` (herdr-installed, its header says a reinstall overwrites it).
168+
To check what each CLI really loaded (not just that the link exists), see README.md § "Verifying what each CLI actually loaded": `devin doctor` / `devin skills list` / `devin rules list`, and for Copilot a `--log-level debug` run.
169+
Copilot's skills dir is gated by the account feature flag `SKILLS_INSTRUCTIONS`, `false` today — the `~/.agents/skills` link is in place but idle on that side; Devin reads it now.
170+
`packages/agents/AGENTS.md` is the shared global rules file for these two CLIs (linked as `~/.config/devin/AGENTS.md` and `~/.copilot/copilot-instructions.md`); keep it harness-neutral — `packages/claude/CLAUDE.md` stays Claude-only because it uses `$CLAUDE_CONFIG_DIR` paths.
161171
- **`packages/pi/extensions/subagent/` is a directory extension** (listed without `.ts` suffix in `PI_EXTENSIONS`); the rest are single-file TS extensions.
162172
Adding a new upstream extension requires editing `PI_EXTENSIONS` in the Makefile.
163-
- **`skills/` is the single source of truth** for skills across pi and both Claude profiles.
164-
The `claude` and `pi` packages each carry a committed `skills -> ../../skills` symlink, so the harness links expose the tree at `~/.pi/skills`, `~/.claude-personal/skills`, `~/.claude-work/skills`.
173+
- **`skills/` is the single source of truth** for skills across pi, both Claude profiles, Devin CLI, and Copilot CLI.
174+
The `claude` and `pi` packages each carry a committed `skills -> ../../skills` symlink, so the harness links expose the tree at `~/.pi/skills`, `~/.claude-personal/skills`, `~/.claude-work/skills`; `~/.agents/skills` links straight to `skills/` and serves Devin and Copilot.
165175
What's *committed* under it, though, is only the authored skill dirs (their source records live in `sources.toml`); vendored skill dirs are gitignored and materialized into place (see the manifest model above), so the symlinked tree the tools read is authored-committed + vendored-materialized.
166176
- **`packages/claude/commands/`, `packages/claude/rules/`, `packages/claude/scripts/`, and `packages/claude/agents/`** are the single source of truth for user-scoped slash commands, rules, helper scripts, and subagents across both Claude profiles.
167177
`commands-link` / `rules-link` / `scripts-link` / `agents-link` symlink them into `~/.claude-personal/` and `~/.claude-work/` (not into `~/.pi/` — pi doesn't consume these; pi has its own vendored `packages/pi/extensions/subagent/agents/`).
@@ -208,9 +218,9 @@ Agents are single `.md` files fetched and tracked by `resource-manager.sh` (see
208218
## Dotfiles conventions
209219

210220
- `$HOME` dotfiles are linked per-file by home-manager (`nix/home/*.nix` pointing at `packages/` sources) — the per-file discipline is a security invariant, explained in README.md § "The nix model"; never point `home.file` at a whole directory.
211-
The harness links (`~/.claude-*`, `~/.pi`) are home-manager out-of-store symlinks (`nix/home/harness.nix`) so the linked content stays mutable; `~/.pi/agent` and `~/.pi/extensions` link per-file because pi writes state beside them.
221+
The harness links (`~/.claude-*`, `~/.pi`, `~/.agents`, `~/.config/devin`, `~/.copilot`) are home-manager out-of-store symlinks (`nix/home/harness.nix`) so the linked content stays mutable; `~/.pi/agent`, `~/.pi/extensions`, and the Devin/Copilot agent dirs link per-file because those tools write state beside them.
212222
- File names in `packages/` use the `dot-` prefix (`dot-zshrc``~/.zshrc`), mapped by the `home.file` entries in `nix/home/`.
213-
- Never add packages for credential-bearing dirs: `gh/hosts.yml`, `gcloud`, `1Password`, `op`, `github-copilot`.
223+
- Never add packages for credential-bearing dirs or files: `gh/hosts.yml`, `gcloud`, `1Password`, `op`, `github-copilot`, `~/.copilot/config.json`.
214224
- `nix/darwin/homebrew.nix` is the curated brew list (`cleanup = "uninstall"`: an undeclared install is removed on the next switch — promote keepers first); `Brewfile.dump` (gitignored) is regenerated via `make brew-dump` for re-curation diffs only. CLI packages come from nixpkgs via `nix/home/packages.nix`.
215225
- `bootstrap.sh` is the new-Mac entry point; keep it idempotent, check-then-act.
216226
- To add a new tool config, follow the numbered recipe in README.md § "Adding a new tool config"; it is the canonical version.

README.md

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# dotfiles
22

3-
One repo for the whole machine: a Nix-declared macOS setup (nix-darwin + home-manager flake, Homebrew for casks, a one-shot bootstrap) **and** portable AI coding-harness configs — shared skills, commands, rules, agents, and settings — provisioned from **one source of truth** across multiple [Claude Code](https://claude.com/claude-code) and [pi](https://github.com/badlogic/pi-mono) profiles.
3+
One repo for the whole machine: a Nix-declared macOS setup (nix-darwin + home-manager flake, Homebrew for casks, a one-shot bootstrap) **and** portable AI coding-harness configs — shared skills, commands, rules, agents, and settings — provisioned from **one source of truth** across multiple [Claude Code](https://claude.com/claude-code) profiles, [pi](https://github.com/badlogic/pi-mono), [Devin CLI](https://docs.devin.ai/cli/), and [GitHub Copilot CLI](https://docs.github.com/en/copilot/concepts/agents/about-copilot-cli).
44

55
A personal repo, published so others can borrow the architecture.
66
Paths are hardcoded to one machine — adapt before adopting (see [Adopt it](#adopt-it)).
@@ -19,7 +19,8 @@ On an existing machine, clone the repo and run `make install`.
1919
## Ideas worth stealing
2020

2121
- **One source of truth, many harnesses.**
22-
A single `skills/` tree feeds Claude Code *and* pi; edit once, every harness sees it immediately.
22+
A single `skills/` tree feeds Claude Code, pi, Devin CLI, and Copilot CLI; edit once, every harness sees it immediately.
23+
Devin and Copilot both read `~/.agents/skills`, so one link covers both; the same `agents/*.md` files link into each CLI under the name it expects.
2324
- **Two Claude profiles via `CLAUDE_CONFIG_DIR`.**
2425
`pclaude` / `wclaude` wrappers keep personal and work accounts isolated while sharing the same skills and rules.
2526
- **Per-resource source tracking.**
@@ -48,6 +49,9 @@ dotfiles/
4849
├── manifests/ # non-brew tools: go-tools.txt, npm-globals.txt, pipx-tools.txt
4950
├── macos/ # deliberately-changed macOS defaults (make macos-apply)
5051
├── packages/ # dotfile sources for $HOME (zsh, git, atuin, btop, gh, bin), linked by home-manager
52+
│ ├── agents/ # AGENTS.md — global rules shared by Devin CLI + Copilot CLI
53+
│ ├── devin/ # Devin CLI config.json
54+
│ └── copilot/ # Copilot CLI settings.json
5155
├── claude/ # Claude Code config, symlinked into both profiles
5256
│ ├── CLAUDE.md # shared global user instructions
5357
│ ├── commands/ # slash commands
@@ -63,7 +67,7 @@ dotfiles/
6367
└── scripts/ # repo tooling (doctor, drift, resource manager — not symlinked)
6468
```
6569

66-
`make install` links `skills/` and `claude/*` into both Claude profiles and `pi/` into `~/.pi` (home-manager out-of-store symlinks) — safe to re-run.
70+
`make install` links `skills/` and `claude/*` into both Claude profiles, `pi/` into `~/.pi`, and the shared skills, agents, and configs into Devin CLI and Copilot CLI (home-manager out-of-store symlinks) — safe to re-run.
6771
Because everything is symlinked, one edit here applies to every profile and both harnesses at once.
6872

6973
## What's inside
@@ -161,7 +165,7 @@ Dotfile sources stay in `packages/` with their `dot-` names;
161165
home-manager links each file individually into `$HOME`,
162166
so `~/.config/<tool>` stays a real directory and credential files written beside managed configs
163167
(e.g. `gh`'s `hosts.yml`) can never land in the repo — the old `--no-folding` invariant, kept.
164-
Never declare files from credential-bearing dirs (`gcloud`, `1Password`, `op`, `github-copilot`).
168+
Never declare files from credential-bearing dirs (`gcloud`, `1Password`, `op`, `github-copilot`) or credential-bearing files (`gh/hosts.yml`, `~/.copilot/config.json`).
165169
Every switch is a numbered generation; `make nix-rollback` returns to the previous one.
166170
Homebrew remains for casks/taps/mas (declared in `nix/darwin/homebrew.nix`, applied by the same switch,
167171
`cleanup = "uninstall"`: an undeclared install is removed on the next switch — promote keepers first).
@@ -172,11 +176,36 @@ and never let home-manager own a directory that holds mutable files.
172176

173177
### Harness links
174178

175-
The harness targets (`~/.claude-*`, `~/.pi`) are home-manager **out-of-store** symlinks into the repo working tree (`nix/home/harness.nix`) —
179+
The harness targets (`~/.claude-*`, `~/.pi`, `~/.agents`, `~/.config/devin`, `~/.copilot`) are home-manager **out-of-store** symlinks into the repo working tree (`nix/home/harness.nix`) —
176180
the linked content stays mutable, so vendored skills materialize in place and repo edits apply live.
177-
`~/.pi/agent` and `~/.pi/extensions` link per-file on purpose: pi writes state beside them,
181+
`~/.pi/agent`, `~/.pi/extensions`, and the Devin/Copilot agent dirs link per-file on purpose: those tools write state beside them,
178182
and a whole-dir link would let a tool write into the repo.
179183

184+
Devin CLI and Copilot CLI both read personal skills from `~/.agents/skills`, so one link serves both.
185+
The subagents in `packages/claude/agents/` link into `~/.config/devin/agents/<name>.md` and `~/.copilot/agents/<name>.agent.md` — same file, two names.
186+
Global always-on rules come from `packages/agents/AGENTS.md`, linked as `~/.config/devin/AGENTS.md` and `~/.copilot/copilot-instructions.md`.
187+
Of each CLI's own config, only the user-editable file is managed (`~/.config/devin/config.json`, `~/.copilot/settings.json`);
188+
`~/.copilot/config.json` holds login state and the two `herdr-agent-state.sh` hook scripts are installed by herdr, so all three stay local files.
189+
190+
#### Verifying what each CLI actually loaded
191+
192+
`make doctor` checks that the links resolve into the repo; these commands check that the tools *use* them.
193+
194+
| Check | Devin CLI | Copilot CLI |
195+
| --- | --- | --- |
196+
| Subagents | `devin doctor` — lists the loaded profiles and warns on unknown frontmatter keys | `copilot --agent <name> -p 'reply OK'` — the run header names the agent |
197+
| Skills | `devin skills list` — each skill with its `~/personal/dotfiles/skills/...` path | not yet active on this account (see below) |
198+
| Global rules | `devin rules list``AGENTS [Standard] always-on` | `copilot -s -p '…answer from your instructions only…'` — it answers from `copilot-instructions.md` |
199+
| Own config | read at `~/.config/devin/config.json` (`--config` overrides it) | `/settings` in an interactive session |
200+
201+
Copilot loads its skills dir behind an account feature flag.
202+
To read the flag: `copilot --log-level debug --log-dir /tmp/cop -p 'hi'`, then `grep SKILLS_INSTRUCTIONS /tmp/cop/*.log`.
203+
On this account it is `false` today, so the `~/.agents/skills` link is in place but idle;
204+
the 0.0.417 binary already carries the path, and Devin reads the same link now.
205+
The same debug log is the ground truth for the other two:
206+
it warns per agent file on an unknown frontmatter key (`effort` is Claude-only, and both CLIs ignore it),
207+
and Copilot maps `model: haiku` to `claude-haiku-4.5`.
208+
180209
zsh keeps its drop-in idea: `~/.zshrc` is a thin loader sourcing `~/.config/zsh/*.zsh` in `NN-` prefix order,
181210
and machine-local uncommitted overrides go in `~/.config/zsh/90-local.zsh`
182211
(a plain untracked file; see `90-local.zsh.example`).

nix/home/harness.nix

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
# AI-harness links (was `make harness-link` / stow): the Claude profiles and
2-
# ~/.pi get out-of-store symlinks into the repo working tree, so vendored
3-
# skills stay materialized/mutable and repo edits apply live.
4-
# ~/.pi/agent and ~/.pi/extensions link per-file on purpose: pi writes state
5-
# beside them, and a whole-dir link would let a tool write into the repo
6-
# (same invariant as the $HOME dotfiles). prompts/skills are whole-dir links,
7-
# matching the old stow folding.
1+
# AI-harness links (was `make harness-link` / stow): the Claude profiles, ~/.pi,
2+
# Devin CLI and Copilot CLI get out-of-store symlinks into the repo working
3+
# tree, so vendored skills stay materialized/mutable and repo edits apply live.
4+
# ~/.pi/agent, ~/.pi/extensions and the Devin/Copilot agent dirs link per-file
5+
# on purpose: those tools write state beside them, and a whole-dir link would
6+
# let a tool write into the repo (same invariant as the $HOME dotfiles).
7+
# prompts/skills are whole-dir links, matching the old stow folding.
88
{ config, lib, ... }:
99
let
1010
repo = "${config.home.homeDirectory}/personal/dotfiles";
@@ -47,7 +47,39 @@ let
4747
".pi/skills".source = link "packages/pi/skills";
4848
".pi/README.md".source = link "packages/pi/README.md";
4949
};
50+
51+
# Devin CLI and Copilot CLI both read personal skills from ~/.agents/skills,
52+
# so one link serves both — never link their per-tool skills dirs too, or a
53+
# skill is discovered twice.
54+
agentNames = map (lib.removeSuffix ".md") (
55+
builtins.attrNames (lib.filterAttrs (n: _: lib.hasSuffix ".md" n) (
56+
builtins.readDir ../../packages/claude/agents
57+
))
58+
);
59+
# Devin takes Claude's agent format as-is; Copilot wants the same file under
60+
# a `<name>.agent.md` name, hence the per-file rename here.
61+
devinAgentLinks = lib.listToAttrs (
62+
map (n: {
63+
name = ".config/devin/agents/${n}.md";
64+
value.source = link "packages/claude/agents/${n}.md";
65+
}) agentNames
66+
);
67+
copilotAgentLinks = lib.listToAttrs (
68+
map (n: {
69+
name = ".copilot/agents/${n}.agent.md";
70+
value.source = link "packages/claude/agents/${n}.md";
71+
}) agentNames
72+
);
73+
# Only the user-editable config of each CLI is managed. ~/.copilot/config.json
74+
# (login state), and both herdr-installed hook scripts, stay local files.
75+
cliLinks = {
76+
".agents/skills".source = link "skills";
77+
".config/devin/config.json".source = link "packages/devin/config.json";
78+
".config/devin/AGENTS.md".source = link "packages/agents/AGENTS.md";
79+
".copilot/settings.json".source = link "packages/copilot/settings.json";
80+
".copilot/copilot-instructions.md".source = link "packages/agents/AGENTS.md";
81+
};
5082
in
5183
{
52-
home.file = claudeLinks // piLinks;
84+
home.file = claudeLinks // piLinks // devinAgentLinks // copilotAgentLinks // cliLinks;
5385
}

packages/agents/AGENTS.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# AGENTS.md
2+
3+
Global, always-on rules for the CLI coding agents that are not Claude Code:
4+
Devin CLI reads this file at `~/.config/devin/AGENTS.md`,
5+
GitHub Copilot CLI reads the same content at `~/.copilot/copilot-instructions.md`.
6+
Both links come from `packages/agents/AGENTS.md` in the dotfiles repo.
7+
8+
Claude Code has its own file (`packages/claude/CLAUDE.md`),
9+
because it uses `$CLAUDE_CONFIG_DIR` paths and profile-specific tooling that do not apply here.
10+
Keep this file harness-neutral: no tool-specific paths, no slash commands.
11+
12+
## Skills first
13+
14+
Shared skills live in `~/.agents/skills/`, which both CLIs read.
15+
Each skill is a directory with a `SKILL.md` file.
16+
Before you start a task, check for a skill that covers it, and follow the skill instead of improvising.
17+
Keep this file short — a rule that only applies to one kind of task belongs in a skill.
18+
19+
## Never publish sensitive data
20+
21+
These rules are absolute:
22+
23+
- Never commit or publish passwords, API keys, or tokens.
24+
- Never commit a `.env` file.
25+
- Before each commit, examine the staged files and make sure that no secret is included.
26+
27+
## Git hygiene
28+
29+
- Never run `git add -A` or `git add .`; stage each path explicitly,
30+
so that unrelated or generated files do not go in with the change.
31+
- Keep each commit small and limited to one logical change.
32+
- Do not add AI attribution lines to commit messages or pull request descriptions.
33+
- Do not force-push or rewrite published history unless the user asks for it.
34+
- Push the branch and let the user open the pull request,
35+
unless the project instructions permit you to open it.
36+
37+
## Communication
38+
39+
- Write in ASD-STE100 Simplified Technical English.
40+
- Lead with the result, and keep the answer short.
41+
- Report outcomes correctly:
42+
if a test fails, show the output;
43+
if you skipped a step, say so.
44+
45+
## Markdown style
46+
47+
Write all markdown with semantic line breaks (<https://sembr.org/>).
48+
Break the line after each sentence,
49+
and after an independent clause when this makes the structure more clear.
50+
CommonMark renders a single newline as a space,
51+
thus the rendered output does not change,
52+
but each `git diff` shows one thought per line.
53+
Do not rewrap a full paragraph only to change one sentence.

packages/copilot/settings.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"hooks": {
3+
"SessionStart": [
4+
{
5+
"bash": "bash \"$HOME/.copilot/hooks/herdr-agent-state.sh\"",
6+
"timeoutSec": 10,
7+
"type": "command"
8+
}
9+
]
10+
},
11+
"renderMarkdown": true,
12+
"theme": "auto"
13+
}

0 commit comments

Comments
 (0)