opendoor-plugin 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +299 -0
- package/dist/browser/pi-web-plugin.js +9499 -0
- package/dist/package.json +52 -0
- package/dist/server-plugin.js +1047 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kai Ritterbusch
|
|
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,299 @@
|
|
|
1
|
+
# opendoor — a Doorstop requirements-management plugin for PI WEB
|
|
2
|
+
|
|
3
|
+
`opendoor` is a **paired** PI WEB plugin that surfaces
|
|
4
|
+
[Doorstop](https://github.com/doorstop-dev/doorstop) requirements stored in
|
|
5
|
+
the workspace next to the code they trace to: a **Requirements** workspace tab
|
|
6
|
+
with the document tree, per-item state (reviewed/unreviewed changes, suspect
|
|
7
|
+
links), validation findings, and one-click `doorstop` CLI actions (validate,
|
|
8
|
+
publish, add/link/unlink, clear suspect links, review), plus agent prompts
|
|
9
|
+
that insert requirement context into the session. When paired, the plugin's
|
|
10
|
+
server entry runs the CLI headlessly and the captured output is displayed in
|
|
11
|
+
the panel itself (a **status bar** at the bottom); when unpaired, every action
|
|
12
|
+
falls back to the workspace terminal. The paired backend also recovers the
|
|
13
|
+
reviewed version of an item file from git history, so the item detail pane
|
|
14
|
+
can show a **Changes since review** diff — and Review can optionally record
|
|
15
|
+
a pathspec-limited review commit (`commitAfterReview`).
|
|
16
|
+
|
|
17
|
+
Full feature spec: [`docs/feature-doorstop-plugin.md`](docs/feature-doorstop-plugin.md)
|
|
18
|
+
(§5 architecture, §6 requirements state model, §7–§8 UI/actions).
|
|
19
|
+
|
|
20
|
+
## Status
|
|
21
|
+
|
|
22
|
+
The plugin is fully wired and green, in two halves:
|
|
23
|
+
|
|
24
|
+
- **Browser entry** (`src/pi-web-plugin.ts`, apiVersion 2, name `Opendoor`)
|
|
25
|
+
delegates to `createOpendoorBrowserContributions` in
|
|
26
|
+
`src/doorstop-contributions.ts`, which contributes:
|
|
27
|
+
|
|
28
|
+
- **panel** `workspace.doorstop` (title **Requirements**, order 60) — the Lit
|
|
29
|
+
body element `<pi-web-opendoor-panel-body>`, driven by the per-workspace
|
|
30
|
+
controller registry (`src/doorstop-panel.ts`): five vertical sections —
|
|
31
|
+
`project-actions` (title, Items/Findings toggle, Refresh / Run validation
|
|
32
|
+
/ Publish HTML), `item-list` (filters row + item rows with state chips),
|
|
33
|
+
`item-details` (links in/out, references, extended attributes,
|
|
34
|
+
collapsible **Changes since review** diff), `item-action-palette`
|
|
35
|
+
(Review / Clear suspect links / Edit / Link / Unlink / Ask-agent menu,
|
|
36
|
+
sized to the host prompt footer), and `status-bar` (status badge,
|
|
37
|
+
duration, Dismiss button, and the auto-expanding stdout/stderr of the
|
|
38
|
+
latest CLI run, plus the review-commit outcome line when the review
|
|
39
|
+
requested a commit);
|
|
40
|
+
- **actions** `view.doorstop` (mod+7) and `workspace.refresh-doorstop`
|
|
41
|
+
(mod+shift+d);
|
|
42
|
+
- **label** `doorstop-status` — the informational requirement-health counts
|
|
43
|
+
(async-cache idiom, feature spec §7.4).
|
|
44
|
+
|
|
45
|
+
- **Server entry** (`src/server-plugin.ts`, default export `PiWebServerPlugin`)
|
|
46
|
+
contributes a workspace provider that claims only projects whose root
|
|
47
|
+
contains a `.doorstop.yml` (cheap `fs.access` probe — everything else stays
|
|
48
|
+
with the bundled Git provider) and serves a six-operation backend:
|
|
49
|
+
`doorstop.run` runs one Doorstop CLI invocation — a review request carrying
|
|
50
|
+
the opt-in `commit: true` flag is followed by a pathspec-limited git commit
|
|
51
|
+
of the item file (`doorstop: review <uid>`) — the read-only
|
|
52
|
+
`doorstop.item-baseline` recovers the reviewed version of an item file from
|
|
53
|
+
git history, and the four git actions `doorstop.git-status`,
|
|
54
|
+
`doorstop.git-stage`, `doorstop.git-unstage`, and `doorstop.git-commit`
|
|
55
|
+
power the panel's git strip and stage/unstage/commit controls (see **Git
|
|
56
|
+
actions** below). `src/doorstop-backend.ts` validates each request, builds argv
|
|
57
|
+
server-side (the browser never sends shell strings), and runs through the
|
|
58
|
+
host's `execFile()` helper with `cwd` = the workspace path, timeouts
|
|
59
|
+
clamped to 8.5 s (the review+git pipeline shares one deadline budget under
|
|
60
|
+
the host's 10 s callback bound), and per-workspace run serialization.
|
|
61
|
+
|
|
62
|
+
Dispatch (per action, in `src/doorstop-panel-elements.ts`): when the workspace
|
|
63
|
+
is opendoor-owned with an active backend, the action sends a structured
|
|
64
|
+
request via `context.backend.request("doorstop.run", …)` (the review-commit
|
|
65
|
+
flag rides along when the setting is enabled; the "Changes since review"
|
|
66
|
+
section fetches via `context.backend.request("doorstop.item-baseline", …)`)
|
|
67
|
+
and the captured output appears in the panel's status bar (auto-expanded);
|
|
68
|
+
the panel auto-rescans on completion. When the backend is absent (unpaired
|
|
69
|
+
install, or a workspace the provider does not own), the action falls back to
|
|
70
|
+
`terminal.runCommand()` with the same shell command, so output stays visible
|
|
71
|
+
and auditable in the terminal.
|
|
72
|
+
|
|
73
|
+
`doorstop.run` and `doorstop.item-baseline`) shared by both halves, plus the
|
|
74
|
+
four git-action contracts (`doorstop.git-status`, `doorstop.git-stage`,
|
|
75
|
+
`doorstop.git-unstage`, `doorstop.git-commit`), live in
|
|
76
|
+
`src/doorstop-backend-contract.ts`. Later modules import these and must not
|
|
77
|
+
redefine them.
|
|
78
|
+
|
|
79
|
+
## Workspace settings (`.pi-web/opendoor.json`)
|
|
80
|
+
|
|
81
|
+
Optional per-workspace configuration, read by the plugin on every load
|
|
82
|
+
(missing file → defaults; malformed or invalid values → warning diagnostics
|
|
83
|
+
in the panel + defaults). Workspace-scoped settings cannot use pi-web's
|
|
84
|
+
`plugins.<id>.settings` config (that is host-side and only reaches server
|
|
85
|
+
entries), so this file plays the same role `.pi-web/tasks.json` plays for the
|
|
86
|
+
bundled workspace-tasks plugin. The *server* entry's host-scoped knobs
|
|
87
|
+
(CLI binary path, exec timeout) are configured through
|
|
88
|
+
`plugins.opendoor.settings` instead — see **Host settings** below:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"version": 1,
|
|
93
|
+
"publish": { "target": "./public" },
|
|
94
|
+
"discovery": { "excludedDirectories": ["published", "vendor"] },
|
|
95
|
+
"commitAfterReview": false
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
- `publish.target` — path passed to `doorstop publish all`. Must be a safe
|
|
100
|
+
workspace-relative path (no `..`, no absolute, no drive prefix, no leading
|
|
101
|
+
`-`). It is shell-quoted at the terminal-command boundary when it contains
|
|
102
|
+
characters outside the inert `\w./-` alphabet; on the backend path the
|
|
103
|
+
target is passed as an argv element, never through a shell.
|
|
104
|
+
- `discovery.excludedDirectories` — additional directory names the
|
|
105
|
+
`.doorstop.yml` walk never enters (merged with the built-in
|
|
106
|
+
`.git`/`node_modules` skips, max 16 names).
|
|
107
|
+
- `commitAfterReview` — opt-in boolean (default `false`): when enabled on a
|
|
108
|
+
paired workspace, the **Review** action's `doorstop review <uid>` is
|
|
109
|
+
followed by a pathspec-limited git commit of the item file with the
|
|
110
|
+
conforming message `doorstop: review <uid>` (git `add` then
|
|
111
|
+
`commit -m … -- <path>` — other staged/unstaged work is left untouched).
|
|
112
|
+
Committing user content is strictly opt-in, and the status bar
|
|
113
|
+
narrates every commit outcome (`commit: <sha>` / `clean` (already
|
|
114
|
+
committed) / `skipped` (not a git repository | item file not found |
|
|
115
|
+
review failed | deadline) / `failed` — `<stderr>`). Under the default,
|
|
116
|
+
Review behaves exactly as before.
|
|
117
|
+
|
|
118
|
+
## Host settings (`plugins.opendoor.settings`)
|
|
119
|
+
|
|
120
|
+
Optional, server-side only (requires the paired install and a sessiond
|
|
121
|
+
restart to take effect). Parsed leniently; wrong types fall back to defaults:
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{ "doorstopPath": "/opt/venv/bin/doorstop", "gitPath": "git", "timeoutMs": 8000 }
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
- `doorstopPath` — the Doorstop CLI binary. Defaults to `"doorstop"` resolved
|
|
128
|
+
on the **sessiond host PATH** (the daemon environment, not your login shell
|
|
129
|
+
— set this when the CLI lives in a venv or is not on the service PATH).
|
|
130
|
+
- `gitPath` — the git binary used by the review→commit pipeline, the
|
|
131
|
+
`doorstop.item-baseline` fetch, and the four git actions (status strip,
|
|
132
|
+
Stage all, per-item Unstage, Git commit). Defaults to `"git"` resolved on the
|
|
133
|
+
**sessiond host PATH** — set it when git is not on the service PATH (the
|
|
134
|
+
same escape hatch as `doorstopPath`).
|
|
135
|
+
- `timeoutMs` — per-run exec timeout in ms. May SHORTEN the 8 500 ms default
|
|
136
|
+
but never exceed it (the host bounds every provider callback at 10 s, so a
|
|
137
|
+
longer doorstop run would be killed by pi-web, not by the plugin). It also
|
|
138
|
+
bounds every git exec of the review→commit pipeline, the baseline fetch,
|
|
139
|
+
and the git actions, which share one deadline budget per callback.
|
|
140
|
+
|
|
141
|
+
## Using the panel
|
|
142
|
+
|
|
143
|
+
The right-hand panel body is divided into five vertical sections, top to
|
|
144
|
+
bottom:
|
|
145
|
+
|
|
146
|
+
1. **`project-actions`** — the project heading (the workspace's root path,
|
|
147
|
+
basename shown, full path on hover), the **Items / Findings** toggle, the
|
|
148
|
+
project-scoped buttons **Refresh**, **Run validation**, and **Publish
|
|
149
|
+
HTML**, and — on paired installs — the **Git status** strip, the **Stage
|
|
150
|
+
all** button, and the **Git commit** control (message input + button); see
|
|
151
|
+
**Git actions** below.
|
|
152
|
+
2. **`item-list`** — a filters row (document chips, state filter, search)
|
|
153
|
+
above the item rows.
|
|
154
|
+
3. **`item-details`** — the selected item's full detail pane (unchanged).
|
|
155
|
+
4. **`item-action-palette`** — the item actions (**Review**, **Clear suspect
|
|
156
|
+
links**, **Edit**, **Unlink**/**Link** + target UID inputs, **Ask agent**
|
|
157
|
+
menu), moved out of the detail pane so the row spans the panel at the same
|
|
158
|
+
height as the center-panel chat prompt footer. A muted "Select an item…"
|
|
159
|
+
placeholder keeps the palette at constant height while nothing is
|
|
160
|
+
selected; the palette is hidden in the Findings view.
|
|
161
|
+
5. **`status-bar`** — the run status row (badge, duration, **Dismiss**),
|
|
162
|
+
sized like the center-panel status bar, plus the captured output.
|
|
163
|
+
|
|
164
|
+
- **Items / Findings toggle** in the project-actions row. Findings lists all
|
|
165
|
+
plugin-computed findings and diagnostics (error > warning > info); a
|
|
166
|
+
finding's UID navigates back to the item. Findings are informational — run
|
|
167
|
+
the **Run validation** button for the authoritative `doorstop` output
|
|
168
|
+
(paired: rendered in the panel's status bar; unpaired: in the workspace
|
|
169
|
+
terminal).
|
|
170
|
+
- **Status bar**: after a paired action completes, the bottom bar shows the
|
|
171
|
+
run's status (ok / failed / killed / error) and duration, and the panel
|
|
172
|
+
re-scans automatically. The captured stdout/stderr *auto-expands* whenever
|
|
173
|
+
a run produces a message to show (any output, an error message, or a
|
|
174
|
+
review-commit line) — no interaction needed; a run with nothing to display
|
|
175
|
+
stays collapsed to its status row. **Dismiss** clears the run and collapses
|
|
176
|
+
the bar; the next run with output re-expands it. Runs killed at the exec
|
|
177
|
+
timeout preserve and show partial output; prefer the workspace terminal for
|
|
178
|
+
long publishes. When a review ran with `commitAfterReview` enabled, the bar
|
|
179
|
+
adds a `commit: …` narration line (`commit: <short-sha>` /
|
|
180
|
+
`clean (already committed)` / `skipped (not a git repository | item file
|
|
181
|
+
not found | review failed | deadline)` / `failed — <stderr excerpt>`); a
|
|
182
|
+
failed or skipped commit never changes the run's ok/failed badge.
|
|
183
|
+
- **State chips** per item (feature spec §6): inactive, non-normative,
|
|
184
|
+
reviewed / unreviewed changes, suspect links, no child links, no links,
|
|
185
|
+
unknown link, missing reference.
|
|
186
|
+
- **Changes since review** (item detail pane, paired installs only): for an
|
|
187
|
+
item with unreviewed changes and a stored `reviewed` fingerprint, a
|
|
188
|
+
collapsed section shows what changed since the last review. Expanding it
|
|
189
|
+
lazily fetches the reviewed version of the item file from git history via
|
|
190
|
+
the `doorstop.item-baseline` backend operation, stamp-matches it against
|
|
191
|
+
the stored fingerprint, and renders a text line diff plus chips for
|
|
192
|
+
`ref`/`references`/link-UID/extended-attribute changes. When no baseline
|
|
193
|
+
can be recovered it says so honestly ("no git history", "could not locate
|
|
194
|
+
the reviewed version — history may have been rewritten"). The
|
|
195
|
+
`doorstop: review <uid>` commit message is the historical anchor that the
|
|
196
|
+
deferred browser-side git parsing of feature spec §12 could not give — the
|
|
197
|
+
recovery runs server-side, where git lives.
|
|
198
|
+
- **Item actions** (item-action-palette): Review (`doorstop review UID` — with
|
|
199
|
+
`commitAfterReview` enabled and paired, followed by the pathspec-limited
|
|
200
|
+
review commit), Clear suspect links (`doorstop clear UID [parents…]`),
|
|
201
|
+
Edit (`doorstop edit UID`), Link/Unlink via inline UID input (validated
|
|
202
|
+
against the Doorstop UID grammar; shell metacharacters rejected), and — on
|
|
203
|
+
paired installs — Stage / Unstage for the selected item's own file
|
|
204
|
+
(`doorstop.git-stage` / `doorstop.git-unstage` with just that item's
|
|
205
|
+
path; see **Git actions** below). Each is independently disabled: Stage
|
|
206
|
+
when the file has no unstaged (`Y`) changes, Unstage when it has nothing
|
|
207
|
+
staged (`X`), and both while a run is in flight.
|
|
208
|
+
- **Git actions** (paired installs only; every git control is hidden
|
|
209
|
+
without the paired backend — terminal-fallback users commit manually as
|
|
210
|
+
today):
|
|
211
|
+
- **Git status strip** — a compact readout above the item list:
|
|
212
|
+
`⎇ <branch> · <n> staged · <n> dirty · ↑<ahead> ↓<behind>` (zero/absent
|
|
213
|
+
parts omitted; `no git` outside a repository; a retry link on fetch
|
|
214
|
+
errors). The strip refreshes on panel load, after every run (a stage,
|
|
215
|
+
unstage, or commit invalidates the cache), and whenever you click it.
|
|
216
|
+
Outside a git repository the strip shows `no git` and the stage/unstage/
|
|
217
|
+
commit controls are no-ops that report `skipped`.
|
|
218
|
+
- **Stage all** — stages every Doorstop-managed file (the root
|
|
219
|
+
`.doorstop.yml`, each document's config file, each item file) via the
|
|
220
|
+
`doorstop.git-stage` backend operation. Staging is idempotent: a second
|
|
221
|
+
click over an already-staged set reports `clean — nothing to stage`
|
|
222
|
+
instead of a git error (staged deletions in particular are skipped
|
|
223
|
+
rather than fatal). Disabled while a run is in progress or when the
|
|
224
|
+
loaded index has no documents. Note: staging is only as fresh as the
|
|
225
|
+
loaded index — files created on disk after the last **Refresh** are
|
|
226
|
+
missed until you refresh and stage again.
|
|
227
|
+
- **Unstage (per item)** — while an item's file has staged (index)
|
|
228
|
+
changes, its list row shows a **minus** in place of the usual plus, and
|
|
229
|
+
the item palette enables an **Unstage** button, both served by the
|
|
230
|
+
`doorstop.git-unstage` operation (`git reset -q -- :(literal)<path>`
|
|
231
|
+
server-side). The row's pointer shortcut prefers the minus when a file
|
|
232
|
+
is both staged and further modified (`MM`), so "I staged by mistake"
|
|
233
|
+
always surfaces a minus; the palette shows Stage and Unstage as two
|
|
234
|
+
independently-disabled buttons in that case. Unstaging restores the
|
|
235
|
+
index entry but never touches the working tree: a staged addition
|
|
236
|
+
returns to `??` untracked, a staged deletion returns to an unstaged
|
|
237
|
+
deletion. It is idempotent — a second Unstage over an already-clean path
|
|
238
|
+
reports `clean — nothing to unstage`. Unmerged paths (`UU`, `AA`, `DD`,
|
|
239
|
+
`DU`, `AU`, `UD`, `UA`) are excluded — a `git reset` on an unmerged path
|
|
240
|
+
would silently resolve the conflict in the index. On unpaired installs no
|
|
241
|
+
git control is rendered at all; while a run is in flight no minus appears
|
|
242
|
+
and the palette buttons are disabled (the per-row affordance is also
|
|
243
|
+
suppressed when the status file list is truncated, so stale path data is
|
|
244
|
+
never acted on).
|
|
245
|
+
- **Git commit** — a single-line message input plus a **Commit** button,
|
|
246
|
+
served by the `doorstop.git-commit` operation. The commit records **the
|
|
247
|
+
staged index** (`git commit -m <message>` with no pathspec and no
|
|
248
|
+
add) — exactly like the git CLI. Consequences, all intended: unstaged
|
|
249
|
+
WIP is never swept into a commit; files you staged yourself (via the
|
|
250
|
+
git CLI or any other tool) are committed too; the strip's `staged`
|
|
251
|
+
count tells you what a commit will record before you click. Empty or
|
|
252
|
+
whitespace-only messages are rejected client-side (git with an empty
|
|
253
|
+
`-m` would open an editor and hang the exec). Committing with nothing
|
|
254
|
+
staged reports `clean — nothing staged`. Outcomes are narrated in the
|
|
255
|
+
status bar (`staged <n> paths` / `committed <sha>` / `clean — nothing
|
|
256
|
+
to stage` / `clean — nothing staged` / `skipped` / `failed — <stderr
|
|
257
|
+
excerpt>`); a failing pre-commit hook surfaces its stderr in the
|
|
258
|
+
expanded status bar body.
|
|
259
|
+
- **Ask the agent** menu: inserts ready-made prompts (explain, fix suspect
|
|
260
|
+
links, draft child requirement, review readiness) into the session prompt
|
|
261
|
+
editor using the item's paths and state (feature spec §7.3).
|
|
262
|
+
- Fingerprints and the requirement state are recomputed in the browser from
|
|
263
|
+
the YAML files; everything mutating goes through the visible `doorstop`
|
|
264
|
+
CLI — paired, headlessly via the server entry (output captured in the
|
|
265
|
+
panel), or in a workspace terminal when unpaired — plus, opt-in, the
|
|
266
|
+
narrated pathspec-limited review commit (`commitAfterReview`) — so all
|
|
267
|
+
effects stay auditable in git.
|
|
268
|
+
|
|
269
|
+
## Commands
|
|
270
|
+
|
|
271
|
+
```sh
|
|
272
|
+
npm install # install dependencies
|
|
273
|
+
npm test # vitest (unit tests under src/**/*.test.ts)
|
|
274
|
+
npm run typecheck # tsc --noEmit (strict)
|
|
275
|
+
npm run build # esbuild: dist/browser/pi-web-plugin.js + dist/server-plugin.js
|
|
276
|
+
# (+ verbatim package.json manifest copy into dist/)
|
|
277
|
+
npm run dev # watch mode build
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Installing the plugin locally (id `opendoor`)
|
|
281
|
+
|
|
282
|
+
```sh
|
|
283
|
+
npm run build
|
|
284
|
+
ln -s "$(pwd)/dist" ~/.pi-web/plugins/opendoor
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Then **restart the session daemon** (server entries load at sessiond startup
|
|
288
|
+
only; the browser bundle hot-reloads on page reload) and reload the PI WEB
|
|
289
|
+
page:
|
|
290
|
+
|
|
291
|
+
```sh
|
|
292
|
+
systemctl --user restart pi-web-sessiond.service
|
|
293
|
+
curl -s http://127.0.0.1:8504/pi-web-plugins/manifest.json
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
The manifest should list an `opendoor` plugin entry **with a
|
|
297
|
+
`backendRevision`** (signing the paired server module). If `backendRevision`
|
|
298
|
+
is missing, the server module failed to load — check the sessiond log; the
|
|
299
|
+
plugin still works fully via the terminal fallback.
|