agent-trellis 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +127 -0
  3. package/dist/adapters/claude-code.d.ts +23 -0
  4. package/dist/adapters/claude-code.js +86 -0
  5. package/dist/adapters/codex.d.ts +27 -0
  6. package/dist/adapters/codex.js +119 -0
  7. package/dist/adapters/jsonMcp.d.ts +24 -0
  8. package/dist/adapters/jsonMcp.js +84 -0
  9. package/dist/adapters/kiro.d.ts +34 -0
  10. package/dist/adapters/kiro.js +175 -0
  11. package/dist/adapters/mcpPlan.d.ts +28 -0
  12. package/dist/adapters/mcpPlan.js +83 -0
  13. package/dist/adapters/pi.d.ts +23 -0
  14. package/dist/adapters/pi.js +108 -0
  15. package/dist/adapters/symlinkPlan.d.ts +33 -0
  16. package/dist/adapters/symlinkPlan.js +120 -0
  17. package/dist/cli.d.ts +7 -0
  18. package/dist/cli.js +135 -0
  19. package/dist/commands/doctor.d.ts +88 -0
  20. package/dist/commands/doctor.js +269 -0
  21. package/dist/commands/init.d.ts +44 -0
  22. package/dist/commands/init.js +150 -0
  23. package/dist/commands/mcp.d.ts +28 -0
  24. package/dist/commands/mcp.js +70 -0
  25. package/dist/commands/migrate.d.ts +38 -0
  26. package/dist/commands/migrate.js +132 -0
  27. package/dist/commands/onboard.d.ts +50 -0
  28. package/dist/commands/onboard.js +155 -0
  29. package/dist/commands/secretsAudit.d.ts +35 -0
  30. package/dist/commands/secretsAudit.js +115 -0
  31. package/dist/commands/sync.d.ts +40 -0
  32. package/dist/commands/sync.js +91 -0
  33. package/dist/core/adapter.d.ts +133 -0
  34. package/dist/core/adapter.js +16 -0
  35. package/dist/core/canonical.d.ts +16 -0
  36. package/dist/core/canonical.js +148 -0
  37. package/dist/core/types.d.ts +201 -0
  38. package/dist/core/types.js +15 -0
  39. package/dist/lib/dirEquals.d.ts +7 -0
  40. package/dist/lib/dirEquals.js +39 -0
  41. package/dist/lib/envVarNames.d.ts +35 -0
  42. package/dist/lib/envVarNames.js +79 -0
  43. package/dist/lib/fsIdentity.d.ts +16 -0
  44. package/dist/lib/fsIdentity.js +53 -0
  45. package/dist/lib/mcpProbe.d.ts +14 -0
  46. package/dist/lib/mcpProbe.js +96 -0
  47. package/dist/lib/probeCommon.d.ts +24 -0
  48. package/dist/lib/probeCommon.js +108 -0
  49. package/dist/lib/secretEnv.d.ts +19 -0
  50. package/dist/lib/secretEnv.js +46 -0
  51. package/dist/lib/skillFile.d.ts +12 -0
  52. package/dist/lib/skillFile.js +26 -0
  53. package/dist/lib/syncArgs.d.ts +16 -0
  54. package/dist/lib/syncArgs.js +17 -0
  55. package/dist/lib/tomlSection.d.ts +57 -0
  56. package/dist/lib/tomlSection.js +162 -0
  57. package/dist/pi-bridge/bundle.js +32074 -0
  58. package/dist/pi-bridge/index.d.ts +48 -0
  59. package/dist/pi-bridge/index.js +188 -0
  60. package/dist/pi-bridge/schemaTranslate.d.ts +55 -0
  61. package/dist/pi-bridge/schemaTranslate.js +40 -0
  62. package/dist/probes/claude-code.d.ts +13 -0
  63. package/dist/probes/claude-code.js +48 -0
  64. package/dist/probes/codex.d.ts +24 -0
  65. package/dist/probes/codex.js +78 -0
  66. package/dist/probes/kiro.d.ts +12 -0
  67. package/dist/probes/kiro.js +48 -0
  68. package/dist/probes/pi.d.ts +14 -0
  69. package/dist/probes/pi.js +53 -0
  70. package/dist/sdk.d.ts +14 -0
  71. package/dist/sdk.js +13 -0
  72. package/docs/architecture.md +367 -0
  73. package/docs/getting-started.md +235 -0
  74. package/docs/implementation-plan.md +341 -0
  75. package/docs/research.md +175 -0
  76. package/docs/roadmap.md +484 -0
  77. package/package.json +59 -0
  78. package/schema/scope.example.yaml +33 -0
  79. package/schema/secrets.policy.example.yaml +43 -0
  80. package/schema/servers.example.yaml +87 -0
