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.
Files changed (50) hide show
  1. package/README.md +12 -0
  2. package/dist/adapters/claude-code.d.ts +3 -2
  3. package/dist/adapters/claude-code.js +21 -8
  4. package/dist/adapters/codex.d.ts +6 -3
  5. package/dist/adapters/codex.js +41 -10
  6. package/dist/adapters/jsonMcp.d.ts +15 -5
  7. package/dist/adapters/jsonMcp.js +38 -29
  8. package/dist/adapters/kiro.d.ts +3 -2
  9. package/dist/adapters/kiro.js +22 -9
  10. package/dist/adapters/mcpPlan.d.ts +11 -6
  11. package/dist/adapters/mcpPlan.js +39 -6
  12. package/dist/cli.js +123 -10
  13. package/dist/commands/mcp.d.ts +101 -7
  14. package/dist/commands/mcp.js +227 -10
  15. package/dist/commands/memory.d.ts +39 -0
  16. package/dist/commands/memory.js +78 -0
  17. package/dist/commands/migrate.d.ts +30 -4
  18. package/dist/commands/migrate.js +83 -16
  19. package/dist/commands/onboard.d.ts +18 -8
  20. package/dist/commands/onboard.js +124 -14
  21. package/dist/commands/skill.d.ts +51 -0
  22. package/dist/commands/skill.js +104 -0
  23. package/dist/core/adapter.d.ts +18 -8
  24. package/dist/core/canonical.d.ts +26 -1
  25. package/dist/core/canonical.js +81 -3
  26. package/dist/core/types.d.ts +17 -0
  27. package/dist/lib/deepEqual.d.ts +8 -0
  28. package/dist/lib/deepEqual.js +26 -0
  29. package/dist/lib/dirEquals.d.ts +9 -0
  30. package/dist/lib/dirEquals.js +15 -1
  31. package/dist/lib/mcpMigrateRead.d.ts +69 -0
  32. package/dist/lib/mcpMigrateRead.js +188 -0
  33. package/dist/lib/mcpOwnership.d.ts +25 -0
  34. package/dist/lib/mcpOwnership.js +50 -0
  35. package/dist/lib/memoryGraph.d.ts +60 -0
  36. package/dist/lib/memoryGraph.js +101 -0
  37. package/dist/lib/realHomeSnapshot.d.ts +26 -0
  38. package/dist/lib/realHomeSnapshot.js +77 -0
  39. package/dist/lib/terminalPicker.d.ts +45 -0
  40. package/dist/lib/terminalPicker.js +193 -0
  41. package/dist/lib/tomlSection.d.ts +20 -6
  42. package/dist/lib/tomlSection.js +78 -12
  43. package/dist/pi-bridge/bundle.js +76 -46
  44. package/dist/pi-bridge/index.js +7 -2
  45. package/dist/probes/codex.js +10 -2
  46. package/docs/architecture.md +7 -4
  47. package/docs/getting-started.md +166 -10
  48. package/docs/roadmap.md +311 -0
  49. package/package.json +1 -1
  50. 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.2.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
- env: [TANKA_EMAIL, TANKA_ENV]
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.