agent-trellis 0.2.0 → 0.3.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/README.md +12 -0
- package/dist/adapters/claude-code.d.ts +3 -2
- package/dist/adapters/claude-code.js +21 -8
- package/dist/adapters/codex.d.ts +6 -3
- package/dist/adapters/codex.js +41 -10
- package/dist/adapters/jsonMcp.d.ts +15 -5
- package/dist/adapters/jsonMcp.js +38 -29
- package/dist/adapters/kiro.d.ts +3 -2
- package/dist/adapters/kiro.js +22 -9
- package/dist/adapters/mcpPlan.d.ts +11 -6
- package/dist/adapters/mcpPlan.js +39 -6
- package/dist/cli.js +123 -10
- package/dist/commands/mcp.d.ts +101 -7
- package/dist/commands/mcp.js +227 -10
- package/dist/commands/memory.d.ts +39 -0
- package/dist/commands/memory.js +78 -0
- package/dist/commands/migrate.d.ts +30 -4
- package/dist/commands/migrate.js +83 -16
- package/dist/commands/onboard.d.ts +18 -8
- package/dist/commands/onboard.js +124 -14
- package/dist/commands/skill.d.ts +51 -0
- package/dist/commands/skill.js +104 -0
- package/dist/core/adapter.d.ts +18 -8
- package/dist/core/canonical.d.ts +26 -1
- package/dist/core/canonical.js +81 -3
- package/dist/core/types.d.ts +17 -0
- package/dist/lib/deepEqual.d.ts +8 -0
- package/dist/lib/deepEqual.js +26 -0
- package/dist/lib/dirEquals.d.ts +9 -0
- package/dist/lib/dirEquals.js +15 -1
- package/dist/lib/mcpMigrateRead.d.ts +69 -0
- package/dist/lib/mcpMigrateRead.js +188 -0
- package/dist/lib/mcpOwnership.d.ts +25 -0
- package/dist/lib/mcpOwnership.js +50 -0
- package/dist/lib/memoryGraph.d.ts +60 -0
- package/dist/lib/memoryGraph.js +101 -0
- package/dist/lib/realHomeSnapshot.d.ts +26 -0
- package/dist/lib/realHomeSnapshot.js +77 -0
- package/dist/lib/terminalPicker.d.ts +45 -0
- package/dist/lib/terminalPicker.js +193 -0
- package/dist/lib/tomlSection.d.ts +20 -6
- package/dist/lib/tomlSection.js +78 -12
- package/dist/pi-bridge/bundle.js +76 -46
- package/dist/pi-bridge/index.js +7 -2
- package/dist/probes/codex.js +10 -2
- package/docs/architecture.md +7 -4
- package/docs/getting-started.md +166 -10
- package/docs/roadmap.md +311 -0
- package/package.json +1 -1
- package/schema/servers.example.yaml +39 -2
package/docs/roadmap.md
CHANGED
|
@@ -600,6 +600,305 @@ exactly that one path as a `conflict` (exit 1) while still correctly
|
|
|
600
600
|
restoring the other two, untouched, unaffected paths in the same
|
|
601
601
|
invocation.
|
|
602
602
|
|
|
603
|
+
**`trellis-mcp-static-env-and-disabled-servers`, implemented and
|
|
604
|
+
sandbox-verified** (modifies `mcp-server-sync`). Found by dogfooding
|
|
605
|
+
`mcp sync` against a real machine's actual, actively-used Codex
|
|
606
|
+
`config.toml` rather than a fixture: `[mcp_servers.supabase_db]` had
|
|
607
|
+
`enabled = false` (a definition kept on hand, deliberately off — no way
|
|
608
|
+
to represent that in canonical short of deleting it), and
|
|
609
|
+
`[mcp_servers.tanka]` used a hardcoded literal `env` table, not the
|
|
610
|
+
`env_vars` name-forwarding array every other real server on the same
|
|
611
|
+
machine uses. Migrating `tanka` as-is into the old names-only model
|
|
612
|
+
would have had `mcp sync` rewrite a working config into a broken one —
|
|
613
|
+
`TANKA_EMAIL`/`TANKA_ENV` were never real process env vars, just plain
|
|
614
|
+
values written straight into the file — and neither `mcp sync` nor
|
|
615
|
+
`secrets audit` would have caught it before the write happened.
|
|
616
|
+
|
|
617
|
+
`McpServerDef` gained `enabled?: boolean` (filtered in `resolveMcpPlan`
|
|
618
|
+
itself, the one choke point every adapter already funnels through — a
|
|
619
|
+
disabled server gets no write and no conflict, not just on Codex but on
|
|
620
|
+
every agent) and `staticEnv?: Record<string, string>` for a value that
|
|
621
|
+
was never a secret in the first place — Codex renders it as an adjacent
|
|
622
|
+
`[mcp_servers.<name>.env]` table (extending `tomlSection.ts`'s section
|
|
623
|
+
boundary logic to treat the pair as one atomic create/repair/remove
|
|
624
|
+
unit), Claude Code/Kiro merge it into the same `env` map their `${VAR}`
|
|
625
|
+
references already use. `resolveMcpPlan` also gained a mandatory fourth
|
|
626
|
+
`policy: SecretsPolicy` parameter: before writing any name-only `env`
|
|
627
|
+
entry, it now resolves it through the same `resolveSecretEnv` `secrets
|
|
628
|
+
audit`/the pi bridge already call, refusing (as a new conflict scoped to
|
|
629
|
+
just that server, on just that agent) a name that wouldn't actually
|
|
630
|
+
resolve — the exact silent-breakage scenario `tanka` would have hit.
|
|
631
|
+
|
|
632
|
+
The on-disk YAML key is `static_env` (snake_case, matching every other
|
|
633
|
+
multi-word key across `.trellis/*.yaml` — `known_host_injected`,
|
|
634
|
+
`allowed_vars` — translated to camelCase in `loadServersYaml`), a real
|
|
635
|
+
gap the design doc missed until implementation: `McpServerDef` had
|
|
636
|
+
always been parsed as-is with zero field translation, since every prior
|
|
637
|
+
field name happened to already be a single word.
|
|
638
|
+
|
|
639
|
+
Sandbox-verified, not just unit-tested: reproducing this machine's exact
|
|
640
|
+
`tanka`/`supabase-db` shapes in `test/fixtures/home` and running a real
|
|
641
|
+
`mcp sync` inside `scripts/sandbox.sh` produced Codex's two-table output
|
|
642
|
+
byte-for-byte identical to the real, working config this change was
|
|
643
|
+
motivated by — and surfaced one more real regression before it could
|
|
644
|
+
ship: the sandbox's own pre-existing fixture servers declare `env` names
|
|
645
|
+
that don't resolve inside the container, which the new pre-write check
|
|
646
|
+
would have refused outright. Fixed by exporting their fixture values in
|
|
647
|
+
`docker/entrypoint.sh`, matching what a real working setup would
|
|
648
|
+
actually have — found only because the fixture was actually run, not
|
|
649
|
+
just reasoned about.
|
|
650
|
+
|
|
651
|
+
Also removed a dead field from `schema/servers.example.yaml`: the
|
|
652
|
+
`figma` example's `auth: oauth` was never a real property on
|
|
653
|
+
`McpServerDef`, never read by any adapter, and never asserted by any
|
|
654
|
+
spec — the YAML parser has no field validation, so it silently did
|
|
655
|
+
nothing. No real OAuth support exists; the line taught a capability that
|
|
656
|
+
was never there.
|
|
657
|
+
|
|
658
|
+
**P11 is done and archived**
|
|
659
|
+
(`openspec/changes/archive/2026-09-13-trellis-migrate-category-selection/`;
|
|
660
|
+
modifies `canonical-source-migration` and `onboarding-flow`). `trellis
|
|
661
|
+
migrate --from <agent>` always planned skills and instructions
|
|
662
|
+
together, one unit, no subset selection — no CLI flag, and no
|
|
663
|
+
interactive picker for this choice either. `--only skills|instructions`
|
|
664
|
+
now restricts a run to just one category, filtered inside
|
|
665
|
+
`collectMigratePlan` itself (not computed then discarded) — the
|
|
666
|
+
excluded kind is never read for comparison and never appears in the
|
|
667
|
+
plan. `trellis onboard`'s migrate step gained a checkbox reusing
|
|
668
|
+
`src/lib/terminalPicker.ts` (the same module `trellis-onboard-
|
|
669
|
+
interactive-picker` built), but only offered when the resolved source
|
|
670
|
+
actually has both real skills and real instructions — a source with
|
|
671
|
+
only one real kind, or `--json`, or a terminal that can't support the
|
|
672
|
+
picker all default silently to migrating whichever kind(s) actually
|
|
673
|
+
have content, with no second, numbered-text fallback UI built, since
|
|
674
|
+
this choice never existed before this change to have a fallback for. An
|
|
675
|
+
empty selection is a valid, distinct outcome ("migrate skipped — no
|
|
676
|
+
categories selected"), not an error, and doesn't stop sync/mcp sync/
|
|
677
|
+
secrets audit from running. A real gap named rather than silently
|
|
678
|
+
folded in: `codex`/`pi`/`kiro` as migration sources remain untested —
|
|
679
|
+
`test/unit/migrate.test.ts` only ever exercises `claude-code`, despite
|
|
680
|
+
`collectMigratePlan`'s own dispatch being fully symmetric by design.
|
|
681
|
+
That's P13's job. Verified with 22 new unit tests (275/275 project-wide,
|
|
682
|
+
zero regressions) — no sandbox pass needed, this change touches no
|
|
683
|
+
adapter or native-config write path.
|
|
684
|
+
|
|
685
|
+
**P12 (✅ done, archived
|
|
686
|
+
[2026-09-13-trellis-canonical-cli-crud](../openspec/changes/archive/2026-09-13-trellis-canonical-cli-crud/)):
|
|
687
|
+
canonical CRUD via CLI.** Neither skills nor MCP servers had any
|
|
688
|
+
command-line add/remove/list surface — `cli.ts`'s `mcp` command
|
|
689
|
+
recognized exactly one subcommand, `sync`, and there was no `skill`
|
|
690
|
+
command at all. `trellis skill list/add/remove` and `trellis mcp
|
|
691
|
+
list/add/remove` are new, canonical-side-only commands (a new
|
|
692
|
+
`canonical-content-management` capability): `skill add`/`mcp add`
|
|
693
|
+
refuse (no write, no `--force`) on an existing name with different
|
|
694
|
+
content, mirroring `migrate`'s own conflict posture exactly, reusing
|
|
695
|
+
the same comparison logic via a newly-extracted
|
|
696
|
+
`decideDirImport(sourceDir, canonicalDir)` (src/lib/dirEquals.ts),
|
|
697
|
+
which also replaced `migrate.ts`'s own inline check
|
|
698
|
+
(behavior-preserving, confirmed by its full pre-existing suite passing
|
|
699
|
+
unmodified). `servers.yaml`'s new writer (`upsertServerYaml`/
|
|
700
|
+
`removeServerYaml` in `canonical.ts`) uses the `yaml` package's
|
|
701
|
+
`Document`-based `parseDocument`/`setIn`/`deleteIn`/`toString`, never a
|
|
702
|
+
full parse-then-restringify, specifically so a hand-authored file's
|
|
703
|
+
comments and untouched entries survive byte-for-byte — verified with a
|
|
704
|
+
dedicated test asserting exactly that. `skill remove` needed zero new
|
|
705
|
+
removal-propagation code: skills already carry an ownership marker (the
|
|
706
|
+
symlink itself), so `sync`'s pre-existing stale-symlink detection
|
|
707
|
+
un-syncs a removed skill automatically on the next run (proven
|
|
708
|
+
end-to-end in a test). `mcp remove` stays canonical-only by design —
|
|
709
|
+
MCP has no such marker yet, so an already-synced agent's native config
|
|
710
|
+
is untouched until P14 closes that gap. `mcp list` never resolves a
|
|
711
|
+
secret: `env` entries print as bare names (never read from
|
|
712
|
+
`process.env`), while `static_env` values print in full since they were
|
|
713
|
+
never secrets by `McpServerDef`'s own contract. Verified with 21 new
|
|
714
|
+
unit tests (296/296 project-wide, zero regressions) plus manual
|
|
715
|
+
smoke-testing of every subcommand (list/add/remove, conflict/
|
|
716
|
+
already-present/invalid-input, `--dry-run`, `--json`) against a
|
|
717
|
+
throwaway sandbox `$HOME` — no sandbox-container pass needed, this
|
|
718
|
+
change touches no adapter or native-config write path.
|
|
719
|
+
|
|
720
|
+
**P13 (✅ done, archived
|
|
721
|
+
[2026-09-13-trellis-real-sandbox-verification](../openspec/changes/archive/2026-09-13-trellis-real-sandbox-verification/)):
|
|
722
|
+
sandbox verification against this machine's real state.** Every sandbox
|
|
723
|
+
run before this (`scripts/sandbox.sh`, `docker/entrypoint.sh`) mounted
|
|
724
|
+
the same single, git-tracked synthetic fixture (`test/fixtures/home`).
|
|
725
|
+
Compounding this, `test/unit/migrate.test.ts` only ever exercised
|
|
726
|
+
`"claude-code"` as a migration source — codex/pi/kiro had never been
|
|
727
|
+
verified as sources even though `collectMigratePlan`'s `PROBES:
|
|
728
|
+
Record<AgentId, ...>` dispatch is fully symmetric by design (claude-code
|
|
729
|
+
was, however, already covered as a sync/mcp-sync *target*, in
|
|
730
|
+
`test/unit/sync.test.ts`/`test/unit/mcp.test.ts` — the earlier draft of
|
|
731
|
+
this entry claimed otherwise; corrected here after checking, not
|
|
732
|
+
assumed). `scripts/sandbox.sh --real` builds a throwaway snapshot from a
|
|
733
|
+
real `$HOME` using an **allowlist**, not the denylist first sketched here
|
|
734
|
+
— every path each probe (`src/probes/*.ts`) is already confirmed to
|
|
735
|
+
read, and nothing else, since this project has no complete knowledge of
|
|
736
|
+
where third-party agents' own real OAuth token flows store credentials
|
|
737
|
+
(design.md D1 explains the reasoning). Actually running this against a
|
|
738
|
+
real, in-use machine (not just reasoning about it) found two real bugs
|
|
739
|
+
before it could even complete: `sync`'s own real output is a symlink
|
|
740
|
+
back into `~/.trellis/`, which a naive symlink-preserving copy leaves
|
|
741
|
+
dangling once mounted into a container with no such path — fixed by
|
|
742
|
+
dereferencing during copy; and a case-insensitive filesystem (macOS
|
|
743
|
+
default) collides two of pi's own case-sensitive instructions-file
|
|
744
|
+
candidates (`AGENTS.md`/`AGENTS.MD`), which needed a
|
|
745
|
+
dest-already-exists guard to avoid a crash. Before any Docker build, the
|
|
746
|
+
snapshot is gated through `trellis secrets audit`'s own, already-shipped
|
|
747
|
+
`homeDir` seam — on the actual real-machine run, this correctly found 5
|
|
748
|
+
genuine `unexpected-var-name` findings and refused to proceed, exactly
|
|
749
|
+
as designed; clearing that machine's own `secrets.policy.yaml` gap and
|
|
750
|
+
running a full container pass against it remains a separate,
|
|
751
|
+
human-initiated action, not something this change forced through.
|
|
752
|
+
codex/kiro/pi as migrate sources are now covered in
|
|
753
|
+
`test/unit/migrateSources.test.ts`, using each probe's own confirmed
|
|
754
|
+
real dotfile paths. Verified with 10 new unit tests (306/306
|
|
755
|
+
project-wide) plus the fixture-based (default, non-`--real`)
|
|
756
|
+
`scripts/sandbox.sh` re-run end-to-end against Docker to confirm zero
|
|
757
|
+
regression to the existing path.
|
|
758
|
+
|
|
759
|
+
**P14 (✅ done — removal half only, archived
|
|
760
|
+
[2026-09-13-trellis-mcp-sync-removal](../openspec/changes/archive/2026-09-13-trellis-mcp-sync-removal/);
|
|
761
|
+
migrate-in remains open, see below): MCP server lifecycle parity with
|
|
762
|
+
skills.** Skills have a full migrate (import) + sync (create/repair) +
|
|
763
|
+
conflict story; MCP had sync only. This was always two distinct, real
|
|
764
|
+
gaps, not one — this change closed the harder, more clearly-specified
|
|
765
|
+
half: `resolveMcpPlan` was deliberately create/repair-only
|
|
766
|
+
(`mcpPlan.ts`'s own stated reasoning: a bare TOML/JSON key has no
|
|
767
|
+
ownership marker to prove Trellis, not the user, put it there), so
|
|
768
|
+
removing a server from `servers.yaml` never removed it from any
|
|
769
|
+
agent's native config. A new `src/lib/mcpOwnership.ts` ledger
|
|
770
|
+
(`~/.trellis/mcp/ownership.json`) records, per agent and server name,
|
|
771
|
+
the exact rendered value Trellis itself last wrote; on a later sync, a
|
|
772
|
+
name gone from canonical is only actually removed from an agent's
|
|
773
|
+
native config if that config's current entry still exactly matches
|
|
774
|
+
what the ledger recorded — a hand-edited entry is left alone,
|
|
775
|
+
indefinitely, never forced. Reused rather than rebuilt:
|
|
776
|
+
`src/lib/tomlSection.ts`'s `removeSection` (built earlier for the
|
|
777
|
+
static-env atomic-range work, never wired into an actual removal path
|
|
778
|
+
until now) for Codex, and each JSON agent's own existing `deepEqual`
|
|
779
|
+
for the "unchanged since" check. **Still genuinely open, not done
|
|
780
|
+
here:** MCP *migrate-in* (importing an already-hand-configured server
|
|
781
|
+
into canonical) — each static-config probe (`codex.ts`/`claude-code.ts`/
|
|
782
|
+
`kiro.ts`) still discards a server's complete real definition (e.g.
|
|
783
|
+
`codex mcp list --json`'s `CodexMcpEntry`, with `command`/`args`/
|
|
784
|
+
`env_vars` intact) down to `{name, transport, probe}`
|
|
785
|
+
(`AgentSnapshotMcpServer`) before anything downstream sees it, and
|
|
786
|
+
there is still no conversion from that discarded, richer shape into
|
|
787
|
+
canonical's `McpServerDef`. pi has no static config to read at all —
|
|
788
|
+
inherently import-less; P12's CLI remains its only route in. A real
|
|
789
|
+
gap, named rather than silently dropped, left for a future change.
|
|
790
|
+
Verified with 3 new/rewritten unit tests across both Claude Code's JSON
|
|
791
|
+
path and Codex's TOML path (308/308 project-wide, zero regressions).
|
|
792
|
+
|
|
793
|
+
**P15 (✅ done — ingestion half only, archived
|
|
794
|
+
[2026-09-13-trellis-memory-sync](../openspec/changes/archive/2026-09-13-trellis-memory-sync/);
|
|
795
|
+
per-agent extraction remains open, see below): shared memory — real
|
|
796
|
+
ingestion and per-agent extraction.** `memories` was parsed
|
|
797
|
+
(`canonical.ts`) into `CanonicalSource.memories: MemoryEntry[]` and
|
|
798
|
+
consumed nowhere — no adapter, no command, no `trellis memory` CLI
|
|
799
|
+
surface existed at all. P6 explicitly left "auto-ingesting
|
|
800
|
+
`~/.trellis/memories/*.md` content into the running memory server's
|
|
801
|
+
store" out of scope; `trellis memory sync` closes that gap (A): each
|
|
802
|
+
canonical memory file becomes one entity
|
|
803
|
+
(`entityType: "trellis-memory"`) in `@modelcontextprotocol/
|
|
804
|
+
server-memory`'s own on-disk JSON-lines graph file (the exact file that
|
|
805
|
+
server itself reads at startup — Trellis never spawns or talks to a
|
|
806
|
+
running server process, a plain file write like everything else this
|
|
807
|
+
project does), requiring `mcp/servers.yaml`'s `memory` server to set
|
|
808
|
+
`static_env.MEMORY_FILE_PATH` explicitly (the server's own unset-env
|
|
809
|
+
default resolves relative to wherever `npx` cached the package, not a
|
|
810
|
+
predictable location). The in-band `entityType` tag is the ownership
|
|
811
|
+
marker — deliberately not a separate ledger file like P14's MCP
|
|
812
|
+
removal, since (unlike per-agent MCP config) every agent connected to
|
|
813
|
+
this one server shares the exact same graph, so there's no per-agent
|
|
814
|
+
render to track. Every entity/relation Trellis didn't create is left
|
|
815
|
+
completely untouched, unconditionally; a name collision with a
|
|
816
|
+
non-Trellis-tagged entity is a conflict, never overwritten. **Still
|
|
817
|
+
genuinely open, not done here (B):** extracting an agent's own
|
|
818
|
+
already-accumulated memory content back into canonical — e.g. Claude
|
|
819
|
+
Code's own per-project memory feature. Investigated, not attempted, for
|
|
820
|
+
two concrete reasons: that content lives under
|
|
821
|
+
`~/.claude/projects/<project-slug>/memory/`, a path
|
|
822
|
+
`trellis-real-sandbox-verification`'s own allowlist already deliberately
|
|
823
|
+
excludes (mixed with real session transcripts); and the project-slug
|
|
824
|
+
encoding scheme Claude Code uses to derive that path from a working
|
|
825
|
+
directory has no authoritative documented source this project could
|
|
826
|
+
verify against, so it was not guessed at (this project's own "verify,
|
|
827
|
+
don't assume" discipline). Kiro's `totalrecallai` (SQLite + local vector
|
|
828
|
+
embeddings) is a further, genuinely different data shape, and was never
|
|
829
|
+
in scope for this half either. A real gap, named rather than silently
|
|
830
|
+
dropped, left for a future change. Verified with 14 new unit tests
|
|
831
|
+
(322/322 project-wide, zero regressions).
|
|
832
|
+
|
|
833
|
+
**P16 (✅ done, archived
|
|
834
|
+
[2026-09-13-trellis-migrate-mcp-servers](../openspec/changes/archive/2026-09-13-trellis-migrate-mcp-servers/)):
|
|
835
|
+
MCP migrate-in — the gap P14 named.** `trellis migrate --from <agent>`
|
|
836
|
+
gains a third `--only` value, `mcp`: claude-code, kiro, and codex each
|
|
837
|
+
get a new, purpose-built reader (`src/lib/mcpMigrateRead.ts`) that
|
|
838
|
+
converts that agent's real, already-configured MCP servers into
|
|
839
|
+
canonical's `McpServerDef` shape — kept entirely separate from each
|
|
840
|
+
probe's own thin `AgentSnapshotMcpServer` (`doctor`'s read path, left
|
|
841
|
+
untouched). Same conflict posture as skill/instructions migration
|
|
842
|
+
throughout: identical is a no-op, a differing definition under the same
|
|
843
|
+
name is a conflict, never overwritten. pi is still never a migrate-in
|
|
844
|
+
source — no static config to read, unchanged from P14's own framing.
|
|
845
|
+
Two fidelity limits, found by actually running each agent's real
|
|
846
|
+
tooling rather than assumed, are handled by refusing rather than
|
|
847
|
+
guessing: **Codex is stdio-transport only at first** (closed as a
|
|
848
|
+
same-day follow-up, see below) — `codex mcp list --json` had no
|
|
849
|
+
evidence in this codebase for any other transport shape at design time;
|
|
850
|
+
a real, one-off run against a locally-installed `codex-cli 0.154.0`
|
|
851
|
+
during implementation *did* observe a `streamable_http` shape (`url`,
|
|
852
|
+
`bearer_token_env_var`, plus three further undocumented fields) for a
|
|
853
|
+
hand-written `url`-based server — recorded as a concrete lead for a
|
|
854
|
+
future change, not built at first, since one data point from one
|
|
855
|
+
version isn't a contract; a non-stdio Codex server was reported
|
|
856
|
+
`skip-unsupported`, named, not silently dropped. **Codex's `static_env`**
|
|
857
|
+
is recovered by reading `[mcp_servers.<name>.env]` directly from
|
|
858
|
+
`config.toml` (a new `readServerEnvTable` in `tomlSection.ts`, the exact
|
|
859
|
+
inverse of that module's own existing writer) since `codex mcp list
|
|
860
|
+
--json` only ever reports variable *names*, never the literal table
|
|
861
|
+
Trellis itself renders separately. **`headers`** — missing from both
|
|
862
|
+
`claude-code.ts`'s and `kiro.ts`'s own probe-facing JSON types despite
|
|
863
|
+
being a real field Trellis's own writer (`jsonMcp.ts`) already produces
|
|
864
|
+
for http/sse servers — is recovered via a parallel, richer local type
|
|
865
|
+
scoped only to this new reader module, leaving both probes' existing,
|
|
866
|
+
`doctor`-tested types untouched. Verified with 19 new unit tests,
|
|
867
|
+
including one exercising the real, locally-installed `codex` binary
|
|
868
|
+
end-to-end against a scratch, HOME-scoped `.codex/config.toml` (341/341
|
|
869
|
+
project-wide, zero regressions). One related, pre-existing gap
|
|
870
|
+
surfaced but deliberately not fixed at first (out of scope for this
|
|
871
|
+
change, named instead): `src/probes/codex.ts`'s own `probe()` never
|
|
872
|
+
scoped its `codex mcp list --json` subprocess call to a passed-in
|
|
873
|
+
`homeDir` via `HOME` env override the way this change's own new reader
|
|
874
|
+
did — meaning that probe's MCP listing always reflected the real
|
|
875
|
+
machine's real codex config regardless of what `homeDir` a caller passed
|
|
876
|
+
it, an inconsistency with the rest of `probe()`'s own home-scoped reads.
|
|
877
|
+
|
|
878
|
+
**Same-day follow-up, both of the above closed (not a separately
|
|
879
|
+
numbered phase — direct fixes to already-shipped, already-specced
|
|
880
|
+
behavior, not new requirements, so no new OpenSpec change):**
|
|
881
|
+
`src/probes/codex.ts`'s `probe()` now scopes both of its `execFileSync`
|
|
882
|
+
calls to the passed-in `homeDir` the same way — verified with a new
|
|
883
|
+
regression test (`test/unit/codexProbe.test.ts`) using two distinct
|
|
884
|
+
scratch homes with two distinct MCP servers, confirming neither leaks
|
|
885
|
+
into the other; confirmed the test actually catches the bug by reverting
|
|
886
|
+
the fix and watching it fail against this real machine's real MCP
|
|
887
|
+
servers before restoring it. Separately, `buildCodexMcpReadResult`
|
|
888
|
+
(`src/lib/mcpMigrateRead.ts`) now converts a non-stdio Codex server when
|
|
889
|
+
it uses only `url` and, optionally, `bearer_token_env_var` — verified
|
|
890
|
+
this exact shape against the real `codex` binary a second time
|
|
891
|
+
(including with a real `bearer_token_env_var` set, not just a bare
|
|
892
|
+
`url`) — while a server using any of the three still-unexplained header
|
|
893
|
+
fields (`http_headers`/`env_http_headers`/`http_headers_helper`, all
|
|
894
|
+
`null` when unused, confirmed by the same real run) still refuses rather
|
|
895
|
+
than guesses. A migrated remote Codex server is always labeled `http`,
|
|
896
|
+
never `sse` — not a guess: Codex's own `config.toml` schema has no field
|
|
897
|
+
distinguishing the two (`upsertSection`'s own codex TOML rendering is
|
|
898
|
+
byte-identical for both transports, now covered by its own test), so
|
|
899
|
+
that distinction was never stored in the first place. 350/350 tests
|
|
900
|
+
passing.
|
|
901
|
+
|
|
603
902
|
| Phase | Deliverable | Depends on |
|
|
604
903
|
|---|---|---|
|
|
605
904
|
| P0 | ✅ `trellis doctor` — read-only, opt-in-for-handshakes scan of all four agents' current skills/MCP/instructions state, reports drift and duplicates | nothing |
|
|
@@ -613,5 +912,17 @@ invocation.
|
|
|
613
912
|
| P8 | ✅ Kiro `${VAR}` fix: adapter also manages `kiroAgent.mcpApprovedEnvVars`, without which Kiro silently never substitutes any env reference Trellis writes | P2 |
|
|
614
913
|
| 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 |
|
|
615
914
|
| P10 | GUI: evaluate embedding into mcp-router's or skills-hub's existing interface before building anything new | P3–P9 |
|
|
915
|
+
| P11 | ✅ Migrate category selection: `--only skills\|instructions` on `migrate`, same interactive picker `onboard` already uses | P1 |
|
|
916
|
+
| P12 | ✅ Canonical CRUD via CLI: `trellis skill add/remove/list`, `trellis mcp add/remove/list` | P1, P2 |
|
|
917
|
+
| P13 | ✅ Sandbox verification against this machine's real, structurally-relevant state (not just fixtures); every agent verified as both migrate source and sync/mcp-sync target | P0–P2 |
|
|
918
|
+
| P14 | ✅ MCP server lifecycle parity with skills: ownership-tracked safe removal on sync (migrate-in closed separately by P16) | P2, P13 |
|
|
919
|
+
| P15 | ✅ Shared memory: ingest canonical `memories/*.md` into the `server-memory` store's own graph file (per-agent extraction into canonical remains a separate, open gap) | P6 |
|
|
920
|
+
| P16 | ✅ MCP migrate-in: `trellis migrate --only mcp` for claude-code/kiro/codex (codex remote transport + probe HOME-scoping closed same-day, see prose above), closing the gap P14 named | P12, P14 |
|
|
616
921
|
|
|
617
922
|
No dates. This is scoped by verification milestones, not calendar time.
|
|
923
|
+
Execution order for P11–P16: P11 → P12 → P13 → P14 → P15 → P16 — CRUD
|
|
924
|
+
(P12) lands before the larger features (P14, P15, P16) so each can be
|
|
925
|
+
adjusted via command line instead of hand-edited files while it's being
|
|
926
|
+
built, and the realistic sandbox (P13) lands before all three so each
|
|
927
|
+
gets verified against real data once, immediately, instead of against
|
|
928
|
+
another synthetic fixture.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-trellis",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
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
5
|
"license": "MIT",
|
|
6
6
|
"author": "Paul Leo",
|
|
@@ -5,6 +5,16 @@
|
|
|
5
5
|
# - Real values come from whatever already populates process env
|
|
6
6
|
# (a plain `.env`-style file, 1Password's `op run`, etc.) — Trellis
|
|
7
7
|
# does not store or transport secret values itself.
|
|
8
|
+
# - `static_env` is the one deliberate exception: values written into
|
|
9
|
+
# the agent's config verbatim, for something that was never a secret
|
|
10
|
+
# in the first place (an email address, an environment tag) — still
|
|
11
|
+
# scanned against `reject_patterns` like everything else, so a real
|
|
12
|
+
# credential accidentally placed there is still refused, not silently
|
|
13
|
+
# written (trellis-mcp-static-env-and-disabled-servers).
|
|
14
|
+
# - `enabled: false` keeps a server's definition in canonical without
|
|
15
|
+
# writing it to any agent — for a server you want to keep configured
|
|
16
|
+
# but not currently active everywhere, matching how a real agent's
|
|
17
|
+
# own config often already represents "off, not deleted".
|
|
8
18
|
# - A server name that a host environment is known to inject at runtime
|
|
9
19
|
# (see `known_host_injected` below) must not be redefined here — on
|
|
10
20
|
# Codex this causes the entire process to fail to start, not just that
|
|
@@ -35,15 +45,32 @@ servers:
|
|
|
35
45
|
# mem0/OpenMemory or a totalrecallai-class semantic-search server are
|
|
36
46
|
# documented opt-in upgrades from this default, not silent defaults —
|
|
37
47
|
# same section of docs/research.md.
|
|
48
|
+
#
|
|
49
|
+
# static_env.MEMORY_FILE_PATH: without this, the server's own unset-env
|
|
50
|
+
# default resolves relative to wherever `npx` happened to cache the
|
|
51
|
+
# package — not a stable location. Setting it explicitly to a path
|
|
52
|
+
# under ~/.trellis/ is also what lets `trellis memory sync`
|
|
53
|
+
# (trellis-memory-sync) find and write the same file the running
|
|
54
|
+
# server itself reads at startup.
|
|
38
55
|
# memory:
|
|
39
56
|
# transport: stdio
|
|
40
57
|
# command: npx
|
|
41
58
|
# args: ["-y", "@modelcontextprotocol/server-memory"]
|
|
59
|
+
# static_env:
|
|
60
|
+
# MEMORY_FILE_PATH: "~/.trellis/memories/graph.jsonl"
|
|
42
61
|
|
|
62
|
+
# `static_env`: a value that was never a secret in the first place (an
|
|
63
|
+
# email address, an environment tag) — written verbatim, never treated
|
|
64
|
+
# as a name to resolve. Prefer `env` (names only) whenever a value
|
|
65
|
+
# actually is a secret; `static_env` is the deliberate, visibly-different
|
|
66
|
+
# exception, still scanned against `reject_patterns` like any other
|
|
67
|
+
# literal field.
|
|
43
68
|
tanka:
|
|
44
69
|
transport: stdio
|
|
45
70
|
command: tanka-mcp
|
|
46
|
-
|
|
71
|
+
static_env:
|
|
72
|
+
TANKA_EMAIL: "you@example.com"
|
|
73
|
+
TANKA_ENV: "sd-or"
|
|
47
74
|
|
|
48
75
|
gitlab:
|
|
49
76
|
transport: stdio
|
|
@@ -51,10 +78,20 @@ servers:
|
|
|
51
78
|
args: ["-y", "@zereight/mcp-gitlab"]
|
|
52
79
|
env: [GITLAB_PERSONAL_ACCESS_TOKEN, GITLAB_API_URL]
|
|
53
80
|
|
|
81
|
+
# `enabled: false` keeps this definition in canonical without writing it
|
|
82
|
+
# to any agent — for something you want configured but not currently
|
|
83
|
+
# active anywhere. Remove the line (or set it to `true`) to turn it back
|
|
84
|
+
# on everywhere at once, with nothing to redefine.
|
|
85
|
+
supabase-db:
|
|
86
|
+
transport: stdio
|
|
87
|
+
command: npx
|
|
88
|
+
args: ["-y", "@supabase/mcp-server-supabase"]
|
|
89
|
+
env: [SUPABASE_ACCESS_TOKEN]
|
|
90
|
+
enabled: false
|
|
91
|
+
|
|
54
92
|
figma:
|
|
55
93
|
transport: http
|
|
56
94
|
url: "https://mcp.figma.com/mcp"
|
|
57
|
-
auth: oauth
|
|
58
95
|
|
|
59
96
|
# Example of a private/agent-specific server: only Claude Code has a use
|
|
60
97
|
# for this one, so the other three agents never see it defined at all.
|