pi-odd-worktree 0.0.0-stage → 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/README.md +223 -2
- package/extensions/odd-worktree-tools.ts +7593 -0
- package/lib/checkout-facts.js +598 -0
- package/lib/clipboard.js +226 -0
- package/lib/gh-issues.js +1555 -0
- package/lib/issue-preview.js +273 -0
- package/lib/issues-view.js +2274 -0
- package/lib/odd-docs.js +505 -0
- package/lib/odd-markdown.js +413 -0
- package/lib/odd-view.js +990 -0
- package/lib/open-external.js +106 -0
- package/lib/preview-scroll-keys.js +192 -0
- package/lib/session-auto-name.js +292 -0
- package/lib/session-worktree-register.js +62 -0
- package/lib/tui-paint.js +273 -0
- package/lib/view-layout.js +368 -0
- package/lib/warp-create-panel.js +722 -0
- package/lib/warp-create-picker.js +906 -0
- package/lib/warp-delete-confirm.js +465 -0
- package/lib/warp-selector-view.js +1265 -0
- package/lib/worktree-card.js +1117 -0
- package/lib/worktree-changes.js +1395 -0
- package/lib/worktree-close-herdr.js +73 -0
- package/lib/worktree-git.js +1048 -0
- package/lib/worktree-hooks.js +738 -0
- package/lib/worktree-model-result.js +291 -0
- package/lib/worktree-registry.js +886 -0
- package/lib/worktree-session-guard.js +595 -0
- package/lib/worktree-symlinks.js +336 -0
- package/lib/worktree-warp-sessions.js +566 -0
- package/package.json +36 -4
- package/tools/commands.test.js +176 -0
- package/tools/fixtures/gh-api-user.json +1 -0
- package/tools/fixtures/gh-issue-list.json +1 -0
- package/tools/fixtures/gh-issue-view.json +1 -0
- package/tools/fixtures/gh-label-list.json +1 -0
- package/tools/fixtures/gh-repo-view.json +1 -0
- package/tools/fixtures/load-extension.js +66 -0
- package/tools/gh-issues-feedback.test.js +359 -0
- package/tools/gh-issues.test.js +1502 -0
- package/tools/issue-preview.test.js +216 -0
- package/tools/issues-command.test.js +1217 -0
- package/tools/issues-view.test.js +4401 -0
- package/tools/odd-docs.test.js +803 -0
- package/tools/odd-markdown.test.js +236 -0
- package/tools/odd-view.test.js +2328 -0
- package/tools/package-surface.test.js +138 -0
- package/tools/t5-session-identity.test.js +306 -0
- package/tools/warp-create-panel.test.js +185 -0
- package/tools/warp-create-picker.test.js +1357 -0
- package/tools/warp-delete-confirm.test.js +616 -0
- package/tools/warp-selector-view.test.js +1463 -0
- package/tools/worktree-changes.test.js +951 -0
- package/tools/worktree-close-herdr-async.test.js +204 -0
- package/tools/worktree-odd-migration.test.js +224 -0
- package/tools/worktree-registry-cache.test.js +163 -0
- package/tools/worktree-registry-odd.test.js +231 -0
- package/tools/worktree-registry.test.js +244 -0
- package/tools/worktree-session-guard.test.js +507 -0
- package/tools/worktree-tools.test.js +434 -0
package/README.md
CHANGED
|
@@ -1,3 +1,224 @@
|
|
|
1
|
-
#
|
|
1
|
+
# odd-worktree
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/jonasotoaguilar/odd-worktree/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
A Pi package for agent worktrees, ODD documents and GitHub issues:
|
|
6
|
+
create/list/close/warp checkouts from the agent, drive the same work from the
|
|
7
|
+
`/warp` panel, read the project's `odd/` documents with `/odd-list`, and read
|
|
8
|
+
this checkout's GitHub issues with `/issues`.
|
|
9
|
+
|
|
10
|
+
It runs in Pi alone. Herdr is optional, and only `worktree_create` uses it (the
|
|
11
|
+
best-effort workspace association): every Herdr call there is attempted first
|
|
12
|
+
and falls back to plain `git`, so nothing here requires a Herdr install. Removal
|
|
13
|
+
never touches Herdr at all — a close never closes, opens or removes a Herdr
|
|
14
|
+
workspace, and it removes the checkout with `git worktree remove`.
|
|
15
|
+
|
|
16
|
+
## Surface
|
|
17
|
+
|
|
18
|
+
Three commands:
|
|
19
|
+
|
|
20
|
+
| Command | What it does |
|
|
21
|
+
|---|---|
|
|
22
|
+
| `/warp` | Move this session to another checkout of the project. No argument opens the selector (list, create, delete); `/warp <name>` jumps straight to the flattened branch slug or `principal`. |
|
|
23
|
+
| `/odd-list` | Two-pane viewer of this checkout's `odd/` documents: list on the left, styled markdown on the right. |
|
|
24
|
+
| `/issues` | Full-screen reader of this checkout's repository issues: My issues / Assigned / Involved / All, live server search, issue preview with comments, and label/assignee, comment and close actions when the repository permits them. |
|
|
25
|
+
|
|
26
|
+
Four agent tools:
|
|
27
|
+
|
|
28
|
+
| Tool | What it does |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `worktree_create` | Create a branch and its checkout from local `main`, register it, run the creation hooks, optionally copy pending changes. |
|
|
31
|
+
| `worktree_list` | Read-only listing: principal first, then the session's own checkout, then the rest. Prunes rows whose checkout is gone. |
|
|
32
|
+
| `worktree_close` | Remove a registered checkout and keep the branch. Needs `force: true` for a dirty checkout. Refuses while a live Pi session is working in that checkout — this session (warp first) or another (let it finish, close it, or warp it away). |
|
|
33
|
+
| `worktree_warp` | Queue the session move to another checkout. The tool only queues it as `/warp <handoff>`; the command performs the move when the turn ends. |
|
|
34
|
+
|
|
35
|
+
Create and close ask for approval when the host offers dialog UI. `list` never
|
|
36
|
+
mutates anything.
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pi install . # from this directory
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`package.json` declares `pi.extensions`, so Pi loads
|
|
45
|
+
`extensions/odd-worktree-tools.ts` on its own.
|
|
46
|
+
|
|
47
|
+
## Requirements
|
|
48
|
+
|
|
49
|
+
- Node.js >= 18 and `git`.
|
|
50
|
+
- Pi (`@earendil-works/pi-coding-agent`, `@earendil-works/pi-tui`) — peer
|
|
51
|
+
dependencies, provided by the host.
|
|
52
|
+
- Optional: `herdr` for the create's workspace association; `codegraph` for the
|
|
53
|
+
create hook's index step. Both are detected at runtime and skipped when
|
|
54
|
+
absent. Gentle Shell's published presence store (when Gentle Shell is
|
|
55
|
+
installed) is what makes the active-session check see *other* sessions; without
|
|
56
|
+
it, only this session's own checkout is detected.
|
|
57
|
+
- `/issues` needs the GitHub CLI (`gh`) installed and authenticated
|
|
58
|
+
(`gh auth login`). Without it the command stays registered, says once per
|
|
59
|
+
session what is missing and never opens the reader.
|
|
60
|
+
|
|
61
|
+
## Where state lives
|
|
62
|
+
|
|
63
|
+
The registry is per repository at `<git-common-dir>/pi-worktrees.json`, so a
|
|
64
|
+
worktree of the same clone sees the same rows. Session names derived from an
|
|
65
|
+
ODD write and the warp's fork/resume bookkeeping travel in the Pi session files
|
|
66
|
+
the host already owns. Nothing is written to user config.
|
|
67
|
+
|
|
68
|
+
## Contract worth knowing
|
|
69
|
+
|
|
70
|
+
- A new branch is born from the LOCAL `main` ref. A repository without a local
|
|
71
|
+
`main` is refused before anything is written, rather than seeding the branch
|
|
72
|
+
from whatever the invoking checkout had.
|
|
73
|
+
- `worktree_create` requires `changes`: `"all"`, `"none"`, or an explicit list
|
|
74
|
+
of checkout-relative paths. The create never guesses.
|
|
75
|
+
- Paths, branches and arguments reach `git`/`herdr`/`gh` as fixed argv
|
|
76
|
+
arrays, never as a shell string.
|
|
77
|
+
- Deleting a checkout is refused while a live Pi session is working in it, and
|
|
78
|
+
the SAME check runs for the `worktree_close` tool and for the warp panel's
|
|
79
|
+
`ctrl+d` — before the pending-change question, and again immediately before
|
|
80
|
+
the removal. This session's own checkout is refused with "warp first"; another
|
|
81
|
+
session's is refused naming its REAL session id — the guard joins the
|
|
82
|
+
presence hash to Gentle Shell's published transport activation record for that
|
|
83
|
+
session. When no record joins (a peer that has not published one, an over-cap
|
|
84
|
+
or unreadable registry), the refusal shows the presence hash and says the
|
|
85
|
+
session id could not be resolved; it never invents one from a label. Nothing
|
|
86
|
+
is auto-warped and nothing is closed on the human's behalf: the history is
|
|
87
|
+
theirs to move.
|
|
88
|
+
- That check is BEST EFFORT, not a guarantee. It reads Gentle Shell's published
|
|
89
|
+
presence records (heartbeat plus workspace) and transport activation records
|
|
90
|
+
(the raw session id) when Gentle Shell is installed and running, and it always
|
|
91
|
+
catches the checkout this session stands on. A record whose heartbeat is older
|
|
92
|
+
than 15 s cannot prove a session is gone, so it is disclosed in the close
|
|
93
|
+
output as a stale record instead of blocking the removal forever; the same
|
|
94
|
+
close output also discloses live records that published no workspace (so they
|
|
95
|
+
could not be attributed to a checkout) and a presence or transport scan that
|
|
96
|
+
hit its bound. A presence store that is absent or unreadable blocks nothing
|
|
97
|
+
and is disclosed. Gentle Shell's scratch ledger is deliberately not used: it
|
|
98
|
+
records what a session *created*, not where it is working.
|
|
99
|
+
- Removal is `git worktree remove <absolute path>`, with `--force` only when the
|
|
100
|
+
caller asked for it. The branch is kept, and no Herdr panel is closed, opened
|
|
101
|
+
or removed on any path.
|
|
102
|
+
- The `/warp` selector never offers the checkout the session already stands in:
|
|
103
|
+
warping there would move the session to the route it is already on. The
|
|
104
|
+
exclusion is by checkout identity (the resolved real path), not by spelling,
|
|
105
|
+
so a symlinked or otherwise differently-spelled path to the current checkout
|
|
106
|
+
is excluded too, while a checkout whose name merely shares a prefix is still
|
|
107
|
+
offered.
|
|
108
|
+
- `/issues` pins the repository identity (`<host>/<owner>/<repo>`) when the
|
|
109
|
+
list loads. Every page, detail, label and assignee read binds it, and so do
|
|
110
|
+
the actions and the `o` browser opener (`gh issue view <n> --web -R`). If the
|
|
111
|
+
checkout's remote moves while the reader is open, mutations refuse and the
|
|
112
|
+
reads keep the displayed repository, so an issue number on screen can never
|
|
113
|
+
be sent to another repository.
|
|
114
|
+
- `/issues` resolves the viewer's permission for that pinned repository before
|
|
115
|
+
an action editor opens, not when the write is sent. The evidence is the
|
|
116
|
+
repository role the issue detail already reads (`viewerPermission`), it is
|
|
117
|
+
cached for the panel session, and `r` re-reads it. Anything but a confirmed
|
|
118
|
+
capability fails closed — the footer hint is absent, the key refuses with the
|
|
119
|
+
reason on screen, and no editor opens and no mutation is attempted:
|
|
120
|
+
|
|
121
|
+
| Action | Minimum role |
|
|
122
|
+
| --- | --- |
|
|
123
|
+
| close / reopen an issue | triage |
|
|
124
|
+
| apply or remove an existing label | triage |
|
|
125
|
+
| assign or unassign | triage |
|
|
126
|
+
| create a repository label | write |
|
|
127
|
+
| comment | none beyond read access |
|
|
128
|
+
|
|
129
|
+
Creating a label is a SEPARATE, write-level capability, so a triage-only
|
|
130
|
+
viewer opens the labels editor normally and sees neither a create row nor its
|
|
131
|
+
`n` keybinding. Commenting is deliberately ungated: GitHub lets anyone with
|
|
132
|
+
read access to the repository comment, so demanding triage would hide an
|
|
133
|
+
action the viewer is allowed to take.
|
|
134
|
+
- Refusals are reported, not swallowed: a rejected name, a dirty checkout, a
|
|
135
|
+
failed move, or a denied GitHub capability names the reason and leaves the
|
|
136
|
+
repository as it was.
|
|
137
|
+
|
|
138
|
+
## Deliberately absent
|
|
139
|
+
|
|
140
|
+
This package is the worktree/ODD/issues surface only. It has no customize
|
|
141
|
+
panel, no plugin-tools toggles, no Herdr plugin management, and no
|
|
142
|
+
worktree↔issue association — there is nothing to configure, and the three
|
|
143
|
+
commands above are always on.
|
|
144
|
+
|
|
145
|
+
## Tests
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
node --test tools/*.test.js
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`tools/package-surface.test.js`, `tools/worktree-tools.test.js`,
|
|
152
|
+
`tools/commands.test.js` and `tools/issues-command.test.js` cover this
|
|
153
|
+
package's own surface: registration, manifest, the three checkout tools against
|
|
154
|
+
a real temporary repository with a deliberately unusable Herdr, the three
|
|
155
|
+
command handlers, and `/issues` end to end against an injected `gh` runner (no
|
|
156
|
+
network, no real GitHub mutation). `tools/worktree-session-guard.test.js` pins
|
|
157
|
+
the active-session guard against synthetic presence stores (live, stale,
|
|
158
|
+
malformed and linked records), `tools/worktree-close-herdr-async.test.js` pins
|
|
159
|
+
that a close removes with git and spawns no Herdr at all, and
|
|
160
|
+
`tools/warp-delete-confirm.test.js` drives the real `/warp` command to prove the
|
|
161
|
+
session check runs before the deletion question and again after it. The
|
|
162
|
+
remaining suites cover the shared `lib/` modules (the warp create panel, warp
|
|
163
|
+
selector view, ODD viewer and document reader, the GitHub issue client and
|
|
164
|
+
view, delete evidence, change transfer, registry, session bookkeeping).
|
|
165
|
+
|
|
166
|
+
Tests keep every temporary repository, stub binary and extension copy under the
|
|
167
|
+
system temp directory and remove them on exit.
|
|
168
|
+
|
|
169
|
+
## CI and releases
|
|
170
|
+
|
|
171
|
+
| Workflow | Trigger | What it runs |
|
|
172
|
+
|---|---|---|
|
|
173
|
+
| `ci.yml` | push to `main`, every pull request | `node --test tools/*.test.js` on Node 18, 20, 22 and 24 — the range `engines` declares, 18 as its floor |
|
|
174
|
+
| `pr-check.yml` | pull request opened, edited, reopened or updated | the PR title and every non-merge commit subject must follow [Conventional Commits](https://www.conventionalcommits.org/), plus one test run on Node 22 |
|
|
175
|
+
| `release.yml` | tag `v*` push, or a manual dispatch naming an existing tag | tag/`package.json` version parity, the suite, the GitHub Release, then `npm publish`, then verification |
|
|
176
|
+
|
|
177
|
+
The suite uses Node builtins only, so CI installs nothing and needs no lockfile.
|
|
178
|
+
No workflow stores a registry credential: npm publication uses
|
|
179
|
+
[Trusted Publishing](https://docs.npmjs.com/trusted-publishers), with
|
|
180
|
+
`id-token: write` granted to the `release` job and nowhere else.
|
|
181
|
+
|
|
182
|
+
### Cutting a release
|
|
183
|
+
|
|
184
|
+
1. Set `version` in `package.json`, then tag the exact commit:
|
|
185
|
+
`git tag -a v0.2.0 -m v0.2.0 && git push origin v0.2.0`. The tag must be `v`
|
|
186
|
+
plus the `package.json` version, or the run stops in preflight.
|
|
187
|
+
2. The workflow creates the GitHub Release with generated base notes. Curate the
|
|
188
|
+
title and body afterwards by editing the published release
|
|
189
|
+
(`gh release edit v0.2.0 --title ... --notes-file ...`); notes are never
|
|
190
|
+
committed to the repository.
|
|
191
|
+
3. A version npm already serves is reported and left alone; a version npm does
|
|
192
|
+
not have is published with provenance. A published version is immutable — fix
|
|
193
|
+
the cause and cut the next patch tag.
|
|
194
|
+
|
|
195
|
+
### npm publication prerequisites
|
|
196
|
+
|
|
197
|
+
`pi-odd-worktree` is not on npm yet, and OIDC cannot publish a
|
|
198
|
+
package's first version, so the npm step of `release.yml` fails visibly until
|
|
199
|
+
these are done by hand. It is never silently skipped.
|
|
200
|
+
|
|
201
|
+
- Publish the first version by hand (`npm publish --access public`, 2FA, no
|
|
202
|
+
stored token).
|
|
203
|
+
- Bind the trusted publisher to this repository, workflow file `release.yml`
|
|
204
|
+
and the `release` environment, then cut the next version within two days — npm
|
|
205
|
+
expires a configuration that has not completed a publish in that window.
|
|
206
|
+
- Add a `repository` field to `package.json` before the first publish: the
|
|
207
|
+
provenance attestation binds to the public repository it names,
|
|
208
|
+
case-sensitively, and published version metadata is immutable. This repository
|
|
209
|
+
is currently private, and provenance requires a public repository, so that has
|
|
210
|
+
to be resolved (make it public, or decide not to publish attestations) before
|
|
211
|
+
`--provenance` can succeed.
|
|
212
|
+
- Create the `release` environment (Settings → Environments) and attach required
|
|
213
|
+
reviewers if a release should pause for approval.
|
|
214
|
+
- Confirm `files[]` still lists every shipped surface with
|
|
215
|
+
`npm pack --dry-run` before the first publish.
|
|
216
|
+
|
|
217
|
+
### Enforcement still manual
|
|
218
|
+
|
|
219
|
+
Workflow presence is not enforcement. Required status checks on `main`
|
|
220
|
+
(`CI / Test (Node …)` and the `PR Check` gates) need a branch protection rule or
|
|
221
|
+
ruleset, which this repository cannot currently apply: it is private, and GitHub
|
|
222
|
+
only offers those settings to public repositories or GitHub Pro.
|
|
223
|
+
`.github/CODEOWNERS` names the owner for review requests; review-from-code-owners
|
|
224
|
+
is part of the same protection rule.
|