dsh-persona-forge 0.0.0-stage → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,301 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
5
+ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.3.0] - 2026-10-09
10
+
11
+ ### Added
12
+
13
+ - **AI 代填(AI draft)**, in the character-card manager. Describe the character
14
+ in a sentence — "a grumpy but utterly reliable old lighthouse keeper" — and the
15
+ model writes the whole card: the style text, a name, an icon, a description and
16
+ the conversion examples for all four intensity levels.
17
+
18
+ The result **only prefills the form**. Nothing is written to the card directory
19
+ until the user reviews it and presses Save, so a generation the user dislikes
20
+ costs one reload — the same review discipline the rewrite panel enforces. The
21
+ generated card is also passed through the very same validator the save path
22
+ uses, so this route cannot hand the form a card that cannot be saved.
23
+
24
+ It sits **inside the new-card flow** (the form that "New character card" opens),
25
+ with a hint line on the settings page so it is discoverable without pressing a
26
+ button. It uses the **harness default model**, and says so: the
27
+ `settings.section` slot receives only `{ close }` from the shell, so it has no
28
+ session id to follow. The resolved model is displayed before and after
29
+ generating, rather than leaving the user to assume it matches the session.
30
+
31
+ - A **free, local preservation check** (`lib/preserve.js`), always on. It compares
32
+ the technical tokens of the draft against the rewrite — identifiers, paths,
33
+ versions, numbers, `--flags`, acronyms — and **names** what went missing. It
34
+ makes no model call, so it costs no tokens and adds no latency.
35
+
36
+ - **Per-message rewrite mode and fidelity** in the 🎭 menu, alongside intensity.
37
+ Mode falls back to the card's own when the card has no content for the chosen
38
+ mode. Fidelity may be **narrowed** (a `strategy` card asked to behave as
39
+ voice-only for one message) but never **widened**, and the server enforces the
40
+ same rule.
41
+
42
+ - A **model-check switch** in the 🎭 menu, remembered per session.
43
+
44
+ - The card form now edits the **per-intensity examples**. They were part of the
45
+ card but invisible in the UI, which is what made the bug below possible.
46
+
47
+ ### Changed
48
+
49
+ - **The model-based preservation check is now off by default** and switchable
50
+ from the menu. It is the only thing that can see a requirement reworded weaker,
51
+ or a constraint added in prose — but it costs a second model call on **every**
52
+ send, and the new local check already proves the common and most damaging
53
+ failure for free. `factCheck` in the plugin config now sets that default, and
54
+ only an explicit `true` enables it.
55
+
56
+ - The Chinese README is the primary `README.md` and the English one is
57
+ `README.en.md`, so GitHub, npm and the repository landing page open in Chinese,
58
+ with a language switch at the top of each file. The npm package description is
59
+ Chinese for the same reason.
60
+
61
+ ### Fixed
62
+
63
+ - **Editing a card silently destroyed its per-intensity examples.** The form did
64
+ not carry `examplesByIntensity`, so pressing Save on any card rebuilt it
65
+ without the four demonstration sets and the card fell back to its flat
66
+ `examples` list. Renaming a bundled card was enough to lose all four intensity
67
+ sets. The form now shows and round-trips them.
68
+
69
+ - **Every bundled card was labelled 自定义 (custom).** Bundled cards are seeded
70
+ into the user card directory as editable starting points, so the tag was
71
+ derived from "does a user file exist for this id" — true for cards nobody had
72
+ ever touched. The row tag now reports real provenance, with a new **已修改
73
+ (edited)** state for a bundled card the user has changed, and it is computed
74
+ with the same semantic comparison the drift check uses, so the tag and the
75
+ check's verdict now agree **by construction** instead of contradicting each
76
+ other on the same screen.
77
+
78
+ - **Delete was offered where it could not take effect.** A card whose content
79
+ still matches the bundle has no user-owned file: removing it cleared a seeded
80
+ copy that the next start wrote straight back, so the button reported success
81
+ and changed nothing. Delete is now shown only where it does something, and the
82
+ confirmation for an edited bundled card says that its changes are discarded
83
+ and the bundled version returns.
84
+
85
+ - **A narrowed fidelity never reached the model.** The route computed the
86
+ narrowed value and reported it in the response, but `buildSystemPrompt` read
87
+ `card.fidelity` directly — so the setting appeared to work and did nothing. The
88
+ effective fidelity is now passed into the prompt builder, and a test asserts on
89
+ the prompt itself rather than only on the response.
90
+
91
+ - Three examples in the bundled 肌肉集团 · 硬邦邦 card dropped the word "bug"
92
+ from their input. The self-check now verifies the per-intensity examples too,
93
+ not just the flat list, which is what surfaced them.
94
+
95
+ ## [0.2.1] - 2026-10-09
96
+
97
+ ### Added
98
+
99
+ - **Check for changes**, in the character-card manager. It compares every user
100
+ card against the bundled card of the same id and reports only the ones that
101
+ actually differ; only a drifted card grows a **Restore bundled** action on its
102
+ row. The alternative — a restore button on every user card — turns a long list
103
+ into noise, and "restore" on a card that never changed is a no-op nobody
104
+ asked for.
105
+ - The drift report names the differing fields (`style`, `examples`, …).
106
+
107
+ ### Fixed
108
+
109
+ - **The card row's description overlapped the buttons.** `.pf-card-desc` is a
110
+ `<span>`, and `overflow: hidden` / `text-overflow: ellipsis` do not apply to
111
+ inline elements, so a long description — such as the bundled 硬邦邦 card's —
112
+ spilled out of the card instead of being clipped. The body is now a flex
113
+ column, which blockifies its children, and the description is clamped to two
114
+ lines. The row's actions are `flex: none`, so the description yields rather
115
+ than the buttons.
116
+
117
+ ### Changed
118
+
119
+ - The drift comparison is by **content, not bytes**. Bundled cards are
120
+ hand-written YAML carrying comments, while a saved card round-trips through
121
+ the store's serializer and loses them, so a byte comparison reported every
122
+ untouched seeded card as modified.
123
+
124
+ ## [0.2.0] - 2026-10-09
125
+
126
+ ### Added
127
+
128
+ - **The composer's own send gesture starts the rewrite.** Pressing the native
129
+ **Send** button, or **Enter**, rewrites the draft instead of sending it raw.
130
+ In review mode the comparison panel appears; in direct mode the rewrite is
131
+ sent. The menu's separate "Rewrite with this persona" button is gone, because
132
+ the send gesture replaced it.
133
+ - **Enter gets its own listener.** Enter is the primary send gesture and does
134
+ not go through the button at all — the editor's keymap calls the shell's
135
+ submit directly. Shift+Enter (newline), IME composition and the modifier
136
+ chords the shell ignores still fall through untouched.
137
+ - **A fourth intensity rung, 重 / Strong.** The ladder was light → medium →
138
+ zealot, which left no way to ask for a strong voice that is not yet full
139
+ ritual. Every bundled card now ships demonstrations for all four rungs.
140
+ - **Restore bundled** in the character-card manager, for a user card that
141
+ shadows a bundled one.
142
+ - Inline explanations for `intensity` and `fidelity` in the card editor.
143
+
144
+ ### Changed
145
+
146
+ - The review-first description now describes what actually happens: the rewrite
147
+ appears in the comparison panel and the draft is left alone until you confirm.
148
+ - Segmented controls are wider, and the review panel is capped to the composer
149
+ card width instead of spanning the whole conversation.
150
+
151
+ ### Fixed
152
+
153
+ - **A seeded card could freeze a bundled card at the version that shipped
154
+ first.** Seeding wrote a copy of each bundled card into the user directory,
155
+ and that copy shadows the bundled one — so an improved bundled card in a later
156
+ release could never reach anyone who had already run an earlier version. The
157
+ store now records what it seeded, together with a hash of the exact bytes: a
158
+ copy still matching that hash is untouched and is refreshed on upgrade, while
159
+ a copy the user edited is never written over. A file with no ledger entry is
160
+ treated as the user's own card and is left alone.
161
+ - **The seed ledger was loaded as a character card**, producing a spurious
162
+ "card has no name" diagnostic on every catalog read. The loader now skips
163
+ dot-files.
164
+ - **`check.mjs` read the developer's real card directory.** The store merges the
165
+ bundled cards with `$DSH_HOME/persona-cards`, so a stale seeded card silently
166
+ replaced the bundled one and the suite's results depended on whatever the
167
+ developer happened to have saved. The suite now isolates `DSH_HOME` to a
168
+ temporary directory before importing the store.
169
+
170
+ ### Security
171
+
172
+ - Both send-intercept paths are fail-safe: the locale service being absent, the
173
+ composer card not being found, a label mismatch, the persona being off, an
174
+ empty draft, or a draft that was already rewritten all fall through to the
175
+ native send. Disabling the plugin unmounts the component and removes its
176
+ listeners entirely, so sending behaves exactly as if it were never installed.
177
+
178
+ ## [0.1.2] - 2026-10-09
179
+
180
+ ### Fixed
181
+
182
+ - **No route was ever registered, so the whole UI was inert.** The plugin
183
+ declares no service dependencies, so it applied *before* the web server
184
+ mounted; reading `ctx.get('webServer')` at that moment found nothing and the
185
+ route registration was silently skipped. Every request then fell through to
186
+ the SPA fallback — an empty `405`/`404` body, which is what made the character
187
+ list empty and "open the card directory" appear to fail. The entry now waits
188
+ for the service with `ctx.inject(['webServer'], …)` and still loads on a
189
+ composition that has no web server.
190
+ - **The menu let the conversation show through it.** `--dsw-menu-surface-fill`
191
+ is translucent by design (~58% alpha) and only reads correctly with the
192
+ host's `backdrop-filter` behind it; the plugin used the fill without the blur.
193
+ Both are now applied together, exactly as the host Menu does, with the opaque
194
+ `--dsw-alias-bg-overlay` as the fallback.
195
+ - **The card watcher could leak on unload.** The watcher is created
196
+ asynchronously, so a plugin unloaded in that window never closed it. A
197
+ disposed flag now closes a watcher created after teardown.
198
+
199
+ ### Added
200
+
201
+ - `host-check.mjs` reproduces the mounting order that caused the missing
202
+ routes: its stub context only runs `inject` callbacks for mounted services,
203
+ and asserts that routes register through `inject` and that a
204
+ web-server-less composition still loads.
205
+
206
+ ## [0.1.1] - 2026-10-09
207
+
208
+ Three defects found by using the plugin in a real session.
209
+
210
+ ### Fixed
211
+
212
+ - **The rewrite could not be started from the UI.** The only trigger was a
213
+ right-click on the 🎭 control, which is undiscoverable — picking a character
214
+ and an intensity changed settings and then nothing happened. The menu now
215
+ opens with a full-width **"Rewrite with this persona"** action, disabled with
216
+ an explanatory hint until a character is selected.
217
+ - **Filled controls were invisible in the light theme.** The selected segment
218
+ and the primary action used `--dsw-alias-brand-primary` as a fill with a
219
+ hard-coded white label. That token is *inverted* — near-white in the dark
220
+ theme and near-black in the light theme — so the label vanished against it.
221
+ Filled controls now use `--dsw-alias-button-primary-fill` with
222
+ `--dsw-alias-label-primary-foreground`, exactly like the host Button, and the
223
+ segmented control uses the host's translucent-track + raised-pill pattern.
224
+ - **The card directory was empty.** The bundled cards live read-only inside the
225
+ package, so "open the card directory" showed an empty folder. The bundled
226
+ cards are now seeded into `~/.dsh/persona-cards/` on first mount as editable
227
+ files. Seeding uses an exclusive create (`wx`), so it can never overwrite a
228
+ card you wrote — including one created in the window between a stat and a
229
+ write.
230
+ - **A half-written card could silently replace a good one.** A single file save
231
+ emits several watcher events, and a reload could read the file mid-write and
232
+ parse a truncated card. Reloads are now debounced so they read settled files.
233
+
234
+ ### Added
235
+
236
+ - **An explicit "No persona" state.** The control previously had no way back to
237
+ sending exactly what you typed; it is now a first-class, persisted choice
238
+ listed alongside the cards, and the state the control starts in.
239
+ - `scripts/host-check.mjs` covers the seeding behaviour, including that a user
240
+ edit is never overwritten.
241
+ - `scripts/check.mjs` asserts the UI contract that regressed here: the off
242
+ state exists, the rewrite is reachable from a visible menu action, and no
243
+ hidden gesture-only trigger remains.
244
+
245
+ ## [0.1.0] - 2026-10-09
246
+
247
+ First public release.
248
+
249
+ ### Added
250
+
251
+ - **Composer persona control** (`conversation.input.left`): pick a character,
252
+ set intensity (light / medium / zealot), and choose **review first** or
253
+ **send directly**. Right-clicking the control runs a rewrite immediately.
254
+ - **Review panel** (`conversation.input.dock`): original vs rewrite side by
255
+ side, with Send, Fill only, Restore original and Rewrite again. The original
256
+ draft is never modified without an explicit action.
257
+ - **Character-card manager** (`settings.section`): create, edit and delete
258
+ cards from the UI, or drop a `.yml` file into `~/.dsh/persona-cards/` — the
259
+ directory is watched, so a new card appears without a restart.
260
+ - **Rewrites follow the session's own model.** The rewrite runs on the exact
261
+ provider/model the session last used, falling back to the harness default
262
+ only before the session's first request. There is no separate model setting.
263
+ - **Technical-fact preservation check**: a second call on the same model
264
+ compares the rewrite against the original and reports whether substance was
265
+ lost or invented. Advisory, never blocking, and never reports approval when
266
+ the check itself failed.
267
+ - **Two fidelity modes**: `style` changes only the voice; `strategy` may also
268
+ add behavioural constraints that fit the character. Fidelity is a property of
269
+ the card, not a per-message toggle.
270
+ - **Template mode**: a card may skip the model entirely and substitute the
271
+ draft into a fixed template.
272
+ - Two bundled cards: **万机之神 · 欧姆弥赛亚** (`omnissiah`, style fidelity) and
273
+ **肌肉集团 · 硬邦邦** (`muscle-crew`, strategy fidelity).
274
+ - **Zero dependencies.** The plugin ships its own YAML subset reader
275
+ (`lib/yaml.js`) instead of importing a YAML library, because a locally
276
+ `link:`-ed plugin does not get its dependencies installed. The reader refuses
277
+ constructs it cannot read faithfully — flow collections, anchors, aliases,
278
+ tags, merge keys, multi-document streams — naming the offending line, rather
279
+ than silently misreading a card.
280
+ - **Zero build step.** `lib/` is plain ESM and the browser half is a plain-JS
281
+ module, so a git checkout installs and runs as-is.
282
+ - Two self-check suites: `scripts/check.mjs` (offline unit: parser, cards,
283
+ prompts, client syntax) and `scripts/host-check.mjs` (host integration: the
284
+ real plugin mounted over a real socket with a stubbed model).
285
+
286
+ ### Security
287
+
288
+ - All HTTP routes are loopback-fenced: the socket must be loopback, the `Host`
289
+ header must name a loopback host (defeating DNS rebinding), and
290
+ proxy-forwarding headers are refused.
291
+ - The rewrite call is standalone: it creates no session turn, cannot call
292
+ tools, and never enters the session log. What the session records is the text
293
+ the user actually sent.
294
+
295
+ [Unreleased]: https://github.com/sugarmaster666/dsh-persona-forge/compare/v0.3.0...HEAD
296
+ [0.3.0]: https://github.com/sugarmaster666/dsh-persona-forge/compare/v0.2.1...v0.3.0
297
+ [0.2.1]: https://github.com/sugarmaster666/dsh-persona-forge/compare/v0.2.0...v0.2.1
298
+ [0.2.0]: https://github.com/sugarmaster666/dsh-persona-forge/compare/v0.1.2...v0.2.0
299
+ [0.1.2]: https://github.com/sugarmaster666/dsh-persona-forge/compare/v0.1.1...v0.1.2
300
+ [0.1.1]: https://github.com/sugarmaster666/dsh-persona-forge/compare/v0.1.0...v0.1.1
301
+ [0.1.0]: https://github.com/sugarmaster666/dsh-persona-forge/releases/tag/v0.1.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 sugarmaster666
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.