dsh-global-todo 0.0.0-stage → 0.3.1
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 +484 -0
- package/CODE_OF_CONDUCT.md +58 -0
- package/CONTRIBUTING.md +101 -0
- package/LICENSE +21 -0
- package/README.md +343 -2
- package/README.zh.md +244 -0
- package/SECURITY.md +42 -0
- package/cordis.patch.yml +23 -0
- package/docs/DESIGN.md +556 -0
- package/docs/ROADMAP.md +182 -0
- package/docs/screenshots/panel-detail.png +0 -0
- package/docs/screenshots/panel-select.png +0 -0
- package/docs/screenshots/panel.png +0 -0
- package/lib/client.js +3374 -0
- package/lib/completion.js +359 -0
- package/lib/diagnostics.js +129 -0
- package/lib/dispatch.js +793 -0
- package/lib/errors.js +76 -0
- package/lib/index.js +431 -0
- package/lib/links.js +149 -0
- package/lib/metrics.js +126 -0
- package/lib/model.js +674 -0
- package/lib/routes.js +545 -0
- package/lib/service.js +641 -0
- package/lib/settings.js +272 -0
- package/lib/store.js +213 -0
- package/lib/tools.js +830 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +71 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.3.1] — 2026-10-17
|
|
11
|
+
|
|
12
|
+
A compatibility release rather than a feature one: DeepSeek Harness moved to `0.2.1-rc.2`, and
|
|
13
|
+
the plugin needed no code change to follow it — but three of its own notes described the old
|
|
14
|
+
Host, and a note that is out of date is what the next change gets planned around.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- **Verified against DSH `0.2.1-rc.2`, and the picker notes now say what is true.** Nothing in
|
|
19
|
+
the plugin had to change: the tool schemas still pass the Host's own validator, every service
|
|
20
|
+
it injects is still there, and the client half touches only `slots` / `locale` / `effect` /
|
|
21
|
+
`uiWorkspace`, whose contract did not move. What moved is the Host — 0.2.1 mounts its own
|
|
22
|
+
directory picker, so the panel's `选择` is now the shell's native dialog and the built-in
|
|
23
|
+
browser is a fallback rather than the normal case. Three comments and one notice said
|
|
24
|
+
otherwise and have been corrected: a note that still claims a capability is missing is worse
|
|
25
|
+
than no note, because the next reader plans around it.
|
|
26
|
+
|
|
27
|
+
## [0.3.0] — 2026-10-17
|
|
28
|
+
|
|
29
|
+
The round after 0.2.0: `/health` grew counters and a JSON export/import round trip, an agent
|
|
30
|
+
gained an honest way to reopen a todo it had finished by mistake, and automatic collect learned
|
|
31
|
+
both when to shut up — one question per message a person wrote — and where never to speak at all,
|
|
32
|
+
a workspace somebody exempted. The quiet auto-close from the middle of that round was removed
|
|
33
|
+
again the same night it shipped; the reason is written down in `DESIGN.md`.
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- **`/health` now reports what the plugin has been doing.** In-process counters for the list
|
|
38
|
+
(`created`, `removed`, `restored`, `imports`, `noted`, `changes`, `size`, `peak`) and for
|
|
39
|
+
hand-offs (`attempts`, `ok`, `failed`, `byMode`, `successRate`), plus a per-tool call count that
|
|
40
|
+
includes failures. The existing fields are untouched; `metrics` is `null` when the plugin is
|
|
41
|
+
mounted without a store. Deliberately **in-memory**: it describes this process, and a counter
|
|
42
|
+
that survives a restart is a second source of truth for facts the store already owns.
|
|
43
|
+
- **The list can be exported and imported as JSON.** `GET /export` returns the store's own
|
|
44
|
+
document — not the read view, so the runtime annotations (`missing`, `sessionDir`) are absent —
|
|
45
|
+
and `POST /import` accepts it back with `replace` or `merge` semantics (`merge` overwrites by id
|
|
46
|
+
and appends new ones). A single unreadable record fails the whole request with a 400 rather than
|
|
47
|
+
writing half of it. The panel gained 导出 / 导入 buttons; the import path defaults to `merge` so
|
|
48
|
+
choosing the wrong file cannot empty the list.
|
|
49
|
+
- The tool count in the docs now matches the tree: **ten** `global_todo_*` tools
|
|
50
|
+
(`add`, `capture`, `list`, `get`, `update`, `link`, `note`, `done`, `reopen`, `remove`), the
|
|
51
|
+
same list `/health.tools.registered` reports. `ROADMAP.md` was re-audited against the code and
|
|
52
|
+
now separates what has landed (with `file:line` citations) from what has not.
|
|
53
|
+
- **`global_todo_reopen` gives a wrongly-finished todo an honest way back.** It does exactly
|
|
54
|
+
one thing — `done` / `cancelled` → `open` — records the act as the agent's (with an optional
|
|
55
|
+
`reason` in the activity trail) and **refuses** an item that was never finished, instead of
|
|
56
|
+
reporting a repair that did not happen. Until now the only agent-side path was `status` on
|
|
57
|
+
`global_todo_done`, i.e. a capability filed under the tool whose name says the opposite, so a
|
|
58
|
+
data repair reached for the HTTP route instead — and was filed as the person's work.
|
|
59
|
+
- **A workspace can be exempted from automatic collect.** The `Exempt` action on a conversation's
|
|
60
|
+
group header takes that conversation's whole **working directory** out of scope, and
|
|
61
|
+
`/health.settings` reports the list so the panel can show which places are out. It is keyed on
|
|
62
|
+
the directory rather than on a session id because the conversations that need it are the ones a
|
|
63
|
+
machine drives — a chat companion's room — and such a session is recreated with a new id when it
|
|
64
|
+
restarts, so an id-keyed exemption would quietly stop applying. Subdirectories count; a shared
|
|
65
|
+
prefix does not, and a conversation whose directory nobody reported is not treated as exempt.
|
|
66
|
+
The directory is remembered from the conversation's own events rather than looked up when the
|
|
67
|
+
timer runs, because these conversations are idle most of the time and an idle conversation is
|
|
68
|
+
one the Host may have unloaded — where a live lookup answers `''`, and the exemption would stop
|
|
69
|
+
applying to exactly the conversations it exists for.
|
|
70
|
+
|
|
71
|
+
### Fixed
|
|
72
|
+
|
|
73
|
+
- **The panel's `/update` and `/status` routes no longer need a lie to accept a write.** Both
|
|
74
|
+
hardcoded `by: 'human'`, so a script repairing data on an agent's behalf filed the change as
|
|
75
|
+
the person's own — the four reopens from the mis-close repair are recorded that way, and the
|
|
76
|
+
only thing saying otherwise was a sentence in a note. Both routes now take an explicit
|
|
77
|
+
`by: 'agent'` and still default to `human`; any other value is a `400`, because quietly
|
|
78
|
+
folding a typo into `human` is the same lie with better manners.
|
|
79
|
+
|
|
80
|
+
- **Automatic collect no longer asks the same conversation over and over.** The question the plugin
|
|
81
|
+
sends *is* a message in the conversation, so it restarted the plugin's own quiet clock, and the
|
|
82
|
+
answer to it ended a clean turn — which put the same conversation straight back in the queue. With
|
|
83
|
+
`cooldownMinutes` on the same half hour as `idleMinutes`, the cooldown had always expired by the
|
|
84
|
+
time the quiet window opened, so one conversation was asked every half hour, all night, with
|
|
85
|
+
nobody there. A conversation is now asked **once per message a person wrote in it**: only
|
|
86
|
+
`source.kind === 'user'` re-arms it, and the plugin's own traffic never does. A restarted Host has
|
|
87
|
+
forgotten the timestamps and so stays quiet, which is the direction to fail in.
|
|
88
|
+
|
|
89
|
+
- **A quiet conversation no longer has its open todos closed for it.** Yesterday's helper read
|
|
90
|
+
"this conversation has gone quiet" as "its work is finished" and closed every open todo that
|
|
91
|
+
conversation had written, under a template summary. On its first night it closed eight that were
|
|
92
|
+
not finished — one waiting on a person, one waiting on an event that had not happened yet — and
|
|
93
|
+
the only thing that told them apart was that summary, because there was no evidence behind it.
|
|
94
|
+
The signal is not weak, it points the wrong way: a conversation falls quiet *because* it is
|
|
95
|
+
waiting, so the items that matter most are the ones it gets wrong. Only paths that carry evidence
|
|
96
|
+
close a todo now — the agent's own `global_todo_done`, the hand-off watcher, or a person in the
|
|
97
|
+
panel — and the module that held the heuristic is gone.
|
|
98
|
+
|
|
99
|
+
## [0.2.0] — 2026-10-15
|
|
100
|
+
|
|
101
|
+
The first published release. 0.1.0 was cut in the repository but never reached a
|
|
102
|
+
registry, so this is the version to install.
|
|
103
|
+
|
|
104
|
+
### Fixed
|
|
105
|
+
|
|
106
|
+
- **A settings write that landed during startup was silently undone.** The Host reads
|
|
107
|
+
`settings.json` without awaiting the read — it cannot block its own mount on a file — and that
|
|
108
|
+
read replaces the whole settings state. A panel toggle, or a cooldown booking from an
|
|
109
|
+
automatic ask, that arrived while the read was still in flight was therefore overwritten by
|
|
110
|
+
the file that read had just loaded. Writes now wait for the startup read to finish.
|
|
111
|
+
- **The diagnostics log is trimmed without ever being seen empty.** The bound was enforced by
|
|
112
|
+
rewriting `client-errors.log` in place, and `writeFile` truncates its target before it writes,
|
|
113
|
+
so a reader that landed in that window got 0 bytes — the CI reader that expected the newest
|
|
114
|
+
report got `''`. The trim now writes a sibling file and renames it over the log, which is
|
|
115
|
+
atomic, and a failed trim leaves no litter beside the log.
|
|
116
|
+
|
|
117
|
+
### Fixed
|
|
118
|
+
|
|
119
|
+
- **The hand-off picker now mirrors the shell's own visibility rule.** It was offering
|
|
120
|
+
conversations the sidebar deliberately hides: **subagent children** (`origin === 'subagent'`),
|
|
121
|
+
**blank sessions** that are not the one in front of the user, and archived sessions whose
|
|
122
|
+
archived set came from a source the picker read too narrowly. The rule is copied from
|
|
123
|
+
`sessionVisible` in the shipped workspace package — the only authority on it — including its one
|
|
124
|
+
exception: a blank session *is* offered while it is the current one. The picker also reports the
|
|
125
|
+
shape of the session snapshot it sees to `/health.clientErrors` once per mount, because a preview
|
|
126
|
+
fixture written by the same hand as the filter can only ever confirm itself.
|
|
127
|
+
|
|
128
|
+
- **A long working directory no longer breaks its own label.** `工作目录:` is a flex child of the
|
|
129
|
+
directory pill, so when the row overflowed it shrank and wrapped mid-word — and the third
|
|
130
|
+
button (`浏览`) was enough to push it over. Only the path yields now: it already ellipsises,
|
|
131
|
+
while the label, the hint and the buttons keep their size.
|
|
132
|
+
|
|
133
|
+
### Added
|
|
134
|
+
|
|
135
|
+
- **A folder browser, because the shell's picker is not always mounted.** `uiWorkspace.pickDirectory()`
|
|
136
|
+
delegates to a directory-picking Remote namespace that this deployment does not have, so it
|
|
137
|
+
throws instead of opening anything (the panel had been reduced to asking you to type a path).
|
|
138
|
+
The Host now serves `GET /api/global-todo/dirs` — visible sub-folders, sorted, capped, with the
|
|
139
|
+
parent — and `选择` walks it as a real picker (`浏览`: current path, `上一级`, folder buttons,
|
|
140
|
+
`用这个目录`). The path field stays for pasting, and the failure is still reported to
|
|
141
|
+
`/health.clientErrors`.
|
|
142
|
+
|
|
143
|
+
### Changed
|
|
144
|
+
|
|
145
|
+
- **A quiet conversation has its finished work ticked off for it.** Asking an agent to close what
|
|
146
|
+
it finished works most of the time, which is not the same as a tidy list — so when a conversation
|
|
147
|
+
that *wrote* todos has been quiet for the idle window after a clean turn, the Host closes the ones
|
|
148
|
+
it owns, with a summary saying why ("插件按「做完就划掉」结束了它写下的这条"), and it is reversible.
|
|
149
|
+
It touches only that conversation's own todos, and it is deliberately independent of the
|
|
150
|
+
automatic-collect switch: tidying is not the same feature as asking for more.
|
|
151
|
+
- **`/health` reports `idleMinutes`** (and the settings write echoes it). The rule in force has to
|
|
152
|
+
be observable — a check that could not see it reported "not deployed" for code that was running.
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
- **Automatic collect now waits for quiet, instead of asking at every turn end.** That was the
|
|
156
|
+
design mistake behind "too frequent": a session being worked in ends turns every few minutes, so
|
|
157
|
+
the question arrived mid-work, repeatedly. A clean turn now only *marks* the conversation as a
|
|
158
|
+
candidate and records **when it went quiet**; a one-minute timer asks when nothing has been said
|
|
159
|
+
for `idleMinutes` (default **30**) — and any new message, or the start of a new turn, restarts
|
|
160
|
+
that clock. Asking is also still gated on the conversation being the one the person is in, the
|
|
161
|
+
turn having ended cleanly, and the per-session cooldown. Not knowing how long it has been quiet
|
|
162
|
+
counts as "not yet" (fail closed).
|
|
163
|
+
- **Captured items are refined and merged rather than transcribed.** The capture prompt now asks
|
|
164
|
+
for one entry per matter, merges scattered points into a single entry's description, targets
|
|
165
|
+
"1–3 entries, not 8", and refuses to record what the list already has. The capture tool enforces
|
|
166
|
+
the last part: an item whose normalised title matches an unfinished entry is skipped with
|
|
167
|
+
「清单里已有同名的未完成条目」 instead of being registered again — which is how the list kept
|
|
168
|
+
filling with near-duplicates.
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
- **A calmer panel.** The header had grown two rows of controls that said the same things
|
|
172
|
+
twice: the three count chips *were* the status filter, and the add form sat open above the
|
|
173
|
+
list forever. Now there are three rows with one job each — **title + summary + `添加`**,
|
|
174
|
+
**filters · grouping · search** in a single row, then a quiet **context line** (working
|
|
175
|
+
directory, hand-off target, `选择`, `自动获取`). Counts moved into the summary line as plain
|
|
176
|
+
text, every "pick one of these" control uses one segmented style (chips are now only facts
|
|
177
|
+
about a todo: tags, levels, notes), and the add form opens from `添加` instead of occupying
|
|
178
|
+
the top of the list.
|
|
179
|
+
|
|
180
|
+
- **The test suite waits for results, not for the clock.** The remaining fixed sleeps
|
|
181
|
+
(`settle()`, `setTimeout`) are gone: the auto-collect, restart and dispatch tests now poll for
|
|
182
|
+
the state their assertions are about, with a bounded timeout that names what it waited for. A
|
|
183
|
+
sleep is a test that reports on the machine, and one of them was already losing that race on
|
|
184
|
+
the slower CI runner.
|
|
185
|
+
- **CI runs `actions/checkout@v7` and `actions/setup-node@v7`**, which are already on Node 24,
|
|
186
|
+
so the Node 20 deprecation warnings are gone. Automatic npm caching is off explicitly: this
|
|
187
|
+
plugin has no dependencies and no lockfile to cache.
|
|
188
|
+
|
|
189
|
+
- **The repository is publishable as it stands.** Two things stood between this tree and a
|
|
190
|
+
public one. The machine-path guard in `scripts/check.mjs` only looked at the shipped halves,
|
|
191
|
+
so a preview fixture, a test constant and a screenshot caption each carried the author's real
|
|
192
|
+
home directory while the guard still reported `OK`; it now covers every committable file and
|
|
193
|
+
allows exactly one placeholder, `/home/me/…`. And `publish.yml` asked Node 22.19 for npm
|
|
194
|
+
10.9.3, which cannot do OIDC at all — trusted publishing needs npm 11.5.1 or later — so the
|
|
195
|
+
job installs that floor before it publishes. The internal research reports left `docs/`, and
|
|
196
|
+
the fixtures and screenshots were regenerated from the same neutral data.
|
|
197
|
+
|
|
198
|
+
## [0.1.0] — 2026-10-14
|
|
199
|
+
|
|
200
|
+
The first release: one JSON list a person and an agent both write to, eight agent tools, a
|
|
201
|
+
panel that hands work over and records how it finished.
|
|
202
|
+
|
|
203
|
+
### Fixed
|
|
204
|
+
|
|
205
|
+
- **「选择」工作目录不再以一条无解的错误结束.** `uiWorkspace.pickDirectory()` exists, but it
|
|
206
|
+
delegates to a directory-picking **Remote namespace** that is not mounted in every
|
|
207
|
+
deployment, where calling it throws a bare
|
|
208
|
+
`Cannot read properties of undefined (reading 'directoryPicker')`. The panel now falls back
|
|
209
|
+
to a **path field** (`手动填` → paste an absolute path → `使用`), says plainly why, and reports
|
|
210
|
+
the throw to the Host diagnostics so it is visible in `/health.clientErrors` instead of only
|
|
211
|
+
in someone's browser.
|
|
212
|
+
- **Both capture actions and the automatic-collect switch reported an error even though they
|
|
213
|
+
worked.** The handlers ended with `void refresh()` — a function that does not exist — so a
|
|
214
|
+
successful request was immediately followed by a `ReferenceError` that the handler's own catch
|
|
215
|
+
turned into an error banner. The write had already landed, which is also why the switch appeared
|
|
216
|
+
impossible to turn off: the panel's copy of the Host settings was only read at mount, so the chip
|
|
217
|
+
never moved and the next click sent the same value again. Health is now refreshed on the poll and
|
|
218
|
+
updated optimistically on write, and `void refresh()` is gone.
|
|
219
|
+
- **A guard against that class of bug**: `tools/scan-client-calls.mjs` scans the shipped client for
|
|
220
|
+
calls to functions nobody defined (comments and strings removed first; destructuring, parameters
|
|
221
|
+
and method shorthand recognised), wired into `npm run check` and covered by its own test. Every
|
|
222
|
+
other check passes such a mistake, and the preview harness cannot see it — an error caught inside
|
|
223
|
+
a handler only appears on a real re-render.
|
|
224
|
+
- **The toolbar's own "collect from this chat" button is gone.** The action lives on each
|
|
225
|
+
conversation's group header, where its subject is unambiguous; a second copy in the toolbar only
|
|
226
|
+
gave the wrong one to click.
|
|
227
|
+
- **The Host's own tooling is no longer locked out by the connection fence.** Deferring to the
|
|
228
|
+
fence fixed LAN access but made *every* credential-less request a 401, because the fence
|
|
229
|
+
authenticates browsers — so `curl`, scripts on the Host and other agents lost access to a list
|
|
230
|
+
they are the main writers of. The fence now judges **browsers** (anything carrying a cookie or
|
|
231
|
+
an `Origin`), while a local, credential-less caller is judged by the loopback rule — the guard
|
|
232
|
+
that existed before the fence did. A fence that throws still fails closed, and a cross-site
|
|
233
|
+
request is never rescued by the carve-out. `/health` reports `fence.localScriptsAdmitted`.
|
|
234
|
+
|
|
235
|
+
- **The panel works from the LAN and from a tunnel again.** The Host's connection fence was read
|
|
236
|
+
once at mount time, when it can still be absent, so every request fell through to the plugin's
|
|
237
|
+
own loopback check. That check is stricter than the Host's trusted-host list: a browser anywhere
|
|
238
|
+
but the Host machine got `403 只接受本机请求` on **every** call — list, picker and both capture
|
|
239
|
+
actions — while `curl` from the Host looked perfectly healthy. The fence is now resolved per
|
|
240
|
+
request, its verdict is honoured verbatim, and `/health` reports which fence is actually in
|
|
241
|
+
force so a silent downgrade cannot hide again.
|
|
242
|
+
|
|
243
|
+
### Added
|
|
244
|
+
|
|
245
|
+
- **`全选` in selection mode.** Tick everything the panel is currently showing (collapsed groups
|
|
246
|
+
included — their todos are still what the user means), and the same control becomes
|
|
247
|
+
`取消全选` once everything is selected.
|
|
248
|
+
|
|
249
|
+
- **Batch assignment (「一键分配」)**. Turn on 「选择」 in the toolbar, tick a few todos, and one
|
|
250
|
+
click sends each of them to the best target available for it — **the conversation that wrote
|
|
251
|
+
it**, then a conversation in the **same workspace** (a candidate the panel works out from its
|
|
252
|
+
own recency and archive data, which the Host cannot see), then a **target you choose** for the
|
|
253
|
+
rest. Those are an attempt order, not a preference: a target that cannot be loaded is the
|
|
254
|
+
reason the next one exists. Whatever still has nowhere to go is reported back and offered a
|
|
255
|
+
picker instead of being sent somewhere nobody chose, and one failure does not abort the batch.
|
|
256
|
+
- **An automatic collect switch** (panel chip `自动获取`, `POST /api/global-todo/settings`). When
|
|
257
|
+
it is on, a conversation you are in that finishes a clean turn and has not been asked for a
|
|
258
|
+
while is asked once whether anything needs following up. It is off until a person turns it on,
|
|
259
|
+
the switch lives on the Host (so it works with the panel closed and is reported by `/health`),
|
|
260
|
+
and every reason *not* to ask is explicit: an aborted/failed turn, a conversation nobody is in,
|
|
261
|
+
a session asked inside the cooldown, or the feature being off. The cooldown is booked before
|
|
262
|
+
the question is asked, so a repeated event or a failed request cannot turn into a loop.
|
|
263
|
+
- **Stronger proactive guidance in the system prompt.** The section now lists the concrete
|
|
264
|
+
triggers for recording something on one's own initiative (a cross-conversation follow-up, "I'll
|
|
265
|
+
do X then Y", a leftover step at the end of a turn), says plainly that asking the user whether
|
|
266
|
+
to record it is wrong, and points at `global_todo_note` for reporting a blocker instead of
|
|
267
|
+
rewriting the description.
|
|
268
|
+
|
|
269
|
+
### Changed
|
|
270
|
+
|
|
271
|
+
- **"Collect from this chat" lives on the conversation it collects from.** A global toolbar
|
|
272
|
+
button could not say *which* conversation it meant, so the action now sits on each
|
|
273
|
+
conversation's group header in the list (`抓取`), and asking a group to collect files the
|
|
274
|
+
results under that conversation. The toolbar keeps a fallback, shown *only* when the
|
|
275
|
+
conversation in front of the user has no group yet — otherwise there would be no way to
|
|
276
|
+
collect from it.
|
|
277
|
+
- **Collecting from a conversation that is not running now resumes it** (the same rule a
|
|
278
|
+
hand-off follows). It used to look the session up in the live registry and die on
|
|
279
|
+
`undefined` for any conversation that was not currently loaded.
|
|
280
|
+
|
|
281
|
+
### Added
|
|
282
|
+
|
|
283
|
+
- **The hand-off picker shows the conversations you actually use, newest first.** Its order is
|
|
284
|
+
the sidebar's own recency key (`updatedAt`), so the conversation you were last in is at the
|
|
285
|
+
top and stale ones sink, and **archived conversations are left out entirely** — an archived
|
|
286
|
+
conversation is not an answer to "where should this work go". Conversations that share a
|
|
287
|
+
title get a short id suffix so two options can be told apart, and if the current target is no
|
|
288
|
+
longer listed (archived since it was chosen) it is shown as `(已归档)` rather than silently
|
|
289
|
+
displaying a different conversation as selected.
|
|
290
|
+
- **Progress notes** (`global_todo_note`, `POST /api/global-todo/note`): an append-only trail of
|
|
291
|
+
who said what and when, capped per todo. An agent reporting "here is where I am stuck" no
|
|
292
|
+
longer has to rewrite the user's description, and — like the other verdict tools — writing a
|
|
293
|
+
note makes automatic completion stand down rather than contradict a stated blocker. The row
|
|
294
|
+
shows the count, the detail shows the trail and a box to add one.
|
|
295
|
+
- **Dead attachments are flagged where you see them.** File attachments that no longer exist
|
|
296
|
+
are marked red on the row and labelled in the detail. The flag is computed on read and never
|
|
297
|
+
stored, so a file that comes back stops being flagged and a stale flag cannot outlive the
|
|
298
|
+
problem.
|
|
299
|
+
- **Search syntax**: `tag:发布` / `#运维`, `is:overdue|active|due|open|doing|done`, `has:link`,
|
|
300
|
+
`has:note`, `level:p0|p1|p2|p3`, plus free words, which are combined with AND and also match
|
|
301
|
+
note text. A plain search still behaves exactly as before.
|
|
302
|
+
- **A new conversation is placed where the work lives.** A hand-off that opens a session now
|
|
303
|
+
joins the workspace owning the conversation that wrote the todo (or the one it was handed
|
|
304
|
+
from), and starts in that workspace's directory — so the agent and the sidebar agree
|
|
305
|
+
instead of the session landing beside unrelated ones. The panel also lists the workspaces,
|
|
306
|
+
so a new conversation can be aimed at one explicitly (`+ new conversation · <workspace>`);
|
|
307
|
+
a named workspace outranks the inherited one. Only when neither is known does the working
|
|
308
|
+
directory decide.
|
|
309
|
+
- **A pre-commit credential audit inside `npm run check`**: the rule the project used to
|
|
310
|
+
apply only to shipped files now runs over every file a commit would carry (51 of them,
|
|
311
|
+
with the same exclusions `.gitignore` applies), and it asserts that the local artifacts
|
|
312
|
+
(`node_modules/`, `preview/`, `todos.json`, backups) cannot reach a commit at all. A
|
|
313
|
+
credential leaked in a test or a doc is exactly as leaked as one in `lib/`.
|
|
314
|
+
- **`npm run check:live`** (`tools/live-check.mjs`): verifies the wired Host rather than
|
|
315
|
+
doubles — health surfaces, a delete → undo round trip compared field by field, and the
|
|
316
|
+
`sessionAlive`/`sessionDir` a row needs to show whether its conversation is running. It
|
|
317
|
+
cleans up its temporary record even when it fails, and stays out of `verify` because it
|
|
318
|
+
needs a reachable Host.
|
|
319
|
+
- **Levels P0–P3.** `urgent` joins the existing three levels (`P0 紧急 / P1 高 / P2 常规 /
|
|
320
|
+
P3 低`), so no migration is needed: `high`/`normal`/`low` stay valid and `normal` stays the
|
|
321
|
+
default. The badge marks P0 in the error colour to separate it from P1.
|
|
322
|
+
- **`POST /api/global-todo/capture` and the panel's 「从本对话抓取」 button**: the panel
|
|
323
|
+
injects a capture request into the conversation the user is in, which then records its
|
|
324
|
+
follow-ups with `global_todo_capture`. It is a request for work, not a hand-off: nothing is
|
|
325
|
+
recorded against a todo and no completion is expected.
|
|
326
|
+
- **Grouping dimensions.** The panel groups by `conversation` (default), `time` or `level`.
|
|
327
|
+
Conversation mode merges everything one agent conversation submitted into a single entry
|
|
328
|
+
that carries the count, collapsing it until asked — a list that several 「抓一下待办」 runs
|
|
329
|
+
filled up stops reading as a wall of rows. Items with no source conversation gather under
|
|
330
|
+
「手动添加」, and the row level (high / normal / low) is visible as a badge and as the
|
|
331
|
+
highest level of a conversation group.
|
|
332
|
+
- **A render check** (`npm run check:render`, part of `verify`): the client half has no unit
|
|
333
|
+
seam, so the generated panel HTML is asserted instead — merged conversations stay one
|
|
334
|
+
entry, single-item ones keep their row, levels and all three grouping buttons render, and
|
|
335
|
+
no literal colour leaks into the markup.
|
|
336
|
+
- **Automatic completion.** A handed-off session that closes its turn cleanly finishes its
|
|
337
|
+
todo by itself: the closing assistant message becomes `completion.summary` and the files
|
|
338
|
+
that turn changed become `completion.artifacts`. Two guards keep it honest — only a
|
|
339
|
+
session this plugin just dispatched to is eligible, and `error` / `aborted` /
|
|
340
|
+
`interrupted` turns leave the todo open. `lib/completion.js` watches `session/event`
|
|
341
|
+
(`assistant/message`, `workspace/changes`, `turn/end`) and resolves file lists through
|
|
342
|
+
the `workspaceChanges` recorder when an event does not carry them.
|
|
343
|
+
- `global_todo_done` accepts `summary` and `artifacts`, so an agent can write the record
|
|
344
|
+
itself; the completion prompt asks for them, and "don't mark it done if you couldn't" is
|
|
345
|
+
stated in the hand-off message.
|
|
346
|
+
- The panel renders the completion: 完成于 on the row, artifact chips next to the
|
|
347
|
+
attachments, and a completion section in the detail.
|
|
348
|
+
- `global_todo_capture`: record a batch of todos in one call, with per-entry validation so
|
|
349
|
+
one bad attachment does not lose the rest.
|
|
350
|
+
- Proactive-capture guidance in the system prompt: when the agent should record something
|
|
351
|
+
on its own initiative, when it must not, and 「抓一下待办」 as the phrase that asks it to
|
|
352
|
+
scan the conversation and capture a batch. The boundary is stated in the prompt too:
|
|
353
|
+
record, never start work.
|
|
354
|
+
- `POST /api/global-todo/restore` and the panel's undo: a deleted todo comes back
|
|
355
|
+
verbatim — same id, creation time, attachments and history — and a record whose id
|
|
356
|
+
is taken is refused rather than merged.
|
|
357
|
+
- `sessionAlive` on every listed todo, derived from the live agent registry on read, so
|
|
358
|
+
the panel can say 「会话在跑」 instead of 「已绑定会话」.
|
|
359
|
+
- `npm run preview`: renders the panel (and an opened detail view) to static HTML with
|
|
360
|
+
no Host and no dependencies, for UI work and review.
|
|
361
|
+
|
|
362
|
+
### Fixed
|
|
363
|
+
|
|
364
|
+
- **The receipt's jump button did nothing.** Client services arrive through
|
|
365
|
+
`ctx.inject([...])`, not `ctx.get(...)`: the directory marks `uiWorkspace` as a hard
|
|
366
|
+
dependency for exactly that reason. It is now obtained by injection, and the button is
|
|
367
|
+
only rendered when `openSession` really exists — a button that does nothing is worse than
|
|
368
|
+
no button.
|
|
369
|
+
- **A hand-off still landed in the wrong conversation.** Inference about "the conversation
|
|
370
|
+
you are in" was wrong twice, so it is no longer the mechanism: the toolbar now lists the
|
|
371
|
+
conversations and the user picks one (`交给:[ 对话名 ▾ ]`). An explicit pick is sent with
|
|
372
|
+
`authoritative: true`, which the Host honours verbatim; the list marks the conversation the
|
|
373
|
+
Host would have chosen on its own, `自动` keeps the old behaviour, and a pick is remembered.
|
|
374
|
+
- **A hand-off pulled the next hand-off with it.** The "conversation you were last in" came
|
|
375
|
+
from `api-session/activity`, which reports *any* user-authored durable message — and a
|
|
376
|
+
hand-off is one: the plugin injects a user-role message into its target. So every dispatch
|
|
377
|
+
moved "the current conversation" to the conversation it had just dispatched to, and the
|
|
378
|
+
next click followed the previous click instead of the person. The tracker now reads the
|
|
379
|
+
session log and keeps only `user/message` events whose `source.kind` is `user`, so model
|
|
380
|
+
turns, tool results and plugin injections cannot move it. The id is also kept next to the
|
|
381
|
+
data (`recent-session.json`), because the tracker lives in memory and this plugin restarts
|
|
382
|
+
with the Host: an empty tracker falls back to the panel's stale guess, which is how a
|
|
383
|
+
hand-off reached the wrong conversation. Covered by two smoke tests — the message sequence,
|
|
384
|
+
and a restart that must not forget.
|
|
385
|
+
- **A hand-off could start work silently in another conversation.** The panel said nothing
|
|
386
|
+
after a successful dispatch, so a person staring at the todo list saw "no reaction" while
|
|
387
|
+
the agent in a different conversation was already working. The panel now names the target
|
|
388
|
+
and offers to open it, and the pre-click label uses the conversation the Host will actually
|
|
389
|
+
pick (`/health`'s recent-session answer) instead of the panel's own guess.
|
|
390
|
+
- **The panel's guess about "the conversation on screen" outranked the real one.** While the
|
|
391
|
+
panel is the main view, its idea of the current session is whatever the view retained
|
|
392
|
+
earlier — hours stale in practice, and it sent work to the wrong conversation. The activity
|
|
393
|
+
feed (a real user message) now wins; a caller that genuinely knows — a button rendered
|
|
394
|
+
inside a conversation — opts in with `authoritative: true`.
|
|
395
|
+
- **Auto-completion recorded an empty artifact list.** `workspace/changes` is not appended
|
|
396
|
+
with `turn/end`: the recorder enqueues a turn-end snapshot and diff when the turn closes
|
|
397
|
+
and announces the result in a later event. Completing on `turn/end` therefore always saw
|
|
398
|
+
no files. The record is now written when that summary arrives, with a bounded wait so a
|
|
399
|
+
turn that changed nothing still completes, and a new turn settles the previous wait.
|
|
400
|
+
- **A stated blocker was overwritten by an automatic "done".** Auto-completion now backs off
|
|
401
|
+
when the agent used a verdict tool of its own (`global_todo_done`, `global_todo_update`,
|
|
402
|
+
`global_todo_remove`) during the turn: an agent that says "this is blocked, here is why"
|
|
403
|
+
must not be contradicted by a heuristic. Read-only tools (`list`, `get`) do not count.
|
|
404
|
+
- **「交给 Agent」 did not mean "hand it to the conversation I am in".** The default was
|
|
405
|
+
`auto`, which reused the session an earlier click had created — so a second click sent the
|
|
406
|
+
work back to that side conversation instead of the chat on screen, and the first click
|
|
407
|
+
opened a session nobody asked for. The panel now defaults to `current`, shows the target
|
|
408
|
+
before the click, and the Host resolves "current" from the `api-session/activity` feed
|
|
409
|
+
(the conversation the user last wrote in), falling back to the conversation the todo was
|
|
410
|
+
written from. `auto` now means bound → current → new, and an unresolvable target fails
|
|
411
|
+
with a reason instead of silently creating a conversation.
|
|
412
|
+
- **A hand-off created sessions with no working directory.** `meta.cwd` was only set when
|
|
413
|
+
`dispatchDir` was configured, so the Host filed the new sessions under `_no-cwd` and the
|
|
414
|
+
agent inside them could do nothing. The directory is now resolved in order — what the
|
|
415
|
+
caller names, the directory the todo was written from, the bound conversation's
|
|
416
|
+
directory, then `dispatchDir` — and a hand-off with none of them is refused with an
|
|
417
|
+
actionable message instead of quietly creating a dead session.
|
|
418
|
+
- A live conversation with no working directory is flagged on its row, and `auto` no longer
|
|
419
|
+
reuses one: it opens a fresh session in a resolved directory instead, so a single click
|
|
420
|
+
recovers a todo that was handed to a dead-end conversation.
|
|
421
|
+
|
|
422
|
+
### Added
|
|
423
|
+
|
|
424
|
+
- Every todo written through a tool records `originDir` and `originSessionId`, read from
|
|
425
|
+
the calling agent in `execute(args, exec)`.
|
|
426
|
+
- The panel's 工作目录 control: follows the conversation you are in (read from the shell's
|
|
427
|
+
`useSessions` state), or a directory you pick with the Host's picker — remembered across
|
|
428
|
+
sessions and overridable per dispatch.
|
|
429
|
+
- **A hot reload kept the previous stylesheet.** The panel's `<style>` tag was reused
|
|
430
|
+
when it already existed, so new markup ran against the old rules: class names the old
|
|
431
|
+
stylesheet never knew rendered with no styles at all — a panel that looked "unchanged"
|
|
432
|
+
while its code was already new. The tag is now replaced (and stamped with the build
|
|
433
|
+
revision), with a bundle test for the reload path.
|
|
434
|
+
- **The agent tools were missing entirely.** Their output schemas used
|
|
435
|
+
`type: ['string', 'null']`, which the Host's schema subset does not accept, so
|
|
436
|
+
registration was rejected for all seven tools. Nullability is now `oneOf`, and
|
|
437
|
+
`test/schema.test.mjs` plus the Host-validator step in `npm run check` keep the
|
|
438
|
+
class of failure out.
|
|
439
|
+
- The `/health` payload reports the tool registration state, which is what surfaced
|
|
440
|
+
the failure in a live Host.
|
|
441
|
+
|
|
442
|
+
### Changed
|
|
443
|
+
|
|
444
|
+
- Rows now keep `▶ 交给 Agent` visible at all times (hiding the only way to hand work to an
|
|
445
|
+
agent behind a hover made a lone todo look inert), list the first attachments as chips
|
|
446
|
+
(a link opens, a document copies its path, `+N` for the rest), and separate the meta line
|
|
447
|
+
with `·`.
|
|
448
|
+
- Row cards use the raised surface and a heavier title, so the list keeps its structure in
|
|
449
|
+
a light theme; the row action is an outline button instead of a filled one.
|
|
450
|
+
- Panel redesign: urgency grouping with collapsible sections, hover-revealed row actions
|
|
451
|
+
behind a menu, count chips that double as filters, a search field with a `/` hint,
|
|
452
|
+
attachment rows with 打开 / 复制路径, an inline edit section with a dirty-state save,
|
|
453
|
+
and a footer that names the keyboard shortcuts.
|
|
454
|
+
- The word "attachment" now covers addresses and documents consistently in the UI.
|
|
455
|
+
|
|
456
|
+
### Added
|
|
457
|
+
|
|
458
|
+
- Global todo list persisted as one JSON file (`$DSH_HOME/global-todo/todos.json`
|
|
459
|
+
by default), written atomically and serialized so the human and the agent can
|
|
460
|
+
never lose each other's change.
|
|
461
|
+
- Creation time on every entry: `createdAt` is set once, never edited, and shown
|
|
462
|
+
in the panel, in the tool output and in every dispatch prompt.
|
|
463
|
+
- Attachments as references — any number of documents (absolute paths) and
|
|
464
|
+
addresses (http/https) per todo, with URL credentials refused; documents stay
|
|
465
|
+
where they are instead of being copied into plugin storage.
|
|
466
|
+
- Seven agent tools: `global_todo_add`, `global_todo_list`, `global_todo_get`,
|
|
467
|
+
`global_todo_update`, `global_todo_link`, `global_todo_done`,
|
|
468
|
+
`global_todo_remove`.
|
|
469
|
+
- Web panel in the sidebar with quick add, filters, text search, a detail view
|
|
470
|
+
showing the activity trail, and inline attachment editing.
|
|
471
|
+
- One-click hand-off to an agent session: `auto` (reuse the bound conversation,
|
|
472
|
+
otherwise start one), `new`, `bound` and `current` modes, with the outcome —
|
|
473
|
+
success or the reason for failure — recorded on the todo.
|
|
474
|
+
- HTTP API (`/api/global-todo/*`) fenced by the host connection fence, with a
|
|
475
|
+
strict loopback fallback and real status codes.
|
|
476
|
+
- Optional-service degradation: with no `webServer`, `tools`, `agents` or
|
|
477
|
+
`agentPresets` service, the plugin loses exactly that surface instead of
|
|
478
|
+
failing to load.
|
|
479
|
+
|
|
480
|
+
[Unreleased]: https://github.com/LUgABeEEE/dsh-global-todo/compare/v0.3.1...HEAD
|
|
481
|
+
[0.3.1]: https://github.com/LUgABeEEE/dsh-global-todo/releases/tag/v0.3.1
|
|
482
|
+
[0.3.0]: https://github.com/LUgABeEEE/dsh-global-todo/releases/tag/v0.3.0
|
|
483
|
+
[0.2.0]: https://github.com/LUgABeEEE/dsh-global-todo/releases/tag/v0.2.0
|
|
484
|
+
[0.1.0]: https://github.com/LUgABeEEE/dsh-global-todo/releases/tag/v0.1.0
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our pledge
|
|
4
|
+
|
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our community a
|
|
6
|
+
harassment-free experience for everyone, regardless of age, body size, visible or invisible
|
|
7
|
+
disability, ethnicity, sex characteristics, gender identity and expression, level of
|
|
8
|
+
experience, education, socio-economic status, nationality, personal appearance, race,
|
|
9
|
+
religion, or sexual identity and orientation.
|
|
10
|
+
|
|
11
|
+
We pledge to act and interact in ways that contribute to an open, welcoming, diverse,
|
|
12
|
+
inclusive, and healthy community.
|
|
13
|
+
|
|
14
|
+
## Our standards
|
|
15
|
+
|
|
16
|
+
Examples of behavior that contributes to a positive environment:
|
|
17
|
+
|
|
18
|
+
- Demonstrating empathy and kindness toward other people
|
|
19
|
+
- Being respectful of differing opinions, viewpoints, and experiences
|
|
20
|
+
- Giving and gracefully accepting constructive feedback
|
|
21
|
+
- Accepting responsibility and apologizing to those affected by our mistakes, and learning
|
|
22
|
+
from the experience
|
|
23
|
+
- Focusing on what is best not just for us as individuals, but for the overall community
|
|
24
|
+
|
|
25
|
+
Examples of unacceptable behavior:
|
|
26
|
+
|
|
27
|
+
- The use of sexualized language or imagery, and sexual attention or advances of any kind
|
|
28
|
+
- Trolling, insulting or derogatory comments, and personal or political attacks
|
|
29
|
+
- Public or private harassment
|
|
30
|
+
- Publishing others' private information, such as a physical or email address, without
|
|
31
|
+
their explicit permission
|
|
32
|
+
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
|
33
|
+
|
|
34
|
+
## Enforcement responsibilities
|
|
35
|
+
|
|
36
|
+
Community leaders are responsible for clarifying and enforcing our standards of acceptable
|
|
37
|
+
behavior and will take appropriate and fair corrective action in response to any behavior
|
|
38
|
+
that they deem inappropriate, threatening, offensive, or harmful.
|
|
39
|
+
|
|
40
|
+
## Scope
|
|
41
|
+
|
|
42
|
+
This Code of Conduct applies within all community spaces, and also applies when an
|
|
43
|
+
individual is officially representing the community in public spaces.
|
|
44
|
+
|
|
45
|
+
## Enforcement
|
|
46
|
+
|
|
47
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the
|
|
48
|
+
maintainer through the contact route in [SECURITY.md](SECURITY.md). All complaints will be
|
|
49
|
+
reviewed and investigated promptly and fairly, and the privacy and security of the reporter
|
|
50
|
+
will be respected.
|
|
51
|
+
|
|
52
|
+
## Attribution
|
|
53
|
+
|
|
54
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1,
|
|
55
|
+
available at
|
|
56
|
+
<https://www.contributor-covenant.org/version/2/1/code_of_conduct.html>.
|
|
57
|
+
|
|
58
|
+
[homepage]: https://www.contributor-covenant.org
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for taking the time. This project is small on purpose: no build step, no runtime
|
|
4
|
+
dependencies, and a test suite that runs in a couple of seconds. Please keep it that way.
|
|
5
|
+
中文亦可,Issue 和 PR 用中文写没问题。
|
|
6
|
+
|
|
7
|
+
## Getting set up
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
git clone https://github.com/LUgABeEEE/dsh-global-todo.git
|
|
11
|
+
cd dsh-global-todo
|
|
12
|
+
npm run verify # static checks + the whole test suite
|
|
13
|
+
dsh plugin --profile web add "$PWD" # optional: try it in a live host
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
There is nothing to install: the plugin has no runtime and no development dependencies.
|
|
17
|
+
Node 22.19 or newer is all you need.
|
|
18
|
+
|
|
19
|
+
## The loop
|
|
20
|
+
|
|
21
|
+
1. **Open an issue first** for anything larger than a bug fix — a new field, a new route, a
|
|
22
|
+
new dispatch mode. Design conversations are cheaper than review cycles.
|
|
23
|
+
2. Keep the change to one concern.
|
|
24
|
+
3. Add or extend tests. A behaviour change without a test will be asked for one.
|
|
25
|
+
4. Run `npm run verify`.
|
|
26
|
+
5. Open a PR and fill in the template.
|
|
27
|
+
|
|
28
|
+
## What the tests are for
|
|
29
|
+
|
|
30
|
+
| File | Protects |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `test/links.test.mjs` | attachment rules, including the credential refusal |
|
|
33
|
+
| `test/model.test.mjs` | the entity: validation, patches, activity, tolerant loads |
|
|
34
|
+
| `test/store.test.mjs` | atomic writes, the write lock, corrupt-file behaviour |
|
|
35
|
+
| `test/service.test.mjs` | filters, sorting, the shared write path |
|
|
36
|
+
| `test/tools.test.mjs` | tool schemas and every tool's success and failure shape |
|
|
37
|
+
| `test/routes.test.mjs` | HTTP status codes, the fence, body limits |
|
|
38
|
+
| `test/dispatch.test.mjs` | all four dispatch modes, preset mounting, recorded failures |
|
|
39
|
+
| `test/bundle.test.mjs` | the browser half: registration, slots, theme tokens, route contract |
|
|
40
|
+
| `test/manifest.test.mjs` | `package.json`, the patch row, exports, version agreement |
|
|
41
|
+
| `test/schema.test.mjs` | the Host's tool-schema subset — a schema outside it is rejected at registration, silently |
|
|
42
|
+
|
|
43
|
+
## House rules
|
|
44
|
+
|
|
45
|
+
- **Plain ESM, no dependencies.** No bundler, no TypeScript, no `@deepseek-ai/*` import in
|
|
46
|
+
`lib/`. Raw JSON Schema for tool parameters; raw `webServer.register` for routes. This is
|
|
47
|
+
what keeps the plugin loading in lean compositions and across `0.2.x` releases.
|
|
48
|
+
- **Comments explain why, not what.** A non-obvious guard should say which failure it
|
|
49
|
+
prevents. Restating a line of code in prose is noise.
|
|
50
|
+
- **User-facing strings need both languages.** Client dictionaries live in `lib/client.js`
|
|
51
|
+
(`zh` and `en`); host-side messages are Chinese, matching the rest of the ecosystem.
|
|
52
|
+
- **Errors are typed.** Raise `TodoError` with a code from `lib/errors.js` rather than
|
|
53
|
+
throwing a bare `Error`; the HTTP layer maps codes to statuses and the tests assert them.
|
|
54
|
+
- **Never write outside your own surface.** The client half contributes through slots and
|
|
55
|
+
its own stylesheet tag; the host half registers routes, tools and one prompt section.
|
|
56
|
+
- **No secrets, ever.** The store is local and the plugin stores no credentials. The URL
|
|
57
|
+
rule that refuses `user:password@` exists for this reason — do not relax it.
|
|
58
|
+
|
|
59
|
+
## Releasing
|
|
60
|
+
|
|
61
|
+
Facts checked on 2026-10-14, so nobody has to re-derive them:
|
|
62
|
+
|
|
63
|
+
- the npm name `dsh-global-todo` is **still free** (`registry.npmjs.org/dsh-global-todo` → 404);
|
|
64
|
+
- the repository `LUgABeEEE/dsh-global-todo` exists and holds the first commit
|
|
65
|
+
(`2a6f716`), so the `repository` field in `package.json` now points at something real;
|
|
66
|
+
CI has run green on both matrix legs (node 22.19.0 and 24.x).
|
|
67
|
+
|
|
68
|
+
The order that avoids publishing a package pointing at nothing:
|
|
69
|
+
|
|
70
|
+
1. **`npm run verify`** — static checks plus the whole suite. This is also what CI runs.
|
|
71
|
+
2. **First commit.** The repository has none yet, so there is no history to preserve:
|
|
72
|
+
`git init` and commit with the identity used across this machine —
|
|
73
|
+
`git -c user.name=LUgABeEEE -c user.email=LUgABeEEE@users.noreply.github.com commit`.
|
|
74
|
+
Before committing, confirm nothing secret is staged:
|
|
75
|
+
`git diff --cached | rg -i "apiKey|password|token|PRIVATE KEY"`.
|
|
76
|
+
`npm run verify` already performs the equivalent audit over every committable file, so a
|
|
77
|
+
green run is the evidence — and it also asserts that `.gitignore` keeps the local
|
|
78
|
+
artifacts (`node_modules/`, `preview/`, `todos.json`, backups) out of the commit.
|
|
79
|
+
3. **Push and watch CI for real.** The workflow matrix is Node 22.19 and 24; it has only
|
|
80
|
+
ever been simulated locally, so the first green run is the first real evidence.
|
|
81
|
+
4. **Publish with Trusted Publishing (OIDC).** `.github/workflows/publish.yml` is ready: it
|
|
82
|
+
verifies, then publishes with provenance when a `v*` tag is pushed or the workflow is
|
|
83
|
+
dispatched by hand. Before its first run, the npm side needs the binding — on the package
|
|
84
|
+
settings page add a trusted publisher with:
|
|
85
|
+
- repository `LUgABeEEE/dsh-global-todo`
|
|
86
|
+
- workflow file `publish.yml`
|
|
87
|
+
- environment: none (or one you create and also name here)
|
|
88
|
+
|
|
89
|
+
No long-lived token is involved, which is the point: nothing to leak from a laptop. Check
|
|
90
|
+
what would ship with `npm pack --dry-run`.
|
|
91
|
+
5. **Cut the version.** Move CHANGELOG's `Unreleased` block under
|
|
92
|
+
`## 0.1.0 — <date>`, keep `package.json`'s `version` and `VERSION` in `lib/index.js`
|
|
93
|
+
equal (a test enforces it), and tag the commit.
|
|
94
|
+
6. **Ask for a listing.** The plugin index (`awesome-dsh-plugin`) takes a PR: one line plus
|
|
95
|
+
a link, with `docs/screenshots/panel.png` as the picture.
|
|
96
|
+
|
|
97
|
+
## Reporting a bug
|
|
98
|
+
|
|
99
|
+
Include the plugin version (Settings → Plugins, or `GET /api/global-todo/health`), the DSH
|
|
100
|
+
version, what you did, what happened, and what you expected. A redacted snippet of
|
|
101
|
+
`todos.json` helps when the problem is about a record.
|