opencode-context-tree 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/CHANGELOG.md ADDED
@@ -0,0 +1,84 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 — 2026-09-03
4
+
5
+ First working version, built against OpenCode 1.18.26.
6
+
7
+ - `/tree` (`ctrl+q`, `/ctree`, `/panel`): combined tree + trajectory view — spine rows with a
8
+ git-log gutter, fold/expand branches, jump (switch or fork with Pi's summarize-on-leave
9
+ picker), labels, filters, search, Input/Model/Tools lanes with Duration/Turns/Calls
10
+ modes, inspector pane, consumers view.
11
+ - The branch you are in is drawn at its fork point as `╰⎇ name … ← here`, with its own rows
12
+ nested under it — so a freshly forked session no longer looks exactly like its trunk.
13
+ - `/branch`, `b`: fork into a named branch, optional model per branch (applied by the
14
+ server half).
15
+ - `/merge`, `m`: squash to a human-confirmed ◆ decision record via `$EDITOR` (or in-app
16
+ confirm), squash without LLM, discard, tournament; `/decisions`, `D`, export to markdown.
17
+ - `c` crop mode: stub tool results or drop whole turns from the model's context
18
+ (append-only journal, applied per request by the server plugin), protections, auto rules,
19
+ `hardCrop` option to also set OpenCode's compacted flag.
20
+ - `x` / `/ctree undo`: revert the last crop, branch, or merge on the current path.
21
+ - Gauge on the prompt line with absolute bands, trend and attribution, red-band and
22
+ compaction-reserve toasts; sidebar card.
23
+ - Native forks (OpenCode's own `/fork`, `session.fork`) are adopted into the tree
24
+ automatically — matched to their parent by the copied message prefix, journalled as
25
+ `branch.opened { kind: "native" }` by both halves, and shown under their session title
26
+ (they have no branch name). Only `… (fork #n)` titles are adopted blindly.
27
+ - Headless `/ctree status|branch|merge --discard|crop|undo|decisions [--export]` for
28
+ non-TUI clients; `/ctree branch` mirrors the tree linkage into `session.metadata`.
29
+ - Options: `storage`, `jumpSummary`, `hardCrop`, `keybinds` (including `open`).
30
+ - `/tree` is now a Pi-style outline of the whole session tree by default: one content-forward row
31
+ per message and tool step (`● user:` / `○ assistant:` / `⚙ [bash $ …]`), branches drawn at their
32
+ fork points with `│ ├ ╰` connectors and folded to their `⎇` header until opened; from any session
33
+ you see the whole tree, your branch open with `← here`. The DeepSeek-Harness trajectory (Input/
34
+ Model/Tools lanes, per-step inspector) is off by default and one keystroke away (`1/2/3`, `i`).
35
+ - Off-path branches: siblings and the trunk's continuation that are not on the active path
36
+ appear as `┆⎇` rows at the bottom of the tree; `→` expands them, `⏎` switches. The trunk's
37
+ own continuation reads `trunk continues` — it is not a branch of itself.
38
+ - Tool results are costed by size (`~`, chars/4) in rows and totals, so ⚠ fires on fat
39
+ results; the context gauge counts the last assistant turn's own output, and the header's
40
+ `~` appears only when part of the total really is a guess.
41
+ - Route keys are inactive while a dialog is open (they used to fire from text typed into the
42
+ search / branch prompts); the sidebar card refreshes after every journal write; the layout
43
+ follows terminal resizes; errored tool calls show in the Tools lane.
44
+ - Server half honours `storage` (use the same value in both config files), fetches whole
45
+ transcripts (OpenCode's message paging cursor is not usable by clients), resolves the state
46
+ dir without blocking startup, never fails a turn on journal I/O errors, and locks registry
47
+ writes against the TUI half.
48
+ - `/ctree decisions --export` defaults to `./ctree-decisions.md`; `/ctree branch` names may
49
+ contain spaces (a trailing `provider/model` picks the branch model); `--min-tokens` /
50
+ `--older-than` reject non-numbers instead of silently doing nothing.
51
+ - Outside a git repo (OpenCode reports worktree `/`) the journal lives in OpenCode's state
52
+ dir instead of the filesystem root.
53
+ - Harness: mock provider, pty driver with pyte screen snapshots (`@!regex` keys wait for fresh
54
+ output), Bun e2e suite; pty runs are sandboxed with their own XDG dirs so they never read
55
+ your real OpenCode config or write sessions into your real database.
56
+
57
+ ### UX pass over the real screens
58
+
59
+ - The prompt gauge and the tree header print the identical context string
60
+ (`ctx ~2.3k/32.8k · low`), so the two surfaces can no longer disagree.
61
+ - Sidebar card: **Context tree** is a heading like OpenCode's own `Context` and `LSP`; the
62
+ branch name and its status sit on separate lines (a long name used to wrap and orphan
63
+ `· open`), and the status line names where you came from — `open · from "Fix flaky test"`.
64
+ - Sidebar card: the crops line only appears when there are crops (`✂ 2 crops · ~31k hidden`),
65
+ and the trunk reads `trunk · 2 branches` when it has any.
66
+ - Merge picker: the title says where the branch is going (`Merge ⎇ try-redis → Fix flaky
67
+ test`), every option carries a subtitle, and **Tournament** is offered only when the branch
68
+ has open siblings. The "no `$EDITOR`" fallback no longer leaks into the option labels.
69
+ - Every merge confirmation — the in-app one and the `$EDITOR` gate — states that your
70
+ transcript is never rewritten and the record is appended to the trunk as a normal message;
71
+ the file `$EDITOR` opens explains that saving is the confirmation.
72
+ - `/branch` from the palette asks with the same wording as `b` in the tree
73
+ (`Branch here → new OpenCode session`), and the model picker that follows says
74
+ `Model for this branch (Enter keeps the current one)`.
75
+ - The tree footer lists the six keys you need (`⏎ go b branch m merge c crop x undo
76
+ ? help q back`); `?` opens a help overlay with how to read the screen and every other key.
77
+ - Rows sit under a `turn step … tokens` column header, the selected row is drawn with a
78
+ readable contrast, previews no longer show raw markdown (`**`, backticks), and estimated
79
+ token counts read `~2.1k` instead of `2.1k~`.
80
+ - The timeline lanes stay hidden until there are three turns to plot; a session with no
81
+ messages says so instead of drawing an empty frame; a branch you just made reads
82
+ `just branched, nothing here yet` instead of offering an empty fold.
83
+ - The crop confirmation says what actually happens: the tokens leave the model's context on
84
+ the next turn, your transcript is never rewritten, `/undo` restores.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Naveen (navbytes)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # opencode-context-tree
2
+
3
+ Pi-style context tree for [OpenCode](https://opencode.ai), with the
4
+ [`pi-context-tree`](https://github.com/navbytes/pi-context-tree) git-style workflow
5
+ (`/branch`, `/merge`, `/crop`, `/undo`) and a DeepSeek-Harness-style trajectory view
6
+ (timeline lanes, per-step tokens — the model's own where it reports them, estimated `~`
7
+ elsewhere — timing, inspector) in one screen.
8
+
9
+ **Status: 0.1.0 — first release. Install with `"plugin": ["opencode-context-tree"]` in both `opencode.json` and `tui.json` (see `docs/USAGE.md`).**
10
+ Branch, jump, labels, filters, search, crop + undo, squash/discard/tournament merge with a
11
+ `$EDITOR` gate, the timeline lanes, inspector and consumers views, the gauge, and the headless
12
+ `/ctree` commands all work against OpenCode 1.18 and are covered by pty-driven e2e tests
13
+ (`bun run test:e2e`). Read [DESIGN.md](./DESIGN.md) — it contains the research
14
+ (Pi, `pi-context-tree`, OpenCode plugin/SDK surface, existing plugins, DSH
15
+ trajectory), the end-user flows, the combined tree + trajectory mockup, the data
16
+ model, architecture, edge cases, and the roadmap.
17
+
18
+ ## The idea in one screen
19
+
20
+ `/tree` is an outline of the whole session tree — Pi's tree — where every message and tool step
21
+ is one content-forward row and branches are drawn at their fork points:
22
+
23
+ ```
24
+ ┌ Context tree · Fix flaky test · trunk ctx ~46k/200k · filling
25
+ │ filter: default 24 rows
26
+ │ ● user: build yourself a tool that reads the context window… ~1.2k
27
+ │ ○ assistant: I'll start by inspecting my environment… 0.3k
28
+ │ ⚙ [bash $ ls -la ~/Documents/] → total 744 … ~2.1k
29
+ │ ● user: decompress the session and show the structure ~0.2k
30
+ │ ╰⎇ try-redis ▸ squashed · 9 turns ~22k
31
+ │ ╰⎇ fix-flaky ▾ open · 6 turns ← here ~14k
32
+ │ │ ● user: the bun test is flaky, find the race ~0.4k
33
+ │ │ ⚙ [bash $ bun test src/foo.test.ts] ⚠ ~4.7k
34
+ │ ◆ Decision: try-redis · Outcome: switched to a write-through cache… ~0.9k
35
+ └ ⏎ go b branch m merge c crop i inspector 1·2·3 lanes x undo ? help q back
36
+ ```
37
+
38
+ Every message and tool call is a row; the gutter draws each branch at the message it was forked
39
+ from; from anywhere you see the whole tree, your current branch open with `← here` and the rest
40
+ folded. It stays close to Pi so the screen is familiar to anyone coming from it. The DeepSeek
41
+ Harness *trajectory* is one keystroke away, not gone: `i` opens the inspector (per-step payload,
42
+ result, timing) and `1/2/3` bring in the Input / Model / Tools lane minimap. Sessions made with
43
+ OpenCode's own `/fork` are adopted into the tree automatically.
44
+
45
+ ## Screenshots
46
+
47
+ Real OpenCode 1.18 TUI, gemma4 via ollama. `/tree` opens as a Pi-style outline of the whole
48
+ session tree: one content-forward row per message and tool step (`● user:` / `○ assistant:` /
49
+ `⚙ [bash $ …]`), branches drawn at their fork points with `│ ├ ╰` connectors and folded to their
50
+ `⎇` header until you open them. Here a trunk about caching a `/users` endpoint has a squashed
51
+ `try-redis` branch (its ◆ decision record is the leaf), a rejected `try-lru`, and two native
52
+ `/fork` sessions:
53
+
54
+ ![tree from the trunk](docs/screenshots/tree-trunk.png)
55
+
56
+ The DeepSeek-Harness trajectory is one keystroke away, not gone: `i` opens the inspector and
57
+ `1/2/3` bring in the Input/Model/Tools lanes:
58
+
59
+ ![tree with the trajectory panels on](docs/screenshots/tree-trajectory.png)
60
+
61
+ From inside a branch, `← here` marks your current step, that branch is expanded, and the rest of
62
+ the tree stays visible and folded — you always see the whole tree:
63
+
64
+ ![tree from a branch](docs/screenshots/tree-from-a-branch.png)
65
+
66
+ `→` expands a branch inline (its turns continue the numbering from the fork point):
67
+
68
+ ![a branch expanded inline](docs/screenshots/tree-expanded.png)
69
+
70
+ `D` decisions · `u` what's filling the context · `?` help · `m` merge · the sidebar card:
71
+
72
+ | | |
73
+ |---|---|
74
+ | ![decisions](docs/screenshots/decisions.png) | ![consumers](docs/screenshots/consumers.png) |
75
+ | ![help](docs/screenshots/help.png) | ![merge picker](docs/screenshots/merge-picker.png) |
76
+
77
+ ![sidebar card](docs/screenshots/sidebar-card.png)
78
+
79
+ ## Try it from source
80
+
81
+ ```sh
82
+ bun install && bun run build
83
+ # opencode.json → "plugin": ["/abs/path/opencode-tree/dist/server.js"]
84
+ # tui.json → "plugin": ["/abs/path/opencode-tree/dist/tui.js"]
85
+ ```
86
+
87
+ The footer inside `/tree` carries six keys — `⏎` go, `b` branch, `m` merge, `c` crop,
88
+ `x` undo, `q` back — and `?` opens a help overlay with the rest: crop mode (`space` mark,
89
+ double for protected, `a` auto, `t` result⇄turn, `⏎` apply), `i` inspector, `u` consumers,
90
+ `D` decisions (`E` export), `L` label, `←→` fold/unfold, `[ ]` hop branches, `f` filter,
91
+ `/` search, `g/G`, and how to read the screen.
92
+
93
+ ## Commands
94
+
95
+ | Command | What it does |
96
+ |---|---|
97
+ | `/tree` (`Ctrl+Q`) | open the combined tree + trajectory view |
98
+ | `/branch <name> [model]` | fork here into a named branch, optionally on a cheaper model |
99
+ | `/merge [--pick \| --no-llm \| --discard \| --tournament]` | close the branch: **Squash** drafts a ◆ decision record you confirm, **Squash without LLM** lets you write it, **Discard** lands nothing, **Tournament** keeps one of several siblings. Your transcript is never rewritten; the record is appended to the trunk as a normal message |
100
+ | `/crop [--top \| --auto …]` | stub fat tool results or drop whole turns from what the model sees; append-only, reversible |
101
+ | `/undo` | revert the last branch / merge / crop |
102
+ | `/decisions [--export]` | list or export decision records |
103
+
104
+ ## How it maps onto OpenCode
105
+
106
+ - a **branch is an OpenCode session** created with `session.fork`; the plugin
107
+ remembers `(parent, anchor)` in an append-only journal and mirrors it into
108
+ `session.metadata`;
109
+ - **crop** is applied per request in `experimental.chat.messages.transform`, so the
110
+ transcript keeps the originals and the model sees stubs;
111
+ - **merge** writes the confirmed record with `session.prompt({ noReply: true })`;
112
+ - the UI is a TUI plugin (`@opencode-ai/plugin/tui`): a route, two slots (gauge,
113
+ sidebar card), dialogs and a keymap layer.
114
+
115
+ Two config entries will be needed once it ships:
116
+
117
+ ```jsonc
118
+ // opencode.json
119
+ { "plugin": ["opencode-context-tree"] }
120
+ // tui.json
121
+ { "plugin": ["opencode-context-tree"] }
122
+ ```