@@ -0,0 +1,341 @@
1
+ # Implementation plan
2
+
3
+ Breaks `docs/roadmap.md`'s phases into concrete tasks. P0–P2 are specified at
4
+ file/function level since they're next. P3–P4 are specified at
5
+ module/decision level. P5–P7 stay at the roadmap's level of detail — planning
6
+ them further now would be speculative, since their shape depends on what P0–P4
7
+ actually reveal.
8
+
9
+ Every task below ends in something that can fail. If a task can't fail, it
10
+ isn't specified enough yet.
11
+
12
+ ---
13
+
14
+ ## P0 — `trellis doctor` (read-only scan, no canonical source required)
15
+
16
+ > **Superseded by `openspec/changes/trellis-doctor-p0/`.** The section below
17
+ > is kept as a historical note only — the authoritative proposal, design,
18
+ > specs, and tasks for P0 live there now. Don't edit this section further.
19
+
20
+ **Key simplification**: P0 does not require `.trellis/` to exist. It
21
+ cross-compares the four agents' *own current state* against each other and
22
+ flags drift/duplication directly — this is the exact manual process used
23
+ throughout `docs/research.md`'s investigation (symlink-vs-realpath checks,
24
+ MCP handshake probes, hash comparisons), now formalized into code instead of
25
+ one-off shell commands.
26
+
27
+ ### 0.1 Shared probing library — `src/lib/`
28
+
29
+ - `src/lib/mcpProbe.ts`
30
+ - `probeMcpServer(def: McpServerDef, env: NodeJS.ProcessEnv, timeoutMs = 10_000): Promise<McpProbeResult>`
31
+ - Spawns the server, sends a JSON-RPC `initialize`, resolves on the first
32
+ `id:1` response or process exit, whichever comes first. This is a direct
33
+ port of the probe script used repeatedly by hand in this project's
34
+ research — it needs to become a tested module, not stay a throwaway
35
+ `/tmp/mcp-probe.mjs`.
36
+ - Fails the task if: a known-good server (e.g. `@modelcontextprotocol/server-memory`)
37
+ does not return `serverInfo` within the timeout in CI.
38
+
39
+ - `src/lib/fsIdentity.ts`
40
+ - `isSymlinkTo(path: string, target: string): boolean`
41
+ - `realpathDedupe(paths: string[]): Map<string /* realpath */, string[] /* original paths pointing there */>`
42
+ - This directly encodes the Codex finding from `docs/research.md`: dedup
43
+ must happen on realpath, not on path string or content hash alone
44
+ (two physical copies with identical content are *not* the same thing to
45
+ an agent's discovery logic — they show up as two entries).
46
+ - Fails the task if: given two physical copies of the same skill directory
47
+ (not symlinks), it correctly reports them as **not** deduplicated —
48
+ inverse of what `isSymlinkTo` would report for an actual symlink pair.
49
+
50
+ - `src/lib/skillFile.ts`
51
+ - `findSkillFile(dir: string): { path: string; caseCorrect: boolean } | null`
52
+ - Case-sensitivity check is explicit and reported, not silently corrected —
53
+ this is the exact bug (`skill.md` vs `SKILL.md`) found and fixed by hand
54
+ in this project's own history.
55
+
56
+ ### 0.2 Per-agent probes — `src/probes/`
57
+
58
+ Each file implements `probe(): Promise<AgentSnapshot>` where
59
+ `AgentSnapshot` is a new type (add to `src/core/types.ts`):
60
+
61
+ ```ts
62
+ interface AgentSnapshot {
63
+ agent: "claude-code" | "codex" | "kiro" | "pi";
64
+ present: boolean;
65
+ version?: string;
66
+ skillRoots: { path: string; isSymlink: boolean; target?: string; skillCount: number }[];
67
+ mcpServers: { name: string; source: "static-config" | "runtime-injected"; probe?: McpProbeResult }[];
68
+ instructionsFile?: { path: string; isSymlink: boolean; target?: string };
69
+ subagentsDir?: { path: string; isSymlink: boolean; count: number };
70
+ }
71
+ ```
72
+
73
+ - `src/probes/claude-code.ts` — reads `~/.claude.json` (`mcpServers` key),
74
+ `~/.claude/skills`, `~/.claude/agents`, `~/.claude/CLAUDE.md`. Best-understood
75
+ agent; build this one first to validate the `AgentSnapshot` shape before
76
+ committing to it for the other three.
77
+
78
+ - `src/probes/codex.ts` — shells out to `codex mcp list` rather than
79
+ hand-parsing `config.toml` (TOML section patching in P2 will need real
80
+ parsing, but P0 is read-only and `codex` already exposes this). Skill root
81
+ is `~/.agents/skills` per Codex's own convention (confirmed in
82
+ `docs/research.md`) — the probe should also check whether `~/.codex/skills`
83
+ independently contains any *non-symlink* skill directories, since that's
84
+ exactly the duplication bug already found and fixed once in this
85
+ environment and is exactly the kind of regression `doctor` exists to catch.
86
+
87
+ - `src/probes/kiro.ts` — same shape as Claude Code:
88
+ `~/.kiro/settings/mcp.json`, `~/.kiro/skills`, `~/.kiro/steering/CLAUDE.md`.
89
+
90
+ - `src/probes/pi.ts` — **has an open unknown, resolve it first**: pi's skill
91
+ discovery path was never pinned down by direct observation in this
92
+ project's research (only confirmed pi *reads* `SKILL.md`-format files and
93
+ `AGENTS.md`/`CLAUDE.md`; the actual discovery directory wasn't isolated
94
+ from the minified bundle). Before writing this probe:
95
+ 1. Run `pi --skill <path>` against a known skill dir and confirm it loads
96
+ (`pi -p "what skills do you have"` or similar, non-interactively).
97
+ 2. Check `pi config` (interactive TUI) for any skill-path setting, and
98
+ check `~/.pi/agent/settings.json` again after that interaction — the
99
+ settings file only had four keys when checked during research; a skill
100
+ path may be set here that wasn't visible before because nothing had
101
+ configured one yet.
102
+ 3. If no fixed discovery directory exists (i.e. skills are strictly
103
+ `--skill`-flag-only, per-invocation), document that as a real
104
+ constraint — the pi adapter in P4 would then need to inject the flag
105
+ via a wrapper rather than write to a filesystem location, which changes
106
+ P4's design meaningfully. **This determination gates P4's design, not
107
+ just P0's probe — do it early.**
108
+
109
+ ### 0.3 Command — `src/commands/doctor.ts`
110
+
111
+ - `runDoctor(opts: { json?: boolean }): Promise<{ exitCode: number }>`
112
+ - Calls all four probes in parallel (`Promise.allSettled` — one agent being
113
+ absent or erroring must not blank out the other three's results).
114
+ - Cross-compares snapshots pairwise for:
115
+ - Same skill name present with different realpaths across agents (drift)
116
+ - Any skill root containing a non-symlinked physical copy where a symlink
117
+ pattern is otherwise established for that agent (duplication risk)
118
+ - MCP server names present in one agent's static config that also appear
119
+ in `known_host_injected` for another — surfaces the exact class of bug
120
+ documented in `docs/research.md` (Codex + mirasim same-name collision)
121
+ *before* it causes a startup failure, not after.
122
+ - Human-readable output matches the table style already used throughout this
123
+ project's manual investigations (✅/⚠️/❌ per row) — this isn't cosmetic,
124
+ it's continuity with how these findings were actually communicated and
125
+ understood during research.
126
+ - `--json` emits `AgentSnapshot[]` for P5's SDK and any future GUI to
127
+ consume without re-parsing terminal output.
128
+ - Exit code is non-zero if any ⚠️/❌ finding exists. `doctor` is meant to be
129
+ runnable in CI/pre-commit, not just read by a human.
130
+
131
+ ### P0 acceptance
132
+
133
+ Run against *this* machine (which has known, already-diagnosed issues
134
+ documented in `docs/research.md`) and confirm `doctor` independently
135
+ rediscovers at least these three already-known findings without being told
136
+ where to look:
137
+ 1. The Codex `openspec-*` skill duplication (already fixed — confirm it now
138
+ reports clean).
139
+ 2. Any current MCP server name collision against `known_host_injected`.
140
+ 3. Any skill file with incorrect case (currently none — confirm it reports
141
+ clean, and separately unit-test the detector against a synthetic
142
+ `skill.md`).
143
+
144
+ If `doctor` can't independently reproduce findings that were previously only
145
+ found by hand, it isn't done — restating `docs/research.md`'s conclusions as
146
+ hardcoded checks doesn't count.
147
+
148
+ ---
149
+
150
+ ## P1 — `trellis sync skills` / `trellis sync instructions`
151
+
152
+ > **Done and archived** (`openspec/changes/archive/2026-09-12-trellis-sync-p1/`).
153
+ > Section kept as a historical note only — see `docs/roadmap.md` for the
154
+ > actual outcome and the two real bugs the acceptance pass caught. Don't
155
+ > edit this section further.
156
+
157
+ Depends on P0's `AgentSnapshot` to decide *what* needs syncing; this phase
158
+ adds the *write* path.
159
+
160
+ - `.trellis/` canonical source becomes real for the first time here — not
161
+ before. `src/core/canonical.ts`: `loadCanonicalSource(): CanonicalSource`
162
+ reads `~/.trellis/skills/*/SKILL.md`, `~/.trellis/agents/*.md`,
163
+ `~/.trellis/agents.md`, `~/.trellis/scope.yaml`. **Global only** — see
164
+ `docs/architecture.md` "Global vs. workspace scope". No `root` parameter,
165
+ no workspace merge; don't build toward the `.agents Protocol` draft's
166
+ global+workspace precedence model until that's an explicit decision, not
167
+ a default carried over from the draft.
168
+ - `src/adapters/claude-code.ts`, `src/adapters/codex.ts`, `src/adapters/kiro.ts`,
169
+ `src/adapters/pi.ts` each implement `TrellisAdapter` (`src/core/adapter.ts`)
170
+ for skills + instructions only — MCP is P2/P4, deliberately kept out of
171
+ this phase's scope. Pi's adapter is the same symlink shape as the other
172
+ three (`~/.pi/agent/skills`, `~/.pi/agent/AGENTS.md` — confirmed real,
173
+ persistent paths per design.md D5 in the archived P0 change; see
174
+ docs/architecture.md's corrected pi adapter notes) — P4 is only pi's MCP
175
+ bridge, not its skills/instructions, which need nothing more than what
176
+ every other agent here needs.
177
+ `plan()` filters skills/subagent profiles through `isInScope` before
178
+ producing any plan item — a skill scoped away from that adapter's `id`
179
+ must produce zero plan items for it, not a plan item that apply() later
180
+ skips. `apply()` handles three cases, not just creation:
181
+ - **create/repair**: no symlink, or a symlink pointing at the wrong
182
+ target — safe to (re)write.
183
+ - **remove**: an agent's skill directory has a Trellis-managed symlink
184
+ (its realpath resolves inside the canonical `skills/` root) whose
185
+ corresponding skill either no longer exists in canonical or was just
186
+ scoped away from this agent — this is the "delete" half of "add once,
187
+ remove once, reaches every agent," and it's easy to build only the
188
+ create path and quietly never finish this half.
189
+ - **refuse**: the path exists as a *real* directory, not a symlink at
190
+ all — this is not Trellis's to touch. Surface it as a conflict and
191
+ stop; never delete something that might be a user's own content just
192
+ because its name matches a skill Trellis also knows about.
193
+
194
+ The realpath check in the remove case is not optional: it's what makes
195
+ removal safe. Only ever delete a symlink Trellis can prove it created
196
+ (points inside the canonical source), never a same-named real directory.
197
+ - `verify()` re-runs the relevant P0 probe and diffs against canonical —
198
+ this is why P0's probes are written as reusable functions, not
199
+ doctor-command-only logic.
200
+
201
+ ### P1 acceptance
202
+
203
+ Point at a scratch `$HOME` (or a container) with none of the four agents'
204
+ skill directories populated, run `trellis sync skills`, then run `trellis
205
+ doctor` and confirm zero findings. Then manually corrupt one symlink
206
+ (point it somewhere wrong) and confirm `doctor` catches it before re-running
207
+ sync to confirm `apply()` repairs it idempotently. Separately: add one
208
+ skill scoped to `[claude-code]` only in `scope.yaml`, run `trellis sync
209
+ skills`, and confirm it appears only in Claude Code's skills directory —
210
+ not Codex's, Kiro's, or pi's.
211
+
212
+ ---
213
+
214
+ ## P2 — `trellis mcp sync`
215
+
216
+ > **Done and archived** (`openspec/changes/archive/2026-09-12-trellis-mcp-sync-p2/`).
217
+ > Section kept as a historical note only — see `docs/roadmap.md` for the
218
+ > actual outcome. Two corrections against this plan's own assumptions,
219
+ > found before/while implementing: neither `@iarna/toml` nor `smol-toml`
220
+ > survives a round-trip (both drop comments, reformat arrays) — Codex is
221
+ > patched by a hand-rolled line-based section locator/splicer instead
222
+ > (`src/lib/tomlSection.ts`), no library dependency at all; and automatic
223
+ > MCP removal turned out to be unsafe without an ownership marker a bare
224
+ > TOML/JSON key has no equivalent of (a symlink's realpath proves it for
225
+ > skills) — deferred until a lock-file mechanism exists, `plan()` only
226
+ > ever produces create/repair + conflict for MCP. Don't edit this section
227
+ > further.
228
+
229
+ The highest-risk phase — this is where `docs/research.md`'s hardest-won
230
+ constraint applies directly: **Codex's adapter must patch a TOML section in
231
+ place, never regenerate the whole file**, because Codex's own `config.toml`
232
+ holds settings Trellis has no business touching (models, trust levels), and
233
+ because mirasim writes to the same file independently.
234
+
235
+ - `src/adapters/codex.ts` (extended): needs a real TOML parser that
236
+ preserves comments/formatting outside the touched section (`@iarna/toml`
237
+ or `smol-toml` — evaluate both for round-trip fidelity before picking one;
238
+ this is exactly the kind of "don't reinvent" call `docs/research.md`
239
+ already commits to). Patch strategy: locate `[mcp_servers.<name>]` by
240
+ section header, replace only that block, leave every byte outside it
241
+ untouched.
242
+ - Collision check runs `known_host_injected` from `mcp/servers.yaml` against
243
+ every server name about to be written; refuses to write and exits non-zero
244
+ on collision, with the exact error class from `docs/research.md`
245
+ (`url is not supported for stdio`) quoted in the failure message so a user
246
+ hitting this via Trellis gets the real cause immediately instead of
247
+ rediscovering it as a mysterious Codex crash.
248
+ - Every adapter's `plan()` filters servers through `isInScope` using each
249
+ server's inline `agents:` field (not `scope.yaml` — see
250
+ `docs/architecture.md` "Private / agent-specific capabilities" for why
251
+ MCP scoping is inline) before generating anything for that agent.
252
+ - `src/adapters/claude-code.ts` / `kiro.ts` (extended): plain JSON deep-merge
253
+ under `mcpServers`, values always emitted as `${VAR}` — never a literal,
254
+ enforced by a check against `secrets.policy.yaml`'s `reject_patterns`
255
+ *before* the write happens, not just audited after (P3 audits adapter
256
+ *output*; this is a pre-write guard specific to the MCP adapter, cheaper
257
+ to catch here than after the fact).
258
+ - **Hub mode branch** (`docs/architecture.md` "MCP hub mode"): every
259
+ adapter checks `canonical.mcp.hub` first. If set, skip the whole
260
+ N-server merge/patch logic above and instead ensure exactly one static
261
+ entry (name TBD, e.g. `trellis-hub`) pointing at `hub.url` exists —
262
+ Codex still patches in place, Claude/Kiro still JSON-merge, just one
263
+ entry instead of N. The collision check still runs, just against that
264
+ one name instead of every server name.
265
+
266
+ ### P2 acceptance
267
+
268
+ Reproduce the exact Codex incident from `docs/research.md` in a sandboxed
269
+ `config.toml` (a stdio server statically defined, then a same-name `url`
270
+ server "injected" via `-c` the way mirasim does) and confirm Trellis's
271
+ collision check refuses the write *before* attempting it, rather than
272
+ letting Codex fail to start. Separately, with `hub.url` set in the fixture's
273
+ `servers.yaml`, confirm each agent's generated config contains exactly one
274
+ MCP entry regardless of how many servers are defined.
275
+
276
+ ---
277
+
278
+ ## P3 — `trellis secrets audit`
279
+
280
+ > **Done and archived** (`openspec/changes/archive/2026-09-12-trellis-secrets-audit-p3/`).
281
+ > Section kept as a historical note only — see `docs/roadmap.md` for the
282
+ > actual outcome. Built as planned, with no dependency added: env-var-name
283
+ > extraction turned out to need only real `JSON.parse` (Claude Code/Kiro)
284
+ > and a five-line regex over one TOML line shape (Codex), not a general
285
+ > parser or a reused write-path module. Don't edit this section further.
286
+
287
+ - `src/commands/secretsAudit.ts`: reads every adapter's *actual output file*
288
+ (not the canonical source — the point is catching what actually landed on
289
+ disk) and runs `secrets.policy.yaml`'s `reject_patterns` against it.
290
+ - Must catch, as regression tests, the two real incidents already on
291
+ record: a GitLab PAT stored under the wrong variable name (this is a
292
+ *naming* bug the pattern-match alone won't catch — audit also needs an
293
+ "unexpected variable name" check against `allowed_vars`, not just a
294
+ literal-value regex) and a literal token value embedded directly in a
295
+ generated config (the regex case).
296
+
297
+ ## P4 — pi MCP bridge extension
298
+
299
+ > **Done and archived** (`openspec/changes/archive/2026-09-12-trellis-pi-mcp-bridge-p4/`).
300
+ > Section kept as a historical note only — see `docs/roadmap.md` for the
301
+ > actual outcome. Built largely as sketched (a symlinked extension, no
302
+ > settings.json write, `registerTool` per MCP tool), but the delivered
303
+ > file had to become a fully bundled, dependency-free build output
304
+ > (`dist/pi-bridge/bundle.js` via `esbuild`) rather than raw TypeScript
305
+ > source — a real sandbox run against the actual `pi` binary showed a
306
+ > symlinked file's own imports resolve relative to the symlink's path,
307
+ > not its target, so an unbundled file could never resolve its
308
+ > dependencies once placed in a real user's home directory. Don't edit
309
+ > this section further.
310
+
311
+ **MCP only** — pi's skills/instructions adapter is P1's `src/adapters/pi.ts`
312
+ (same symlink shape as every other agent, see P1 above); P4 is exclusively
313
+ the MCP bridge, since pi has no native MCP client at all (docs/research.md).
314
+
315
+ §0.2's investigation resolved during P0 (design.md D5 in the archived
316
+ `trellis-doctor-p0` change): pi has a real discovery directory
317
+ (`~/.pi/agent/skills`), settling which of the two candidate shapes below
318
+ applies — the first one:
319
+
320
+ - A pi extension package, installed once, that on startup reads
321
+ `.trellis/mcp/servers.yaml`. If `mcp.hub` is unset, it opens an
322
+ `@modelcontextprotocol/sdk` `StdioClientTransport` per server and calls
323
+ pi's `registerTool` for each tool the server reports; if `mcp.hub.url` is
324
+ set, it instead opens a single `StreamableHTTPClientTransport` to that
325
+ URL — one connection instead of N processes to keep alive, see
326
+ `docs/architecture.md` "MCP hub mode".
327
+
328
+ This is the one adapter that is genuine runtime code, not a config
329
+ generator — budget real testing time against a live pi session, not just
330
+ unit tests of the bridging logic in isolation.
331
+
332
+ ---
333
+
334
+ ## Sequencing note
335
+
336
+ Build order: **Claude Code → Codex → Kiro → pi**, in that order, for every
337
+ phase through P4. Claude Code and Codex are already the best-understood
338
+ (most of `docs/research.md`'s hard-won findings are about them); Kiro is
339
+ structurally identical to Claude Code and should be fast once that shape is
340
+ validated; pi is last because it has the one open unknown (§0.2) and the one
341
+ genuinely novel engineering problem (§P4).
@@ -0,0 +1,175 @@
1
+ # Research: what already exists
2
+
3
+ Surveyed 2026-09-11. This is why Trellis is a thin orchestration layer, not a
4
+ from-scratch rebuild of anything below.
5
+
6
+ ## Rules / skills sync (crowded — don't rebuild)
7
+
8
+ | Project | Covers | Kiro | pi | MCP | Memory | Secrets |
9
+ |---|---|---|---|---|---|---|
10
+ | [block/ai-rules](https://github.com/block/ai-rules) (Rust, Apache-2.0, Block/Square) | 11 agents (AMP, Claude Code, Cline, Codex, Copilot, Cursor, Firebender, Gemini, Goose, Kilocode, Roo) | ❌ | ❌ | config generation only | ❌ | ❌ |
11
+ | [lbb00/ai-rules-sync](https://github.com/lbb00/ai-rules-sync) | Cursor/Claude/Copilot/OpenCode/Trae/Codex/Gemini/Warp | ❌ | ❌ | ❌ | ❌ | ❌ |
12
+ | [shanliuling/skills-link](https://github.com/shanliuling/skills-link) | 41+ agents, pure symlink | unlisted | unlisted | ❌ | ❌ | ❌ |
13
+ | [runkids/skillshare](https://github.com/runkids/skillshare), [qufei1993/skills-hub](https://github.com/qufei1993/skills-hub) | Codex/Claude/OpenClaw etc. | ❌ | ❌ | ❌ | ❌ | ❌ |
14
+ | [PanisHandsome/ai-rules-sync](https://github.com/PanisHandsome/ai-rules-sync) | AGENTS.md/CLAUDE.md/.cursorrules/Copilot/Windsurf/Cline/Aider/Gemini | ❌ | ❌ | ❌ | ❌ | ❌ |
15
+ | [yelmuratoff/agent_sync](https://github.com/yelmuratoff/agent_sync) | Claude/Cursor/Copilot/Gemini + 10 more | ❌ | ❌ | ❌ | ❌ | ❌ |
16
+
17
+ **No existing tool covers Kiro or pi.** None combine rules+skills with MCP,
18
+ memory, and secret hygiene as one system. That's the actual gap.
19
+
20
+ ## The `.agents Protocol` draft
21
+
22
+ [dotagentsprotocol.com](https://dotagentsprotocol.com) (status: DRAFT,
23
+ 2026-02-24, community-maintained, no vendor backing yet) proposes exactly the
24
+ directory shape this problem needs:
25
+
26
+ ```
27
+ .agents/
28
+ ├── agents.md # instructions (compatible with the AGENTS.md standard)
29
+ ├── mcp.json # MCP servers
30
+ ├── skills/*/skill.md # skill definitions
31
+ ├── agents/*/agent.md # subagent profiles
32
+ ├── tasks/*/task.md # recurring tasks
33
+ └── memories/*.md # persistent memory entries
34
+ ```
35
+
36
+ It explicitly has **no reference implementation** yet. Trellis targets
37
+ alignment with this draft rather than inventing a competing schema — being
38
+ the first working implementation is more leverage than being a sixth
39
+ standard. See [`schema/`](../schema) for where Trellis's schema matches the
40
+ draft and where it deliberately extends it (Kiro/pi adapters, secret
41
+ references, MCP env passthrough semantics — none of which the draft
42
+ specifies).
43
+
44
+ ## MCP aggregation (reuse, don't rebuild)
45
+
46
+ | Project | Stars | Model | Notes |
47
+ |---|---|---|---|
48
+ | [MetaMCP](https://github.com/metatool-ai/metamcp) | 2.6k, MIT | Docker + Postgres, namespaces, OAuth | Heaviest, most complete; remote-only endpoints need a local stdio bridge for desktop clients |
49
+ | [mcp-hub](https://github.com/ravitemer/mcp-hub) | 516★, 227 commits, MIT | Plain Node process, no Docker, single HTTP endpoint (`/mcp`), JSON config with `command`/`args`/`env` (stdio) or `url`/`headers` (remote) — matches Trellis's own `servers.yaml` fields almost 1:1 | Dynamic add/remove without client restart — SSE-pushes `servers_updated`/`tool_list_changed` to already-connected agents |
50
+ | [mcp-router](https://github.com/mcp-router/mcp-router) | — | Desktop app + CLI, Sustainable Use License | Already deployed in this environment; token-based, has a GUI. **First-hand incident**: its `MCPR_TOKEN` existed in three different values across `secrets.env`/`config.toml`/`kiro/mcp.json` simultaneously, one silently invalid — see below |
51
+
52
+ Decision: Trellis still does not implement aggregation/routing logic
53
+ itself. It generates native MCP config for each agent directly — that's
54
+ what actually needed solving, since none of the above touch Kiro/pi/Codex's
55
+ TOML format. But it now natively *supports* routing every agent through a
56
+ single external endpoint instead of N direct definitions ("hub mode," see
57
+ `docs/architecture.md`) — not a product integration, just an optional
58
+ `hub.url` field every adapter checks. What runs behind that URL (mcp-hub,
59
+ mcp-router, anything else) is the user's choice, not Trellis's; the type
60
+ (`HubConfig`) deliberately has no engine/product field to keep in sync.
61
+
62
+ The mcp-router incident above is the concrete argument for preferring a
63
+ self-hosted hub whose config Trellis generates from the same
64
+ `servers.yaml` over one managed through an external dashboard: the latter
65
+ becomes a second place "what servers exist" is defined, outside `.trellis/`
66
+ entirely, and this project hit real drift from exactly that shape of setup
67
+ before hub mode existed as a documented option.
68
+
69
+ ## Shared memory (reuse)
70
+
71
+ Checked directly against this real machine before writing this section,
72
+ rather than trusting an earlier draft's claim at face value — the result
73
+ is more nuanced than either "confirmed" or "wrong":
74
+
75
+ - `src/commands/doctor.ts`'s `DEFAULT_KNOWN_HOST_INJECTED` already lists
76
+ `"memory"`, documented there as "mirasim's actual known connectors on
77
+ this project's own machine" (P0's own empirical finding, not a guess).
78
+ Runtime-injected connectors are, by design, invisible in any agent's
79
+ *static* config — that's the entire reason `known_host_injected` exists
80
+ as a concept distinct from what a config file shows. So this likely
81
+ *is* real: mirasim probably does inject a `server-memory`-class
82
+ connector named `memory` at runtime for every session on this machine,
83
+ and no amount of reading `.claude.json`/`config.toml`/`mcp.json` could
84
+ confirm or deny that either way.
85
+ - What static config *does* show, independent of whatever mirasim
86
+ injects: Kiro alone has an *additional* memory server explicitly
87
+ configured — [`totalrecallai`](https://github.com/Auriti-Labs/kiro-memory)
88
+ (npm `totalrecallai`) — SQLite-backed (`better-sqlite3`) with a local
89
+ embedding model for semantic/vector search (`fastembed` + `onnxruntime`),
90
+ a bundled React web viewer, AGPL-3.0 licensed, its own repo literally
91
+ named `kiro-memory` (purpose-built for Kiro, later marketed as
92
+ MCP-client-agnostic). Claude Code and Codex have no additional static
93
+ memory server at all. Whatever mirasim injects at runtime is presumably
94
+ uniform across all three; this static difference is real, additional,
95
+ and Kiro-specific — the single-source fragmentation problem Trellis
96
+ exists to solve, found in the wild rather than assumed on paper.
97
+
98
+ The official reference server
99
+ [`@modelcontextprotocol/server-memory`](https://github.com/modelcontextprotocol/servers/tree/main/src/memory)
100
+ is zero-dependency, local JSON knowledge graph, MIT-licensed, maintained
101
+ as part of the official `modelcontextprotocol/servers` repo. Trellis
102
+ documents this as the default (`schema/servers.example.yaml`'s `memory`
103
+ entry) — the safe, unopinionated choice for a default any `.trellis/`
104
+ setup can adopt without pulling in native bindings or a copyleft license
105
+ obligation. It's very likely already what a mirasim-hosted session gets
106
+ via runtime injection (see `known_host_injected` above) — on a host like
107
+ that, declaring it again in `mcp/servers.yaml` collides with the
108
+ already-injected connector, exactly the class of incident
109
+ `known_host_injected` exists to refuse (see the schema example's own
110
+ comment for how to tell which situation applies). On a host with no such
111
+ injection, declaring it is what actually wires the default. Either way,
112
+ `totalrecallai`'s semantic-search/SQLite/viewer feature set belongs in
113
+ the same *opt-in upgrade* category as mem0/OpenMemory below, not silently
114
+ adopted as the default just because it happened to already be configured
115
+ on one agent, on one machine.
116
+
117
+ [mem0 / OpenMemory MCP](https://github.com/mem0ai/mem0) — "private,
118
+ local-first memory layer with a built-in UI, compatible with all MCP
119
+ clients." Mature, but needs Docker + an LLM key. Documented as the
120
+ upgrade path for cross-machine or richer semantic memory, alongside
121
+ `totalrecallai` as a second real option in that same category.
122
+
123
+ ## Secrets (formalize existing practice, don't build new infra)
124
+
125
+ The 2026 consensus across 1Password, Vault, Infisical writeups is uniform:
126
+ **configs hold references, real values resolve only at process-spawn time,
127
+ never persisted twice.** This is exactly what this environment was already
128
+ doing by hand (`${VAR}` in Claude Code's `mcpServers`, `env_vars` passthrough
129
+ in Codex's `config.toml`) before Trellis existed — confirmed working via two
130
+ real incidents during development (a GitLab PAT stored under the wrong
131
+ variable name, and three divergent values of the same router token, one of
132
+ which was silently invalid). Trellis's contribution here is `trellis secrets
133
+ audit` — a linter that fails a build if any adapter output contains a raw
134
+ credential — not a new secret store.
135
+
136
+ ## pi coding agent — the one place we write real code, not config generation
137
+
138
+ Reverse-engineered directly from the installed binary
139
+ (`@earendil-works/pi-coding-agent`, confirmed via `strings` on
140
+ `dist/bundle/chunks/*.js`, not from documentation):
141
+
142
+ - pi **natively discovers** `AGENTS.override.md`, `AGENTS.md`, `CLAUDE.md`
143
+ (in that priority order) — instructions and `SKILL.md`-format skills need
144
+ no adapter work at all.
145
+ - pi has **no native MCP client**. The only `mcpServers` string in its bundle
146
+ belongs to the Google Gen AI SDK's Vertex tool-schema translator, which
147
+ explicitly throws `"mcpServers parameter is not supported"` — unrelated to
148
+ MCP proper.
149
+ - pi's capability surface is **extensions**: JS modules loaded via
150
+ `--extension`/discovery, with a real API (`createExtension`,
151
+ `registerTool`). Giving pi access to the same MCP servers every other agent
152
+ uses means writing a bridge extension that spins up
153
+ `@modelcontextprotocol/sdk` stdio clients from `mcp.json` and registers
154
+ their tools through this API. This is the one adapter in Trellis that is
155
+ genuine code, not config templating — see
156
+ [`src/adapters/pi/`](../src/adapters/pi).
157
+
158
+ ## Codex — three hard constraints learned by breaking them
159
+
160
+ All three below came from real incidents, not upfront design, and any Codex
161
+ adapter must respect them:
162
+
163
+ 1. **Skill discovery dedups by realpath, not by content.** Two physical
164
+ copies of an identical skill directory both get listed (duplicate tool
165
+ entries); a symlink to the same target is correctly merged into one. Never
166
+ copy a skill into `~/.codex/skills` — always symlink.
167
+ 2. **`SKILL.md` must match byte-for-byte**, including case. A file saved as
168
+ `skill.md` is silently dropped from discovery — no warning, no error.
169
+ 3. **Same-name MCP server across a static config and a `-c` runtime override
170
+ is not "last write wins" — it's a field-level merge.** If Codex's own
171
+ `config.toml` defines a server as stdio (`command`) and something injects
172
+ a `url` under the same name at runtime, the *entire* `codex` process fails
173
+ to start (`url is not supported for stdio`), not just that one server.
174
+ Trellis must never let a locally-defined server name collide with a name
175
+ a host environment (like mirasim) is known to inject.