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.
- package/LICENSE +21 -0
- package/README.md +127 -0
- package/dist/adapters/claude-code.d.ts +23 -0
- package/dist/adapters/claude-code.js +86 -0
- package/dist/adapters/codex.d.ts +27 -0
- package/dist/adapters/codex.js +119 -0
- package/dist/adapters/jsonMcp.d.ts +24 -0
- package/dist/adapters/jsonMcp.js +84 -0
- package/dist/adapters/kiro.d.ts +34 -0
- package/dist/adapters/kiro.js +175 -0
- package/dist/adapters/mcpPlan.d.ts +28 -0
- package/dist/adapters/mcpPlan.js +83 -0
- package/dist/adapters/pi.d.ts +23 -0
- package/dist/adapters/pi.js +108 -0
- package/dist/adapters/symlinkPlan.d.ts +33 -0
- package/dist/adapters/symlinkPlan.js +120 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.js +135 -0
- package/dist/commands/doctor.d.ts +88 -0
- package/dist/commands/doctor.js +269 -0
- package/dist/commands/init.d.ts +44 -0
- package/dist/commands/init.js +150 -0
- package/dist/commands/mcp.d.ts +28 -0
- package/dist/commands/mcp.js +70 -0
- package/dist/commands/migrate.d.ts +38 -0
- package/dist/commands/migrate.js +132 -0
- package/dist/commands/onboard.d.ts +50 -0
- package/dist/commands/onboard.js +155 -0
- package/dist/commands/secretsAudit.d.ts +35 -0
- package/dist/commands/secretsAudit.js +115 -0
- package/dist/commands/sync.d.ts +40 -0
- package/dist/commands/sync.js +91 -0
- package/dist/core/adapter.d.ts +133 -0
- package/dist/core/adapter.js +16 -0
- package/dist/core/canonical.d.ts +16 -0
- package/dist/core/canonical.js +148 -0
- package/dist/core/types.d.ts +201 -0
- package/dist/core/types.js +15 -0
- package/dist/lib/dirEquals.d.ts +7 -0
- package/dist/lib/dirEquals.js +39 -0
- package/dist/lib/envVarNames.d.ts +35 -0
- package/dist/lib/envVarNames.js +79 -0
- package/dist/lib/fsIdentity.d.ts +16 -0
- package/dist/lib/fsIdentity.js +53 -0
- package/dist/lib/mcpProbe.d.ts +14 -0
- package/dist/lib/mcpProbe.js +96 -0
- package/dist/lib/probeCommon.d.ts +24 -0
- package/dist/lib/probeCommon.js +108 -0
- package/dist/lib/secretEnv.d.ts +19 -0
- package/dist/lib/secretEnv.js +46 -0
- package/dist/lib/skillFile.d.ts +12 -0
- package/dist/lib/skillFile.js +26 -0
- package/dist/lib/syncArgs.d.ts +16 -0
- package/dist/lib/syncArgs.js +17 -0
- package/dist/lib/tomlSection.d.ts +57 -0
- package/dist/lib/tomlSection.js +162 -0
- package/dist/pi-bridge/bundle.js +32074 -0
- package/dist/pi-bridge/index.d.ts +48 -0
- package/dist/pi-bridge/index.js +188 -0
- package/dist/pi-bridge/schemaTranslate.d.ts +55 -0
- package/dist/pi-bridge/schemaTranslate.js +40 -0
- package/dist/probes/claude-code.d.ts +13 -0
- package/dist/probes/claude-code.js +48 -0
- package/dist/probes/codex.d.ts +24 -0
- package/dist/probes/codex.js +78 -0
- package/dist/probes/kiro.d.ts +12 -0
- package/dist/probes/kiro.js +48 -0
- package/dist/probes/pi.d.ts +14 -0
- package/dist/probes/pi.js +53 -0
- package/dist/sdk.d.ts +14 -0
- package/dist/sdk.js +13 -0
- package/docs/architecture.md +367 -0
- package/docs/getting-started.md +235 -0
- package/docs/implementation-plan.md +341 -0
- package/docs/research.md +175 -0
- package/docs/roadmap.md +484 -0
- package/package.json +59 -0
- package/schema/scope.example.yaml +33 -0
- package/schema/secrets.policy.example.yaml +43 -0
- package/schema/servers.example.yaml +87 -0
package/docs/roadmap.md
ADDED
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
# Roadmap
|
|
2
|
+
|
|
3
|
+
Each phase must ship with a `trellis doctor` check that verifies its own
|
|
4
|
+
claim — no phase is "done" on the strength of a config file existing, only
|
|
5
|
+
on the strength of a passing verification against the real running agent.
|
|
6
|
+
|
|
7
|
+
See [`implementation-plan.md`](implementation-plan.md) for P1–P4 broken down
|
|
8
|
+
to file/function level.
|
|
9
|
+
|
|
10
|
+
**P0 is done and archived** (`openspec/changes/archive/2026-09-12-trellis-doctor-p0/`;
|
|
11
|
+
living spec at `openspec/specs/agent-state-probing/` and
|
|
12
|
+
`openspec/specs/capability-drift-detection/`). Verified against this real
|
|
13
|
+
machine, not just fixtures: independently rediscovered the already-fixed
|
|
14
|
+
Codex `openspec-*` duplication as clean, found a real, previously-unknown
|
|
15
|
+
MCP name collision (`sentry` statically configured on Claude Code and Kiro
|
|
16
|
+
while also being a `known_host_injected` name), and found a real wrong-case
|
|
17
|
+
skill file (`e2e-test`). `trellis doctor` (no flags) does none of this by
|
|
18
|
+
spawning anything — MCP handshake probing is opt-in via `--probe-mcp`
|
|
19
|
+
(see `docs/architecture.md`), added after a real run showed the default
|
|
20
|
+
had a much larger blast radius than "read-only" should mean.
|
|
21
|
+
|
|
22
|
+
This is the format every later phase uses: one `openspec/changes/<phase>/`
|
|
23
|
+
directory per phase, `openspec new change <name>` → tasks implemented →
|
|
24
|
+
`openspec archive <name>`. `implementation-plan.md`'s P0/P1 sections are
|
|
25
|
+
kept only as historical notes; don't edit them further.
|
|
26
|
+
|
|
27
|
+
**P1 is done and archived** (`openspec/changes/archive/2026-09-12-trellis-sync-p1/`;
|
|
28
|
+
living spec at `openspec/specs/canonical-source-loading/` and
|
|
29
|
+
`openspec/specs/skill-instructions-sync/`). Covers all four agents,
|
|
30
|
+
including pi — a corrected assumption found while starting this change
|
|
31
|
+
(pi previously assumed to need "no adapter"; it needs the same symlink
|
|
32
|
+
treatment as the other three once its real global directory,
|
|
33
|
+
`~/.pi/agent/skills`, was confirmed in P0). Verified against a scratch
|
|
34
|
+
`$HOME` and `scripts/sandbox.sh`'s real container, never this developer's
|
|
35
|
+
actual dotfiles. The acceptance pass itself caught two real bugs before
|
|
36
|
+
they'd have shipped: a removal check that used `realpathSync` and
|
|
37
|
+
silently never fired on a symlink whose canonical target had just been
|
|
38
|
+
deleted (exactly the case it existed to catch — broken symlinks throw on
|
|
39
|
+
realpath), and a plural/singular string mismatch between the CLI's
|
|
40
|
+
`target` option and `AdapterPlanItem.kind` that made `trellis sync skills`
|
|
41
|
+
silently apply nothing while reporting "already in sync." Both were only
|
|
42
|
+
found because the sandbox was actually run end-to-end, not just unit
|
|
43
|
+
tests of the pieces — see docs/architecture.md's testing philosophy.
|
|
44
|
+
|
|
45
|
+
**P2 is done and archived** (`openspec/changes/archive/2026-09-12-trellis-mcp-sync-p2/`;
|
|
46
|
+
living spec at `openspec/specs/mcp-server-sync/`). `trellis mcp sync`
|
|
47
|
+
covers create/repair only — automatic removal is deliberately deferred: a
|
|
48
|
+
symlink's realpath proves Trellis ownership for skills, but a bare TOML/
|
|
49
|
+
JSON key has no equivalent marker, so "gone from canonical" and "the user
|
|
50
|
+
configured this directly" are indistinguishable without a `trellis.lock.json`
|
|
51
|
+
ownership-tracking mechanism that doesn't exist yet. Two write mechanisms
|
|
52
|
+
were chosen only after empirically ruling out the obvious ones first: both
|
|
53
|
+
`@iarna/toml` and `smol-toml` silently drop comments and reformat arrays on
|
|
54
|
+
a bare parse→stringify round-trip (so Codex's `config.toml` is patched by a
|
|
55
|
+
hand-rolled, purpose-built line-based section locator/splicer,
|
|
56
|
+
`src/lib/tomlSection.ts`, never a general parser), and `codex mcp add
|
|
57
|
+
--env` only accepts literal `KEY=VALUE` with no bare-name form (so it's
|
|
58
|
+
never shelled out to for the general write path — writing a real secret
|
|
59
|
+
value into a config file is the exact thing the secrets policy forbids).
|
|
60
|
+
Claude Code and Kiro use a plain JSON parse/merge/stringify, safe because
|
|
61
|
+
JSON has no comments to lose. The sandbox acceptance pass caught a real
|
|
62
|
+
bug unit tests alone hadn't: a comment block immediately introducing the
|
|
63
|
+
*next* `[header]` was being swallowed into the *previous* section's range,
|
|
64
|
+
so updating that previous section would have silently deleted an
|
|
65
|
+
unrelated comment — see docs/architecture.md's testing philosophy for why
|
|
66
|
+
this project always runs the real container, not just fixtures in memory.
|
|
67
|
+
|
|
68
|
+
**P3 is done and archived** (`openspec/changes/archive/2026-09-12-trellis-secrets-audit-p3/`;
|
|
69
|
+
living spec at `openspec/specs/secrets-audit/`). `trellis secrets audit`
|
|
70
|
+
reads each present MCP-capable agent's real, on-disk config (never
|
|
71
|
+
canonical) and runs two independent checks: a whole-file scan against
|
|
72
|
+
`secrets.policy.yaml`'s `reject_patterns`, and a check of every declared
|
|
73
|
+
environment-variable *name* against `allowed_vars`. The second check
|
|
74
|
+
exists because a value-only scan structurally cannot catch the first real
|
|
75
|
+
incident on record — a GitLab PAT stored under the *wrong variable name*
|
|
76
|
+
was still a well-formed `${VAR}` reference, just the wrong name. Env-var-
|
|
77
|
+
name extraction stays dependency-free and format-narrow (real `JSON.parse`
|
|
78
|
+
for Claude Code/Kiro; a five-line regex over the one `env_vars = [...]`
|
|
79
|
+
line shape for Codex) rather than reusing P2's write-path
|
|
80
|
+
`tomlSection.ts` or adding a TOML library for reads — a deliberate,
|
|
81
|
+
separately-reasoned choice, not an oversight (P2's D2 rejected TOML
|
|
82
|
+
libraries specifically for round-trip fidelity on *writes*; that concern
|
|
83
|
+
doesn't apply to a read-only linter, but a full parser is still more than
|
|
84
|
+
this one narrow extraction needs). Both real incidents on record were
|
|
85
|
+
reproduced and caught live in the sandbox, not just in unit tests.
|
|
86
|
+
|
|
87
|
+
**P4 is done and archived** (`openspec/changes/archive/2026-09-12-trellis-pi-mcp-bridge-p4/`;
|
|
88
|
+
living spec at `openspec/specs/pi-mcp-bridge/`). pi's MCP access is a
|
|
89
|
+
single symlinked extension (`~/.pi/agent/extensions/trellis-mcp-bridge.js`),
|
|
90
|
+
not a settings.json entry — found by reading pi's own extension-loader
|
|
91
|
+
source directly (same technique as P0's D5): `~/.pi/agent/extensions/`
|
|
92
|
+
is auto-discovered on every startup, including symlinks, with zero
|
|
93
|
+
configuration. The genuinely hard problem, found only by actually running
|
|
94
|
+
the real `pi` binary against the real symlink in a dedicated sandbox
|
|
95
|
+
(`docker/pi-sandbox.Dockerfile`) rather than trusting the design on paper:
|
|
96
|
+
a symlinked file's own bare-specifier imports (`@modelcontextprotocol/sdk`,
|
|
97
|
+
`typebox`) resolve relative to the *symlink's own path*, not its
|
|
98
|
+
target — the reverse of the initial assumption — so no dependency
|
|
99
|
+
declared in Trellis's own `package.json` could ever make an unbundled
|
|
100
|
+
bridge file resolve once placed in an arbitrary user's home directory.
|
|
101
|
+
Fixed by bundling the whole bridge into one dependency-free
|
|
102
|
+
`dist/pi-bridge/bundle.js` via `esbuild`, confirmed by re-running the
|
|
103
|
+
same sandbox check: the bridge loaded cleanly (no more "Failed to load
|
|
104
|
+
extension"), reaching pi's own unrelated "no API key configured" failure
|
|
105
|
+
instead — proof positive without ever spending a real model call.
|
|
106
|
+
|
|
107
|
+
**P5 is done and archived** (`openspec/changes/archive/2026-09-12-trellis-sdk-p5/`;
|
|
108
|
+
living spec at `openspec/specs/trellis-sdk/`). `agent-trellis` now has a
|
|
109
|
+
real `"exports"` map: `import { loadCanonicalSource } from "agent-trellis"`
|
|
110
|
+
resolves without touching the CLI at all. Deliberately narrow (a curated
|
|
111
|
+
`src/sdk.ts` barrel — canonical-source loading and its types only, never
|
|
112
|
+
`src/adapters/*`/`src/commands/*`) and deliberately one package, not a
|
|
113
|
+
separately-published `@trellis/sdk` — the roadmap's naming was a working
|
|
114
|
+
label, not a monorepo commitment, and there is no second consumer yet to
|
|
115
|
+
justify that cost. Verified with a real package-resolution check
|
|
116
|
+
(`scripts/verify-sdk-export.sh`: pack the actual tarball, install it into
|
|
117
|
+
a throwaway scratch project, import via the bare `"agent-trellis"`
|
|
118
|
+
specifier), not just a source-relative `tsx` import — the two use
|
|
119
|
+
different resolution algorithms, and only the former would have caught an
|
|
120
|
+
`"exports"` map that looked right but didn't actually resolve for a real
|
|
121
|
+
consumer.
|
|
122
|
+
|
|
123
|
+
**P6 is done and archived** (`openspec/changes/archive/2026-09-12-trellis-memory-p6/`;
|
|
124
|
+
living spec at `openspec/specs/memory-defaults/`). `@modelcontextprotocol/server-memory`
|
|
125
|
+
is the documented default (`schema/servers.example.yaml`), confirmed by
|
|
126
|
+
the user over `totalrecallai` — a real, third-party memory server found
|
|
127
|
+
statically configured on this machine's Kiro install only (Claude
|
|
128
|
+
Code/Codex had none at all). No new adapter code: an unscoped `memory`
|
|
129
|
+
entry reaches all three native-config agents via P2's existing pipeline
|
|
130
|
+
and pi via P4's bridge, verified end-to-end in the real sandbox with the
|
|
131
|
+
actual `@modelcontextprotocol/server-memory` package name (never
|
|
132
|
+
spawned — `mcp sync` only writes config). One real wrinkle corrected
|
|
133
|
+
along the way: the schema example already lists `memory` under
|
|
134
|
+
`known_host_injected` (a genuine, empirically-grounded P0 finding about
|
|
135
|
+
mirasim's own runtime injection, not a stale guess), so the new default
|
|
136
|
+
entry ships commented out with guidance on which of the two situations
|
|
137
|
+
applies, rather than shipping an example that self-collides with
|
|
138
|
+
Trellis's own guard. Deliberately out of scope: auto-ingesting
|
|
139
|
+
`~/.trellis/memories/*.md` content into the running memory server's
|
|
140
|
+
store — a real, separate problem left as an open question, not silently
|
|
141
|
+
resolved.
|
|
142
|
+
|
|
143
|
+
**P7 is done and archived** (`openspec/changes/archive/2026-09-12-trellis-secrets-env-management-p7/`;
|
|
144
|
+
living specs at `openspec/specs/secret-env-resolution/`, plus
|
|
145
|
+
modifications to `pi-mcp-bridge` and `secrets-audit`). Two real gaps
|
|
146
|
+
found testing the pi bridge against this real machine, not assumed: (1)
|
|
147
|
+
`secrets audit` only ever checked declared env var *names* against an
|
|
148
|
+
allow-list, never whether a name actually resolves to a value — a
|
|
149
|
+
machine migration could carry every name across while every value stayed
|
|
150
|
+
unset, silently, until some agent's tool call 401'd; (2) the pi bridge's
|
|
151
|
+
`connectStdio` already read real secret values (unavoidable — it's
|
|
152
|
+
Trellis's own code spawning the MCP subprocess, unlike Claude Code/
|
|
153
|
+
Codex/Kiro's own native clients resolving `${VAR}` in their own process),
|
|
154
|
+
but did so unconditionally from ambient `process.env` — every credential
|
|
155
|
+
the parent `pi` process's shell exported, not just the one or two names
|
|
156
|
+
the active server declared. Fixed with one shared, dependency-free
|
|
157
|
+
resolver (`src/lib/secretEnv.ts`) both the bridge and the audit call, and
|
|
158
|
+
one new optional `secrets.policy.yaml` field, `env_file`: when set, it's
|
|
159
|
+
the *sole* source for a declared name (no fallback to ambient — a silent
|
|
160
|
+
fallback would defeat the isolation this exists to offer), read via a
|
|
161
|
+
narrow hand-rolled `KEY=VALUE` parser, same "no new dependency" reasoning
|
|
162
|
+
as P3's `envVarNames.ts`. Verified with a real spawned MCP subprocess
|
|
163
|
+
(not a mock) reading back its own env by name
|
|
164
|
+
(`test/unit/piBridge.test.ts`), proving `env_file`'s value wins even when
|
|
165
|
+
ambient holds a different one — and separately in the real (non-pi)
|
|
166
|
+
Docker sandbox, `trellis secrets audit` correctly reporting
|
|
167
|
+
`missing-env-value` and exiting non-zero. Deliberately scoped down from
|
|
168
|
+
the original plan: no second `pi`-sandbox rebuild, since the bridge's own
|
|
169
|
+
unit test already exercises the exact changed code path with the same
|
|
170
|
+
rigor, and P4's own sandbox pass already covers the pi-extension-loading
|
|
171
|
+
surface this change never touches (tasks.md 5.1 records the reasoning,
|
|
172
|
+
not a silent skip). Honestly scoped non-goal, stated rather than
|
|
173
|
+
discovered later: `missing-env-value` is authoritative for pi, but only a
|
|
174
|
+
best-effort proxy for Claude Code/Codex/Kiro — those three resolve
|
|
175
|
+
`${VAR}` in their own process, which this audit cannot observe directly.
|
|
176
|
+
|
|
177
|
+
**P8 is done and archived** (`openspec/changes/archive/2026-09-12-trellis-kiro-approved-env-vars/`;
|
|
178
|
+
living spec at `openspec/specs/kiro-env-var-approval/`). A real,
|
|
179
|
+
already-shipped correctness gap, found by reading Kiro's own installed
|
|
180
|
+
extension source directly
|
|
181
|
+
(`kiro.kiro-agent/dist/extension.js`'s `expandEnvironmentVariables`):
|
|
182
|
+
Kiro's `${VAR}` substitution is gated by a workspace/user setting,
|
|
183
|
+
`kiroAgent.mcpApprovedEnvVars` — a name absent from that list is
|
|
184
|
+
silently left as the literal, unresolved string, no error. On this real
|
|
185
|
+
machine that list was entirely empty, which is exactly why the one real
|
|
186
|
+
Kiro server needing a secret (`mcp-router`) held a literal token instead
|
|
187
|
+
of a `${VAR}` reference. Every Kiro MCP server Trellis had ever
|
|
188
|
+
generated with a `${VAR}` reference was, by default, silently broken —
|
|
189
|
+
P2 never knew this second gate existed. Fixed: the Kiro adapter now
|
|
190
|
+
also ensures every env name it references is present in
|
|
191
|
+
`kiroAgent.mcpApprovedEnvVars`, in Kiro's own global, VS-Code-style
|
|
192
|
+
`settings.json` (a different file from `~/.kiro/settings/mcp.json`,
|
|
193
|
+
shared with hundreds of unrelated editor preferences) — additive only,
|
|
194
|
+
parses the whole file and preserves every other key, refuses to touch a
|
|
195
|
+
file it can't parse. Verified in the real sandbox twice: a clean machine
|
|
196
|
+
gets the file created with exactly the needed name; an already-correct,
|
|
197
|
+
pre-seeded machine (including an unrelated key) produces zero writes and
|
|
198
|
+
comes out byte-identical. Deliberately narrow: only covers `env` names
|
|
199
|
+
(not the not-yet-built `headers` field), and only macOS's settings path
|
|
200
|
+
(Linux/Windows equivalents are the well-known convention but unverified
|
|
201
|
+
against a real install, stated as an open question).
|
|
202
|
+
|
|
203
|
+
**P9 is done and archived** (`openspec/changes/archive/2026-09-12-trellis-mcp-transport-auth/`;
|
|
204
|
+
living specs at modifications to `mcp-server-sync`, `pi-mcp-bridge`,
|
|
205
|
+
`secrets-audit`, and `kiro-env-var-approval`). `McpServerDef` gains
|
|
206
|
+
`headers?: Record<string,string>` (`${VAR}` references, same discipline
|
|
207
|
+
as `env`) and `Transport` gains `"sse"`; the dead `auth?: "oauth" |
|
|
208
|
+
"bearer-env"` field — never read anywhere in the codebase — is removed.
|
|
209
|
+
Four real, verified per-agent schemas (CLI `--help` output, Kiro's own
|
|
210
|
+
installed extension source, the MCP SDK's real `.d.ts`) converged on:
|
|
211
|
+
Claude Code and Kiro accept the identical plain headers map, rendered
|
|
212
|
+
verbatim; Codex has no generic headers concept at all, only a single
|
|
213
|
+
purpose-built `bearer_token_env_var` field (matching what its own `mcp
|
|
214
|
+
add --bearer-token-env-var` generates) — a server needing more than one
|
|
215
|
+
header is refused for Codex specifically (a real conflict, not a silent
|
|
216
|
+
drop or lossy approximation) while still reaching every other agent; the
|
|
217
|
+
pi bridge passes resolved headers into both `connectHttp` and a new
|
|
218
|
+
`connectSse` via the SDK's `requestInit.headers`. Real OAuth (browser
|
|
219
|
+
redirect, token refresh) is explicitly not built anywhere — all three
|
|
220
|
+
native-config agents already have their own working flow (`claude mcp
|
|
221
|
+
add --client-id`, `codex mcp login`, Kiro's own `oauth` schema fields);
|
|
222
|
+
pi has none and gains none here, a stated limitation. A cross-cutting
|
|
223
|
+
fix shipped alongside the feature rather than after: both P7's
|
|
224
|
+
`missing-env-value` check and P8's Kiro approved-env-vars list now see
|
|
225
|
+
names embedded in `headers` values too, via one shared
|
|
226
|
+
`declaredEnvNames` helper — shipping `headers` without teaching both of
|
|
227
|
+
them about it would have reproduced P8's own root cause for a new
|
|
228
|
+
field. Verified with a real, unmocked network test (a plain `node:http`
|
|
229
|
+
server capturing the bridge's actual outbound request headers) and in
|
|
230
|
+
the real Docker sandbox: both a bearer-token-shaped and a
|
|
231
|
+
two-header-shaped fixture server, confirming Claude Code/Kiro render
|
|
232
|
+
`headers` verbatim, Codex renders `bearer_token_env_var` or refuses with
|
|
233
|
+
the exact expected conflict message, and `secrets audit` catches a
|
|
234
|
+
headers-embedded name both on the canonical side and in a real,
|
|
235
|
+
already-written agent config file.
|
|
236
|
+
|
|
237
|
+
**Post-P9 hardening, done and archived**
|
|
238
|
+
(`openspec/changes/archive/2026-09-12-trellis-mcp-connect-timeout/`;
|
|
239
|
+
modifies `pi-mcp-bridge`). Found live, not hypothesized: a real, still-
|
|
240
|
+
published MCP server CLI (`@harness-fe/cli mcp`) that exits in under a
|
|
241
|
+
second with `stdio: "ignore"` but, spawned exactly the way a real stdio
|
|
242
|
+
MCP client must (a readable stdin pipe attached), never writes a byte
|
|
243
|
+
and never exits — indistinguishable from "still starting up" without a
|
|
244
|
+
bound. The pi bridge's `Promise.all` over every configured server had
|
|
245
|
+
no timeout on `client.connect()`, so one such server stalled the entire
|
|
246
|
+
extension load, plus every other configured server's tool registration
|
|
247
|
+
with it. Each of `connectStdio`/`connectHttp`/`connectSse` (and
|
|
248
|
+
`listTools()` after a successful connect) now races against a fixed
|
|
249
|
+
10-second timeout — the same constant `doctor --probe-mcp`'s
|
|
250
|
+
`mcpProbe.ts` already used independently, confirmed by reading it, not
|
|
251
|
+
assumed. Fixing this surfaced a second, real bug beyond the plan: a
|
|
252
|
+
timed-out `connectStdio` was leaking its spawned child process forever
|
|
253
|
+
(discovered when a test run took 12-19s instead of the expected ~300ms,
|
|
254
|
+
and confirmed by a live orphaned process still in `ps` after the test
|
|
255
|
+
exited) — every connect function now calls `transport.close()` on any
|
|
256
|
+
failure, timeout or otherwise, which for a stdio transport kills the
|
|
257
|
+
underlying process. Verified three ways: a dedicated hanging-server
|
|
258
|
+
fixture (two modes — silent from the first byte, and silent only after
|
|
259
|
+
a real `initialize` reply) proves the isolation and the process-leak
|
|
260
|
+
fix in the unit suite; the real Docker pi sandbox re-ran end to end
|
|
261
|
+
with the fix in place, hitting the exact timeout path for real (a
|
|
262
|
+
`memory` server's `npx` cold-start exceeding 10s), while two other,
|
|
263
|
+
differently-broken servers failed fast and pi still reached its own
|
|
264
|
+
unrelated "no API key" stage — proof nothing stalled. A pre-existing,
|
|
265
|
+
unrelated gap was found (not fixed here, out of this change's scope):
|
|
266
|
+
none of `sync skills`/`sync instructions`/`mcp sync`'s target filters
|
|
267
|
+
cover the bridge extension symlink's `kind: "extension"` — only bare
|
|
268
|
+
`trellis sync` delivers it.
|
|
269
|
+
|
|
270
|
+
**`pi-bridge-lifecycle`, done and archived**
|
|
271
|
+
(`openspec/changes/archive/2026-09-12-pi-bridge-lifecycle/`; modifies
|
|
272
|
+
`pi-mcp-bridge`). The bridge now owns the full lifecycle of every MCP
|
|
273
|
+
client it connects — a `clients: Set<Client>` tracked from a successful
|
|
274
|
+
`connectStdio`/`connectHttp`/`connectSse`, closed (idempotently, best-
|
|
275
|
+
effort) on pi's `session_shutdown` event or when `tools/list` fails
|
|
276
|
+
after connecting. Started as an open proposal carried forward from
|
|
277
|
+
earlier work with only 1 of 3 declared scenarios under test; closed out
|
|
278
|
+
here by adding the two missing ones — multiple connected servers are
|
|
279
|
+
*all* released on shutdown (not just the first), and calling shutdown
|
|
280
|
+
twice is a no-op on the second call, not an error or a double-close —
|
|
281
|
+
both verified against real spawned subprocesses, not mocks. The fourth
|
|
282
|
+
declared scenario ("a server that never connects is not cleaned
|
|
283
|
+
twice") needed no dedicated test: it's enforced by control flow, not a
|
|
284
|
+
runtime check — a failed connect never reaches `clients.add(client)`,
|
|
285
|
+
so it was never a candidate for cleanup in the first place, and
|
|
286
|
+
`trellis-mcp-connect-timeout`'s own tests already exercise that exact
|
|
287
|
+
failure path. Real pi-sandbox verification was judged already covered
|
|
288
|
+
by `trellis-mcp-connect-timeout`'s own Docker run, which exercised this
|
|
289
|
+
same session_shutdown/`closeAllClients` code path with no dedicated
|
|
290
|
+
second real-CLI run needed.
|
|
291
|
+
|
|
292
|
+
**`trellis-cli-init`, done and archived**
|
|
293
|
+
(`openspec/changes/archive/2026-09-12-trellis-cli-init/`; adds
|
|
294
|
+
`canonical-source-bootstrap`, modifies `capability-drift-detection`).
|
|
295
|
+
New `trellis init` command: creates `~/.trellis/agents.md`,
|
|
296
|
+
`mcp/servers.yaml` (`servers: {}`, `known_host_injected: []`), and
|
|
297
|
+
`secrets.policy.yaml` (`reject_patterns` read live from
|
|
298
|
+
`schema/secrets.policy.example.yaml`, not retyped) whenever they don't
|
|
299
|
+
already exist — per file, never overwriting real content — then prints
|
|
300
|
+
which of the four agents are present, each pointing at `trellis migrate
|
|
301
|
+
--from <agent>` (next). Turns `sync`/`mcp sync`/`secrets audit`'s
|
|
302
|
+
existing "no canonical source, create it" refusal into something an
|
|
303
|
+
actual command satisfies, closing a gap this session's own real pi
|
|
304
|
+
onboarding had to route around entirely by hand (`mkdir`, hand-copied
|
|
305
|
+
skills, hand-written `scope.yaml`). Found and fixed alongside it, not
|
|
306
|
+
separately: `doctor`'s collision check had been permanently stuck on a
|
|
307
|
+
hardcoded `DEFAULT_KNOWN_HOST_INJECTED` list since before canonical
|
|
308
|
+
source loading existed — `collectDoctorReport` already accepted an
|
|
309
|
+
override, but `src/cli.ts` never passed one, so `doctor` and `mcp
|
|
310
|
+
sync`'s own collision refusal (which does read canonical) could
|
|
311
|
+
disagree about what counts as a collision on any machine whose real
|
|
312
|
+
host-injected connectors differ from this project's own development
|
|
313
|
+
machine. `doctor` now resolves `known_host_injected` from canonical
|
|
314
|
+
when it exists, falling back to the hardcoded default only when it
|
|
315
|
+
doesn't (unchanged P0 behavior). Verified in the real Docker sandbox —
|
|
316
|
+
and that run caught a second real, unrelated bug of its own: neither
|
|
317
|
+
`docker/sandbox.Dockerfile` nor `docker/pi-sandbox.Dockerfile` copied
|
|
318
|
+
`schema/` into the image, so `init` crashed on `ENOENT` reading
|
|
319
|
+
`schema/secrets.policy.example.yaml` the first time it ran inside a
|
|
320
|
+
container — both Dockerfiles fixed, `package.json`'s own `files` array
|
|
321
|
+
also gained `schema` for the same underlying reason (the published npm
|
|
322
|
+
package didn't ship it either, and README already told users to read
|
|
323
|
+
it).
|
|
324
|
+
|
|
325
|
+
**`trellis-cli-migrate`, done and archived**
|
|
326
|
+
(`openspec/changes/archive/2026-09-12-trellis-cli-migrate/`; adds
|
|
327
|
+
`canonical-source-migration`). `trellis migrate --from <agent>`: probes
|
|
328
|
+
the named agent, then per real (non-symlinked, case-correct) skill and
|
|
329
|
+
per real instructions file, plans one of `create` (no canonical entry
|
|
330
|
+
yet), `already-migrated` (byte-identical to what's already in
|
|
331
|
+
canonical — `src/lib/dirEquals.ts`, a real recursive directory-content
|
|
332
|
+
comparison, not a name/mtime/hash shortcut), or `conflict` (differs —
|
|
333
|
+
reported, never overwritten). A symlinked or case-broken skill is
|
|
334
|
+
skipped and reported rather than migrated, since there's nothing real
|
|
335
|
+
of that agent's own to import. Canonical `agents.md` still at `trellis
|
|
336
|
+
init`'s placeholder is treated the same as "doesn't exist yet" so a
|
|
337
|
+
first real migrate always lands. No `scope.yaml` entry is ever written
|
|
338
|
+
— a migrated skill stays unscoped (visible to all agents), matching
|
|
339
|
+
`sync`'s own default. `--dry-run` computes and prints the same plan
|
|
340
|
+
with zero writes. This closes `trellis init`'s own per-agent pointer
|
|
341
|
+
message (`trellis migrate --from <agent>` — next), completing the
|
|
342
|
+
init → migrate → sync → mcp sync → secrets audit onboarding path for a
|
|
343
|
+
user who already has real content in one of the four agents. Verified
|
|
344
|
+
in the real Docker sandbox in the sequence a real user would actually
|
|
345
|
+
run it: `migrate --from claude-code` against the fixture home correctly
|
|
346
|
+
created a new canonical skill from claude-code's real content and
|
|
347
|
+
correctly conflicted on instructions (fixture's canonical `agents.md`
|
|
348
|
+
already has real content); a subsequent `trellis sync` in the same
|
|
349
|
+
container then correctly symlinked the newly migrated skill out to
|
|
350
|
+
kiro and pi (byte-identical content confirmed via `diff`), while
|
|
351
|
+
correctly reporting conflicts — not overwriting — on claude-code and
|
|
352
|
+
codex, since both already have their own real, non-canonical
|
|
353
|
+
`sample-skill` at that exact path.
|
|
354
|
+
|
|
355
|
+
**Agent auto-install scope decision, addendum to `trellis-cli-migrate`:**
|
|
356
|
+
`trellis init` prints each undetected agent's real, currently-correct
|
|
357
|
+
install command/URL (`npm install -g @anthropic-ai/claude-code`,
|
|
358
|
+
`npm install -g @openai/codex`, `npm install -g
|
|
359
|
+
@earendil-works/pi-coding-agent`, and `https://kiro.dev/downloads/`
|
|
360
|
+
for Kiro — a desktop IDE with no CLI package) — never spawns an
|
|
361
|
+
installer itself. A global package install or IDE download is exactly
|
|
362
|
+
the kind of system-wide, hard-to-reverse action this project's own
|
|
363
|
+
safety discipline requires an explicit human "yes" for, not a silent
|
|
364
|
+
side effect of running `trellis init`; a detect-and-print pointer
|
|
365
|
+
delivers "help me install the agents you support" without that risk.
|
|
366
|
+
|
|
367
|
+
**`trellis-cli-onboard`, done and archived**
|
|
368
|
+
(`openspec/changes/archive/2026-09-12-trellis-cli-onboard/`; adds
|
|
369
|
+
`onboarding-flow`). `trellis onboard` chains `init` → detect all four
|
|
370
|
+
agents (skill names/count, real-instructions presence — the exact
|
|
371
|
+
subset `migrate` can act on, nothing about MCP) → resolve a single base
|
|
372
|
+
agent → `migrate --from <base>` → `sync`, purely by calling each
|
|
373
|
+
command's own already-tested plan/apply functions — no new skill-copy,
|
|
374
|
+
symlink, or conflict-detection judgment exists in `onboard.ts` itself.
|
|
375
|
+
Base-agent resolution: zero present prints every agent's install
|
|
376
|
+
hint and stops (exit 0 — not a failure, a correct stopping point);
|
|
377
|
+
exactly one present auto-selects with no prompt; two or more resolve
|
|
378
|
+
via `--agent <id>` (the scriptable/sandbox/`--json` path) or an
|
|
379
|
+
interactive `node:readline/promises` prompt when stdin is a real
|
|
380
|
+
terminal, refusing cleanly with the present-agent list rather than
|
|
381
|
+
guessing when neither is available. `--json` never prompts even if
|
|
382
|
+
stdin happens to be a TTY, matching the same principle
|
|
383
|
+
`scripts/sandbox.sh` already uses for its own `-it`/`-i` branching.
|
|
384
|
+
Found and fixed alongside it: `trellis sync` itself had no dry-run
|
|
385
|
+
mode at all before this change (`collectSyncReport` always called
|
|
386
|
+
`adapter.apply()` unconditionally) — added as `RunSyncOptions.dryRun`,
|
|
387
|
+
independently useful on its own (`trellis sync --dry-run`) and a
|
|
388
|
+
prerequisite for `onboard --dry-run`'s own true-no-writes guarantee
|
|
389
|
+
across the whole chain. Verified in the real Docker sandbox: `trellis
|
|
390
|
+
onboard --agent claude-code` against the fixture home (which has
|
|
391
|
+
multiple present agents) produced the identical real result to running
|
|
392
|
+
`migrate --from claude-code` then `sync` by hand — same canonical
|
|
393
|
+
`sample-skill` created, same instructions conflict correctly reported
|
|
394
|
+
and left untouched, same real distribution of the migrated skill out
|
|
395
|
+
to kiro and pi.
|
|
396
|
+
|
|
397
|
+
**Merge mode — named future work, not a silent gap:** today, two or
|
|
398
|
+
more present agents with genuinely *different* real content still
|
|
399
|
+
resolves to "pick one as the base"; the others' own differing content
|
|
400
|
+
stays exactly as `migrate`/`sync` already report it (conflict,
|
|
401
|
+
untouched — never silently dropped or overwritten). Actually merging
|
|
402
|
+
differing skills/instructions from more than one agent into one
|
|
403
|
+
canonical result is real, separate design work (whose content wins per
|
|
404
|
+
file? per skill? does the user review a diff before it's written?) —
|
|
405
|
+
deliberately out of scope for `trellis-cli-onboard`, tracked here as an
|
|
406
|
+
explicit next step rather than something a user has to discover is
|
|
407
|
+
missing.
|
|
408
|
+
|
|
409
|
+
**Pre-release closed-loop audit, before first publish.** A full,
|
|
410
|
+
skeptical review of the whole new-user path — every command's usage
|
|
411
|
+
text against its real implementation, every doc claim against the
|
|
412
|
+
actual code, `package.json`'s shipped files against everything read at
|
|
413
|
+
runtime — found and fixed five real issues, none caught by the
|
|
414
|
+
existing test suite because each lived in code path or a doc claim
|
|
415
|
+
nothing exercised directly:
|
|
416
|
+
- `trellis sync --dry-run` was broken as a flags-first invocation:
|
|
417
|
+
`src/cli.ts`'s target parsing only ever checked `rest[0]`, so a flag
|
|
418
|
+
placed before a target (or with no target at all) was misread as an
|
|
419
|
+
unknown target named e.g. `"--dry-run"`. Fixed by parsing the whole
|
|
420
|
+
`rest` array for a recognized target instead of assuming position,
|
|
421
|
+
and the parsing logic (`parseSyncArgs`) was pulled out into its own
|
|
422
|
+
small, directly unit-tested function (`test/unit/cli.test.ts`) —
|
|
423
|
+
`src/cli.ts` previously had zero direct test coverage of its own argv
|
|
424
|
+
dispatch, which is exactly why a pure-parsing bug like this shipped
|
|
425
|
+
unnoticed. Adding that test surfaced a second real issue: importing
|
|
426
|
+
`cli.ts` for the pure function ran the whole CLI against the test
|
|
427
|
+
runner's own argv as an unguarded side effect (`main(...)` had no
|
|
428
|
+
entrypoint check) — fixed with the standard `import.meta.url ===
|
|
429
|
+
file://${process.argv[1]}` guard.
|
|
430
|
+
- `sync --dry-run` was undocumented in `printUsage()`, `README.md`, and
|
|
431
|
+
`docs/getting-started.md` despite being real and (once fixed) working
|
|
432
|
+
— all three now document it.
|
|
433
|
+
- `onboard.ts` had its own, second copy of `migrate`'s and `sync`'s
|
|
434
|
+
report-printing logic, and that copy was missing `migrate.ts`'s own
|
|
435
|
+
"nothing to migrate" empty-plan case — a real gap for exactly the
|
|
436
|
+
fresh-Claude-Code-install scenario this project's own new-user
|
|
437
|
+
persona represents (config present, zero skills, no instructions
|
|
438
|
+
file). Fixed by exporting and reusing `migrate.ts`'s `printPlan` and
|
|
439
|
+
`sync.ts`'s `printReport` directly instead of a second copy that
|
|
440
|
+
could silently drift.
|
|
441
|
+
- `package.json`'s `files` shipped `dist` and `schema` but not `docs` —
|
|
442
|
+
every globally-installed user's local `README.md` (which *is*
|
|
443
|
+
shipped) links to `docs/getting-started.md`, `docs/architecture.md`,
|
|
444
|
+
and `docs/roadmap.md`, none of which existed on their machine. Same
|
|
445
|
+
root cause `trellis-cli-init`'s own entry already fixed once for
|
|
446
|
+
`schema/` — the fix pattern (add it to `files`) applied again here.
|
|
447
|
+
- `docs/architecture.md`'s canonical-schema diagram listed
|
|
448
|
+
`trellis.lock.json` with no "not built yet" annotation, unlike every
|
|
449
|
+
other not-yet-built thing described in the same document — annotated.
|
|
450
|
+
|
|
451
|
+
**A near-miss caught by re-running the real installed-tarball check, not
|
|
452
|
+
by the test suite:** the first fix for `parseSyncArgs`'s test coverage
|
|
453
|
+
gap put the pure function in `src/cli.ts` itself and guarded that
|
|
454
|
+
file's top-level `main()` call with `import.meta.url ===
|
|
455
|
+
file://${process.argv[1]}` so importing the function for a test
|
|
456
|
+
wouldn't run the whole CLI as a side effect. That guard is not
|
|
457
|
+
symlink-safe: npm's `bin` entry is a symlink, and a symlink's
|
|
458
|
+
`import.meta.url` (resolved) never equals its own symlink path
|
|
459
|
+
(`process.argv[1]`, unresolved) — so the real, installed `trellis`
|
|
460
|
+
binary silently did nothing and exited 0 on every invocation. `npm
|
|
461
|
+
test` stayed green throughout, because nothing in the unit suite runs
|
|
462
|
+
through an actual symlinked bin — only `scripts/verify-cli-install.sh`
|
|
463
|
+
does, and it caught this immediately on the very next run. Fixed
|
|
464
|
+
properly by moving `parseSyncArgs` to its own zero-side-effect module
|
|
465
|
+
(`src/lib/syncArgs.ts`) instead of trying to make the entrypoint guard
|
|
466
|
+
symlink-safe — the reminder here: a real installed-package check is
|
|
467
|
+
not a redundant formality alongside the unit suite, it's the only
|
|
468
|
+
thing in this project that exercises the actual `bin` symlink at all.
|
|
469
|
+
|
|
470
|
+
| Phase | Deliverable | Depends on |
|
|
471
|
+
|---|---|---|
|
|
472
|
+
| P0 | ✅ `trellis doctor` — read-only, opt-in-for-handshakes scan of all four agents' current skills/MCP/instructions state, reports drift and duplicates | nothing |
|
|
473
|
+
| P1 | ✅ `trellis sync skills` / `trellis sync instructions` — symlink-based distribution to Claude Code, Codex, Kiro, and pi | P0 |
|
|
474
|
+
| P2 | ✅ `trellis mcp sync` — incremental, in-place adapters for Claude Code (JSON merge), Codex (TOML section patch), Kiro (JSON merge); collision check against known host-injected server names | P1 |
|
|
475
|
+
| P3 | ✅ `trellis secrets audit` — scans every adapter's output for literal credential patterns and unexpected env var names, fails non-zero on any hit | P2 |
|
|
476
|
+
| P4 | ✅ pi bridge extension — MCP tool registration via `registerTool`, sourced from the same `mcp/servers.yaml` | P2 |
|
|
477
|
+
| P5 | ✅ `@trellis/sdk` — read-only API over the canonical source, for third-party agents to consume without depending on the CLI | P1–P4 stable |
|
|
478
|
+
| P6 | ✅ Memory: document and wire the `server-memory` default; write the mem0/OpenMemory upgrade guide | P2 |
|
|
479
|
+
| P7 | ✅ Secrets/env management: shared `resolveSecretEnv` + `secrets.policy.yaml`'s `env_file`, pi bridge stops reading raw ambient env, `secrets audit` gains a `missing-env-value` check | P3, P4 |
|
|
480
|
+
| P8 | ✅ Kiro `${VAR}` fix: adapter also manages `kiroAgent.mcpApprovedEnvVars`, without which Kiro silently never substitutes any env reference Trellis writes | P2 |
|
|
481
|
+
| P9 | ✅ MCP transport/auth expansion: `headers` field for static bearer/API-key remote auth (Claude Code/Codex/Kiro/pi bridge, each via its own real schema), `sse` transport; real OAuth flows explicitly delegated to each agent's own native support, not reimplemented | P2, P4, P8 |
|
|
482
|
+
| P10 | GUI: evaluate embedding into mcp-router's or skills-hub's existing interface before building anything new | P3–P9 |
|
|
483
|
+
|
|
484
|
+
No dates. This is scoped by verification milestones, not calendar time.
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agent-trellis",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A single source of capability for every coding agent — skills, MCP, subagents, memory, and secret policy, adapted natively into Claude Code, Codex, Kiro, and pi.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Paul Leo",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"trellis": "./dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"main": "./dist/sdk.js",
|
|
12
|
+
"types": "./dist/sdk.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/sdk.d.ts",
|
|
16
|
+
"import": "./dist/sdk.js"
|
|
17
|
+
},
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"schema",
|
|
23
|
+
"docs"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc -p tsconfig.json && node scripts/build-pi-bridge.mjs",
|
|
27
|
+
"dev": "tsx src/cli.ts",
|
|
28
|
+
"typecheck": "tsc --noEmit",
|
|
29
|
+
"test": "tsx --test test/unit/**/*.test.ts",
|
|
30
|
+
"prepublishOnly": "npm run typecheck && npm test && npm run build"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=20"
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/paul-leo/Trellis.git"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"mcp",
|
|
41
|
+
"model-context-protocol",
|
|
42
|
+
"claude-code",
|
|
43
|
+
"codex",
|
|
44
|
+
"kiro",
|
|
45
|
+
"agent-skills",
|
|
46
|
+
"agents.md"
|
|
47
|
+
],
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
50
|
+
"@types/node": "^22.0.0",
|
|
51
|
+
"esbuild": "^0.28.2",
|
|
52
|
+
"tsx": "^4.19.0",
|
|
53
|
+
"typebox": "^1.3.0",
|
|
54
|
+
"typescript": "^5.6.0"
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"yaml": "^2.9.1"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# .trellis/scope.yaml — exceptions only. Anything NOT listed here is
|
|
2
|
+
# shared with all four agents — that's the default Trellis exists for.
|
|
3
|
+
# Only write an entry when a capability should NOT go everywhere.
|
|
4
|
+
#
|
|
5
|
+
# Why this lives in its own file instead of inside each SKILL.md/agent.md:
|
|
6
|
+
# Codex validates SKILL.md frontmatter against an allow-list of known keys
|
|
7
|
+
# and rejects unrecognized ones (see docs/research.md). A Trellis-only
|
|
8
|
+
# `scope:` field embedded in SKILL.md's own frontmatter would break that
|
|
9
|
+
# skill specifically on Codex. Scope declarations stay outside every file
|
|
10
|
+
# an agent itself parses.
|
|
11
|
+
#
|
|
12
|
+
# MCP servers are the one exception: `mcp/servers.yaml` is Trellis's own
|
|
13
|
+
# format, never handed to an agent directly (always translated by an
|
|
14
|
+
# adapter), so its per-server `agents:` field is declared inline there
|
|
15
|
+
# instead of here — see schema/servers.example.yaml.
|
|
16
|
+
|
|
17
|
+
skills:
|
|
18
|
+
# This skill only makes sense for Claude Code's tool surface (e.g. it
|
|
19
|
+
# references the Task tool for subagent delegation, which the other
|
|
20
|
+
# three agents don't have an equivalent of).
|
|
21
|
+
claude-subagent-dispatch: [claude-code]
|
|
22
|
+
|
|
23
|
+
agents:
|
|
24
|
+
# Subagent profiles are implicitly Claude-Code-only today — Codex has no
|
|
25
|
+
# persistent subagent concept, Kiro/pi unconfirmed (see docs/research.md).
|
|
26
|
+
# No entries needed here until another agent gains an equivalent
|
|
27
|
+
# mechanism and a specific profile should NOT go to all of them.
|
|
28
|
+
|
|
29
|
+
memories:
|
|
30
|
+
# A memory entry that only makes sense in the context of one agent's
|
|
31
|
+
# own quirks (e.g. a note about Codex's TOML in-place-patch behavior)
|
|
32
|
+
# rather than something every agent's context should carry.
|
|
33
|
+
codex-toml-patch-gotcha: [codex]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# .trellis/secrets.policy.yaml — allow-list of variable names an adapter
|
|
2
|
+
# output is permitted to reference. `trellis secrets audit` uses this to
|
|
3
|
+
# tell "a legitimate ${VAR} reference" apart from "a name that shouldn't be
|
|
4
|
+
# here" — it does NOT store or see the actual secret values, only names.
|
|
5
|
+
#
|
|
6
|
+
# The real values live wherever your environment already resolves them from
|
|
7
|
+
# (a local .env-style file sourced by your shell, 1Password's `op run`,
|
|
8
|
+
# anything that populates process env before an adapter's generated command
|
|
9
|
+
# runs). Trellis never reads, writes, or transports the values themselves —
|
|
10
|
+
# with one narrow, unavoidable exception: pi's MCP bridge is Trellis's own
|
|
11
|
+
# code spawning the MCP server subprocess (unlike Claude Code/Codex/Kiro,
|
|
12
|
+
# whose own native MCP client resolves `${VAR}` in its own process), so it
|
|
13
|
+
# already has to read a value into memory to hand to that subprocess.
|
|
14
|
+
#
|
|
15
|
+
# env_file (optional) narrows *where* the bridge reads those values from —
|
|
16
|
+
# a dotenv-format file (KEY=VALUE per line, no quoting/interpolation), never
|
|
17
|
+
# merged with ambient process env once set: a name absent from it resolves
|
|
18
|
+
# to missing, it does NOT fall back to the shell's own exported value. This
|
|
19
|
+
# also lets `trellis secrets audit` check that every name your canonical
|
|
20
|
+
# mcp/servers.yaml declares actually resolves to something, agent-agnostic.
|
|
21
|
+
#
|
|
22
|
+
# WARNING: whatever file you point this at holds plaintext secrets. Never
|
|
23
|
+
# point it at a path inside a git-tracked directory (this file's own
|
|
24
|
+
# directory, ~/.trellis/, included) — a `git add .` there would commit them.
|
|
25
|
+
# env_file: ~/.config/agent-env/secrets.env
|
|
26
|
+
|
|
27
|
+
allowed_vars:
|
|
28
|
+
- TANKA_EMAIL
|
|
29
|
+
- TANKA_ENV
|
|
30
|
+
- GITLAB_PERSONAL_ACCESS_TOKEN
|
|
31
|
+
- GITLAB_API_URL
|
|
32
|
+
- MCPR_TOKEN
|
|
33
|
+
- NOTION_OPENAPI_MCP_HEADERS
|
|
34
|
+
|
|
35
|
+
# Patterns `trellis secrets audit` treats as "this looks like a literal
|
|
36
|
+
# credential, not a reference" and fails the build on sight. Extend this
|
|
37
|
+
# list as new credential shapes turn up — it grows from real incidents,
|
|
38
|
+
# not speculative coverage.
|
|
39
|
+
reject_patterns:
|
|
40
|
+
- 'glpat-[A-Za-z0-9_-]{20,}' # GitLab personal access token
|
|
41
|
+
- 'mcpr_[A-Za-z0-9_-]{20,}' # mcp-router token
|
|
42
|
+
- 'sk-[A-Za-z0-9]{20,}' # OpenAI-style API key
|
|
43
|
+
- 'ghp_[A-Za-z0-9]{30,}' # GitHub personal access token
|